なぜSQSの障害を「わざと」起こすのか
本番環境でSQSキューへのメッセージ送受信ができなくなったら、何が起きるでしょうか。IAMポリシーの設定ミス、ネットワークパーティション、不正なデプロイ、一時的なサービス障害など原因は様々ですが、アプリケーション側から見れば同じくAccessDeniedエラーが発生し始めます。
問題は、多くのチームがこの状況を一度もテストしたことがないという点です。サーキットブレーカーを実装したけれど実際に開くのか、ローカルバッファリングが機能するのか、コンシューマーがバックログを処理しきれるのか — コードを読んで「たぶん大丈夫」で済ませてしまいがちです。
本質的な視点: この実験の目的は「SQSが正しく動くか」を検証することではありません。「SQSが落ちたとき、自分たちのアプリケーションがどう振る舞うか、そしてそれを検知できるか」 を確認することです。回復メカニズムと可観測性を同時に試すことになります。
本記事では、AWS Fault Injection Service(AWS FIS)とAWS Systems Manager(SSM)Automationを活用し、SQSへのアクセスを段階的に遮断・復旧する実験の構成方法を解説します。日本でもカオスエンジニアリングの導入を検討するチームが増えていますが、SQSのようなマネージドサービスに依存するアーキテクチャであれば、この実験は良い出発点になります。

実験の構造と核心となるコード
全体アーキテクチャ
実験は次の流れで進みます。
- SQSキューに
FIS-Ready: Trueタグを付与し、実験対象の範囲を限定 - SSM Automationドキュメントが対象キューのリソースポリシーにデータプレーン操作のみを拒否するDeny文を追加
- AWS FIS実験テンプレートが2分 → 5分 → 7分 → 15分と段階的に遮断時間を延ばし、間に回復期間を配置
- CloudWatchメトリクスでプロデューサー/コンシューマー/キューの反応を観察
⚠️ 絶対にやってはいけないこと: sqs:*全体のDeny
ここは非常に重要な落とし穴です。IAMポリシーの評価では、明示的なDenyはすべてのAllowに優先します。 sqs:*をDenyすると、キューの管理操作(SetQueueAttributes、AddPermission、RemovePermission)までブロックされ、Denyを適用したロール自身がポリシーを削除できなくなるロックアウト状態に陥ります。
必ずデータプレーン操作のみをDenyしてください:
{
"Sid": "FISTemporaryDeny",
"Effect": "Deny",
"Principal": "*",
"Action": [
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:ChangeMessageVisibility",
"sqs:PurgeQueue"
],
"Resource": "arn:aws:sqs:ap-northeast-1:123456789012:my-queue"
}
ヒント:
aws:CurrentTimeにDateLessThan条件を付けると、Denyが自動失効します。クリーンアップステップが失敗しても指定時刻を過ぎれば自動解除されるため、安全装置として有効です。
特定のロールだけを遮断する(Principalスコープ)
"Principal": "*"はそのキューに対するすべての呼び出し元(管理者、カナリア、他のコンシューマー)を遮断します。共有キューでは実験対象アプリ以外のトラフィックまで影響を受けてしまいます。特定のアプリケーションだけを対象にしたい場合は、IAMロールARNでスコープを絞ってください:
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/order-producer-role"
}
ロールARNはそのロールのすべてのセッションにマッチするため、アプリの呼び出しだけを正確に遮断できます。
FIS実験テンプレートの4段階の段階的障害
| フェーズ | 期間 | 明らかになること |
|---|---|---|
| Impair 1 | 2分 | Fail-fast動作、サーキットブレーカーの起動有無 |
| Recover | 3分 | バッファ再送、メトリクスの正常化 |
| Impair 2 | 5分 | キューのバックログ蓄積 |
| Recover | 3分 | バックログの消化速度 |
| Impair 3 | 7分 | スレッドプール/メモリの圧迫 |
| Recover | 2分 | 大きなバックログ下での復旧 |
| Impair 4 | 15分 | 長期障害時のシステム限界 |
ストップコンディションは「顧客影響」指標で設定する
ここは多くの方が誤るポイントです。ApproximateAgeOfOldestMessageやNumberOfMessagesSentのようなキューメトリクスでアラームを設定してはいけません。 これらのメトリクスは実験中に動くのが正常だからです。それでアラームを設定すると、最初の2分フェーズで即座に実験が停止してしまいます。
代わりに、レジリエンスメカニズムが正常なら健全に保たれるべきシグナルで設定してください:
- アプリケーションのエラー率(例: 分あたりの失敗注文数)— 最も直接的な顧客影響指標
- ALBの
HTTPCode_Target_5XX_Count— ビジネスメトリクスがない場合のプロキシ - DLQ深度(
ApproximateNumberOfMessagesVisible)— 永続的な失敗のシグナル
例えば仮説が「復旧後2分以内に正常化」であれば、アラームは分あたりの失敗注文数 > 10、1分間隔で2回連続程度に設定します。サーキットブレーカーが開く瞬間のスパイクは許容し、仮説ウィンドウを超えた失敗は実験を停止させる、という設計です。
aws fis start-experiment \
--experiment-template-id EXT1234567890 \
--tags Purpose=resilience-test

