Backpressure, Lag, and Operational Signals

LESSON

Event-Driven Architecture and Streaming Foundations

014 30 min intermediate

Backpressure, Lag, and Operational Signals

By the end of this lesson, you will be able to...

  • Interpret lag, throughput, retry, and saturation signals in an event-driven system.

  • Trace where pressure builds across producer, broker, and consumer stages.

  • Choose a mitigation that protects the user promise instead of only hiding the metric.

Idea in one sentence: Lag is not the problem by itself; it is evidence that work is arriving, waiting, failing, or being retried somewhere faster than the system can safely finish it.

Core Insight

An order platform publishes OrderPaid.

Three consumers react:

fulfillment-service -> starts packing
email-service       -> sends receipt
analytics-service   -> updates revenue projection

At 09:00, a promotion begins. Order volume triples.

At 09:15, the dashboard shows this:

Signal Value
orders.paid publish rate 900 events/min
Fulfillment consumer rate 850 events/min
Email consumer rate 300 events/min
Analytics consumer rate 950 events/min
Email lag 18,000 events
Email retry rate high
Broker storage growing

A teammate says:

The queue is the problem.

Maybe. But maybe not.

The broker is where waiting becomes visible. The cause may be somewhere else: a slow email provider, too much producer traffic, a consumer bug, a retry storm, a bad partition key, or a downstream database that cannot accept writes quickly enough.

Backpressure is the system's way of saying:

Slow down, drop lower-value work, shed load, or change the path.
I cannot safely finish all of this at the current rate.

Lag is the visible backlog. It tells you that published work has not yet been completed by some consumer. It does not tell you why.

The operational skill is to read the signals together.

The Naive Reaction

The naive reaction is:

Lag is high.
Add more consumers.

Sometimes that is correct.

If each email worker can send 50 receipts per minute and the provider allows more parallel requests, adding workers may reduce lag safely.

But adding workers can also make the incident worse:

The queue is only one part of the path.

Plain meaning:

Backpressure means pressure travels backward through the system. A slow stage forces earlier stages to buffer, slow down, reject, or reshape work.

In this scenario:

The email provider accepts only 300 receipt sends per minute. The order service publishes 900 paid orders per minute. The email consumer cannot finish work as fast as it receives it, so lag grows.

Technical name:

The waiting work is consumer lag or queue depth, depending on the broker model. The mechanism that prevents unlimited waiting is backpressure. The evidence you use to decide what to do is operational signals: rates, saturation, retries, errors, age of oldest message, DLQ movement, and user impact.

What Each Stage Can See

Make the path visible before choosing a fix.

producer -> broker/log -> consumer -> dependency -> side effect

Each stage sees a different part of the truth:

Stage What it can see What it cannot prove alone
Producer publish rate, publish errors, request load whether consumers completed side effects
Broker or log stored messages, offsets, queue depth, age of oldest message whether downstream work is safe or valuable
Consumer processing rate, handler errors, retry count, ack rate whether producers should create less work
Dependency rate limits, latency, saturation, failures which event boundary created the pressure
User-facing system delayed emails, delayed fulfillment, stale projections where the queue is growing

This is why one graph is rarely enough.

High lag with healthy processing may mean the system is catching up after a planned backfill.

High lag with rising retries may mean the same events are failing again and again.

High lag with low consumer CPU may mean workers are waiting on an external dependency.

High lag on one partition may mean the partition key put too much work behind one customer, tenant, or merchant.

A Worked Incident Trace

Return to the promotion incident.

The team has these facts:

09:00 promotion starts
09:05 email provider latency rises from 120 ms to 1.8 s
09:08 email consumer timeout stays at 1 s
09:10 timeout retries begin
09:15 email lag reaches 18,000 events
09:20 customers ask where receipts are

Now trace the pressure:

Step Input Transition Intermediate state Output or decision Naive failure contrast
1 900 OrderPaid events/min Producer publishes facts into the broker Broker accepts work faster than email can finish it Lag starts growing for email only Naive view blames the whole event system
2 Provider latency > consumer timeout Consumer times out before provider answers Some sends may have succeeded, but the consumer does not know Retries begin with uncertain side effects Naive fix adds workers and sends more duplicate attempts
3 Retries share the same provider limit Fresh events and retry events compete Oldest message age rises; retry rate rises Protect provider and user-facing receipt path Naive fix treats every queued event as equal priority
4 Fulfillment and analytics stay healthy Only email side effect is delayed Order packing still starts; revenue projection stays current Incident scope is email receipts, not order processing Naive fix pauses all consumers
5 Email provider rate limit is 300/min System cannot finish 900/min through this path Backlog will grow until incoming rate drops or path changes Throttle, shed, batch, or defer lower-value work Naive fix waits for lag to vanish without changing capacity or demand

The important intermediate state is step 2.

The consumer timed out, but the provider may still have accepted some emails. That means retries are not just extra work. They may be duplicate side effects.

The better operational question is:

What work should continue now, what work should slow down, and what work needs idempotency before retry?

So far:

lag tells us work is waiting
retry rate tells us some work is not completing cleanly
dependency latency tells us where the wait starts
oldest message age tells us user-visible delay
idempotency records tell us whether retry is safe

Check: In the trace above, why is "add more email consumers" risky before checking provider limits and idempotency records?

