awssnssqss3event-drivenfan-out

SNS ファンアウト — 1 つの S3 イベントを 2 つの SQS キューとメールへ

TPThuanPD2026年9月6日75分新着
AWS

コンソールでイベント駆動の経路を構築します。S3 が ObjectCreated:Put を SNS topic に publish し、topic が 2 つの SQS キューと 1 つのメールへファンアウト。全体が動くか静かに失敗するかを決める 3 つの最小権限リソースポリシー付き。

アーキテクチャ図: S3 バケットが ObjectCreated:Put を SNS topic に publish し、2 つの SQS キューと 1 つのメール subscriber へファンアウトする

Lab Overview and Learning Objectives

はじめる前に

このラボでは AWS コンソールだけでイベント駆動の経路を一通り構築します。S3 にオブジェクトが PUT されるたびにバケットが SNS topic へメッセージを publish し、topic がそれを同時に 3 か所へ複製します — 処理システム用の SQS キュー 2 つと、担当者用のメール 1 つ。これが典型的なファンアウトです。S3 は宛先を 1 つだけ知っていればよく、consumer が何個あるかは SNS の問題になります。リソースはすべて us-east-1 に作り、ラボ規模なら Free Tier の範囲内です。

  1. Amazon SNS、Amazon SQS、Amazon S3 を操作できる権限を持つ AWS アカウント。
  2. すぐ開けるメールアドレス 1 つ — 受信箱の確認リンクをクリックする手順があります。
  3. Account ID を手元にメモ: このラボの ARN はすべて必要とします。
  4. 3 つのサービスすべてで同一リージョンを使うこと。本ラボは us-east-1。
  5. トリガー用の小さいファイルを 1 つ用意(数 KB の .json など)。
注意:SNS と SQS は、リソースポリシーが送信元を許可していないとメッセージを受け取りません。このラボでよくある失敗 4 つのうち 3 つはポリシー起因で、しかもエラーがどこにも出ず静かに失敗します。手順 9、12、23 が勝負どころです。
目的:最後にファイルを 1 つアップロードすると、同じイベントが 3 か所 — 受信箱、demo-sqs-worker-1、demo-sqs-worker-2 — にそれぞれ独立したコピーとして届きます。
1

1. ファンアウト構成と構築順序

図が経路全体を示しています。S3 が producer、SNS topic が broker、SQS キュー 2 つとメール 1 つが subscriber です。要点は SNS がメッセージを保存しないこと — 即座に配信するので、その瞬間に受け手がいなければメッセージは失われます。だから間に SQS を置きます。キューはメッセージを保持して consumer が後で取りに来られるようにし、メールは人間が状況を知るためだけに存在します。構築順序は入れ替えられません。subscription は Topic ARN を必要とするので topic が先。S3 の event notification は topic が存在し、かつ S3 の publish が許可済みであることを要求するので、バケットとポリシーはイベント作成より先に終わらせます。

  1. 手順 2–6: SNS topic を作成し、Topic ARN をコピー。
  2. 手順 7–14: SQS キューを 2 つ作成し、それぞれ topic からの送信を許可。
  3. 手順 15–21: subscription を 3 つ登録(キュー 2 + メール 1)し、メールを確認。
  4. 手順 22–27: バケット作成 → topic への publish 許可 → event notification 作成。
  5. 手順 28–35: ファイルを 1 つアップロードし、同じイベントを 3 つの subscriber で追跡。
  6. 手順 36–38: consumer 側で JSON を展開し、障害を切り分け、後片付け。
ファンアウト図: バケット demo-source-data-test が s3:ObjectCreated:Put を SNS topic demo-s3-alerts-topic へ発行し、topic が SQS キュー demo-sqs-worker-1 / demo-sqs-worker-2 と AWS 外のメール subscriber へ複製する
ファンアウト図: バケット demo-source-data-test が s3:ObjectCreated:Put を SNS topic demo-s3-alerts-topic へ発行し、topic が SQS キュー demo-sqs-worker-1 / demo-sqs-worker-2 と AWS 外のメール subscriber へ複製する
完了条件:何かをクリックする前に経路全体を頭に描けており、各手順が鎖のどの環を作っているのか分かります。
2

2. Amazon SNS コンソールを開く

コンソール上部の検索欄に「SNS」と入力します。Services の中に「Simple Notification Service」が「SNS managed message topics for Pub/Sub」という説明付きで出るので、それを選びます。この時点で右上のリージョンが US East (N. Virginia) us-east-1 であることを確認してください。SNS、SQS、S3 event notification は同一リージョンでないと接続できませんが、コンソールは別リージョンで作っても警告してくれません。

  1. AWS Management Console にサインイン。
  2. 右上のリージョンが US East (N. Virginia) us-east-1 であることを確認。
  3. SNS で検索し、Simple Notification Service を選択。
AWS Management Console の検索欄で SNS サービスを検索している画面
AWS Management Console の検索欄で SNS サービスを検索している画面
完了条件:us-east-1 の Amazon SNS ダッシュボードが表示されます。
3

3. Topics から Create topic を押す

左メニューで Topics を選びます。新規アカウントでは「No topics — To get started, create a topic」と空のテーブルが出ます。Create topic はテーブル右上と空白領域の中央の 2 か所にあり、どちらも同じフォームを開きます。左メニューには Subscriptions もあり、これは全 topic の subscription を一覧する画面で、手順 19 で使います。

  1. 左メニューで Topics を選択。
  2. Create topic をクリック。
空の Amazon SNS Topics 画面と、強調された Create topic ボタン
空の Amazon SNS Topics 画面と、強調された Create topic ボタン
完了条件:Create topic フォームが開き、先頭に Details ブロックが表示されます。
4

4. Type は Standard、topic に名前を付ける

Type は作成後に変更できず、このラボでは Standard 必須です。理由は明快で、FIFO topic は protocol が SQS の 1 種類しか使えないため、FIFO を選ぶと手順 18 のメール subscription が作れません。代償として Standard は best-effort ordering と at-least-once delivery しか保証しません — 順序が入れ替わることも、2 回届くこともあるので、consumer は冪等に作る必要があります。Name に demo-s3-alerts-topic を入力。Display name はメールや SMS で送信者として表示される名前なので、同じ値を入れて送信元が分かるようにします。

  1. Type で Standard を選択。
  2. Name に demo-s3-alerts-topic を入力。
  3. Display name に demo-s3-alerts-topic を入力。
  4. Encryption、Access policy、Delivery policy、Delivery status logging は既定のまま — access policy は Bucket ARN が揃う手順 23 で編集します。
Create topic フォームで Type が Standard、Name と Display name がどちらも demo-s3-alerts-topic
Create topic フォームで Type が Standard、Name と Display name がどちらも demo-s3-alerts-topic
注意:FIFO は選ばないこと。FIFO topic は SQS subscription しか受け付けず、名前も .fifo で終わる必要があります。間違えると Type が不変なので topic を削除して手順 3 からやり直しです。
完了条件:topic が Standard・名前 demo-s3-alerts-topic となり、SQS subscriber とメール subscriber の両方を受け入れられる状態です。
5

