SJ blog
backend
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

SQS可視性タイムアウトとDLQ — メッセージ処理失敗時の設計

SQSの可視性タイムアウトの仕組み、延長方法、デッドレターキュー(DLQ)の設定と最大受信回数、VisibilityTimeoutとLambdaのタイムアウトの関係を解説。

一言結論

SQSの可視性タイムアウトが処理時間より短いと二重処理が発生するため十分な余裕を持たせる必要があり、LambdaとSQSの組み合わせではSQSのタイムアウトをLambdaタイムアウトの6倍以上に設定し、繰り返し失敗するメッセージはDLQで隔離・監視することが信頼性設計の基本だ。

可視性タイムアウトの仕組み

SQSのメッセージはコンシューマーが取得(ReceiveMessage)した直後、他のコンシューマーから一時的に見えなくなる。この期間を「可視性タイムアウト」と呼ぶ。

可視性タイムアウトのフロー:

1. コンシューマーA がメッセージをReceiveMessage
   → メッセージはキューに残るが、他から見えなくなる(可視性タイムアウト開始)

2. コンシューマーAが処理完了
   → DeleteMessageを呼び出してキューから削除 ✅

3. コンシューマーAが処理失敗(クラッシュ等)
   → 可視性タイムアウト経過後、メッセージが再度見えるようになる
   → 別のコンシューマー(または同一コンシューマー)が再取得 🔄

可視性タイムアウトのデフォルト: 30秒
範囲: 0秒 〜 12時間

設定値の決め方

可視性タイムアウト = 処理時間の最大値 × 安全係数

例: 処理に最大60秒かかる場合
  → 可視性タイムアウト: 120秒(2倍程度の余裕を持たせる)

注意: 短すぎると二重処理が発生
      長すぎると失敗したメッセージの再処理が遅延する
# キューの可視性タイムアウトを設定
aws sqs set-queue-attributes \
  --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/my-queue \
  --attributes '{"VisibilityTimeout": "120"}'

可視性タイムアウトの延長(ChangeMessageVisibility)

長時間処理が必要な場合、途中でタイムアウトを延長できる。

import boto3
import threading

sqs = boto3.client('sqs')
QUEUE_URL = 'https://sqs.ap-northeast-1.amazonaws.com/123456789012/my-queue'

def extend_visibility(receipt_handle, interval=60):
    """処理中に定期的にタイムアウトを延長する"""
    def _extend():
        while not stop_event.is_set():
            stop_event.wait(interval)
            if not stop_event.is_set():
                sqs.change_message_visibility(
                    QueueUrl=QUEUE_URL,
                    ReceiptHandle=receipt_handle,
                    VisibilityTimeout=120  # 120秒延長
                )
    return threading.Thread(target=_extend, daemon=True)

# メッセージ処理
response = sqs.receive_message(
    QueueUrl=QUEUE_URL,
    MaxNumberOfMessages=1
)

if 'Messages' in response:
    message = response['Messages'][0]
    receipt_handle = message['ReceiptHandle']
    
    stop_event = threading.Event()
    extender = extend_visibility(receipt_handle)
    extender.start()
    
    try:
        # 長時間処理
        process_message(message)
        # 処理完了後に削除
        sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=receipt_handle)
    finally:
        stop_event.set()

LambdaとSQSの可視性タイムアウト

LambdaをSQSのコンシューマーとして使う場合、設定の整合性が重要だ。

推奨設定:
  SQSの可視性タイムアウト ≥ Lambda関数タイムアウト × 6

理由: Lambdaはバッチ内の一部メッセージが失敗した場合、
      成功したメッセージもタイムアウト後に再処理されるため

例:
  Lambda タイムアウト: 5分(300秒)
  SQS 可視性タイムアウト: 1800秒(30分)推奨
# Lambda関数でSQSバッチを部分的に処理する例
import json

def lambda_handler(event, context):
    failed_message_ids = []
    
    for record in event['Records']:
        try:
            body = json.loads(record['body'])
            process(body)
        except Exception as e:
            # 失敗したメッセージIDをリストに追加
            failed_message_ids.append({
                'itemIdentifier': record['messageId']
            })
    
    # 失敗したメッセージのみをキューに戻す(BatchItemFailures)
    return {
        'batchItemFailures': failed_message_ids
    }

デッドレターキュー(DLQ)

指定回数を超えて処理失敗したメッセージをDLQに移動する。

# DLQの作成
aws sqs create-queue \
  --queue-name my-queue-dlq

# メインキューにDLQを設定(最大受信回数: 3回)
aws sqs set-queue-attributes \
  --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/my-queue \
  --attributes '{
    "RedrivePolicy": "{
      \"deadLetterTargetArn\": \"arn:aws:sqs:ap-northeast-1:123456789012:my-queue-dlq\",
      \"maxReceiveCount\": \"3\"
    }"
  }'
DLQの動作:
  maxReceiveCount: 3 の場合
  
  1回目受信 → 処理失敗 → タイムアウト → キューに戻る
  2回目受信 → 処理失敗 → タイムアウト → キューに戻る
  3回目受信 → 処理失敗 → DLQへ移動
  
  DLQでの保持期間はメインキューとは別に設定可能(最長14日)

DLQリドライブ(DLQからの再処理)

DLQに蓄積したメッセージを修正後に再処理する機能がある。

# DLQのメッセージをソースキューに戻す
aws sqs start-message-move-task \
  --source-arn arn:aws:sqs:ap-northeast-1:123456789012:my-queue-dlq \
  --destination-arn arn:aws:sqs:ap-northeast-1:123456789012:my-queue \
  --max-number-of-messages-per-second 10

メッセージの保持期間

デフォルト: 4日間
最小: 60秒
最大: 14日間

可視性タイムアウト中でもカウントされる
→ 長い可視性タイムアウトを設定する場合、保持期間も長くする必要がある
# メッセージ保持期間を7日に設定
aws sqs set-queue-attributes \
  --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/my-queue \
  --attributes '{"MessageRetentionPeriod": "604800"}'

試験頻出ポイント

シナリオ回答
メッセージが重複処理される可視性タイムアウトが短すぎる
Lambda + SQSの推奨タイムアウト比SQS可視タイムアウト ≥ Lambda × 6
3回失敗後に別キューに移動DLQ + maxReceiveCount: 3
長時間処理でタイムアウトを延長ChangeMessageVisibility
DLQ内メッセージを再処理Message Move Task(リドライブ)

まとめ

可視性タイムアウトは処理時間より十分に長く設定し、LambdaとSQSの組み合わせでは6倍ルールを守る。DLQはmaxReceiveCountで毒メッセージをキャプチャし、アラーム設定で検知する体制を整えることが重要だ。