1. Important Points#

Amazon SQS 是 managed message queue,用来解耦 producer 和 consumer。它不是 event bus,也不是 durable event log;核心语义是 message 被 consumer 成功处理后删除。

SQS 是一个消息队列系统,Producer -> 发送消息,Queue -> 存储消息,Consumer -> 拉取并处理消息,消费者必须主动拉取(Pull Model),不是推送(Push)。

Polling(轮询)是 Consumer 通过 ReceiveMessage 请求不断向 SQS 查询是否有消息,Short Polling -> 立即返回,有消息就返回,没有就空返回,Long Polling -> 最多等待 0~20 秒,有消息立即返回,没有则等待超时。

MessageId 是 SQS 自动生成的唯一消息标识,用于标识每条消息,DeduplicationId 是 FIFO 用于去重的 ID,可以由用户提供,也可以通过 Content-based dedup(基于消息内容 hash 自动生成),用于防止消息重复进入队列,不是防止重复消费。

FIFO Queue 中引入 Message Group(MessageGroupId),用于控制顺序,同一个 Message Group 内消息必须严格顺序处理,不同 Message Group 之间可以并行。

FIFO 核心机制是 Message Group Lock,同一 Message Group 同一时刻只能有一个消费进度,msg1 被消费并未 delete 前 msg2 会被阻塞,只有 msg1 delete 后 msg2 才会被允许进入消费流程。

Consumer 在 FIFO 中是竞争关系,不绑定固定 Message Group,谁 poll 到消息谁处理,但同一个 Message Group 在同一时刻只会被一个“顺序执行权”占用。

Visibility Timeout 是消息被 Consumer 获取后在一段时间内对其他 Consumer 隐藏的机制,用于防止重复消费,如果 Consumer 未能 delete 消息,Timeout 到期后消息会重新可见并可能被其他 Consumer 再次消费。

At-least-once delivery 表示每条消息至少会被投递一次,但可能被投递多次,重复指的是同一条业务消息可能被多个 Consumer 多次处理(重复消费),不是消息内容重复。

Standard Queue 不保证顺序但吞吐高并发强,可能重复消费,FIFO Queue 保证顺序(Message Group 内)并通过 Dedup + Group Lock 控制顺序与去重,Group 内串行 Group 间并行。
核心原则:
    every consumer must be idempotent
    message is not processed until it is deleted
    visibility timeout must match processing time
    use long polling to reduce empty receives and cost
    use DLQ for poison messages
    create DLQ before source queue RedrivePolicy references its ARN
    monitor queue age, visible messages, in-flight messages, and DLQ count
    use FIFO only when ordering requirement is real
    message body should be small; large payload should go to S3 and send pointer

2. Service Configuration#

queue type#

Type Use Case Delivery / Ordering Notes
Standard high throughput, task queue, normal async jobs at-least-once, best-effort ordering duplicates and out-of-order delivery must be handled
FIFO ordered workflow, per-user command sequence, financial/order state changes FIFO within MessageGroupId, deduplication support lower default throughput; design message groups carefully
standard queue:
    choose by default
    scale consumer horizontally
    duplicate messages are possible
    order is not guaranteed

FIFO queue:
    queue name must end with .fifo
    each message needs MessageGroupId
    deduplication uses MessageDeduplicationId or content-based dedup
    messages in same group are processed one by one
    use many message groups for parallelism
Setting Recommended Default Notes
Message retention 4 days or business retry window allowed range is 60 seconds to 14 days
Visibility timeout 2-6x p99 processing time default is 30 seconds; max is 12 hours from receive
Receive wait time 20 seconds enables long polling
Max receive count 5 or 8 then move to DLQ
DLQ retention longer than source queue retention especially important for standard queues
SSE enabled use SSE-SQS by default, SSE-KMS for stricter control
Tags env, service, owner, cost-center, data-classification cost and ownership

SQS / SNS naming#

Use hyphen (-) as the default separator. SQS and SNS allow underscore (_), but hyphen naming is more consistent with AWS resource names, Kubernetes-style names, DNS-like names, Terraform variables, dashboards, and alerts.

base pattern:
    <env>-<team>-<project>-<service>-<purpose>

fields:
    env:
        prod / staging / dev

    team:
        owning team, for example platform / payment / data

    project:
        product or project boundary, for example checkout / billing / warehouse

    service:
        service or bounded context, for example order / invoice / user-sync

    purpose:
        events / worker / task / command / state / notification / export

abbreviation:
    allowed when the full name becomes too long
    abbreviation must be stable and documented by the team
    do not remove env or ownership/project identity

standard queue:
    pattern:
        <env>-<team>-<project>-<service>-<purpose>
        <env>-<team>-<project>-<service>-<purpose>-dlq

    examples:
        prod-platform-checkout-order-payment-capture
        prod-platform-checkout-order-payment-capture-dlq
        dev-data-warehouse-user-sync-worker
        dev-data-warehouse-user-sync-worker-dlq

SNS topic:
    pattern:
        <env>-<team>-<project>-<service>-events

    examples:
        prod-platform-checkout-order-events
        prod-payment-billing-invoice-events

FIFO queue:
    pattern:
        <env>-<team>-<project>-<service>-<purpose>.fifo
        <env>-<team>-<project>-<service>-<purpose>-dlq.fifo

    examples:
        prod-platform-checkout-order-state.fifo
        prod-platform-checkout-order-state-dlq.fifo
        prod-platform-checkout-order-events.fifo