5. Name タグを付けて Create topic

下へスクロールして Tags ブロックを開き、Key = Name、Value = demo-s3-alerts-topic を追加します。Name タグはコンソールが読みやすいラベルとして使い、Cost Explorer と Resource Groups はタグでリソースをグループ化します。ラボでは単なる規律ですが、その規律のおかげで手順 38 で全部を探して消せます。オレンジ色の Create topic ボタンは画面右下です。

  1. Tags - optional ブロックを開く。
  2. Add new tag をクリックし、Key = Name、Value = demo-s3-alerts-topic を入力。
  3. Active tracing (AWS X-Ray) はスキップ — 追加課金があり、ラボには不要。
  4. Create topic をクリック。
Name = demo-s3-alerts-topic のタグと、右下の Create topic ボタン
Name = demo-s3-alerts-topic のタグと、右下の Create topic ボタン
完了条件:topic が作成され、demo-s3-alerts-topic の詳細画面に遷移します。
6

6. Topic ARN をコピーする

topic 詳細画面の Details ブロックには Name、ARN、Display name、Type の 4 項目が並びます。ARN の横のコピーアイコンを押すと「ARN copied」というチップが出ます。ARN は arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic の形で、手順 16・17・18(各 subscription の Topic ARN)と手順 9・12(queue policy の aws:SourceArn 条件)で貼り付けます。5 回も戻ってこないよう、テキストファイルに控えておいてください。下の Subscriptions タブは「Subscriptions (0)」— 現時点では想定どおりです。

  1. Details ブロックの ARN 横のコピーアイコンをクリック。
  2. ARN を Account ID と一緒に作業用テキストへ貼る。
  3. Subscriptions タブが Subscriptions (0) であることを確認。
demo-s3-alerts-topic の詳細画面。ARN をコピーした直後で、Subscriptions テーブルは空
demo-s3-alerts-topic の詳細画面。ARN をコピーした直後で、Subscriptions テーブルは空
完了条件:Topic ARN が手元にあります: arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic。topic は存在しますが subscriber がいないので、今 publish したものは破棄されます。
7

7. Amazon SQS コンソールを開く

Amazon SQS に移ります。「SQS」で検索するか、上部のピン留めサービスバーを使います。左メニューは Queues の 1 項目だけです。新規アカウントでは「No queues — No queues available」と表示されます。Messages available と Messages in flight の列に注目 — この 2 つの数値は手順 33 で検証に使います。

  1. SQS で検索し、Simple Queue Service を選択。
  2. リージョンが us-east-1 のままであることを確認。
  3. 左メニューで Queues を選び、Create queue をクリック。
空の Amazon SQS Queues 画面と、強調された Create queue ボタン
空の Amazon SQS Queues 画面と、強調された Create queue ボタン
完了条件:Create queue フォームが開きます。
8

8. キュー demo-sqs-worker-1 を作成

Standard topic に合わせて Standard を選びます。コンソールも「You can't change the queue type after you create a queue」と注意してくれます。Name は demo-sqs-worker-1。Configuration の 4 つの値はラボでは既定でよいのですが、障害時の挙動を決めるので意味は押さえておく価値があります。Visibility timeout 30 秒は、受信後にそのメッセージが他の consumer から隠れる時間で、30 秒以内に削除しきれなければメッセージが再出現し二重処理されます。Message retention period 4 日は誰も取らないメッセージを保持する期間。Maximum message size 1024 KiB は S3 イベントが 1–2 KB なので十分。Receive message wait time 0 秒は short polling を意味します。

  1. Type で Standard を選択。
  2. Name に demo-sqs-worker-1 を入力。
  3. Visibility timeout は 30 Seconds、Message retention period は 4 Days のまま。
  4. Delivery delay 0、Maximum message size 1024 KiB、Receive message wait time 0 Seconds のまま。
  5. まだ Create queue は押さず、Encryption と Access policy までスクロール。
Create queue フォームで Type Standard、Name demo-sqs-worker-1、Configuration は既定値
Create queue フォームで Type Standard、Name demo-sqs-worker-1、Configuration は既定値
完了条件:キューが Standard・名前 demo-sqs-worker-1・既定設定になりました。
9

9. キュー 1 の Encryption と access policy

ここが SNS からメッセージを受け取れるかどうかを決める手順です。Encryption は Server-side encryption = Enabled、Encryption key type = Amazon SQS key (SSE-SQS) のまま。SSE-SQS のキューには SNS が追加設定なしで配信できます。SSE-KMS のカスタマー管理キーに変えると、KMS キーポリシーで sns.amazonaws.com に kms:GenerateDataKey と kms:Decrypt を許可するまでメッセージが静かに消えます — SNS→SQS パターンで最も時間を溶かす罠です。Access policy は Advanced を選び、JSON を直接編集して topic からの送信を許可する statement を追加します。

  1. Server-side encryption = Enabled、Encryption key type = Amazon SQS key (SSE-SQS) を維持。
  2. Access policy で Advanced を選択。
  3. AWS が生成した __owner_statement はそのまま残す — 消すと自分の管理権限を失う。
  4. 下の JSON の AllowSnsTopicToSendMessage statement を追加し、<account-id> を自分の Account ID に置換。
  5. Resource が arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-1 を指していることを確認。
demo-sqs-worker-1 · access policy (Advanced)
{
  "Version": "2012-10-17",
  "Id": "demo-sqs-worker-1-policy",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::<account-id>:root" },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-1"
    },
    {
      "Sid": "AllowSnsTopicToSendMessage",
      "Effect": "Allow",
      "Principal": { "Service": "sns.amazonaws.com" },
      "Action": "sqs:SendMessage",
      "Resource": "arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-1",
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic"
        }
      }
    }
  ]
}
SSE-SQS の Encryption ブロックと、Advanced モードでキューの JSON ポリシーを表示した Access policy ブロック
SSE-SQS の Encryption ブロックと、Advanced モードでキューの JSON ポリシーを表示した Access policy ブロック
注意:元のスクリーンショットは Principal が {"AWS": "*"}、Action が SQS:* — 任意の呼び出し元にキューの全権を与える状態です。動くので多くの記事はそのままですが、本番に持ち込まないこと。下の JSON は 1 サービス・1 アクション・1 送信元 topic に絞っています。aws:SourceArn 条件が、他人の topic から自分のキューへゴミを流し込まれるのを防ぎます。
完了条件:queue policy が principal を sns.amazonaws.com のみ、action を sqs:SendMessage のみに絞り、さらに demo-s3-alerts-topic 発のときだけ許可する状態になります。
10

10. Redrive・DLQ・タグを設定して Create queue

