Nuberio
  • Pricing
  • Blog
  • Tools
  • Security
  • About
Log inStart free →
Nuberio

Root cause, not noise.

Start free →

Product

  • Audit
  • Watch
  • Diagnose

Free Tools

  • CloudWatch Alarm Builder
  • CloudFormation Checker
  • CloudWatch Timestamp Converter

Compare

  • Vs PagerDuty
  • Vs incident.io
  • Vs Datadog
  • Vs Resolve.ai
  • Vs Rootly
  • Vs AWS DevOps Guru
  • Vs Squadcast
  • Vs Komodor / Klaudia
  • Vs Sentry
  • Vs Coroot
  • Vs Datadog Watchdog
  • Vs New Relic
  • Vs Grafana Cloud
  • Vs OpsGenie

Company

  • Pricing
  • Blog
  • Security
  • About

Connect

  • X (Twitter)
  • LinkedIn

© 2026 Nuberio. All rights reserved.

Privacy Policy · Cookie Policy · Security

Built at 2am, for a 2am.

← All posts

AWS SQS monitoring: the one metric that matters more than queue depth

July 11, 2026·9 min read

Queue depth feels like the obvious thing to alarm on for SQS — a growing number of visible messages looks like a problem. But depth spikes constantly for entirely normal reasons: a producer flushes a batch, traffic has a burst, a scheduled job enqueues a day's worth of work at once. None of that is an incident. The metric that actually tells you when something is wrong is ApproximateAgeOfOldestMessage — how long the single oldest message in the queue has been waiting.

What are the 3 metrics that matter for SQS monitoring?

ApproximateAgeOfOldestMessage (processing lag — the primary alarm), ApproximateNumberOfMessagesVisible (queue depth — context, not a primary alarm), and the dead-letter queue's own ApproximateNumberOfMessagesVisible (poison-pill detection). Age tells you if consumers are keeping up regardless of volume; depth tells you how much backlog exists; DLQ depth tells you if specific messages are failing repeatedly rather than just queuing up.

MetricWhat it measuresRole
ApproximateAgeOfOldestMessageSeconds since the oldest non-deleted message was sentPrimary alarm — directly threatens data loss as it approaches your retention period
ApproximateNumberOfMessagesVisibleMessages available to be received right nowContext — normal to spike; alarm on sustained growth, not absolute count
ApproximateNumberOfMessagesNotVisibleMessages currently in flight (received, visibility timeout running)Diagnostic — high and flat means consumers received messages but aren't deleting them
DLQ: ApproximateNumberOfMessagesVisibleMessages that exhausted maxReceiveCount and moved to the dead-letter queuePoison-pill alarm — anything above zero warrants investigation

Why age, not depth, is the right primary alarm

Depth answers "how much work is queued." Age answers "is anything actually stuck." A queue can have 50,000 visible messages and be completely healthy if consumers are churning through them within seconds of arrival — high depth, low age. A queue can have 20 visible messages and be in genuine crisis if consumers have been down for an hour — low depth, high age. Age is the metric that correlates with real user impact and real data-loss risk; depth on its own does neither.

Per AWS's SQS documentation: set an alarm on ApproximateAgeOfOldestMessage as it approaches your queue's configured retention period. If a message exceeds the retention period, it is permanently deleted and unrecoverable — age is the metric with a direct line to data loss, not just delay.

Setting the actual alarm threshold

Tie the threshold to your queue's MessageRetentionPeriod, not a flat number — a threshold that makes sense at the 4-day default is meaningless if you've extended retention to 14 days. Use two tiers: an early-warning alarm at 50% of retention (time to investigate before anything is at risk) and a critical alarm at 80% (messages will start expiring soon without intervention).

Retention periodWarning threshold (50%)Critical threshold (80%)
4 days (default)2 days (172,800s)3.2 days (276,480s)
7 days3.5 days (302,400s)5.6 days (483,840s)
14 days (maximum)7 days (604,800s)11.2 days (967,680s)
SqsOldestMessageCritical:
  Type: AWS::CloudWatch::Alarm
  Properties:
    AlarmName: sqs-oldest-message-critical
    AlarmDescription: >-
      Oldest message age is at 80% of retention period — messages will
      start expiring and being permanently deleted without intervention.
    Namespace: AWS/SQS
    MetricName: ApproximateAgeOfOldestMessage
    Dimensions:
      - Name: QueueName
        Value: !GetAtt MyQueue.QueueName
    Statistic: Maximum
    Period: 60
    EvaluationPeriods: 3
    Threshold: 276480 # 80% of 4-day (345600s) default retention
    ComparisonOperator: GreaterThanThreshold
    TreatMissingData: notBreaching

