Article

📨 SNSからSQSへ必要なイベントだけ届ける:FilterPolicyの実装とハマりどころ

aws, sns, sqs, nodejs

SNS(Amazon Simple Notification Service)は、あるイベントを複数の購読先へ配信する仕組みです。SQS(Amazon Simple Queue Service)は、受け取った仕事をキューに蓄積し、ワーカーが後から処理できるサービスです。

たとえば注文システムで「注文作成」「決済完了」「キャンセル」をSNSに流し、発送ワーカーには決済完了だけを渡したいとします。すべての通知をSQSに入れてワーカーで捨てることもできますが、不要なメッセージをキューまで運ぶ必要はありません。

そこで使うのが、SNSのサブスクリプション(配信先の登録)に設定する FilterPolicy(フィルターポリシー) です。

この記事では2026年10月23日時点のAWS公式ドキュメントをもとに、CloudFormationによる構成、Node.jsからの送信、本文フィルタとの使い分け、見落としがちな権限や設定反映の注意点を整理します。

1. フィルターを置く場所

注文アプリからSNS TopicにイベントをPublish(送信)し、SNSは各SubscriptionのFilterPolicyを評価します。

注文アプリ
   |
   | SNS Publish
   v
OrdersTopic
   |
   +--> 決済完了だけ --> PaidOrdersQueue --> 発送ワーカー
   |
   +--> (別の購読先)キャンセルだけ --> CancellationQueue

重要なのは、FilterPolicyはTopic自体ではなく、Subscriptionごとに設定することです。同じTopicから異なる条件で複数のSQSキューへ配信できます。FilterPolicyを設定しないSubscriptionには、原則としてすべてのメッセージが配信対象になります。

2. MessageAttributesとMessageBodyの違い

フィルタリングする対象は FilterPolicyScope で選びます。

設定値 評価する対象 向いている設計
MessageAttributes(初期値) SNS Publish時のMessageAttributes イベント種別や送信元など、ルーティングに必要なメタ情報を独立管理する
MessageBody Messageに入れたJSONオブジェクト 既存のイベント本文の項目で振り分けたい

例として、次のFilterPolicyを考えます。

{
  "eventType": ["order.paid"],
  "source": ["checkout"]
}

この場合は両方のキーが一致する必要があります(キー間はAND)。同じキーの配列に複数値を指定した場合は、いずれかに一致すればよい条件(値の間はOR)として使えます。

MessageAttributesを使うなら、JSON本文に同じ値を書くだけでは一致しません。送信時に MessageAttributesとして指定する必要があります。

逆に MessageBody を指定した場合は、Messageの本文をJSONオブジェクトとして解釈して評価します。JSON文字列以外の本文や想定外の構造を送ると、条件に一致しません。

3. CloudFormationでSNS、SQS、FilterPolicyをまとめて定義する

CloudFormationはAWSのリソースをYAMLなどの設定ファイルで作成・更新する仕組みです。次の例は同一AWSアカウント・同一リージョンの標準SNS TopicとSQSキューを想定しています。

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  OrdersTopic:
    Type: AWS::SNS::Topic

  PaidOrdersQueue:
    Type: AWS::SQS::Queue

  PaidOrdersQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref PaidOrdersQueue
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowOnlyOrdersTopic
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: sqs:SendMessage
            Resource: !GetAtt PaidOrdersQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref OrdersTopic

  PaidOrdersSubscription:
    Type: AWS::SNS::Subscription
    DependsOn: PaidOrdersQueuePolicy
    Properties:
      TopicArn: !Ref OrdersTopic
      Protocol: sqs
      Endpoint: !GetAtt PaidOrdersQueue.Arn
      RawMessageDelivery: true
      FilterPolicyScope: MessageAttributes
      FilterPolicy:
        eventType:
          - order.paid
        source:
          - checkout

Outputs:
  TopicArn:
    Value: !Ref OrdersTopic
  QueueUrl:
    Value: !Ref PaidOrdersQueue

見るべき点は3つです。

① SQSのQueuePolicyが必要。 SNSからSQSへ届けるには、SQS側で sns.amazonaws.com に sqs:SendMessage を許可します。例では aws:SourceArn を特定のTopicのARNへ絞っています。これは「別のトピックを使って自分のキューへ送らせる」権限の混同を防ぐために重要です。

② FilterPolicyScopeは明示する。 初期値はMessageAttributesですが、設定ファイルに書けばチーム内で意図が共有できます。

③ RawMessageDeliveryはフィルタリングとは別設定。 trueならSQSに届く本文はSNSの通知ラッパーを含まない元メッセージになります。falseの場合、一般にSNSの通知情報を含むJSONがSQS本文に入ります。受信側のパーサーが変わるため、既存システムで切り替える際には注意してください。

上記の例は構成を理解するための最小形です。実運用ではSQSの暗号化、メッセージ保持期間、DLQ、監視、アプリケーション側IAM権限も検討します。

4. Node.js(AWS SDK for JavaScript v3)から送信する

次のコマンドはSDKのSNSクライアントをインストールします。

npm install @aws-sdk/client-sns

npm install は依存パッケージを追加するコマンドです。Node.jsプロジェクトにインストールしたうえで、次を publish.mjs として保存します。

import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";

const topicArn = process.env.TOPIC_ARN;
if (!topicArn) {
  throw new Error("TOPIC_ARN が未設定です");
}

