Traffic and messaging
Backpressure and autoscaling
A queue only stores work, it doesn't create capacity. Learn how to tell when the backlog is growing, how to scale workers on the right signal, and what to do when you can't add more.
A queue can hide a problem for hours. Work keeps arriving, the queue keeps accepting it, and nothing looks broken until the oldest message is so old that nobody wants it. The question is how to notice the backlog growing, and what to do about it.
Concept
Backpressure is a signal that flows backwards, from a slow component to the faster one feeding it. Its purpose is to make the producer slow down, rather than letting work pile up without limit.
The big idea
Use the queue as a shock absorber for short spikes. It can't absorb a long gap in capacity. If work arrives faster than it's processed for long enough, the backlog grows forever, and message age grows with it.
Two things follow:
- You must add workers when the backlog grows, or the system never catches up.
- When you can't add workers, because the bottleneck is a shared database or a rate-limited API, you must slow the producers or drop work.
Choosing the scaling signal
CPU is a poor signal for queue workers. An I/O-bound worker waiting on a database looks idle, but it is busy. Scale on the queue instead.
- Backlog per worker. Divide visible messages by running workers. If each worker can handle 32 messages per second and you can tolerate 60 seconds of delay, a worker should not have more than
60 × 32 = 1,920messages waiting for it. A backlog of 200,000 needs about200,000 / 1,920 ≈ 105workers. - Age of the oldest message. This is the best service-level signal, because it matches what users feel. "Emails go out within 2 minutes" is a statement about age, not depth. In SQS it is
ApproximateAgeOfOldestMessage.
Tip
Alert on the age of the oldest message, and use backlog per worker to decide how many workers to add. Depth alone can look fine while the oldest message is an hour old.
Walking through a backlog
Traffic is 2,000 messages per second. You run 90 workers, each handling 32 messages per second at 0.25 s per message, which is 2,880 per second in total. Then a dependency slows down, and each message takes twice as long.
Before the slowdown: 90 workers × 32/s = 2,880/s (more than the 2,000/s arriving)
After the slowdown: 90 workers × 16/s = 1,440/s (each message now takes 0.5 s)
Arrivals: 2,000/s
Net backlog growth: 2,000 − 1,440 = 560/s -> 33,600 more messages per minuteAdding workers helps only if the dependency can take the extra load. If the database is the slow part, 100 workers just means 100 workers waiting on the same database. The queue moves; it doesn't disappear.
What to do when you can't add workers
- Slow the producer. Apply rate limits so new work enters at the rate you can handle.
- Reject with a clear error. Return "try again later" to the caller instead of accepting work you will never finish.
- Shed low-priority work. Drop or defer the least valuable messages first, such as analytics refreshes, to keep the important ones moving.
- Put a TTL on messages that go stale. A push notification about yesterday's game score is worse than none.
Warning
A huge backlog of old work can be worse than dropping it. If the queue holds three hours of "game score" notifications and processes them first-in-first-out, users get three-hour-old news while current messages wait behind it. Use a TTL and consider processing the newest work first while you recover.
Common mistakes
- Scaling on CPU. Workers look idle while they wait on I/O, so no scaling happens.
- Scaling without checking the bottleneck. More workers hitting the same slow database make the outage worse.
- No producer-side limit. The queue grows without bound, and the only signal is a full disk or an expired message.
Key takeaways
- A queue buffers work but does not create capacity. A long gap in capacity means a growing backlog.
- Scale workers on backlog per worker and oldest-message age, not CPU.
- Use Little's law to size the pool, then check the bottleneck before adding workers.
- When capacity is fixed, apply backpressure: slow producers, reject with clear errors, or shed low-priority work.
Further learning
Next, see how the most common queue products compare in choosing a queue product.
Related
Little's law
A one-line formula that links how many items are in a system, how fast they arrive, and how long each one stays. It sizes queues, worker pools and servers.
Rate limiting
Why systems cap how much work a client can send, which algorithm counts, where the limit lives and what the client is told. Start here for the rate-limiting map.
Message queues
A durable buffer between the code that creates work and the code that does it. Start here for the map of queue concepts.