残る 3 ブロックは既定のままです。Redrive allow policy = Disabled、Dead-letter queue = Disabled で十分です。DLQ は consumer が規定回数を超えて処理失敗したメッセージを受け止める場所で、本番では不可欠ですが、失敗する consumer がまだ居ないここでは意味がありません。Key = Name、Value = demo-sqs-worker-1 のタグを付け、右下の Create queue を押します。

  1. Redrive allow policy = Disabled のまま。
  2. Dead-letter queue = Disabled のまま。
  3. タグ Name = demo-sqs-worker-1 を追加。
  4. Create queue をクリック。
Redrive allow policy と Dead-letter queue がどちらも Disabled、タグ Name = demo-sqs-worker-1、Create queue ボタン
Redrive allow policy と Dead-letter queue がどちらも Disabled、タグ Name = demo-sqs-worker-1、Create queue ボタン
完了条件:demo-sqs-worker-1 が作成され、詳細画面が開きます。そこに https://sqs.us-east-1.amazonaws.com/<account-id>/demo-sqs-worker-1 形式の Queue URL があります。
11

11. キュー demo-sqs-worker-2 を作成

Queues に戻り、もう一度 Create queue を押します。2 つ目のキューはファンアウトの本質を示すために存在します — topic に publish された 1 通のメッセージが subscriber ごとに独立したコピーになり、片方のキューが遅くても壊れても他方には影響しません。設定は名前以外キュー 1 と同一です。

  1. Create queue をクリック。
  2. Type で Standard を選択。
  3. Name に demo-sqs-worker-2 を入力。
  4. Configuration ブロックはキュー 1 と同じままにする。
Create queue フォームで Type Standard、Name demo-sqs-worker-2
Create queue フォームで Type Standard、Name demo-sqs-worker-2
完了条件:2 つ目のキューが Standard・名前 demo-sqs-worker-2 になりました。
12

12. キュー 2 の access policy

手順 9 をこのキューで繰り返しますが、1 点だけ注意深く読んでください。Resource は ...:demo-sqs-worker-2 でなければならず、worker-1 ではありません。これはラボ中で最も多いコピペミスであり、しかも最も気づきにくい形で壊れます — subscription は作成でき、状態も Confirmed のまま、エラーもどこにも出ず、ただキュー 2 にメッセージが永遠に来ないだけ。SQS のポリシーはリソースポリシーなので、Resource の ARN が違えばその statement は編集中のキューに適用されません。

  1. Encryption はキュー 1 と同じ SSE-SQS を維持。
  2. Access policy で Advanced を選択。
  3. 手順 9 の JSON を貼り、Id と Resource の両方を demo-sqs-worker-2 に変更。
  4. aws:SourceArn 条件は同じ demo-s3-alerts-topic を指したまま。
  5. スクロールする前に Resource をもう一度読み直す。
2 つ目のキューの Advanced access policy。Resource が arn:aws:sqs:us-east-1:...:demo-sqs-worker-2 を指している
2 つ目のキューの Advanced access policy。Resource が arn:aws:sqs:us-east-1:...:demo-sqs-worker-2 を指している
注意:後でキュー 1 にメッセージがありキュー 2 が空だった場合は、他を調べる前にこの Resource フィールドへ戻ってください。
完了条件:demo-sqs-worker-2 が、自分自身の ARN を指した statement で同じ topic からの送信を許可します。
13

13. タグを付けてキュー 2 を作成

手順 10 と同じです。Redrive allow policy と Dead-letter queue は Disabled、Key = Name、Value = demo-sqs-worker-2 のタグを付けて Create queue を押します。

  1. Redrive allow policy と Dead-letter queue は Disabled のまま。
  2. タグ Name = demo-sqs-worker-2 を追加。
  3. Create queue をクリック。
タグ Name = demo-sqs-worker-2 と右下の Create queue ボタン
タグ Name = demo-sqs-worker-2 と右下の Create queue ボタン
完了条件:demo-sqs-worker-2 が作成されます。
14

14. 2 つのキューが揃ったことを確認

Queues 画面が「Queues (2)」になります。次へ進む前に 4 列を確認してください。Name が正確に demo-sqs-worker-1 と demo-sqs-worker-2(小文字に注意)、Type が両方 Standard、Encryption が Amazon SQS key (SSE-SQS)、そして Messages available が 0 — これが基準値で、手順 33 では 1 か 2 に変わっている必要があります。

  1. テーブルが Queues (2) になっていることを確認。
  2. 両キューの名前、Type = Standard、Encryption = Amazon SQS key (SSE-SQS) を照合。
  3. 両方の Messages available が 0 であることを記録。
Queues (2) テーブルに demo-sqs-worker-1 と demo-sqs-worker-2 が並び、どちらも Standard・SSE-SQS
Queues (2) テーブルに demo-sqs-worker-1 と demo-sqs-worker-2 が並び、どちらも Standard・SSE-SQS
完了条件:topic と同一リージョンに Standard キューが 2 つあり、どちらも空です。producer 側と consumer 側は揃い、両者を繋ぐ配線がまだありません。
15

15. topic に戻って Create subscription

Amazon SNS → Topics → demo-s3-alerts-topic に戻ります。Subscriptions タブはまだ「Subscriptions (0)」で「You don't have any subscriptions to this topic」と出ています。Create subscription は先ほどと同じ 2 か所にあります。隣の 3 つのボタンも覚えておく価値があります。Request confirmation は保留中のメール subscription へ確認メールを再送、Confirm subscription は確認トークンを手貼りする画面、ページ上部の Publish message は S3 を介さずテストメッセージを送る手段で、手順 37 の切り分けに使います。

  1. Amazon SNS → Topics → demo-s3-alerts-topic を開く。
  2. Subscriptions タブで Create subscription をクリック。
demo-s3-alerts-topic の詳細画面。Subscriptions タブは空で Create subscription ボタンがある
demo-s3-alerts-topic の詳細画面。Subscriptions タブは空で Create subscription ボタンがある
完了条件:Create subscription フォームが開き、Topic ARN が入力済みになっています。
16

16. キュー 1 を topic に subscribe

フォームは 3 項目です。topic ページから来た場合 Topic ARN は入力済みですが、念のため照合してください。Protocol は Amazon SQS を選びます。選んだ瞬間に Endpoint の説明が「Only Amazon SQS standard queues will be listed」に変わります — これは topic が Standard であることの直接の帰結です。Endpoint はキューの ARN なので arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-1 を貼ります。「Enable raw message delivery」を未チェックのままにするのは意図的です。ON にすると SQS は S3 が送った内容そのものを受け取り、OFF なら SNS が MessageId・Timestamp・Signature・UnsubscribeURL を持つ封筒で包みます — 手順 35 でその封筒を解剖して違いを可視化します。

  1. Topic ARN が arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic であることを確認。
  2. Protocol で Amazon SQS を選択。
  3. Endpoint に arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-1 を貼る。
  4. Enable raw message delivery は未チェックのまま。
  5. Subscription filter policy と Redrive policy はスキップし、Create subscription をクリック。