Think first, then reveal.

Answer: More consumers can create more parallel calls to the same slow provider. If some timed-out calls actually succeeded, more retries can create duplicate receipts unless the consumer has a stable idempotency key or sent-receipt record.

Reading Common Signals

Use signals as clues, not verdicts.

Signal pattern Likely meaning First useful question
Lag grows, consumer rate is flat, errors are low Demand is higher than safe capacity Is this expected load, replay load, or producer bug?
Lag grows, retry rate rises, DLQ grows Work is failing, not merely slow What error class dominates the failures?
Lag grows on one partition only Hot key or ordering constraint Which key owns the stuck work?
Consumer CPU is low, dependency latency is high Workers are waiting outside the process Which dependency controls completion?
Broker storage grows across many topics Producers are outpacing total downstream capacity Which work is critical, deferrable, or lossy?
Oldest message age rises while count is stable A small number of old messages are stuck Is there a poison event or blocked partition?
Backfill starts and live lag rises Recovery work competes with live work Should recovery have a lower-priority path or window?

This table keeps the diagnosis close to the evidence.

Lag alone says:

not done yet

The surrounding signals say:

too much input
too little capacity
bad retry behavior
blocked partition
unsafe dependency
poison event
recovery work competing with live work

Choosing a Mitigation

A mitigation should match the pressure point.

If producers are creating too much low-value work, slow or filter the producers. For example, collapse repeated CartViewed events into a sampled analytics stream.

If a consumer is slow but safe to parallelize, add workers or partitions. This helps only when the dependency and ordering model allow parallelism.

If retries are amplifying failure, add backoff, retry budgets, or a circuit breaker. A retry budget says: after a limit, stop retrying through the hot path and move the event to a controlled recovery path.

If one partition is hot, review the partition key. A key like merchant_id may serialize all work for one large merchant. A better key may spread work while preserving the ordering scope the business actually needs.

If live traffic and backfill traffic compete, separate their lanes. Recovery work may need lower priority, a smaller window, or a shadow projection so live user-facing work stays healthy.

If a side effect is uncertain, do not blindly retry. First find the idempotency key, inbox record, provider request id, or sent-effect table that says whether the effect already happened.

The decision should protect the user promise.

For the email incident, the promise might be:

Customers should receive a receipt eventually.
They should not receive five copies.
Fulfillment should not stop just because receipts are delayed.

That promise suggests a concrete response:

  1. Keep OrderPaid publishing, because the fact is true and fulfillment needs it.
  2. Rate-limit email sending to provider-safe throughput.
  3. Use order_id + receipt_type as the idempotency key before re-sending.
  4. Move events that exceed retry budget to a receipt recovery queue.
  5. Alert on oldest unsent receipt age, not only total lag.

Trade-offs and Limits

Backpressure improves safety because it prevents a slow stage from silently creating unlimited work.

It costs latency, complexity, or dropped work.

If you throttle producers, users may see slower acceptance or rejected requests. If you buffer more, users may see delayed side effects. If you add consumers, dependencies may saturate. If you drop low-value events, analytics may become approximate. If you split traffic into priority lanes, operations becomes more complex.

Backpressure does not tell you the business priority of work. The system may know that two events are waiting. It does not know that one receipt affects a customer and one analytics click can be sampled unless you design that policy.

Lag also has limits as a metric.

A topic with 1,000 tiny events may be healthier than a topic with 10 old events that always fail. A count can hide age. An average can hide one stuck partition. A successful consumer ack can hide a side effect that was skipped incorrectly.

You can see the boundary when:

The trade-off is direct:

Event-driven systems buy time by putting work between services.
That time becomes useful only when the system also has policies for slowing, prioritizing, retrying, and observing the work.

Common Confusions

Confusion: Lag means the broker is broken

Why it is tempting:

The backlog is visible in the broker dashboard, so the broker feels like the failing component.

Better model:

The broker is often the pressure gauge. The cause may be producer volume, consumer speed, dependency limits, retry behavior, partitioning, or poison events.

Confusion: More consumers always reduce lag

Why it is tempting:

If one worker is slow, more workers sound like more capacity.

Better model:

More consumers help only when work can be parallelized and downstream dependencies can handle the extra concurrency. They can worsen rate limits, duplicate side effects, and retry storms.

Confusion: Backpressure is just an infrastructure concern

Why it is tempting:

Backpressure often appears as queue depth, broker storage, worker pools, or stream-processing metrics.

Better model:

Application architecture decides which work may wait, which work may be dropped, which side effects must be protected, and which user promise matters most. Infrastructure exposes the pressure, but product and service boundaries define the response.

Practice

Review this incident:

The billing service publishes InvoiceReady.
The PDF service consumes the event, generates an invoice PDF, stores it, and sends InvoicePdfCreated.
A new enterprise customer imports 80,000 invoices.
PDF consumer lag rises from 200 to 60,000.
CPU on PDF workers is 95%.
The object store is healthy.
One customer support page waits for InvoicePdfCreated before showing invoice status.
Analytics also consumes InvoiceReady, but it is healthy.

Choose a response plan.

A good answer should mention:

Resources

Key Takeaways

PREVIOUS Replay, Backfills, DLQs, and Poison Events NEXT Event-Driven Architecture Review Check