const client = new SNSClient({});

async function publishOrder(eventType) {
  const event = {
    eventType,
    orderId: "order-demo-001",
    occurredAt: new Date().toISOString(),
  };

  const result = await client.send(new PublishCommand({
    TopicArn: topicArn,
    Message: JSON.stringify(event),
    MessageAttributes: {
      eventType: {
        DataType: "String",
        StringValue: eventType,
      },
      source: {
        DataType: "String",
        StringValue: "checkout",
      },
    },
  }));

  console.log(eventType, result.MessageId);
}

await publishOrder("order.created");
await publishOrder("order.paid");

AWS SDKは通常、実行環境のIAMロールや開発環境の認証設定から認証情報とリージョンを取得します。コード内にアクセスキーを埋め込まないでください。送信元のIAM権限には対象Topicへの sns:Publish が必要です。

この例では、フィルタ設定が有効でキューへの配信権限が正しければ、PaidOrdersQueueへ配信対象となるのは order.paid のほうだけです。Publishが成功したという応答は、すべての購読先で処理が完了した証明ではありません。

実環境では注文IDを毎回同じデモ値にせず、業務イベントごとに一意なIDを持たせてください。

5. 本文の項目で絞り込みたい場合

発行元でMessageAttributesを付けられない場合や、既存イベントのJSON形式を使いたい場合は、Subscriptionを次のように変更できます。

FilterPolicyScope: MessageBody
FilterPolicy:
  eventType:
    - order.paid
  customer:
    plan:
      - premium

例えば Message に以下のJSONオブジェクトを文字列として送れば、eventTypeとcustomer.planの両方が一致するかで評価できます。

{
  "eventType": "order.paid",
  "orderId": "order-demo-001",
  "customer": {
    "plan": "premium"
  }
}

MessageBodyフィルタは入れ子のJSONに対応します。一方、MessageAttributesフィルタに同じ入れ子構造をそのまま持ち込むことはできません。属性によるルーティングはキーをフラットに保つ設計が分かりやすいです。

どちらを選んでも、フィルタは「受信先を振り分ける仕組み」であり、認可(アクセスを許可するかの判断)の代替ではありません。機密情報やユーザーの権限判断をフィルタ設定だけに依存させないでください。

6. 実務でハマりやすい4点

① 設定変更がすぐ効くとは限らない

AWS公式ドキュメントでは、SubscriptionのFilterPolicyの追加・変更が完全に反映されるまで最大15分かかる場合があると説明されています。

切り替え直後に古い条件で届いた/届かなかったとしても、即座にプログラムの不具合とは断定しないこと。移行時は古い形式も読める期間を設け、公開側と受信側に相関ID付きログを残すと追跡しやすくなります。

② 文字列の大文字・小文字と属性名がずれる

eventType と eventtype は別のキーです。また、order.paid と ORDER.PAID は、通常の文字列一致では同じ値とみなされません。スキーマを定数や型で共有し、テストで一致/不一致を確認してください。

③ RawMessageDeliveryと属性数に制約がある

AWS公式ドキュメントによると、SQS購読で RawMessageDelivery を有効にした場合、送信するメッセージ属性は最大10個です。属性が多い設計では、配信方式と制約を確認する必要があります。必要なルーティング情報だけ属性に出し、詳細データは本文に置くほうが保守しやすくなります。

④ フィルタで受け取らなかったメッセージは「処理失敗」とは違う

条件に一致しないイベントがそのキューへ届かないのは正常動作です。一方、条件に一致したのにキューへ配信できない場合は、SQSのポリシー、SNSのサブスクリプション状態、KMS権限などを確認します。コンシューマーの処理失敗に備えるSQSのDLQと、SNSから配信できなかった場合に備えるSNSサブスクリプションのRedrivePolicyは、役割が異なります。

また、通常のSNS/SQSでの非同期処理では重複配信を想定し、注文IDとイベント種別等を用いた冪等処理(同じイベントを複数回処理しても結果が崩れない作り)を検討します。

7. 本番に入れる前の確認

以下を、CloudFormation適用後の結合試験で確認するのが実務的です。

テスト 期待結果
order.created / source=checkout PaidOrdersQueueには配信されない
order.paid / source=checkout PaidOrdersQueueに配信される
order.paid / source=other PaidOrdersQueueには配信されない
source属性なし PaidOrdersQueueには配信されない
フィルタ設定変更の直後 反映遅延を許容して観測し、即時切替を前提にしない
同一イベントを再受信 ワーカーが安全に重複を扱える

ここに示したコードとテンプレートは公式API仕様に基づく構成例であり、この原稿作成時点では実際のAWSアカウントへのデプロイ/配信テストは実施していません。環境ごとのIAM、暗号化、クロスアカウント設定は個別に検証してください。

まとめ

SNSのFilterPolicyを使えば、必要なイベントを必要な購読先へ届ける構成を、アプリケーション側の複雑な振り分け処理なしで表現できます。

実装で特に大切なのは、フィルタ対象(属性か本文)を揃えること、SQS側の送信許可を正しく絞ること、設定の反映遅延を前提にすることです。フィルタを設定したら終わりではなく、受信処理の冪等性や障害時の追跡まで含めて設計しましょう。

参考資料(2026年10月23日確認)