Create subscription フォームで Protocol が Amazon SQS、Endpoint が demo-sqs-worker-1 の ARN
Create subscription フォームで Protocol が Amazon SQS、Endpoint が demo-sqs-worker-1 の ARN
注意:endpoint が無効と言われる原因はほぼ 3 つです。キューが topic と別リージョンにある、キュー名の大文字小文字が違う、あるいは Queue ARN (arn:aws:sqs...) ではなく Queue URL (https://sqs...) を貼っている。
完了条件:subscription が作成され、ほぼ即座に Confirmed になります — SQS subscription は自動確認され、メールのような手動確認は不要です。
17

17. キュー 2 を topic に subscribe

手順 16 を Endpoint だけ demo-sqs-worker-2 に変えて繰り返します。この 2 つの subscription は完全に独立で、それぞれ固有の SubscriptionArn、filter policy、配信状態を持ちます。これがファンアウトの利点です — 3 つ目、4 つ目の subscriber を足しても S3 側は一切変更不要です。

  1. もう一度 Create subscription をクリック。
  2. Protocol は Amazon SQS のまま。
  3. Endpoint に arn:aws:sqs:us-east-1:<account-id>:demo-sqs-worker-2 を貼る。
  4. Create subscription をクリック。
Create subscription フォームで Endpoint が demo-sqs-worker-2 の ARN
Create subscription フォームで Endpoint が demo-sqs-worker-2 の ARN
完了条件:topic に Confirmed 状態の SQS subscription が 2 つ揃いました。
18

18. メールアドレスを topic に subscribe

3 つ目の subscription は Email protocol を使います — 手順 4 で Standard topic を選んだ理由がこれです。Endpoint に自分のメールアドレスを入力します。SQS との重要な違いは、メール subscription は自動確認されないこと。AWS がそのアドレスへ確認メールを送り、誰かがリンクを押すまで subscription は Pending confirmation のままで、その間は何も受信しません。確認リンクは 3 日で失効し、その後は Request confirmation ボタンで再送する必要があります。

  1. もう一度 Create subscription をクリック。
  2. Protocol で Email を選択。
  3. Endpoint にすぐ開けるメールアドレスを入力。
  4. Create subscription をクリック。
Create subscription フォームで Protocol が Email、Endpoint にメールアドレス
Create subscription フォームで Protocol が Email、Endpoint にメールアドレス
注意:Email protocol はプレーンテキストのみで HTML は送れず、FIFO topic では使えません。整形や大量配信が必要なら、正しい宛先は SNS ではなく Amazon SES です。
完了条件:メール subscription が Pending confirmation で作成され、AWS が「AWS Notification - Subscription Confirmation」を即座に送信します。
19

19. Subscriptions 一覧を読む: Confirmed と Pending

左メニューの Subscriptions を開くと全 topic の subscription が並ぶので、demo-s3-alerts-topic で絞り込みます。読むべきは Status 列です。SQS の 2 行は緑のチェック付きで Confirmed、EMAIL の行は時計アイコン付きで Pending confirmation、しかも ID 列が SubscriptionArn ではなく「Pending confirmation」という文字列になっています。subscriber が何も受け取らないときは必ずこの画面に戻ってください — 保留状態が原因の第 1 位です。

  1. 左メニューで Subscriptions を選択。
  2. Search 欄に demo-s3-alerts-topic を入力して絞り込む。
  3. Protocol 列を確認: SQS が 2 行、EMAIL が 1 行。
  4. Status 列を確認: SQS = Confirmed、EMAIL = Pending confirmation。
demo-s3-alerts-topic で絞り込んだ Subscriptions テーブル。SQS 行が Confirmed、EMAIL 行が Pending confirmation
demo-s3-alerts-topic で絞り込んだ Subscriptions テーブル。SQS 行が Confirmed、EMAIL 行が Pending confirmation
完了条件:topic に subscription が 3 つあります。2 つのキューは受信可能、メールはまだで、手順 20 まで何も受け取りません。
20

20. メールを開いて Confirm subscription を押す

受信箱で no-reply@sns.amazonaws.com からの「AWS Notification - Subscription Confirmation」を探します。本文には subscribe した topic ARN と Confirm subscription リンクが書かれています。押す前にメール内の ARN が本当に demo-s3-alerts-topic かを確認してください — SNS の確認メールはフィッシングの定番の題材であり、このリンクを押す行為そのものが「自分のアドレスへの送信を許可する」という意思表示です。メールが見つからないときは、まず迷惑メールを確認し、それでも無ければコンソールの Request confirmation を使います。

  1. subscribe したアドレスの受信箱を開く。
  2. AWS Notification - Subscription Confirmation メールを探す。
  3. メール内の Topic ARN が自分の topic と一致することを確認。
  4. Confirm subscription リンクをクリック。
AWS Notification - Subscription Confirmation メールと、強調された Confirm subscription リンク
AWS Notification - Subscription Confirmation メールと、強調された Confirm subscription リンク
完了条件:ブラウザが sns.us-east-1.amazonaws.com の SNS 確認ページを開きます。
21

21. Subscription confirmed ページ

「Subscription confirmed! You have successfully subscribed」という緑のパネルと、発行された SubscriptionArn、unsubscribe リンクが表示されます。コンソールに戻って Subscriptions を再読み込みすると、EMAIL 行が Confirmed になり、ID 列が Pending confirmation の文字列から実際の SubscriptionArn に変わっています。

  1. 確認ページの Subscription confirmed! を読む。
  2. SNS コンソールに戻り Subscriptions を再読み込み。
  3. 3 行すべてが Confirmed であることを確認。
登録成功を伝える Amazon SNS の Subscription confirmed! ページ
登録成功を伝える Amazon SNS の Subscription confirmed! ページ
完了条件:subscriber 3 つ — demo-sqs-worker-1、demo-sqs-worker-2、メールアドレス — がすべて準備完了。SNS 側はこれで完成です。
22

22. バケット demo-source-data-test を作成

Amazon S3 → Buckets → Create bucket に移ります。Bucket type は General purpose、Bucket namespace は Global namespace のまま。Bucket name に demo-source-data-test を入力します。バケット名はグローバルに一意なので、埋まっていたら自分用の接尾辞を足し、実際に使った名前を必ず控えてください(以降の手順すべてがそれを参照します)。Object Ownership は ACLs disabled (recommended) のまま — これが正しい既定で、アクセス制御は ACL ではなく bucket policy で行います。残り(Block Public Access は ON、Versioning は OFF、SSE-S3 暗号化)は既定のまま Create bucket を押します。

  1. Amazon S3 → Buckets → Create bucket を開く。
  2. Bucket type = General purpose、Bucket namespace = Global namespace のまま。
  3. Bucket name に demo-source-data-test を入力。
  4. Object Ownership = ACLs disabled (recommended) のまま。
  5. Block all public access は有効のまま Create bucket をクリック。
S3 の Create bucket フォームで Bucket name が demo-source-data-test、ACLs disabled
S3 の Create bucket フォームで Bucket name が demo-source-data-test、ACLs disabled
注意:バケットは SNS topic と同一リージョンでなければなりません。S3 event notification は別リージョンの topic へ配信できず、しかもコンソールは手順 26 で保存するときに漠然とした検証エラーしか返しません。
完了条件:demo-source-data-test が us-east-1 に作られ、非公開でオブジェクトは 0 件です。
23

23. Bucket ARN をコピーし、S3 に publish を許可する

バケット → Properties タブ → Bucket overview で、Amazon Resource Name (ARN) 横のコピーアイコンを押して arn:aws:s3:::demo-source-data-test を取得します。バケット ARN にリージョンとアカウントの区画が無いのは、バケット名がすでにグローバルに一意だからです。この ARN はラボで最も重要な手順に今すぐ必要です。SNS topic は既定でアカウント所有者の publish しか許可しないため、S3 には現時点で送信権限がありません。Amazon SNS → demo-s3-alerts-topic → Edit → Access policy で、既存の __default_statement_ID を残したまま下の AllowS3BucketToPublish statement を追加します。

  1. バケットの Properties タブで Bucket ARN をコピー。
  2. Amazon SNS → Topics → demo-s3-alerts-topic → Edit を開く。
  3. Access policy ブロックを開き、下の AllowS3BucketToPublish statement を追加。
  4. <account-id> を自分の Account ID に置換し、aws:SourceArn がコピーした Bucket ARN と一致することを確認。
  5. Save changes をクリック。
demo-s3-alerts-topic · access policy
{
  "Version": "2012-10-17",
  "Id": "demo-s3-alerts-topic-policy",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": { "AWS": "*" },
      "Action": [
        "SNS:GetTopicAttributes", "SNS:SetTopicAttributes",
        "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic",
        "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish"
      ],
      "Resource": "arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic",
      "Condition": {
        "StringEquals": { "AWS:SourceOwner": "<account-id>" }
      }
    },
    {
      "Sid": "AllowS3BucketToPublish",
      "Effect": "Allow",
      "Principal": { "Service": "s3.amazonaws.com" },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic",
      "Condition": {
        "StringEquals": { "aws:SourceAccount": "<account-id>" },
        "ArnLike": { "aws:SourceArn": "arn:aws:s3:::demo-source-data-test" }
      }
    }
  ]
}
demo-source-data-test の Properties タブで Bucket ARN arn:aws:s3:::demo-source-data-test をコピーした直後
demo-source-data-test の Properties タブで Bucket ARN arn:aws:s3:::demo-source-data-test をコピーした直後
注意:この statement を忘れるのが典型的な失敗です。手順 26 で Save changes を押すと S3 が「Unable to validate the following destination configurations」を返します。aws:SourceAccount と aws:SourceArn の 2 条件は飾りではありません — 無ければ誰のどの S3 バケットからでもあなたの topic へ publish できてしまいます。
完了条件:topic policy が s3.amazonaws.com サービスに sns:Publish を許可します。ただし自分のアカウントの demo-source-data-test バケットからのリクエストに限ります。
24

