All posts

Clear a message queue backlog: capacity, retries, and safe DLQ recovery

· 10 min read

More workers may leave a queue growing, while a DLQ redrive can trigger another outage. Use Amazon SQS examples to calculate drain time, measure business completion, preserve idempotency, and replay within downstream capacity.

What to inspect when a queue will not shrink

A queue buffers temporary excess traffic for later processing. Fast acknowledgement does not establish that an order or notification will finish on time. Define an acceptance-to-completion deadline, then align new arrivals, distinct completions, unfinished jobs, and the age of the oldest unfinished business operation on one timeline.

ObservationPossible stateNext check
Arrivals exceed completions and age increasesInsufficient capacity or a slow dependencyActive workers, database waits, API limits
Attempts increase but completions stay flatRetries consume processing slotsSuccess by attempt number and error category
Source queue shrinks while DLQ growsFailures are being quarantinedUnfinished business operations and recovery owner
Only a subset remains oldInvalid input, hot keys, or ordering constraintsFailure samples, key skew, and dependencies

Amazon SQS queue counts and ages are approximate. The oldest-message metric in standard queues can exclude repeatedly failing messages; moving to a DLQ changes the age reference. Preserve the original acceptance timestamp in the payload or business ledger so that transfers do not erase the history.

Calculate drain time: B / (μ − λ)

The following is an original hypothetical calculation. Assume a stable job mix and processing-time distribution, constant arrivals λ and effective completion capacity μ, and no departures through quarantine or expiry. If μ exceeds λ, draining B unfinished jobs takes B / (μ - λ). Merely reducing arrivals to match capacity leaves no spare capacity to clear existing work.

Accepting 1,200 jobs/second while completing 1,000/second for 15 minutes leaves 180,000 jobs. If arrivals then fall to 800/second, the backlog shrinks by 200/second and drains in 15 minutes. Keeping arrivals at 1,200/second but raising completion capacity to 1,800/second drains it in five minutes.

Python 3: drain time under constant rates
# Python 3: illustrative constant-rate model, not a latency prediction
backlog = (1200 - 1000) * (15 * 60)
print(backlog)  # 180000 logical jobs

def drain_seconds(backlog, arrival_per_second, completion_per_second):
    if completion_per_second <= arrival_per_second:
        return None  # no finite drain time at these constant rates
    return backlog / (completion_per_second - arrival_per_second)

print(drain_seconds(backlog, 800, 1000))   # 900.0 seconds
print(drain_seconds(backlog, 1200, 1800))  # 300.0 seconds
print(drain_seconds(backlog, 1200, 1000))  # None
Calculated drain time for the same 180,000 jobs, in seconds, with constant arrival and completion rates.
  • 800 arrivals/s; 1,000 completions/s: 900 seconds (15 minutes)
  • 1,200 arrivals/s; 1,800 completions/s: 300 seconds (5 minutes)

Separate attempt throughput from business completions

Executing 2,000 attempts/second does not add completion capacity if they repeat the same failures. Record initial acceptances, all attempts, first successes by business ID, and quarantines separately. Inspect both queued and in-flight work; receiving a message is not completion.

Queue departures can include DLQ transfers and expiry, so they cannot directly supply μ in the previous model. Keep pending, successful, and investigation-required states in a business ledger. Decide whether an expired notification may be discarded or an order requires compensation. Retention expiry alone cannot establish success.

A longer visibility timeout does not prevent duplicate effects

After an SQS receive, the visibility timeout temporarily hides a message. Work running beyond that deadline can be delivered again without establishing that the original worker has stopped. Extend visibility when appropriate, while protecting side effects against repeated delivery.

Check shared downstream limits before adding workers

Suppose a downstream API can safely accept 1,000 calls/second in total, live work uses 700, and retries use 100. Replay has at most 200 calls/second available. That is a calculated ceiling assuming one call per job and no other users; leave additional operating headroom. At two calls per job, those 200 calls support at most 100 replayed jobs/second.

More instances can increase database connections and outbound calls. A 200-calls/second limiter on each of ten processes does not enforce a global 200-calls/second limit. Use shared admission control or allocated budgets, and bound waiting work and its lifetime. Limit retry counts, elapsed retry budgets, and spacing when a dependency is saturated.

A procedure for safe DLQ recovery

  1. Inspect business IDs, original timestamps, attempts, and error categories. Identify whether credentials, malformed input, or temporary overload needs correction.
  2. Apply the fix and verify duplicate handling and ambiguous external outcomes. Provide a separate correction or quarantine path for invalid data.
  3. Estimate spare capacity including live work and retries, and start at a low replay rate. SQS redrive does not filter or modify messages during movement; perform required corrections separately.
  4. Increase the rate only after checking successful business outcomes, database waits, API limiting, and live-work latency. Stop replay on degradation and track work already moved.
  5. Reconcile completed, compensated, and investigation-required operations before closing recovery. Record an owner and deadline for unresolved work, beyond the falling DLQ count.

Replay while live traffic continues in recovery tests

Slow a dependency temporarily, create a backlog, and restore service. A test with live arrivals stopped misses replay competing with normal work. Include old message schemas, repeated business operations, a worker crash immediately after processing, and ambiguous external API results. Check both duplicate effects and abandoned operations.

Define acceptance using live-work deadlines, drain time, downstream waits, zero duplicate effects, and visible unresolved cases alongside queue depth. Updating the model with measured completion capacity makes worker counts, admission limits, and replay rates actionable during the next incident.

Tips this post digs into