avoid:
    prod_order_payment_capture
    prod_order_payment_capture_dlq
    prod-order-payment-capture
    prod-platform-checkout-order-payment-capture-dead-letter-queue

message limits#

message size:
    max 1 MiB including body and message attributes

message attributes:
    max 10 custom attributes
    included in 1 MiB message size limit
    use for routing hints / trace id / content type / schema version

batch:
    SendMessageBatch max 10 messages
    DeleteMessageBatch max 10 messages
    ChangeMessageVisibilityBatch max 10 messages

delay:
    queue delay / per-message delay can delay delivery
    max delay is 15 minutes

3. Architecture / Core Concepts#

message lifecycle#

producer:
    SendMessage
        -> message stored durably in SQS

consumer:
    ReceiveMessage
        -> message becomes invisible for visibility timeout

consumer success:
    DeleteMessage
        -> message removed from queue

consumer failure / crash:
    no DeleteMessage
        -> visibility timeout expires
        -> message becomes visible again
        -> another receive attempt

too many receive attempts:
    RedrivePolicy maxReceiveCount reached
        -> message moved to DLQ

visibility timeout#

visibility timeout controls:
    how long a received message is hidden from other consumers

too short:
    duplicate processing while first consumer is still working
    high ApproximateReceiveCount

too long:
    failed message retries slowly
    DLQ detection delayed
    in-flight messages remain high

best practice:
    set queue default to normal processing time
    extend with ChangeMessageVisibility for long jobs
    never use visibility timeout as job scheduler

long polling#

long polling:
    ReceiveMessage WaitTimeSeconds > 0
    max 20 seconds
    queries all SQS servers instead of a subset
    reduces empty responses and cost

recommended:
    set ReceiveMessageWaitTimeSeconds = 20 on queue
    consumer HTTP/read timeout must be greater than WaitTimeSeconds

DLQ#

DLQ is for poison messages:
    invalid payload
    missing downstream data
    permanent business failure
    consumer bug

DLQ is not:
    normal retry queue
    archive
    replacement for alerting
DLQ rules:
    best practice is to keep source queue and DLQ in the same account and region
    DLQ retention should be longer than source queue retention
    alarm when DLQ visible messages > 0
    redrive only after root cause is understood
    be careful with FIFO DLQ because moving messages can break end-to-end ordering semantics

See DLQ for redrive policy, redrive allow policy, and incident runbook.

source queue and DLQ relationship#

source queue:
    the queue producer sends to and consumer polls from

DLQ:
    another SQS queue created for failed messages from the source queue
    must exist before source queue RedrivePolicy can reference its ARN
    relation is by deadLetterTargetArn, not by queue name

type matching:
    standard source queue -> standard DLQ
    FIFO source queue -> FIFO DLQ, and DLQ name must also end with .fifo

recommended:
    create one dedicated DLQ per important source queue
    do not rely on naming convention alone; bind by ARN in RedrivePolicy

4. Producer / Consumer Best Practices#

producer#

producer checklist:
    send small JSON message or pointer to S3 object
    include schema_version
    include idempotency key / business id
    include trace id in message attributes
    use SendMessageBatch for high throughput
    retry throttling or transient network errors with backoff
    for FIFO, set MessageGroupId intentionally

Standard message:

{
  "schema_version": 1,
  "event_type": "payment.capture.requested",
  "request_id": "req-123",
  "order_id": "ord-1001",
  "amount_cents": 1200
}

FIFO design:

MessageGroupId:
    good:
        order_id when order events must be serial
        user_id when user commands must be serial

    bad:
        "default" for all messages
        tenant_id only when one tenant can be very hot

MessageDeduplicationId:
    use stable business id:
        payment_capture:req-123
        order_state:ord-1001:version-7

consumer#

consumer loop:
    ReceiveMessage with WaitTimeSeconds=20 and MaxNumberOfMessages=10
    validate schema
    process idempotently
    DeleteMessage only after successful processing
    on recoverable failure, do not delete
    on long work, extend visibility timeout
    emit metrics for success/failure/latency
idempotency examples:
    use order_id + event_type + request_id as processed key
    conditional write in DynamoDB
    unique constraint in PostgreSQL
    external API idempotency key
    compare expected state version before update

poison message policy#

recommended default:
    maxReceiveCount = 5
    DLQ alert immediately when visible message count > 0
    consumer logs message id, request id, schema_version, receive count
    do not log sensitive message body

when to increase maxReceiveCount:
    downstream failures are commonly transient
    retry cost is low
    visibility timeout is short

when to decrease maxReceiveCount:
    invalid payload should fail fast
    duplicate side effect is expensive
    DLQ triage should happen early

5. Reliability / Failure Handling#

availability:
    SQS is regional managed service
    messages are stored redundantly across multiple servers
    design producer/consumer retry and backoff anyway

backpressure:
    queue depth increases when producer > consumer
    scale consumers based on visible messages and oldest message age
    protect downstream dependencies from worker over-scaling

large messages:
    keep SQS message <= 1 MiB
    for large payload:
        store payload in S3
        send bucket/key/version/checksum in SQS
        consumer deletes S3 object only when retention policy allows

redrive:
    use DLQ redrive after fix
    replay slowly when downstream side effects are not fully idempotent
    keep original message attributes for traceability