24. Properties → Event notifications → Create

バケットの Properties タブで AWS CloudTrail data events を通り過ぎ、Event notifications ブロックまでスクロールします。今は「Event notifications (0)」「No event notifications」です。Create event notification をクリック。すぐ下の Amazon EventBridge ブロックが Off になっていることにも注目 — これが代替ルートです。EventBridge を有効にするとバケットの全イベントがそちらへ流れ、宛先を個別に固定する代わりに rule と pattern でルーティングできます。このラボ程度の単純なファンアウトなら SNS 直結のほうが素直です。

  1. Properties タブで Event notifications ブロックまでスクロール。
  2. Create event notification をクリック。
バケットの空の Event notifications ブロックと、強調された Create event notification ボタン
バケットの空の Event notifications ブロックと、強調された Create event notification ボタン
完了条件:Create event notification フォームが開きます。
25

25. イベント名・prefix/suffix・event type を設定

Event name に Test-notify-sns-handle-ups3 を入力(スクリーンショットに合わせます)。Prefix と Suffix はこのラボでは空のままですが、意味は知る価値があります。prefix に images/ を入れればそのフォルダ配下のオブジェクトだけ、suffix に .jpg を入れれば画像だけを捕まえられます — SNS のメッセージを 1 通消費する前に S3 自身でフィルタする唯一の方法です。Event types では「Put — s3:ObjectCreated:Put」だけにチェックを入れます。「All object create events」(s3:ObjectCreated:*) は選ばないこと。Post・Copy・CompleteMultipartUpload まで一括で含むため、ログを読んだときにオブジェクトの出自が判別できなくなり、大きいファイルの multipart upload 1 回が予想と全く違うイベントを生みます。

  1. Event name に Test-notify-sns-handle-ups3 を入力。
  2. Prefix と Suffix は空のまま。
  3. Object creation で Put (s3:ObjectCreated:Put) にチェック。
  4. All object create events にはチェックせず、Object removal・Restore・Replication・Intelligent-Tiering・Annotation の各グループはスキップ。
Create event notification フォームで Event name が Test-notify-sns-handle-ups3、Put の event type にチェック
Create event notification フォームで Event name が Test-notify-sns-handle-ups3、Put の event type にチェック
注意:同じ event type で prefix/suffix が重なる event notification を 2 つ作ると、S3 が「Configurations overlap」で拒否します。後で複数の宛先が必要になったら、重ならない prefix で分けるか、SNS topic 1 つにファンアウトさせてください — まさにこのラボの構成です。
完了条件:event notification は新しいオブジェクトが PUT されたときだけ発火し、削除・コピー・multipart upload には反応しません。
26

26. destination に SNS topic を選ぶ

Destination ブロックまでスクロールします。選択肢は Lambda function、SNS topic、SQS queue の 3 つ — SNS topic を選びます。S3 に宛先を 1 つだけ知らせたまま、多数の consumer が同じイベントを共有できるようにするのがこれです。Specify SNS topic では「Choose from your SNS topics」を選び、ドロップダウンで demo-s3-alerts-topic を指定します。「Enter SNS topic ARN」は別アカウントの topic 用です。ブロック先頭の青い情報パネルは手順 23 でやったことをそのまま述べています — 先に S3 principal へ権限を与える必要がある、と。Save changes をクリック。

  1. Destination で SNS topic を選択。
  2. Choose from your SNS topics を選択。
  3. SNS topic ドロップダウンで demo-s3-alerts-topic を選択。
  4. Save changes をクリック。
Destination ブロックで SNS topic が選択され、ドロップダウンが demo-s3-alerts-topic を指している
Destination ブロックで SNS topic が選択され、ドロップダウンが demo-s3-alerts-topic を指している
注意:「Unable to validate the following destination configurations」が出た場合、ここは何も変えずに手順 23 に戻って topic の access policy を確認してください。ほぼすべてのケースで真因はそこにあります。
完了条件:S3 が topic の存在とこのバケットの publish 許可を検証し、設定を保存します。
27

