Back to blog

The Queue Dashboard Was Green for Six Hours While Your Backlog Rotted

Sep 21, 2026
Series · Day 15
Solution Architecture in 30 Days
View all lessons →
The Queue Dashboard Was Green for Six Hours While Your Backlog Rotted

Day 15: A Queue Decouples Deploy Timing, Not Failure

Add a message queue and you buy exactly one thing: the producer and consumer stop having to run on the same clock. That's it. A queue does nothing to protect you from a consumer that quietly dies — and worse, it will hide that death from you, cheerfully, for as long as it takes the backlog to become unrecoverable.

The dashboard said everything was fine

Broker dashboard: green. Alerts: zero. Meanwhile six hours of messages piled up behind a consumer group that had died without telling anyone. Nobody got paged, because the only thing anyone was watching was whether the broker itself was up — and it was, the whole time. The broker did its one job perfectly: accept messages, hold onto them. It just had nobody left to hand them to.

Replay the timeline

  • 02:14 — a downstream deploy triggers an OOM in the consumer process; the orchestrator restarts the container, but it rejoins the consumer group in a crash loop that never stabilizes
  • 02:14–08:00 — the producer has no idea and no reason to check; it keeps publishing at completely normal rate
  • Queue depth climbs hour over hour, disk usage on the broker climbs with it — nothing pages, because uptime and liveness checks on the broker itself are all green
  • 08:03 — a customer support ticket asks why an order confirmation never arrived; on-call greps logs and finds the consumer group has zero active members
  • 08:20 — consumer is restarted and immediately has to process six hours of backlog, which is where the second incident starts

The misconception nobody said out loud

In the design pitch, "the queue decouples the services" meant one thing: producer and consumer can deploy on different schedules, run different stacks, scale independently. Nobody meant "if the consumer dies, the producer's world is unaffected." Those are two separate claims, and the pitch only ever made the first one. A queue buys you deploy independence. It does not buy you failure independence — it just gives failure somewhere to hide.

What actually breaks, physically

  • Unbounded queue growth eats memory and disk on the broker — most brokers aren't built to hold infinite backlog forever without page-cache pressure or retention limits kicking in
  • Recovery becomes its own outage: a "replay storm," where six hours of backlog drains into downstream systems — databases, APIs, other services — far faster than they'd normally see traffic
  • At-least-once delivery means some of that replay reprocesses messages that had partially succeeded before the crash — duplicate emails, duplicate charges, duplicate side effects
  • Scale the consumer pool out to drain the backlog faster and you get a thundering herd instead: a pile of cold workers hitting the same downstream dependency — DB connections, an LLM API, a cache — all at once

The fix: alert on lag, not liveness

Broker-up is necessary. It is not sufficient. The metric that actually tells you the truth is consumer lag — the gap between the latest offset the producer has written and the last offset a consumer group has committed — or better still, backlog age: how old is the oldest message still waiting. Either one pages you in minutes. This one showed up as a support ticket six hours later instead.

yaml
# Prometheus alerting rule sketch — exact metric names vary by exporter
# kafka_consumergroup_lag is exposed by the common kafka_exporter (message-count lag)
- alert: ConsumerGroupLagTooHigh
  expr: kafka_consumergroup_lag > 5000
  for: 5m
  labels:
    severity: page
  annotations:
    summary: "Consumer group {{ $labels.consumergroup }} lag > 5000 messages"

# Time-based lag needs a time-aware exporter (e.g. kafka-lag-exporter, Burrow) —
# confirm the exact metric name against your exporter's docs before shipping this
- alert: OldestUnprocessedMessageTooOld
  expr: kafka_consumergroup_group_lag_seconds > 600
  for: 2m
  labels:
    severity: page
  annotations:
    summary: "Oldest unconsumed message is over 10 minutes old"

Ten minutes of lag paging you beats six hours of backlog paging your customers.

The bigger lesson — and why it matters more with AI in the stack

This exact failure is now everywhere AI agents show up. Run an agent fleet where a task queue feeds a pool of LLM workers, and the "consumer" is that worker pool making inference calls. One crashes, or a rate limit quietly stalls it, and the queue just keeps absorbing whatever comes in — user requests, background jobs, other agents' tool calls — while nobody notices, because everyone's watching whether the queue service is reachable, not whether tasks are actually draining. Recovery is worse than the outage here: a replay storm means a burst of duplicate LLM calls slamming your rate limits and your bill at the same moment, and a thundering herd means dozens of cold agent workers re-establishing context and hitting the same model endpoint simultaneously. Carry this forward: a queue is a buffer with its own failure mode, not a failure boundary. You'll need that assumption when you design consumer autoscaling and dead-letter queues later in this series.

Flashcards
Check yourself

Extend your knowledge

  • Read your broker's documentation on consumer group rebalancing and how lag is actually computed (e.g. Kafka's records-lag-max, or SQS's ApproximateAgeOfOldestMessage)
  • Check whether your current alerting covers consumer lag / backlog age at all, or only broker liveness — most default dashboards only ship the latter
  • Look into dead-letter queue patterns for your broker so a poison message can't block an entire partition's backlog
  • If you run LLM or agent worker pools behind a queue, review backpressure and autoscaling patterns so backlog drains gradually instead of as a thundering herd against your model endpoint
Test yourself on this lesson

Discussion

Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.

Ask me anything about “The Queue Dashboard Was Green for Six Hours While Your Backlog Rotted” — trade-offs, decisions, or the story behind it.