b8c.io All articles
Engineering Strategy

Dead Letters Don't Lie: What Your Message Queue Is Hiding From You

b8c.io
Dead Letters Don't Lie: What Your Message Queue Is Hiding From You

Photo: server message queue data pipeline dark abstract technology, via www.racksolutions.com

There's a particular kind of engineering confidence that comes from watching a queue depth hover near zero. Everything looks calm. Your producer is humming, your consumer metrics show steady throughput, and your dashboard is a sea of green. You feel good about this system.

Then someone on the customer success team mentions that a batch of orders placed Tuesday never triggered the fulfillment workflow. You dig in. The events were published. The queue accepted them. But somewhere between "enqueued" and "processed," they just… stopped existing.

Welcome to the async graveyard.

The Problem With Fire-and-Forget Thinking

Message queues are sold on a promise: decouple your services, smooth out traffic spikes, and let your system breathe. That promise is real. But most teams adopt async patterns and then mentally check out. The queue becomes a tunnel you throw things into and assume they come out the other side.

The dangerous part isn't that failures happen — it's that they happen silently. A synchronous API call fails loudly. You get a 500, an exception, a log entry that screams at you. Async failures are different. Your producer returns a success acknowledgment. Your monitoring shows the message was accepted. And then nothing. The consumer choked, retried three times, hit the retry limit, and quietly shuffled the message to a dead letter queue (DLQ) that nobody has checked since you set it up in 2022.

The data inconsistency compounds from there. Your order was marked as "processing" in the primary database. The fulfillment event died. Now you have a record in a limbo state with no automatic recovery path, and your users are getting increasingly confused support tickets answered by a rep who can't explain why the system shows one thing and reality shows another.

Why Traditional Monitoring Misses This

Here's the brutal truth about most observability setups: they're built to catch what's happening, not what stopped happening.

You're probably tracking queue depth, message throughput, and consumer lag. Those are useful signals for capacity planning. They're nearly useless for catching data corruption from silent failures. A DLQ can fill up over 72 hours while every other metric looks completely healthy. Your queue depth stays low because messages are moving — just not to where they're supposed to go.

The other issue is that most teams instrument their producers well and their consumers poorly. You know how many events you're publishing per minute. You have almost no visibility into what percentage of those events actually resulted in a completed, verified side effect downstream. That gap between "sent" and "confirmed done" is where your data integrity goes to die.

What Queue Decay Actually Looks Like

Queue decay isn't a single catastrophic event. It's a slow rot. It usually starts with one consumer that's slightly misconfigured — maybe it's failing on a specific message schema that crept in after a seemingly minor API change. Those messages fail, retry, fail again, and land in the DLQ. The consumer keeps working fine for every other message type, so nothing alerts.

A week later, a developer pushes a change that introduces a subtle serialization bug. More messages hit the DLQ. Still no alert, because DLQ depth isn't wired to anything that pages anyone.

By the time someone notices the data inconsistencies — usually because a user or a downstream system surfaces something weird — you're looking at hundreds or thousands of unprocessed events and no clean way to replay them without understanding exactly which ones are safe to reprocess and in what order.

This is the real cost. Not just the lost events, but the forensic archaeology you now have to do to understand what state your system is actually in.

A Practical Audit Framework

If you want to get ahead of this, start with a DLQ audit. Pull everything out of your dead letter queues and categorize failures by error type. You'll almost always find a small number of root causes responsible for the majority of failures. That's your immediate fix list.

Next, build explicit success confirmation into your async flows. Don't just track whether an event was consumed — track whether it produced the expected downstream state change. For an order fulfillment event, that means verifying the fulfillment record was actually created, not just that the consumer acknowledged the message. This is sometimes called "saga pattern" thinking, and it's worth the implementation overhead.

Third, wire your DLQ depth to a real alert. This sounds obvious, but a shocking number of production systems have DLQs that alert nobody when they grow. Set a threshold — even something as simple as "more than 10 messages in the DLQ triggers a Slack notification" — and treat DLQ growth as a first-class incident signal.

Finally, build a replay strategy before you need one. When you do have a backlog of failed messages, you need to be able to replay them safely. That means idempotent consumers (non-negotiable), a way to filter messages by failure type, and a controlled replay mechanism that doesn't just blast the full backlog at your consumer at 3 AM.

The Visibility Layer You're Probably Missing

Beyond alerting on DLQ depth, consider adding message tracing to your async flows. Tools like AWS X-Ray, Datadog APM, or even a simple correlation ID that propagates through your entire event chain can make the difference between a two-hour debug session and a two-day one.

The goal is to be able to answer, for any given business event, a simple question: did this complete successfully end-to-end? Right now, most teams can answer "did we publish this event?" They cannot answer the more important question.

Some teams go further and build lightweight reconciliation jobs — scheduled processes that compare expected state against actual state and surface discrepancies proactively. It's extra work, but for systems where data integrity matters (payments, inventory, user account state), it's the kind of belt-and-suspenders thinking that prevents the 2 AM "how did we lose 400 orders" conversation.

Async Is Still Worth It

None of this is an argument against message queues. Async processing is genuinely powerful and the decoupling benefits are real. But the engineering community has collectively underinvested in the observability and failure-handling layer that makes async systems trustworthy.

Your queue isn't a black hole. It's infrastructure. Treat it with the same rigor you'd apply to your database — with backup strategies, integrity checks, and real alerting — and it'll reward you with the reliability it promises. Keep treating it like a fire-and-forget mechanism and it'll keep quietly burying your data where nobody can find it.

The dead letter queue is telling you something. Start listening.

All Articles

Related Articles

When Governance Became the Bottleneck: Breaking Free from Approval Theater

When Governance Became the Bottleneck: Breaking Free from Approval Theater

Your Postmortem Is a Press Release, Not a Plan

Your Postmortem Is a Press Release, Not a Plan

The Release Ritual: How Your Deployment Process Became a Three-Day Prayer Circle

The Release Ritual: How Your Deployment Process Became a Three-Day Prayer Circle