27. event notification が作成されたことを確認

緑のバナー「Successfully created event notification "Test-notify-sns-handle-ups3"」が出て、テーブルが「Event notifications (1)」になります。4 列を読んで設定を確定させましょう。Name は Test-notify-sns-handle-ups3、Event types は Put、Filters はダッシュ(prefix/suffix フィルタなし)、Destination type は SNS topic、Destination は demo-s3-alerts-topic へのリンクです。配線はここで完成、まだテストはしていません。

  1. 作成成功の緑バナーを読む。
  2. Event notifications (1) テーブルの Event types = Put を確認。
  3. Destination 列が demo-s3-alerts-topic を指していることを確認。
Event notifications (1) テーブルに Test-notify-sns-handle-ups3、event type Put、宛先 SNS topic demo-s3-alerts-topic
Event notifications (1) テーブルに Test-notify-sns-handle-ups3、event type Put、宛先 SNS topic demo-s3-alerts-topic
完了条件:バケットが topic に繋がりました。これ以降、オブジェクトの PUT ごとに demo-s3-alerts-topic へメッセージが 1 通生まれ、3 つの subscriber に複製されます。
28

28. Objects タブを開いて Upload

バケットの Objects タブに戻ります。テーブルは「Objects (0)」「You don't have any objects in this bucket」です。Upload をクリック。この瞬間から、アップロードはすべて本物のテストになります — 受信箱にメールが飛び、両方のキューにメッセージが入るので、検証中に大量アップロードはしないでください。

  1. demo-source-data-test の Objects タブを選択。
  2. テーブルが Objects (0) であることを確認。
  3. Upload をクリック。
バケットの空の Objects タブと、強調された Upload ボタン
バケットの空の Objects タブと、強調された Upload ボタン
完了条件:ドラッグ&ドロップ領域を持つ Upload 画面が開きます。
29

29. ファイルを選んで Upload

Add files を押して小さいファイルを選びます — スクリーンショットは約 11.2 KB の ModelOrchConfig.json です。Files and folders テーブルにこれから上げるファイルの名前・Type・Size が並び、Destination ブロックが s3://demo-source-data-test を示します。このアップロード経路は PutObject を使うので、手順 25 で選んだ event type と一致します。注意: おおよそ 16 MB を超えるファイルはコンソールが multipart upload に切り替えるため、発生するイベントは s3:ObjectCreated:CompleteMultipartUpload になり、このラボの notification では捕まえられません。小さいファイルを使ってください。

  1. Add files をクリックし、数 KB の .json など小さいファイルを選択。
  2. Destination が s3://demo-source-data-test であることを確認。
  3. Permissions と Properties はスキップ。
  4. Upload をクリック。
Upload 画面。ModelOrchConfig.json が 11.2 KB、宛先は s3://demo-source-data-test
Upload 画面。ModelOrchConfig.json が 11.2 KB、宛先は s3://demo-source-data-test
注意:小さいファイルを使うこと。multipart upload は Put ではなく CompleteMultipartUpload を生むので、大きいファイルを上げると永遠に来ない通知を待ち続け、設定が壊れていると誤解します。
完了条件:S3 がアップロードを開始し、Upload: status 画面が開きます。
30

30. Upload succeeded

Upload: status 画面に緑の「Upload succeeded」バナーと Summary(Succeeded 1 file (100.00%)、Failed 0 files)が出ます。Files and folders テーブルではそのファイルの Status が Succeeded です。この時刻を覚えておいてください — S3 イベントはまさに今 publish されており、手順 31 のメールの Timestamp、手順 34 のキュー到着時刻と突き合わせられます。

  1. Upload succeeded バナーと Summary を読む。
  2. ファイルの Status 列が Succeeded であることを確認。
  3. 後で照合するためにアップロード時刻を記録。
Upload succeeded バナーと 1 ファイル 100% を示す Upload: status 画面
Upload succeeded バナーと 1 ファイル 100% を示す Upload: status 画面
完了条件:オブジェクトがバケットに入りました。S3 は ObjectCreated:Put メッセージを demo-s3-alerts-topic へ publish 済みで、SNS はそれを 3 つに複製しました。
31

31. メールで Amazon S3 Notification を確認

受信箱を開きます。新着メールの件名は「Amazon S3 Notification」、送信者は demo-s3-alerts-topic と表示されます — 手順 4 で設定した Display name がここで効きます。本文は S3 イベントの生 JSON で、eventName は ObjectCreated:Put、awsRegion は us-east-1、bucket.name は demo-source-data-test、object.key はアップロードしたファイル名で size と eTag も付きます。通常は数秒で届きます。届かないときは迷惑メールを確認してください。

  1. 手順 21 で確認したアドレスの受信箱を開く。
  2. demo-s3-alerts-topic からの件名 Amazon S3 Notification を探す。
  3. eventName が ObjectCreated:Put、object.key がアップロードしたファイルであることを確認。
eventName ObjectCreated:Put と object key ModelOrchConfig.json を含むイベント JSON が載った Amazon S3 Notification メール
eventName ObjectCreated:Put と object key ModelOrchConfig.json を含むイベント JSON が載った Amazon S3 Notification メール
完了条件:1 つ目の subscriber がイベントを受け取りました。S3 → SNS → Email が動いたので、手順 23 の topic policy と手順 26 の event notification は両方とも正しいと分かります。
32

32. キューを開いて Send and receive messages

