WeChat's DAGOR overload control (SoCC 2018) sheds load using the time requests spend queued inside a service rather than response time, and drops by business and user priority. How would you apply that to a device-fleet ingest tier, and what must the device do for it to work?
Show the full answer Hide the answer
The situation they were in
WeChat's backend is a large microservice estate where overload in one service propagates as retries and timeouts through everything upstream. The published design makes three choices worth stealing.
Queuing time, not response time, as the load signal. Response time includes every downstream call, so a slow dependency makes a healthy service look overloaded and shed traffic it could have served. The average time a request waits in the local pending queue is a property of this service alone. With a default task timeout of 500 ms, the paper reports an overload threshold of 20 ms average queuing time, evaluated over a window of one second or 2000 requests, whichever comes first.
Two-dimensional priority. A business priority derived from a table of user actions — logging in and paying rank above updating a feed — and a user priority derived from a hash of the user id, which is periodically re-hashed.
Propagation upstream. A service that raises its shedding threshold informs its callers, so requests are rejected at the start of the pipeline rather than after four services have done work that will be thrown away.
Applying it to a device fleet
The priority table writes itself, and it is where most of the value is:
- Registration, authentication and command acknowledgements. A device that cannot authenticate is a device you have lost; a command ack that is shed makes the operator repeat the command.
- Live telemetry, at the current sampling rate.
- Backfill of buffered history, which is by definition not urgent and is also the traffic that surges after any outage.
Shedding backfill first is what stops the recovery from re-creating the overload — the failure that makes IoT outages last hours rather than minutes.
The hashed user priority matters more here than in a consumer app. Without it, the same devices are shed every time, and a device that never gets through is indistinguishable from a broken one, so your fleet grows a permanently dark segment that nobody notices.
What the device must do
Admission control is a contract, and the client holds half of it. The device must treat a rejection as a signal rather than an error: honour the retry delay it is given, add randomised jitter, back off exponentially, and never convert one rejection into an immediate retry. It must mark its own traffic with a class so the server can shed backfill without shedding acks, and it must bound its buffer and decide in advance what to drop when the buffer is full — for most telemetry, the oldest samples, since stale readings are worth less than fresh ones.
Publishing the current admission level to devices closes the loop: a fleet that self-throttles costs the ingest tier nothing, while a fleet that retries into a closed door is a denial-of-service attack you paid for.
Where copying this would be a mistake, and when not to
At a few thousand devices with one ingest service, a fixed rate limit and a bounded queue are enough, and adaptive control adds a feedback loop you now have to reason about during an incident. DAGOR's design answers a problem created by scale and by service depth: many services, deep call chains, and overload that propagates. If your call chain is two deep, the propagation machinery buys nothing — take the priority ladder and the client contract, and leave the rest.