Diagnosing consumer failures with Logs Insights

SQS itself doesn't emit per-message processing logs — the useful signal lives in your consumer's logs. For a Lambda consumer, query the function's log group for errors correlated with the time window when age started climbing.

fields @timestamp, @message
| filter @message like /ERROR|Task timed out|Rate Exceeded/
| stats count() as errorCount by bin(5m)
| sort errorCount desc

"Rate Exceeded" in Lambda logs while consuming from SQS usually means the function is being throttled — check ConcurrentExecutions against your account or reserved concurrency limit. "Task timed out" errors clustering around one message body pattern usually means a poison pill, not a capacity problem.

The three SQS failure modes, and how to tell them apart

  • Poison pill — one message fails processing repeatedly, gets returned to the queue after each visibility timeout, and eventually moves to the DLQ after maxReceiveCount attempts. Tell: DLQ ApproximateNumberOfMessagesVisible rises while the main queue's age stays roughly flat — everything else is processing normally.
  • Consumer stopped — no messages are being deleted at all while messages continue arriving. Tell: NumberOfMessagesDeleted flatlines at zero while ApproximateNumberOfMessagesVisible climbs steadily. This is the pattern the consumer-stopped metric math alarm is built to catch directly.
  • Consumer under-provisioned — messages are being processed, just slower than they're arriving. Tell: NumberOfMessagesDeleted is greater than zero and roughly steady, but ApproximateAgeOfOldestMessage climbs gradually rather than spiking. This needs more consumer throughput (higher Lambda concurrency, more ECS tasks), not a bug fix.
maxReceiveCount on a redrive policy has no universal default — you set it explicitly when configuring a dead-letter queue. A common starting point is 3-5 attempts for idempotent consumers; too low and transient failures (a brief downstream timeout) prematurely dead-letter a message that would have succeeded on retry, too high and a genuine poison pill blocks the queue's visibility-timeout cycle for longer than necessary.

Related reading

  • → CloudWatch Logs Insights queries: the practical library
  • → The Complete AWS CloudWatch Alarm Setup Guide
  • → CloudWatch metric math: how to build alarms no static threshold can match
  • → Lambda Errors — threshold & debugging guide

Frequently asked questions

Frequently asked questions

What is ApproximateAgeOfOldestMessage and why does it matter more than queue depth?

It's the age, in seconds, of the oldest message still in an SQS queue that hasn't been deleted. It matters more than depth because it directly measures whether consumers are keeping up with arrivals, regardless of volume — a queue can have high depth and be healthy (fast consumers, big batch), or low depth and be in crisis (consumers down, a handful of stuck messages aging out).

What's a good CloudWatch alarm threshold for SQS message age?

Set it as a percentage of your queue's MessageRetentionPeriod rather than a flat number: a 50% threshold as an early warning, and 80% as critical. For the default 4-day (345,600 second) retention, that's roughly 2 days for warning and 3.2 days for critical. If you extend retention to 14 days, the same percentages scale up proportionally.

How do I know if messages are being lost from an SQS queue?

Messages are silently and permanently deleted once they exceed the queue's MessageRetentionPeriod — there's no separate 'message expired' event to alarm on directly. The only reliable early warning is alarming on ApproximateAgeOfOldestMessage before it reaches the retention period, since once a message expires it cannot be recovered.

What causes SQS poison-pill messages, and how do I detect them?

A poison-pill message is one that fails processing every time it's received — a malformed payload, an edge case your consumer code doesn't handle, or a downstream dependency that will never succeed for that specific message. After maxReceiveCount failed attempts, SQS moves it to the configured dead-letter queue. Detect it by alarming on the DLQ's own ApproximateNumberOfMessagesVisible metric — any sustained value above zero warrants investigation.

Does SQS log every message to CloudWatch Logs?

No. SQS does not emit per-message processing logs to CloudWatch Logs by default — its CloudWatch metrics (queue depth, age, sent/received/deleted counts) are aggregate statistics, not individual message logs. To diagnose why a specific message failed, check your consumer's logs (Lambda function logs, ECS task logs) rather than looking for SQS-side logging, since SQS itself doesn't retain that detail.

Related reading

  • → CloudWatch Logs Insights queries: the practical library
  • → The Complete AWS CloudWatch Alarm Setup Guide
  • → CloudWatch metric math: how to build alarms no static threshold can match

Still debugging incidents manually?

Nuberio does this automatically — root cause in under 60 seconds, delivered to WhatsApp or Slack.

Try Nuberio free →
N

Nitesh Bhavsar

Founder, Nuberio

Published

July 2026

Updated

July 2026

Have feedback? [email protected]