Amazon SQS → Queues → demo-sqs-worker-1 へ。Details ブロックには後で使う 3 つがあります。ARN、Queue URL(https://sqs.us-east-1.amazonaws.com/<account-id>/demo-sqs-worker-1 — SDK が必要とするのは ARN ではなくこちら)、そして Encryption。下のタブ列には SNS subscriptions があり、このキューがどの topic を購読しているか確認できます。右上の Send and receive messages をクリック。

  1. Amazon SQS → Queues → demo-sqs-worker-1 を開く。
  2. 手順 36 のコード用に Details の Queue URL をコピー。
  3. Send and receive messages をクリック。
demo-sqs-worker-1 の詳細画面。ARN、Queue URL、Send and receive messages ボタン
demo-sqs-worker-1 の詳細画面。ARN、Queue URL、Send and receive messages ボタン
完了条件:Send message と Receive messages の 2 ブロックを持つ Send and receive messages 画面が開きます。
33

33. Poll for messages

Receive messages ブロックでは先に 4 つの数値を読みます。Messages available は 2 — バケットがイベントを生んだ回数と一致します(元のスクリーンショットは 2 回アップロードしています)。Polling duration は 30 秒、Maximum message count は 10、Messages (0) はまだ poll していないから。重要なのは、メッセージはこの画面を開いた時ではなくアップロードの時点からキューに入っているということ。SQS は pull 型で、誰も consumer に push しません。consumer 自身が ReceiveMessage を呼ぶ必要があります。Poll for messages をクリック。

  1. Messages available を読む — 0 より大きいこと。
  2. Poll for messages をクリック。
  3. Polling progress が Success になるまで待つ。
Messages available が 2 の Receive messages ブロックと、強調された Poll for messages ボタン
Messages available が 2 の Receive messages ブロックと、強調された Poll for messages ボタン
注意:メールは届いたのに Messages available が 0 なら、原因は queue policy です。手順 9 と 12 に戻り、Resource フィールドと aws:SourceArn 条件を確認してください。メールが届いたことは S3 と topic が正常な証拠なので、残るのは topic → queue の区間だけです。
完了条件:SQS がキュー内のメッセージを返し、Polling progress が Success に変わります。
34

34. キューに 2 通のメッセージ

テーブルが「Messages (2)」になり、各メッセージの ID・Sent・Size・Receive count が並びます。サイズ 1.19 KB と 1.78 KB は S3 のペイロード単体よりかなり大きく、各メッセージが SNS の封筒も抱えているためです。Receive count はそのメッセージが何回受信されたかを数えます。削除せず poll するたび増え、実システムではこの数値が dead-letter queue へ送るタイミングを決めます。demo-sqs-worker-2 も同じ件数であることを相互確認してください — それがファンアウトの証拠です。

  1. Messages テーブルを想定イベント数と照合。
  2. Size 列を読む — 各メッセージ約 1–2 KB。
  3. poll ごとに Receive count が増えることに注目。
  4. demo-sqs-worker-2 を開き、同様に poll して比較。
demo-sqs-worker-1 の Messages (2) テーブル。ID、送信時刻、Size、Receive count
demo-sqs-worker-1 の Messages (2) テーブル。ID、送信時刻、Size、Receive count
完了条件:両キューが同じ件数・同じ内容のメッセージを、互いに独立して保持しています。1 つの S3 イベントが 3 つのコピー — メール 1 通とメッセージ 2 通 — になりました。
35

35. message body を読む: S3 イベントを包む SNS の封筒

メッセージの ID をクリックし Body タブを見ます。ラボで一番じっくり見るべき場所です。consumer が扱う構造がここで分かります。外側は SNS の封筒で、Type・MessageId・TopicArn・Subject・Timestamp・Signature・SigningCertURL・UnsubscribeURL を持ちます。本物の S3 イベントは Message フィールドの中 — しかも入れ子オブジェクトではなく「文字列」なので、コンソールでは \" のエスケープだらけに見えます。その文字列の中にようやく Records があり、eventName ObjectCreated:Put、configurationId Test-notify-sns-handle-ups3、bucket.name、object.key が入っています。手順 16 で raw message delivery を ON にしていればこの封筒は消え、Body が S3 の JSON そのものになります — すっきりしますが、検証に使う Signature と UnsubscribeURL を失います。

  1. メッセージ ID をクリックし Body タブを開く。
  2. 外層を特定: Type = Notification、TopicArn、Subject = Amazon S3 Notification。
  3. Message フィールドを見つけ、エスケープ済み JSON 文字列だと認識する。
  4. その中を読む: eventName、configurationId、bucket.name、object.key。
  5. Attributes タブと Details タブで SQS が付けたメタデータも見る。
message body · SNS の封筒(抜粋)
{
  "Type": "Notification",
  "MessageId": "f60f601f-c151-429c-9314-63547c38a6c0",
  "TopicArn": "arn:aws:sns:us-east-1:<account-id>:demo-s3-alerts-topic",
  "Subject": "Amazon S3 Notification",
  "Message": "{\"Records\":[{\"eventVersion\":\"2.5\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"us-east-1\",\"eventTime\":\"2026-09-06T14:40:51.153Z\",\"eventName\":\"ObjectCreated:Put\",\"s3\":{\"configurationId\":\"Test-notify-sns-handle-ups3\",\"bucket\":{\"name\":\"demo-source-data-test\",\"arn\":\"arn:aws:s3:::demo-source-data-test\"},\"object\":{\"key\":\"ModelOrchConfig.json\",\"size\":11466,\"eTag\":\"56c3528f3ba14b1fef28f978f7142758\"}}}]}",
  "Timestamp": "2026-09-06T14:40:51.814Z",
  "SignatureVersion": "1",
  "Signature": "UtWyhwoUS4d2Onilzwl+DpUbggV43djhxE07e1WFZ7RPDjVjovpzKHGY0DjTMRNO/...",
  "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-....pem",
  "UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=..."
}
Received message ダイアログ。body に SNS の封筒があり、その Message フィールドがエスケープされた S3 イベント JSON
Received message ダイアログ。body に SNS の封筒があり、その Message フィールドがエスケープされた S3 イベント JSON
完了条件:consumer が JSON を 2 層剥がす必要があること、そして raw message delivery を ON にすると何が変わるかを正確に把握できました。
36

36. consumer 側で JSON を 2 層剥がす

コンソールからコードへ移ります。下の Python はキューを読み、手順 35 で見えた 3 点を正しく処理します。第 1 に json.loads を 2 回: Body から封筒を得るために 1 回、envelope['Message'] から S3 イベントを得るためにもう 1 回 — 2 回目を省くのが SNS→SQS パターン最大の落とし穴です。第 2 に object.key は URL エンコードされているため「my report.pdf」は「my+report.pdf」で届きます。unquote_plus しないと、今アップロードしたそのファイルに対して GetObject が NoSuchKey を返します。第 3 に処理後は必ず delete_message すること。visibility timeout 30 秒が切れるとメッセージはキューに戻り再処理されますし、Standard キューはそもそも at-least-once なので consumer は冪等でなければなりません。

  1. boto3 を入れ、sqs:ReceiveMessage と sqs:DeleteMessage 権限のある認証情報を設定。
  2. QUEUE_URL を手順 32 でコピーした Queue URL に置換。
  3. コードを実行し、出力を手順 31 のメールの object.key と照合。
  4. もう一度 poll する: メッセージが消えていれば delete_message が効いた証拠。
consume_s3_event.py
import json
from urllib.parse import unquote_plus

import boto3

REGION = "us-east-1"
QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/<account-id>/demo-sqs-worker-1"

sqs = boto3.client("sqs", region_name=REGION)

response = sqs.receive_message(
    QueueUrl=QUEUE_URL,
    MaxNumberOfMessages=10,
    WaitTimeSeconds=20,   # long polling: one call instead of 20 empty ones
)

for message in response.get("Messages", []):
    # Layer 1: Body is the SNS envelope (Type, TopicArn, Message, Signature...).
    envelope = json.loads(message["Body"])

    # Layer 2: envelope["Message"] is still A STRING holding the S3 event.
    # Skipping this is the most common break when reading SNS -> SQS.
    event = json.loads(envelope["Message"])

    for record in event["Records"]:
        bucket = record["s3"]["bucket"]["name"]
        # The key is URL-encoded: "my report.pdf" arrives as "my+report.pdf".
        key = unquote_plus(record["s3"]["object"]["key"])
        print(record["eventName"], f"s3://{bucket}/{key}", record["s3"]["object"]["size"])

    # Without the delete, the message returns after the 30s visibility timeout.
    sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=message["ReceiptHandle"])
