対象読者: AWSをこれから触る人。 AWS勉強シリーズの1本目で、Amazon SQSがどういうものかを勉強します。全体の地図は索引記事にあります。
SQSとは何か
Amazon SQS(Simple Queue Service)は、AWSが提供するフルマネージド型のメッセージキューサービスです。ソフトウェアの部品同士の間でメッセージ(仕事の依頼)を送信・保存・受信するための待ち行列を用意してくれるもので、キューを動かすサーバーの構築や運用は要りません。量に応じて自動で伸縮し、メッセージを失わずに保管します。
主な用途
- サービスの分離: Webの受付(フロント)と重い処理(バックエンド)の間にキューを挟み、受付は即応答、処理は後ろの係(ワーカー)が順番に捌く。ワーカーが止まっていても依頼はキューに溜まって失われない
- 負荷の平準化: 依頼が急増しても、ワーカーは自分のペースで取り出せる。ワーカーの台数を増減してスケールする
- 順序が大事な処理: FIFOキューを使うと、送った順序を守ったまま大量のメッセージを処理できる
料金の考え方
リクエスト(送信・受信・削除などのAPI呼び出し)の回数に対する従量課金です。毎月100万リクエストまでは無料枠に含まれます。ペイロードは64KBごとに1リクエストとして数えられ、FIFOキューは標準キューと料金が異なります。個人の学習用途なら無料枠の中でまず収まります。
関連サービス
- SNS: 1回の発行を複数の宛先に配る通知サービス。SNS→SQSの組み合わせが定番(次回で扱います)
- Lambda: SQSキューをトリガーにして関数を動かせる
- EventBridge: サービス間のイベント連携。SQSはその配送先の一つになる
登場する用語
| 用語 | 意味 |
|---|---|
| キュー | メッセージの待ち行列本体 |
| メッセージ | キューに入れる仕事1件分の中身 |
| ワーカー | キューから仕事を取り出して処理する側のプログラム |
| 可視性タイムアウト | 受信したワーカーがそのメッセージを独占できる制限時間 |
| ReceiptHandle | 受信ごとに発行される受領証。削除に使う |
| DLQ(デッドレターキュー) | 何回やっても処理に失敗するメッセージの隔離先 |
一番大事な仕組み。受信してもメッセージは消えない
SQSで最初につまずくのがここです。メッセージは受信しただけでは消えません。 受信すると一定時間(可視性タイムアウト、既定30秒)だけ「不可視」になり、他のワーカーから見えなくなります。時間内に削除されなければ、メッセージはキューに復活して、また誰かが受信できるようになります。
なぜこうなっているかというと、処理の途中でワーカーが落ちても仕事を失わないためです。受信した瞬間に消える設計だと、処理する前にワーカーが死んだらその仕事は永遠に消えます。SQSは「削除の連絡が来るまで完了と見なさない」ことで、これを防いでいます。
だから使う側のルールはこうなります。
- 受信= 仕事の予約(独占権をもらう)
- 削除= 処理完了の報告
- 削除を忘れると、成功した処理が可視性タイムアウトのたびに再配達されて二重に走る
このコードを動かす前提
この記事のコードはPython(boto3)です。動かす方法は2通りあります。
A. AWSアカウント無しで試す(おすすめ): motoというAWSのそっくりさんを使うと、課金もサインアップもなしでPCの中だけで動きます。
python3 -m venv venv
./venv/bin/pip install boto3 moto
from moto import mock_aws
@mock_aws # この中のboto3呼び出しは全部ローカルの偽AWSに行く
def main() -> None:
... # 以下の記事のコードをここに入れる
main()
B. 本物のAWSで動かす: 先にIAMユーザーとアクセスキーを作って aws configure で登録しておく必要があります。手順はIAMの記事にあります。
使い方
札は次の4段階を通ります。
4つ目まで進んで、初めてメッセージが消えます。
基本は「キューを作る→送る→受け取る→削除する」の4手です。Python(boto3)での最小の形はこうなります。
import boto3
sqs = boto3.client("sqs", region_name="ap-northeast-1")
# ① キューを作る(URLがそのキューの宛先になる)
q = sqs.create_queue(QueueName="my-queue")["QueueUrl"]
# ② 送る
sqs.send_message(QueueUrl=q, MessageBody="やってほしい仕事の中身")
# ③ 受け取る
res = sqs.receive_message(QueueUrl=q, WaitTimeSeconds=20) # 最大20秒待つ(ロングポーリング)
msgs = res.get("Messages", []) # 本物のAWSでは空で返ることがあるので必ず確認する
if not msgs:
raise SystemExit("メッセージなし")
msg = msgs[0]
print(msg["Body"]) # -> やってほしい仕事の中身
# ④ 処理が終わったら削除する(これをやらないと同じ仕事がまた来る)
sqs.delete_message(QueueUrl=q, ReceiptHandle=msg["ReceiptHandle"])
可視性タイムアウトを時間軸で見る
上の「消えない」を時間軸に並べるとこうなります。可視性タイムアウトを検証用に2秒にして、受信を3回叩いた実測です。
0.0秒 send_message("job-1")
0.0秒 receive_message → 1件 (ApproximateReceiveCount=1) ← ここから2秒間は他から見えない
0.0秒 receive_message → 0件 ← 不可視
2.5秒 receive_message → 1件 (ApproximateReceiveCount=2) ← 削除しなかったので復活。2回目の配達
ApproximateReceiveCount が「何回配られたか」のカウンタで、DLQの maxReceiveCount が見ているのもこの数字です。
処理が長引いて既定の30秒を超えそうなら、削除の前に change_message_visibility で延長できます。
sqs.change_message_visibility(QueueUrl=q, ReceiptHandle=msg["ReceiptHandle"],
VisibilityTimeout=300) # あと5分は他に見せない
延長直後にもう一度受信すると0件に戻ります(実測)。「処理時間の見積もりが読めない仕事」は、短い可視性タイムアウト+処理中に延長、の組み合わせにします。
受信の待ち方。ショートポーリングとロングポーリング
receive_message には2つの待ち方があります。
| WaitTimeSeconds | 空のとき | 向き | |
|---|---|---|---|
| ショートポーリング | 0(既定) | 即0件で返る | 待てない場面。ただし空振りのたびに課金とCPUを使う |
| ロングポーリング | 1〜20 | メッセージが来るまで最大その秒数待ってから返る | ワーカーの常駐ループ。空振りが減る |
空のキューに対して実測すると、WaitTimeSeconds=0 は0.00秒で、=3 は3.03秒で返ってきました。ワーカーをループで回すなら20秒にしておくのが定石で、上の「使い方」のコードでも20を入れています。キュー側の属性 ReceiveMessageWaitTimeSeconds に入れておけば、毎回指定しなくても効きます。
もう1つ、ショートポーリングには「複数サーバーに分散しているキューの一部しか見ない」性質があり、メッセージがあるのに0件が返ることがあります。「送ったはずなのに受信できない」の正体がこれで、ロングポーリングにすると全サーバーを見るので解消します。
失敗し続けるメッセージはDLQへ隔離する
中身が壊れていて何回処理しても失敗するメッセージがキューに居座ると、復活→失敗→復活を繰り返します。対策がDLQ(デッドレターキュー)で、「指定回数受信されても削除されなかったメッセージを、別のキューへ自動で移す」設定です。
# 退避先キューを作って、本体キューにRedrivePolicyを設定する
dlq = sqs.create_queue(QueueName="my-queue-dlq")["QueueUrl"]
dlq_arn = sqs.get_queue_attributes(
QueueUrl=dlq, AttributeNames=["QueueArn"])["Attributes"]["QueueArn"]
q = sqs.create_queue(
QueueName="my-queue",
Attributes={
"RedrivePolicy": '{"deadLetterTargetArn":"%s","maxReceiveCount":"3"}' % dlq_arn
})["QueueUrl"]
maxReceiveCountは「何回までの受信を許すか」です。3にすると、3回受信されても削除されなかったメッセージが、次の受信のタイミングでDLQへ移ります。本番でDLQ無しの運用は、壊れたメッセージの行き場が無くなるので避けてください。
標準キューとFIFOキュー
キューには2種類あります。
| 標準キュー | FIFOキュー(名前が .fifo で終わる) |
|
|---|---|---|
| 順序 | 保証されない | 送った順に届く(同じメッセージグループ内で保証) |
| 重複 | まれに同じメッセージが2回届く | 同じ内容の重複送信を自動で1件に畳める |
| 性能 | ほぼ無制限 | スループット上限あり |
| 使い分け | 量を捌く。受信側を「2回走っても大丈夫」に作る | 順序と一意性が大事な処理(決済など) |
| 送信時の必須項目 | なし |
MessageGroupId(順序を守る単位。例: ユーザーID)。無いとエラー |
無料で試す方法
本物のAWSアカウントが無くても、motoというローカルのそっくりさんでSQSのAPIはひと通り動かせます。
python3 -m venv venv
./venv/bin/pip install boto3 moto
スクリプトの先頭に2行足すだけで、上のコードがそのままPCの中で動きます。
from moto import mock_aws
@mock_aws # この中のboto3呼び出しは全部ローカルの偽AWSに行く
def main() -> None:
...
motoはAPIの動きを再現するもので、本物の分散システムとしての性質(標準キューのまれな重複など)までは再現しない点だけ頭に置いてください。
下の絵が、この記事で一番大事な点です。
左が削除を忘れた場合、右が削除した場合です。
まとめ
- SQSはサービス間の待ち行列。受付と処理を切り離す緩衝材
- 受信では消えない。削除が処理完了の報告で、忘れると二重実行になる
- 本番はDLQを設定して、失敗し続けるメッセージの行き場を作る
次回: SNS入門。1回のpublishが購読者全員に届く仕組みと、SQSとの違い
参考
- Amazon SQS 公式ドキュメント
- シリーズ索引: AWSとは何か