実験中に観察すべきポイントと注意事項
⚠️ この実験が検証しないこと
Denyポリシーが返すのは**AccessDenied(403)、つまりリトライ不可能なエラーです。したがってこの実験は「自分たちのコードが403を認識して停止するか」を検証するものであり、「バックオフとリトライ経路が正しく動作するか」を検証するものではありません。** リトライ/バックオフを試すには、スロットリングやタイムアウトのようなリトライ可能な障害を別途注入する必要があります。
プロデューサー側の観察
| 段階 | 健全な反応 | 不健全な反応 | 監視するシグナル |
|---|---|---|---|
| 最初のSendMessage失敗 | AccessDeniedを認識してfail-fast | クラッシュ/ハング/スレッドブロック | NumberOfMessagesSent → 0 |
| 3〜5回連続失敗 | サーキットブレーカーがオープン、負荷遮断またはバッファリング | 無限リトライ | サーキットブレーカー状態メトリクス |
| 送信断念 | durable fallbackストレージに保存、またはアラート | 静かにドロップ(恒久的損失) | failed send / droppedカウンター |
コンシューマー側の観察
| 段階 | 健全な反応 | 不健全な反応 | 監視するシグナル |
|---|---|---|---|
| 最初のReceive/Delete失敗 | ポーリングループをバックオフ | コンシューマーループがクラッシュ | NumberOfMessagesReceived減少 |
| バックログ蓄積 | キュー深度ベースのスケーリング | 無限増加、アイドルループ | ApproximateNumberOfMessagesVisibleの平坦化 |
| 再配信 | 冪等処理 | 重複副作用 | 重複書き込みメトリクス |
🚨 DLQは障害中には埋まらない
誤解されがちですが、DLQはmaxReceiveCountに基づいて動作します。メッセージがN回受信されても削除されない場合にDLQへ移動しますが、ReceiveMessage自体が遮断されると受信カウントが増加しません。つまりDLQは復旧時に見るべきものであり、障害中に見るものではありません。
復旧時にありがちな失敗パターン
- サーキットブレーカーが閉じない: stale failure stateに陥り手動再起動が必要なケース
- バックログ消化が停止: レートがスパイク後に0に落ちてそのまま — コンシューマーが圧倒されたかstuck
- DLQメッセージがretention期間を超えて削除される: 復旧後に必ずDLQの残存メッセージを確認
日本の開発エコシステムにおける適用文脈
日本のSI/スタートアップ環境では、単一アカウントで共有キューを使うケースが多く、"Principal": "*"のDenyをそのまま適用すると隣のチームのサービスまで影響を受ける可能性があります。必ずtargetPrincipalArnパラメータでスコープを絞り、非本番環境で先に試してください。また、CloudWatchダッシュボードが十分に整備されていないケースも多いですが、この実験は可観測性そのものを試すものなので、ダッシュボードとアラームを先に整備することを推奨します。

まとめ: 仮説 → 実験 → 修正のサイクル
この実験で最も重要な成果物は「合格/不合格」ではなく、「仮説と実際のギャップ」 です。そのギャップこそが、皆さんのシステムが手を入れるべきポイントになります。
実務適用の順序
- 非本番環境で2分フェーズから始めてください。壊れるところから直します。
- 全体シーケンス(2→5→7→15分)を回し、アプリケーションが進化するたびに繰り返してください。
- 拡張フェーズ: 部分障害(一部のキューのみ)、コンシューマー側のみの遮断(
ReceiveMessage/DeleteMessageのみDeny)、EC2終了やネットワーク遅延との複合障害へ拡張してください。
必ず備えるべき4つのパターン
- リトライ: リトライ可能なエラー(スロットリング、タイムアウト、一時的な5xx)のみ指数バックオフ+ジッターで。403のような非リトライエラーは即fail-fast。
- サーキットブレーカー: 連続失敗の閾値でオープン、回復タイムアウト後にhalf-open。代替経路へ静かに切り替えないこと — fallbackは障害時にしか使われず、一緒に落ちるケースが多いです。
- DLQ: すべてのキューに設定。
maxReceiveCountは3〜5が一般的。DLQのretentionをソースキューより長く設定し、調査時間を確保してください。 - モニタリング: キューメトリクス+アプリケーションメトリクス(エラー率、サーキットブレーカー状態、DLQ深度、E2E処理遅延)を1つのダッシュボードに。
一言まとめ: SQSが落ちたとき、皆さんのアプリは静かにメッセージをドロップしていませんか?この実験はその問いへの答えを、コードではなく観測された事実として返してくれます。