注意:WaitTimeSeconds=20 は long polling で、既定として使うべきです。空リクエスト 20 回を 1 回に置き換え、コストとレイテンシの両方を下げます。short polling(0 秒)はキューにメッセージがあっても空を返しがちです — キューを保持するサーバーの一部にしか問い合わせないためです。
完了条件:consumer がイベントを読み、正しい bucket/key/size を出力し、メッセージをキューから削除しました。S3 → SNS → SQS → コードの一巡が閉じました。
37

37. よくある失敗と切り分け方

下の 4 つでこのパターンの故障はほぼ網羅できますが、共通点は「静かに壊れる」ことです。切り分けの原則は、subscriber から producer へ逆向きに辿り、subscriber 間の差分を手がかりに使うこと。メールは届くがキューが空 → 原因は確実に queue policy。どこにも届かない → 原因は topic policy か event notification。片方のキューだけ届く → 原因は届かないキューの Resource フィールド。下の CLI コマンドは、それぞれの問いに推測ではなく実データで答えます。

  1. どこにも届かず、S3 が「Unable to validate the following destination configurations」と言う — topic policy に s3.amazonaws.com の許可が無い。手順 23 を見直す。
  2. メールが届かないがキューにはある — メール subscription がまだ Pending confirmation、または確認メールが迷惑メールにある。手順 19・20 を見直す。
  3. メールは届くが両キューが空 — queue policy が sns.amazonaws.com の sqs:SendMessage を許可していない。手順 9・12 を見直す。
  4. 片方のキューだけメッセージがある — もう一方の Resource フィールドの ARN が違う。手順 12 を見直す。
  5. SSE-KMS のカスタマー管理キーで暗号化したキューが空 — キーポリシーが sns.amazonaws.com に kms:GenerateDataKey と kms:Decrypt を許可していない。
  6. 大きいファイルを上げてもイベントが来ない — コンソールが multipart upload を使ったので、イベントは Put ではなく CompleteMultipartUpload。手順 25・29 を見直す。
AWS CLI による障害の切り分け
REGION=us-east-1
ACCOUNT=<account-id>
TOPIC_ARN=arn:aws:sns:$REGION:$ACCOUNT:demo-s3-alerts-topic

# 1) Which subscriptions are unconfirmed? SubscriptionArn = "PendingConfirmation".
aws sns list-subscriptions-by-topic --region $REGION --topic-arn $TOPIC_ARN \
  --query 'Subscriptions[].{Protocol:Protocol,Endpoint:Endpoint,Arn:SubscriptionArn}' \
  --output table

# 2) Does the topic let s3.amazonaws.com publish yet?
aws sns get-topic-attributes --region $REGION --topic-arn $TOPIC_ARN \
  --query 'Attributes.Policy' --output text

# 3) What is the bucket publishing, and where to?
aws s3api get-bucket-notification-configuration --region $REGION \
  --bucket demo-source-data-test

# 4) Is the queue policy blocking SNS, and is anything waiting?
QUEUE_URL=https://sqs.$REGION.amazonaws.com/$ACCOUNT/demo-sqs-worker-1
aws sqs get-queue-attributes --region $REGION --queue-url $QUEUE_URL \
  --attribute-names Policy ApproximateNumberOfMessages

# 5) Take S3 out of the picture: publish to the topic by hand.
#    Reaches the queue -> the fault is on the S3 side.
#    Does not        -> the fault is the topic or queue policy.
aws sns publish --region $REGION --topic-arn $TOPIC_ARN \
  --subject "manual test" --message "hello from cli"
注意:コマンド 5 が最も効く切り分けです — topic へ手動 publish する。メッセージがキューに届けば topic → queue 区間は正しく原因は S3 側、届かなければ原因は topic か queue policy。1 コマンドで原因候補の半分を消せます。
完了条件:行き当たりばったりの試行ではなく順序のある診断手順を持ち、どのコマンドがどの問いに答えるか分かっています。
38

38. リソースの後片付け

ラボ規模のコストはほぼゼロです。SNS は月 100 万リクエストとメール通知 1,000 通、SQS は 100 万リクエストが無料、S3 は数 KB のストレージ課金だけ。それでも片付ける実務的な理由が 2 つあります。event notification が生きている間はこのバケットへの今後のアップロードすべてがメールを飛ばすこと、そして demo-source-data-test という名前はバケットを削除するまでグローバル名前空間に押さえられ続けること。下の順序は意図的です — データを消す前にイベントを止めます。

  1. バケットの event notification を無効化、または Properties → Event notifications で削除。
  2. 全オブジェクトを削除してからバケットを削除(バケットは空でないと削除できません)。
  3. topic demo-s3-alerts-topic を削除 — subscription 3 つも一緒に消えるので個別削除は不要。
  4. キュー demo-sqs-worker-1 と demo-sqs-worker-2 を削除。
  5. 最後の 2 コマンドを実行し、ラボのリソースが残っていないことを確認。
cleanup.sh
REGION=us-east-1
ACCOUNT=<account-id>
BUCKET=demo-source-data-test
TOPIC_ARN=arn:aws:sns:$REGION:$ACCOUNT:demo-s3-alerts-topic

# 1) Switch the event notification off FIRST. Otherwise clearing objects in
#    step 2 can still produce notifications and look like leftover config.
aws s3api put-bucket-notification-configuration --region $REGION \
  --bucket $BUCKET --notification-configuration '{}'

# 2) A bucket must be empty before it can be deleted.
aws s3 rm s3://$BUCKET --recursive
aws s3api delete-bucket --region $REGION --bucket $BUCKET

# 3) Delete the topic: all of its subscriptions go with it.
aws sns delete-topic --region $REGION --topic-arn $TOPIC_ARN

# 4) Delete both queues. A deleted queue name is reserved for ~60 seconds;
#    recreating it immediately returns QueueDeletedRecently.
for q in demo-sqs-worker-1 demo-sqs-worker-2; do
  aws sqs delete-queue --region $REGION \
    --queue-url https://sqs.$REGION.amazonaws.com/$ACCOUNT/$q
done

# 5) Confirm nothing is left.
aws sns list-topics --region $REGION --query 'Topics[?contains(TopicArn, `demo-s3-alerts`)]'
aws sqs list-queues --region $REGION --queue-name-prefix demo-sqs-worker
注意:削除したキュー名は SQS が約 60 秒押さえます。同じ名前で即座に作り直すと QueueDeletedRecently が返ります。ラボをやり直すなら 1 分待つか、名前の接尾辞を変えてください。
完了条件:全リソースが消え、受信箱に通知が来なくなり、バケット名が解放されました。

参考ドキュメント