Article
📨 SNSからSQSへ必要なイベントだけ届ける:FilterPolicyの実装とハマりどころ
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側の送信許可を正しく絞ること、設定の反映遅延を前提にすることです。フィルタを設定したら終わりではなく、受信処理の冪等性や障害時の追跡まで含めて設計しましょう。