- AWS
Amazon SQSの使い方|送受信・削除と可視性タイムアウト、FIFO
Amazon SQSは、メッセージをキューに送り(send-message)、受け取り(receive-message)、処理が終わったら自分で消す(delete-message)という3つの操作で使うメッセージキューです。 受信しただけではメッセージは消えず、可視性タイムアウトという時間が過ぎると、もう一度受信できる状態に戻ります。
この記事では、キューの作成から送信・受信・削除・可視性タイムアウトの変更・全消去までを、AWS CLI と boto3 の両方で並べ、応答の JSON をそのまま載せます。さらに「受信しただけでは消えない」「タイムアウト切れで再受信される」「受信回数が増えて DLQ へ移る」という動きを、経過秒数つきの実行結果で確かめます。後半は標準キューと FIFO キューの違い、MessageGroupId を省いたときのエラー文、属性の早見表です。
確認環境:Ubuntu 24.04 / AWS CLI 1.46.1 / Python 3.11.15 / boto3 1.43.103。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しています。掲載コマンドは接続先の指定を外してあり、実際の AWS でそのまま使える形です。出力中のキュー URL のホスト名は、実際の AWS の形に置き換えています。
SQS操作の早見表(CLIとboto3)
| やりたいこと | AWS CLI | boto3 client | 主な応答 |
|---|---|---|---|
| キューを作る | aws sqs create-queue --queue-name 名前 |
sqs.create_queue(QueueName=) |
QueueUrl |
| キューの URL を調べる | aws sqs get-queue-url --queue-name 名前 |
sqs.get_queue_url(QueueName=) |
QueueUrl |
| 送る | aws sqs send-message --queue-url URL --message-body 本文 |
sqs.send_message(QueueUrl=, MessageBody=) |
MessageId、MD5OfMessageBody |
| 受け取る | aws sqs receive-message --queue-url URL |
sqs.receive_message(QueueUrl=) |
Messages(Body、ReceiptHandle など) |
| 消す | aws sqs delete-message --queue-url URL --receipt-handle ハンドル |
sqs.delete_message(QueueUrl=, ReceiptHandle=) |
なし |
| 見えない時間を変える | aws sqs change-message-visibility ... --visibility-timeout 秒 |
sqs.change_message_visibility(...) |
なし |
| 全部消す | aws sqs purge-queue --queue-url URL |
sqs.purge_queue(QueueUrl=) |
なし |
| 件数や設定を見る | aws sqs get-queue-attributes --attribute-names All |
sqs.get_queue_attributes(...) |
Attributes |
| 設定を変える | aws sqs set-queue-attributes --attributes 名前=値 |
sqs.set_queue_attributes(...) |
なし |
| まとめて送る(10件まで) | aws sqs send-message-batch --entries |
sqs.send_message_batch(Entries=) |
Successful、Failed |
メッセージがたどる流れは次のとおりです。
sequenceDiagram
participant P as 送る側
participant Q as キュー
participant C as 受け取る側
P->>Q: send-message
C->>Q: receive-message
Q-->>C: Body と ReceiptHandle
Note over Q: 可視性タイムアウトの間は他から見えない
C->>Q: delete-message(処理が終わったら)
Note over Q: 消さずに時間が過ぎると、また受信できる状態に戻る
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Amazon SQSの使い方|送受信・削除と可視性タイムアウト、FIFO」
キューを作る・URLを調べる
SQS の操作は、キュー名ではなくキュー URLを指定して行います。作成時の応答に入っているほか、名前から調べることもできます。
aws sqs create-queue --queue-name orders-queue
Q=$(aws sqs get-queue-url --queue-name orders-queue --query QueueUrl --output text)
echo "$Q"
{
"QueueUrl": "https://sqs.ap-northeast-1.amazonaws.com/123456789012/orders-queue"
}
https://sqs.ap-northeast-1.amazonaws.com/123456789012/orders-queue
以降の CLI の例では、この $Q を使います。boto3 では次のように書きます。
import boto3
sqs = boto3.client("sqs")
url = sqs.create_queue(QueueName="demo-queue", Attributes={"VisibilityTimeout": "3"})["QueueUrl"]
print(url)
print(sqs.get_queue_url(QueueName="demo-queue")["QueueUrl"] == url)
https://sqs.ap-northeast-1.amazonaws.com/123456789012/demo-queue
True
Attributes の値は、数値でも文字列で渡します。ここでは後の確認を短い時間で済ませるため、可視性タイムアウトを3秒にしています(既定は30秒)。
メッセージを送る(send-message)
aws sqs send-message --queue-url "$Q" --message-body '{"order_id":"0001","item":"ノート"}'
{
"MD5OfMessageBody": "584b39cce659cabe09b65265ee75e4c7",
"MessageId": "63566910-0a10-43b0-bcf5-fed96174fbc5"
}
応答の MessageId は SQS が付けた ID、MD5OfMessageBody は本文の MD5 で、送った内容の照合に使えます。
本文とは別に、種類や回数などの付加情報をメッセージ属性として持たせることもできます。
aws sqs send-message --queue-url "$Q" --message-body 'second' \
--message-attributes '{"kind":{"DataType":"String","StringValue":"order"},"retry":{"DataType":"Number","StringValue":"0"}}'
aws sqs get-queue-attributes --queue-url "$Q" \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible
{
"MD5OfMessageBody": "a9f0e61a137d86aa9db53465e0801612",
"MD5OfMessageAttributes": "09857a05c2706866de09a43a8ca66997",
"MessageId": "cd4fa904-0375-4c2d-9a72-6d6d5f66c628"
}
{
"Attributes": {
"ApproximateNumberOfMessages": "2",
"ApproximateNumberOfMessagesNotVisible": "0"
}
}
ApproximateNumberOfMessages が受信できるメッセージの数、ApproximateNumberOfMessagesNotVisible が受信済みでまだ消されていない(処理中の)数です。
boto3 では、本文は文字列で渡します。dict を送りたいときは json.dumps で文字列にします。
import json, hashlib
body = json.dumps({"order_id": "0001", "item": "ノート"}, ensure_ascii=False)
r = sqs.send_message(QueueUrl=url, MessageBody=body)
print({k: r[k] for k in ("MessageId", "MD5OfMessageBody")})
print("手元で計算した MD5:", hashlib.md5(body.encode("utf-8")).hexdigest())
{'MessageId': '512be379-574b-424e-8500-86859f36d461', 'MD5OfMessageBody': 'ace2de3fed515ebe90e26055aa380104'}
手元で計算した MD5: ace2de3fed515ebe90e26055aa380104
メッセージを受け取る(receive-message)
aws sqs receive-message --queue-url "$Q" \
--max-number-of-messages 1 \
--message-system-attribute-names All \
--message-attribute-names All \
--wait-time-seconds 1 | tee msg.json
{
"Messages": [
{
"MessageId": "63566910-0a10-43b0-bcf5-fed96174fbc5",
"ReceiptHandle": "rkuyjstoghretsmddhoenqorryxtpxvptoozbgquxhaaxspbbkkhdawsmrohvlsjyvghnaupiycellmcmhiwsfuganlolqtkfncusbojvbdclydwxzshjarkrtmuqqxfgbclnwfcoxyosntdsiahzgpdsrpuxxawktdxpslbeqkadkpvzrzwnvmha",
"MD5OfBody": "584b39cce659cabe09b65265ee75e4c7",
"Body": "{\"order_id\":\"0001\",\"item\":\"ノート\"}",
"Attributes": {
"SenderId": "AIDAIT2UOQQY3AUEKVGXU",
"SentTimestamp": "1791021867408",
"ApproximateReceiveCount": "1",
"ApproximateFirstReceiveTimestamp": "1791021868542"
}
}
]
}
| 応答のキー | 内容 |
|---|---|
| MessageId | 送信時と同じ ID |
| ReceiptHandle | 「この受信」を表す文字列。削除と可視性タイムアウトの変更に使う。受信のたびに変わる |
| MD5OfBody | 本文の MD5 |
| Body | 本文 |
| Attributes | システムが付ける属性。--message-system-attribute-names を指定したときだけ入る |
| MessageAttributes | 送信側が付けた属性。--message-attribute-names を指定したときだけ入る |
--max-number-of-messages は1〜10です。キューが空のとき、CLI は何も表示せずに終了し、boto3 の応答には Messages キーが入りません。boto3 では r.get("Messages", []) で受けると、空のときもそのままループに渡せます。
受信した直後の件数を見ると、メッセージは消えておらず「処理中」に移っただけだと分かります。
aws sqs get-queue-attributes --queue-url "$Q" \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible
{
"Attributes": {
"ApproximateNumberOfMessages": "1",
"ApproximateNumberOfMessagesNotVisible": "1"
}
}
メッセージを消す(delete-message)
削除には、受信の応答に入っていた ReceiptHandle を渡します。MessageId ではない点に気をつけます。
RH=$(jq -r '.Messages[0].ReceiptHandle' msg.json)
aws sqs delete-message --queue-url "$Q" --receipt-handle "$RH"
echo "exit=$?"
aws sqs get-queue-attributes --queue-url "$Q" \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible
exit=0
{
"Attributes": {
"ApproximateNumberOfMessages": "1",
"ApproximateNumberOfMessagesNotVisible": "0"
}
}
delete-message は成功しても何も表示しません。処理中の数が0に戻りました。boto3 では「受信 → 処理 → 削除」を1つのループにまとめます。
def handle(body):
print("処理:", body)
msgs = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=10, WaitTimeSeconds=1).get("Messages", [])
for m in msgs:
handle(m["Body"])
sqs.delete_message(QueueUrl=url, ReceiptHandle=m["ReceiptHandle"]) # 処理が終わってから消す
print("残り:", sqs.receive_message(QueueUrl=url, WaitTimeSeconds=1).get("Messages", []))
処理: {"order_id": "0001", "item": "ノート"}
残り: []
削除は処理が終わった後に行います。 先に消すと、処理の途中で止まったときにメッセージが失われます。後に消す順にしておけば、途中で止まっても次の節の仕組みで再び受信できます。
受信しただけでは消えない:可視性タイムアウトの動き
可視性タイムアウトは、受信されたメッセージが他の受信者から見えなくなる時間です。公式開発者ガイドによると既定は30秒、設定できる範囲は0秒〜12時間です。この時間内に削除されなかったメッセージは、再び受信できる状態に戻ります。
可視性タイムアウト3秒のキューで、経過秒数を付けて確かめます。
import time
T0 = time.time()
def log(msg):
print(f"[{time.time() - T0:5.1f}s] {msg}")
def counts(u):
a = sqs.get_queue_attributes(QueueUrl=u, AttributeNames=[
"ApproximateNumberOfMessages", "ApproximateNumberOfMessagesNotVisible"])["Attributes"]
return f"受信可={a['ApproximateNumberOfMessages']} 処理中={a['ApproximateNumberOfMessagesNotVisible']}"
def recv(u):
return sqs.receive_message(QueueUrl=u, MaxNumberOfMessages=1,
MessageSystemAttributeNames=["ApproximateReceiveCount"]).get("Messages", [])
sqs.send_message(QueueUrl=url, MessageBody="task-1")
log("送信した直後 " + counts(url))
m = recv(url)[0]; h1 = m["ReceiptHandle"]
log(f"1回目の受信 ReceiveCount={m['Attributes']['ApproximateReceiveCount']} " + counts(url))
log(f"すぐにもう一度受信 {len(recv(url))}件")
time.sleep(3.2)
log("3秒たった(削除せず) " + counts(url))
m = recv(url)[0]; h2 = m["ReceiptHandle"]
log(f"2回目の受信 ReceiveCount={m['Attributes']['ApproximateReceiveCount']} ReceiptHandle が変わった={h1 != h2}")
sqs.delete_message(QueueUrl=url, ReceiptHandle=h2)
log("削除した " + counts(url))
time.sleep(3.2)
log(f"さらに3秒後に受信 {len(recv(url))}件")
[ 0.0s] 送信した直後 受信可=1 処理中=0
[ 0.0s] 1回目の受信 ReceiveCount=1 受信可=0 処理中=1
[ 0.0s] すぐにもう一度受信 0件
[ 3.2s] 3秒たった(削除せず) 受信可=1 処理中=0
[ 3.2s] 2回目の受信 ReceiveCount=2 ReceiptHandle が変わった=True
[ 3.2s] 削除した 受信可=0 処理中=0
[ 6.4s] さらに3秒後に受信 0件
読み取れることは4つです。
- 受信しても消えず、「処理中」に移るだけです。その間、別の受信には出てきません。
- 3秒(可視性タイムアウト)を過ぎると、同じメッセージが再び受信できる状態に戻ります。
- 2回目の受信では
ApproximateReceiveCountが2になり、ReceiptHandle は新しい値に変わります。 - 削除すると、時間がたっても戻ってきません。
このため受け取る側の処理は、同じメッセージが2回届いても結果が変わらないように作っておくのが基本です。
処理に時間がかかるとき:change-message-visibility
処理が可視性タイムアウトより長引きそうなときは、受信済みのメッセージの見えない時間を延ばせます。
aws sqs send-message --queue-url "$Q" --message-body 'long-task' --query MessageId --output text
RH=$(aws sqs receive-message --queue-url "$Q" --query 'Messages[0].ReceiptHandle' --output text)
aws sqs change-message-visibility --queue-url "$Q" --receipt-handle "$RH" --visibility-timeout 120
echo "exit=$?"
4d3b2f70-c956-4849-b3a8-014eb2398e43
exit=0
boto3 でも同じ引数です。延長している間は受信されないこと、0を指定するとすぐに受信できる状態へ戻せることを確かめます。
T0 = time.time()
sqs.send_message(QueueUrl=url, MessageBody="task-2")
m = recv(url)[0]
sqs.change_message_visibility(QueueUrl=url, ReceiptHandle=m["ReceiptHandle"], VisibilityTimeout=8)
log("8秒に延長")
time.sleep(3.2)
log(f"元の3秒を過ぎて受信 {len(recv(url))}件(延長が効いている)")
sqs.change_message_visibility(QueueUrl=url, ReceiptHandle=m["ReceiptHandle"], VisibilityTimeout=0)
m2 = recv(url)
log(f"0秒にして受信 {len(m2)}件 ReceiveCount={m2[0]['Attributes']['ApproximateReceiveCount']}")
sqs.delete_message(QueueUrl=url, ReceiptHandle=m2[0]["ReceiptHandle"])
[ 0.0s] 8秒に延長
[ 3.2s] 元の3秒を過ぎて受信 0件(延長が効いている)
[ 3.2s] 0秒にして受信 1件 ReceiveCount=2
| やりたいこと | 指定 |
|---|---|
| 処理が長引くので延ばしたい | 残したい秒数を指定(最初の受信から12時間が上限。公式開発者ガイド) |
| 処理できなかったので、すぐ他へ回したい | 0 を指定 |
| キュー全体の既定を変えたい | set-queue-attributes --attributes VisibilityTimeout=秒 |
何度も処理できないメッセージをDLQへ移す
形式が壊れているなど、何度受信しても処理できないメッセージが残ると、同じメッセージの受信が繰り返されます。DLQ(デッドレターキュー)を設定すると、決めた回数を超えて受信されたメッセージを別のキューへ移せます。
設定は、元のキューの RedrivePolicy 属性に、移動先キューの ARN と maxReceiveCount(受信できる回数)を JSON で入れます。
dlq = sqs.create_queue(QueueName="demo-dlq")["QueueUrl"]
dlq_arn = sqs.get_queue_attributes(QueueUrl=dlq, AttributeNames=["QueueArn"])["Attributes"]["QueueArn"]
main = sqs.create_queue(QueueName="demo-main", Attributes={
"VisibilityTimeout": "2",
"RedrivePolicy": json.dumps({"deadLetterTargetArn": dlq_arn, "maxReceiveCount": "3"}),
})["QueueUrl"]
sqs.send_message(QueueUrl=main, MessageBody="broken-payload")
T0 = time.time()
for i in range(1, 6):
ms = recv(main)
got = f"ReceiveCount={ms[0]['Attributes']['ApproximateReceiveCount']}(削除しない)" if ms else "0件"
log(f"受信{i}回目: {got} / 元のキュー {counts(main)} / DLQ {counts(dlq)}")
time.sleep(2.2)
ms = sqs.receive_message(QueueUrl=dlq, WaitTimeSeconds=1).get("Messages", [])
log(f"DLQ から受信: Body={ms[0]['Body']}")
[ 0.0s] 受信1回目: ReceiveCount=1(削除しない) / 元のキュー 受信可=0 処理中=1 / DLQ 受信可=0 処理中=0
[ 2.2s] 受信2回目: ReceiveCount=2(削除しない) / 元のキュー 受信可=0 処理中=1 / DLQ 受信可=0 処理中=0
[ 4.4s] 受信3回目: ReceiveCount=3(削除しない) / 元のキュー 受信可=0 処理中=1 / DLQ 受信可=0 処理中=0
[ 6.7s] 受信4回目: 0件 / 元のキュー 受信可=0 処理中=0 / DLQ 受信可=1 処理中=0
[ 8.9s] 受信5回目: 0件 / 元のキュー 受信可=0 処理中=0 / DLQ 受信可=1 処理中=0
[ 11.1s] DLQ から受信: Body=broken-payload
3回目までは元のキューで受信でき、4回目の受信のタイミングで DLQ へ移り、元のキューからは受信されなくなりました。DLQ に入ったメッセージは、通常のキューと同じく receive-message で取り出して中身を調べられます。
CLI で設定する場合は、JSON の中に JSON 文字列を入れる形になります。
DLQ=$(aws sqs create-queue --queue-name orders-dlq --query QueueUrl --output text)
ARN=$(aws sqs get-queue-attributes --queue-url "$DLQ" --attribute-names QueueArn --query Attributes.QueueArn --output text)
aws sqs set-queue-attributes --queue-url "$Q" \
--attributes "{\"RedrivePolicy\":\"{\\\"deadLetterTargetArn\\\":\\\"$ARN\\\",\\\"maxReceiveCount\\\":\\\"5\\\"}\"}"
aws sqs get-queue-attributes --queue-url "$Q" --attribute-names RedrivePolicy
{
"Attributes": {
"RedrivePolicy": "{\"deadLetterTargetArn\": \"arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq\", \"maxReceiveCount\": 5}"
}
}
公式開発者ガイドで確認できた、DLQ の決まりと勧められている設定です。
| 項目 | 内容 |
|---|---|
| キューの種類 | 標準キューの DLQ は標準キュー、FIFO キューの DLQ は FIFO キューにする |
| maxReceiveCount | 一時的なエラーで再試行できる余地が残る回数にする |
| 保持期間 | DLQ の保持期間は、元のキューより長くすることが勧められている |
| 戻す | DLQ のメッセージを元のキューへ戻す機能(リドライブ)がある |
待ち方を選ぶ:ロングポーリング
receive-message の WaitTimeSeconds は、キューが空のときに待つ秒数です。0ならすぐに返り(ショートポーリング)、1〜20なら届くまで待ちます(ロングポーリング)。
import threading
empty = sqs.create_queue(QueueName="demo-empty")["QueueUrl"]
for w in (0, 3):
t = time.time()
r = sqs.receive_message(QueueUrl=empty, WaitTimeSeconds=w)
print(f"WaitTimeSeconds={w}: {time.time() - t:.1f}秒で応答 Messages あり={'Messages' in r}")
threading.Timer(1.0, lambda: boto3.client("sqs").send_message(QueueUrl=empty, MessageBody="late")).start()
t = time.time()
r = sqs.receive_message(QueueUrl=empty, WaitTimeSeconds=10)
print(f"WaitTimeSeconds=10・1秒後に送信: {time.time() - t:.1f}秒で応答 {len(r.get('Messages', []))}件")
WaitTimeSeconds=0: 0.0秒で応答 Messages あり=False
WaitTimeSeconds=3: 3.0秒で応答 Messages あり=False
WaitTimeSeconds=10・1秒後に送信: 1.0秒で応答 1件
10秒待つ指定でも、メッセージが届けばその時点で返ります。公式開発者ガイドによると、ショートポーリングは一部のサーバーだけを調べるため、メッセージがあっても空の応答になることがあり、ロングポーリングは全サーバーを調べて空の応答を減らします。受信ループでは WaitTimeSeconds=20 を基本にすると、呼び出し回数も抑えられます。
待ち時間はキューの属性 ReceiveMessageWaitTimeSeconds で既定値にもでき、受信時に WaitTimeSeconds を指定した場合はそちらが優先されます。
まとめて送る・消す・全部消す
aws sqs send-message-batch --queue-url "$Q" \
--entries '[{"Id":"a","MessageBody":"m1"},{"Id":"b","MessageBody":"m2"}]' \
--query '{Successful:Successful[].Id,Failed:Failed}'
{
"Successful": [
"a",
"b"
],
"Failed": []
}
Id は応答と突き合わせるための、リクエスト内の目印です(応答には各メッセージの MessageId と MD5 も入ります)。1回で送れるのは10件までで、11件渡すと次のエラーになります。受信したメッセージは delete_message_batch でまとめて消せます。
r = sqs.send_message_batch(QueueUrl=empty, Entries=[{"Id": str(i), "MessageBody": f"m{i}"} for i in range(10)])
print("成功", len(r["Successful"]), "エラー", len(r.get("Failed", [])))
from botocore.exceptions import ClientError
try:
sqs.send_message_batch(QueueUrl=empty, Entries=[{"Id": str(i), "MessageBody": f"m{i}"} for i in range(11)])
except ClientError as e:
print(e.response["Error"]["Code"], "|", e.response["Error"]["Message"])
total = 0
while True:
ms = sqs.receive_message(QueueUrl=empty, MaxNumberOfMessages=10, WaitTimeSeconds=1).get("Messages", [])
if not ms:
break
r = sqs.delete_message_batch(QueueUrl=empty, Entries=[
{"Id": m["MessageId"], "ReceiptHandle": m["ReceiptHandle"]} for m in ms])
total += len(r["Successful"])
print("受信して削除した件数:", total)
成功 10 エラー 0
AWS.SimpleQueueService.TooManyEntriesInBatchRequest | Maximum number of entries per request are 10. You have sent 11.
受信して削除した件数: 10
バッチの応答は、全体が成功でも Failed に個別のエラーが入ることがあるので、Failed が空かどうかを確認します。
キューの中身を全部消すのが purge-queue です。処理中のメッセージも含めて消えます。
aws sqs purge-queue --queue-url "$Q"
echo "exit=$?"
aws sqs get-queue-attributes --queue-url "$Q" \
--attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible
exit=0
{
"Attributes": {
"ApproximateNumberOfMessages": "0",
"ApproximateNumberOfMessagesNotVisible": "0"
}
}
boto3 では sqs.purge_queue(QueueUrl=url) です。元に戻せない操作なので、テスト用のキューを空にする用途に向いています。
標準キューとFIFOキューの違い
| 観点 | 標準キュー | FIFO キュー |
|---|---|---|
| 順序 | できるだけ送信順(保証はされない) | メッセージグループの中で送信順を保つ |
| 重複 | 少なくとも1回届く(2回以上届くことがある) | 5分間の重複排除で、同じメッセージを1回だけ届ける |
| キュー名 | 英数字・ハイフン・アンダースコア | 末尾を .fifo にする |
| 作成時の属性 | なし | FifoQueue=true |
| MessageGroupId | - | 送信時に必須 |
| MessageDeduplicationId | - | 必須(ContentBasedDeduplication が有効なら省略できる) |
| スループット | ほぼ無制限 | API ごとに毎秒300回、バッチで毎秒3,000メッセージ(高スループットモードあり) |
| 向いている場面 | 順序を問わない大量の処理 | 同じ対象への操作を順番どおりに処理したいとき |
順序・重複・スループットの行は公式開発者ガイド「Amazon SQS queue types」の記述、それ以外は以下で動かした結果です。
FIFOキューを作って送る
echo "--- 名前が .fifo で終わっていない"
aws sqs create-queue --queue-name tasks --attributes FifoQueue=true
echo "--- 正しい作り方"
aws sqs create-queue --queue-name tasks.fifo --attributes FifoQueue=true
F=$(aws sqs get-queue-url --queue-name tasks.fifo --query QueueUrl --output text)
--- 名前が .fifo で終わっていない
An error occurred (InvalidParameterValue) when calling the CreateQueue operation: Queue name must end in .fifo for FIFO queues
--- 正しい作り方
{
"QueueUrl": "https://sqs.ap-northeast-1.amazonaws.com/123456789012/tasks.fifo"
}
MessageGroupIdを省いたときのエラー
echo "--- MessageGroupId なし"
aws sqs send-message --queue-url "$F" --message-body 'step1'
echo "--- MessageGroupId あり・MessageDeduplicationId なし"
aws sqs send-message --queue-url "$F" --message-body 'step1' --message-group-id C001
echo "--- 両方あり"
aws sqs send-message --queue-url "$F" --message-body 'step1' \
--message-group-id C001 --message-deduplication-id d-001
--- MessageGroupId なし
An error occurred (MissingParameter) when calling the SendMessage operation: The request must contain the parameter MessageGroupId.
--- MessageGroupId あり・MessageDeduplicationId なし
An error occurred (InvalidParameterValue) when calling the SendMessage operation: The queue should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly
--- 両方あり
{
"MD5OfMessageBody": "2e957eb8c030fc2d10de5607715f4c55",
"MessageId": "953e8154-df47-4c9b-9d08-edd0b53c26bd",
"SequenceNumber": "84826278019568613503"
}
| エラー | 直し方 |
|---|---|
| MissingParameter: The request must contain the parameter MessageGroupId. | --message-group-id(boto3 は MessageGroupId=)を付ける |
| InvalidParameterValue: The queue should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly | --message-deduplication-id を付けるか、キューの ContentBasedDeduplication を true にする |
| InvalidParameterValue: Queue name must end in .fifo for FIFO queues | キュー名の末尾を .fifo にする |
FIFO の送信が成功すると、応答に SequenceNumber が加わります。
重複排除の動き
同じ MessageDeduplicationId でもう一度送ると、送信は受け付けられますが、キューには追加されません。公式開発者ガイドによると、この重複排除は5分間はたらきます。
aws sqs send-message --queue-url "$F" --message-body 'step1' \
--message-group-id C001 --message-deduplication-id d-001 --query MD5OfMessageBody --output text
aws sqs get-queue-attributes --queue-url "$F" --attribute-names ApproximateNumberOfMessages \
--query Attributes.ApproximateNumberOfMessages --output text
2e957eb8c030fc2d10de5607715f4c55
1
2回送っても、キューにあるのは1件です。ContentBasedDeduplication を有効にすると、本文から重複排除用の ID が自動で作られ、MessageDeduplicationId を省けます。
aws sqs set-queue-attributes --queue-url "$F" --attributes ContentBasedDeduplication=true
aws sqs send-message --queue-url "$F" --message-body 'step2' --message-group-id C001 --query MD5OfMessageBody --output text
aws sqs send-message --queue-url "$F" --message-body 'step2' --message-group-id C001 --query MD5OfMessageBody --output text
aws sqs receive-message --queue-url "$F" --max-number-of-messages 10 \
--message-system-attribute-names All \
--query 'Messages[].[Body,Attributes.MessageGroupId,Attributes.MessageDeduplicationId]' --output json
9b782e40768fc9007786b032ba7911aa
9b782e40768fc9007786b032ba7911aa
[
[
"step1",
"C001",
"d-001"
],
[
"step2",
"C001",
"99cb29fd2d2e3f41f1923c47972861b3beb6852cfb8e608950d83441b7b4b904"
]
]
同じ本文の step2 を2回送りましたが、届いたのは1件です。
メッセージグループと順序
FIFO キューの順序は、MessageGroupId が同じメッセージの間で保たれます。2つのグループを交互に送って、受信の順を確かめます。
fifo = sqs.create_queue(QueueName="demo.fifo", Attributes={
"FifoQueue": "true", "ContentBasedDeduplication": "true"})["QueueUrl"]
for g, b in [("C001", "A-1"), ("C002", "B-1"), ("C001", "A-2"), ("C001", "A-3"), ("C002", "B-2")]:
sqs.send_message(QueueUrl=fifo, MessageBody=b, MessageGroupId=g)
def recv_fifo(n):
ms = sqs.receive_message(QueueUrl=fifo, MaxNumberOfMessages=n,
MessageSystemAttributeNames=["MessageGroupId"]).get("Messages", [])
return ms, [(m["Attributes"]["MessageGroupId"], m["Body"]) for m in ms]
ms1, v = recv_fifo(1); print("1件だけ受信 :", v)
ms2, v = recv_fifo(10); print("A-1 を消す前に受信 :", v)
for m in ms1 + ms2:
sqs.delete_message(QueueUrl=fifo, ReceiptHandle=m["ReceiptHandle"])
ms3, v = recv_fifo(10); print("削除した後に受信 :", v)
1件だけ受信 : [('C001', 'A-1')]
A-1 を消す前に受信 : [('C002', 'B-1'), ('C002', 'B-2')]
削除した後に受信 : [('C001', 'A-2'), ('C001', 'A-3')]
グループ C001 の先頭(A-1)が処理中の間、同じグループの A-2 と A-3 は受信されず、別グループの C002 だけが届きました。A-1 を削除すると、続きの A-2、A-3 が順番どおりに届きます。順番を守りたい単位(顧客 ID や注文 ID など)を MessageGroupId にするのが設計の要点です。全メッセージを同じグループにすると全体が1列になり、並行して処理できる数が1つに限られます。
属性の早見表
キューの属性
set-queue-attributes や create-queue の --attributes で指定します。範囲と既定値は公式 API リファレンス(SetQueueAttributes)の記載です。
| 属性 | 意味 | 範囲 | 既定 |
|---|---|---|---|
| VisibilityTimeout | 受信後に見えなくなる秒数 | 0〜43,200(12時間) | 30 |
| MessageRetentionPeriod | メッセージを保持する秒数 | 60〜1,209,600(14日) | 345,600(4日) |
| DelaySeconds | 送信後、受信できるようになるまでの秒数 | 0〜900(15分) | 0 |
| ReceiveMessageWaitTimeSeconds | 受信時に待つ秒数の既定 | 0〜20 | 0 |
| MaximumMessageSize | メッセージの最大サイズ(バイト) | 1,024〜1,048,576 | 1,048,576 |
| RedrivePolicy | DLQ の ARN と maxReceiveCount の JSON | - | なし |
| FifoQueue | FIFO キューにする(作成時のみ) | true | - |
| ContentBasedDeduplication | 本文から重複排除 ID を作る(FIFO のみ) | true / false | false |
属性の変更が全体に行き渡るまでには最大60秒、MessageRetentionPeriod は最大15分かかることがあると、同リファレンスに書かれています。
受信メッセージのシステム属性
| 属性 | 意味 |
|---|---|
| ApproximateReceiveCount | これまでに受信された回数。DLQ へ移す判定に使われる |
| SentTimestamp | 送信された時刻(ミリ秒) |
| ApproximateFirstReceiveTimestamp | 最初に受信された時刻(ミリ秒) |
| SenderId | 送信した IAM の ID |
| MessageGroupId | FIFO のグループ ID |
| MessageDeduplicationId | FIFO の重複排除 ID |
| SequenceNumber | FIFO でメッセージに付く連番 |
エラーと例外処理
boto3 では、エラーは botocore.exceptions.ClientError として届き、e.response["Error"]["Code"] で種類を見分けます。
def show(label, f):
try:
f(); print(label, "-> OK")
except ClientError as e:
print(f"{label}\n -> {type(e).__name__} / Code={e.response['Error']['Code']}\n {e.response['Error']['Message']}")
except Exception as e:
print(f"{label}\n -> {type(e).__name__}: {e}")
show("存在しないキュー名で get_queue_url", lambda: sqs.get_queue_url(QueueName="no-such-queue"))
show("でたらめな ReceiptHandle で削除", lambda: sqs.delete_message(QueueUrl=url, ReceiptHandle="invalid"))
show("本文に dict を渡す", lambda: sqs.send_message(QueueUrl=url, MessageBody={"a": 1}))
show("同じ名前・違う属性で create_queue",
lambda: sqs.create_queue(QueueName="demo-queue", Attributes={"VisibilityTimeout": "10"}))
show("同じ名前・同じ属性で create_queue",
lambda: sqs.create_queue(QueueName="demo-queue", Attributes={"VisibilityTimeout": "3"}))
show("FIFO で MessageGroupId なし", lambda: sqs.send_message(QueueUrl=fifo, MessageBody="x"))
存在しないキュー名で get_queue_url
-> QueueDoesNotExist / Code=AWS.SimpleQueueService.NonExistentQueue
The specified queue does not exist.
でたらめな ReceiptHandle で削除
-> ReceiptHandleIsInvalid / Code=ReceiptHandleIsInvalid
The input receipt handle is invalid.
本文に dict を渡す
-> ParamValidationError: Parameter validation failed:
Invalid type for parameter MessageBody, value: {'a': 1}, type: <class 'dict'>, valid types: <class 'str'>
同じ名前・違う属性で create_queue
-> QueueNameExists / Code=QueueAlreadyExists
The specified queue already exists.
同じ名前・同じ属性で create_queue -> OK
FIFO で MessageGroupId なし
-> ClientError / Code=MissingParameter
The request must contain the parameter MessageGroupId.
| Code | 状況 | 直し方 |
|---|---|---|
| AWS.SimpleQueueService.NonExistentQueue | キューが無い、またはリージョンが違う | aws sqs list-queues で名前とリージョンを確認 |
| ReceiptHandleIsInvalid | ReceiptHandle の値が正しくない | 受信の応答の ReceiptHandle をそのまま渡す(MessageId ではない) |
| QueueAlreadyExists | 同じ名前で属性の違うキューを作ろうとした | 属性をそろえるか、set-queue-attributes で変更する |
| MissingParameter | FIFO で MessageGroupId が無い | MessageGroupId を付ける |
| ParamValidationError(通信前) | 本文が文字列でない | json.dumps で文字列にする |
同じ名前・同じ属性の create_queue はエラーにならず、既存の URL が返ります。 起動時に「無ければ作る」処理として安心して呼べます。
キューが無いときの例外クラスは、上の出力のとおり QueueDoesNotExist です。コードの文字列は長い形なので、except sqs.exceptions.QueueDoesNotExist: と例外クラスで受けると読みやすくなります。
受信ループ全体の形は次のとおりです。処理で例外が出たメッセージは削除せず、可視性タイムアウトと DLQ に任せます。
def worker(queue_url, handler, max_loops):
for _ in range(max_loops):
msgs = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=10,
WaitTimeSeconds=1).get("Messages", [])
for m in msgs:
try:
handler(m["Body"])
except Exception as e:
print(f" 処理できず(消さずに残す): {m['Body']} / {e}")
continue
sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=m["ReceiptHandle"])
print(f" 完了して削除: {m['Body']}")
def handler(body):
if body == "bad":
raise ValueError("形式が違います")
for b in ["ok-1", "bad", "ok-2"]:
sqs.send_message(QueueUrl=url, MessageBody=b)
worker(url, handler, max_loops=1)
print(counts(url))
完了して削除: ok-1
処理できず(消さずに残す): bad / 形式が違います
完了して削除: ok-2
受信可=0 処理中=1
掲載コードでは確認のため待ち時間を1秒、ループを1回にしています。常時動かす処理では WaitTimeSeconds=20 にして、ループを続ける形にします。
よくある質問
Q. 受信したのにメッセージが消えないのはなぜですか? A. SQS は受信しただけでは消さない仕組みだからです。処理が終わったら、受信の応答に入っている ReceiptHandle を渡して delete-message を呼びます。消さずにいると、可視性タイムアウトの後に再び受信されます。
Q. 標準キューと FIFO キューはどちらを選べばよいですか? A. 順番が入れ替わっても、同じメッセージが2回届いても困らない処理なら標準キューが向いています。同じ対象への操作を送信した順に処理したい場合は FIFO キューを選び、順番を守りたい単位を MessageGroupId にします。
Q. ReceiptHandle と MessageId はどう違いますか? A. MessageId はメッセージそのものの ID で、何度受信しても同じです。ReceiptHandle は受信のたびに発行される値で、削除や可視性タイムアウトの変更にはこちらを使います。
まとめ
- SQS の基本は「送る・受け取る・消す」の3操作。 受信しただけでは消えず、ReceiptHandle を渡して delete-message を呼ぶ
- 削除しないまま可視性タイムアウト(既定30秒)が過ぎると、同じメッセージが再び受信できる状態に戻る
- 処理が長引くときは change-message-visibility で延ばし、0を指定するとすぐ他へ回せる
- 何度も受信されるメッセージは、RedrivePolicy の maxReceiveCount を超えた時点で DLQ へ移る
- 受信ループは WaitTimeSeconds=20 のロングポーリングが基本。 空のときは Messages キーが無いので
get("Messages", [])で受ける - FIFO キューは名前の末尾が
.fifo、送信に MessageGroupId が必須。 省くと MissingParameter になる - FIFO の順序はメッセージグループ単位。 順番を守りたい単位を MessageGroupId にする
参考・出典
- Amazon SQS 開発者ガイド「Amazon SQS visibility timeout」(既定30秒、上限12時間、受信では削除されないこと)
- Amazon SQS 開発者ガイド「Amazon SQS queue types」(標準と FIFO の順序・配信・スループット、.fifo の命名)
- Amazon SQS 開発者ガイド「Using dead-letter queues in Amazon SQS」(maxReceiveCount、キューの種類、保持期間、リドライブ)
- Amazon SQS 開発者ガイド「Amazon SQS short and long polling」(待ち時間の上限20秒、指定の優先順位)
- Amazon SQS 開発者ガイド「Using the Amazon SQS message deduplication ID」(重複排除の5分間)
- Amazon SQS API リファレンス「SetQueueAttributes」(各属性の範囲と既定値、変更が行き渡るまでの時間)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Amazon SQSの使い方|送受信・削除と可視性タイムアウト、FIFO」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。