Back to docs
Operations
Runbook: SQS DLQ Backlog
**Applies to:** the async event bus. The main queue is `dagy-events-queue-<env>` (visibility timeout 900s); after **5** failed delivery attempts messages are dead-lettered to `dagy-events-dlq-<env>` (14-day retention). The alarm `dagy-events-dlq-depth-<env>` fires on the **first** visible DLQ message. EventBridge scheduler/reconciler ticks also dead-letter failed target deliveries here.
Severity: Sev-2 (events are being dropped/delayed but the core control plane is up).
Symptoms
- Alarm
dagy-events-dlq-depth-<env>in ALARM. - Notifications/lineage/side-effects that ride the event bus not firing.
ApproximateNumberOfMessagesVisible > 0on the DLQ.
Diagnosis
export ENV=develop REGION=us-east-1
DLQ_URL=$(aws sqs get-queue-url --region "$REGION" --queue-name dagy-events-dlq-$ENV --query QueueUrl --output text)
MAIN_URL=$(aws sqs get-queue-url --region "$REGION" --queue-name dagy-events-queue-$ENV --query QueueUrl --output text)
-
How deep is the DLQ?
aws sqs get-queue-attributes --region "$REGION" --queue-url "$DLQ_URL" \ --attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible -
Confirm the alarm state:
aws cloudwatch describe-alarms --region "$REGION" \ --alarm-names dagy-events-dlq-depth-$ENV \ --query 'MetricAlarms[0].{State:StateValue,Reason:StateReason}' -
Peek (without deleting) at a sample message to learn why it failed:
aws sqs receive-message --region "$REGION" --queue-url "$DLQ_URL" \ --max-number-of-messages 5 --visibility-timeout 0 \ --message-attribute-names All --attribute-names All- EventBridge-sourced messages carry the rule name — a scheduler/reconciler tick that keeps failing points at scheduler-stall or lambda-error-spike.
- Application events carry the event type/payload — correlate with the
consumer logs in
/aws/lambda/dagy-api-<env>(the SQS mapping targets the API alias).
-
Find the consumer error:
aws logs tail "/aws/lambda/dagy-api-$ENV" --region "$REGION" \ --since 1h --filter-pattern '?"events" ?ERROR ?Traceback'
Remediation
- Fix the root cause first (bad deploy → roll back the consumer alias; bad IAM → redeploy stack; poison payload → patch the handler). Replaying before fixing just re-fills the DLQ.
- Replay DLQ messages back onto the main queue once the consumer is healthy:
(Prefer an SQS redrive policy / start-message-move-task in the console for large backlogs.)# Drain a batch from the DLQ and re-enqueue to the main queue while true; do MSG=$(aws sqs receive-message --region "$REGION" --queue-url "$DLQ_URL" \ --max-number-of-messages 10 --wait-time-seconds 2) [ -z "$MSG" ] && break echo "$MSG" | jq -c '.Messages[]?' | while read -r m; do BODY=$(echo "$m" | jq -r '.Body'); RH=$(echo "$m" | jq -r '.ReceiptHandle') aws sqs send-message --region "$REGION" --queue-url "$MAIN_URL" --message-body "$BODY" aws sqs delete-message --region "$REGION" --queue-url "$DLQ_URL" --receipt-handle "$RH" done echo "$MSG" | jq -e '.Messages | length > 0' >/dev/null || break done - Truly poison messages that can never succeed → purge only after capturing them for forensics.
Escalation
- Page L2 if the DLQ keeps refilling after a replay (root cause not fixed) or depth is growing.
- Page L3 if dropped events imply customer-visible data loss (e.g. missed billing/usage events).