ネットワーク障害でリトライしたら、同じ注文が2件発生した――そんなトラブルはオンプレでも起きていたが、クラウドの分散環境では発生頻度が格段に上がる。マイクロサービス間通信やSQSキュー処理では「少なくとも1回は届く(at-least-once)」がデフォルトであり、重複処理対策を設計に組み込まないと予期しない副作用が本番環境に滲み出てくる。
この記事では、冪等性(Idempotency)の概念から、AWSで使える具体的な実装パターンまでをオンプレ経験者にもわかりやすく解説する。SQSの重複排除機能、DynamoDBを使った冪等キー管理、AWS Lambda Powertoolsの活用まで、現場で今すぐ使えるレベルで整理した。
なぜクラウドで冪等性が重要になるのか
オンプレ環境との最大の違いは「通信の信頼性の前提」だ。
オンプレの場合、サーバー間はL2スイッチで繋がれた安定したネットワーク上にあり、接続が切れること自体がイレギュラーだった。データベースへの書き込みはACID特性が保証され、「1回のリクエストは1回だけ処理される」という暗黙の前提が成立していた。
クラウドのマイクロサービス環境はまったく異なる。TCP接続の途中切断、コンテナの再起動、ロードバランサーのタイムアウト――こうした「部分失敗(Partial Failure)」が日常的に起きる。
特に次の3つのシナリオで重複が発生しやすい。
・HTTPリクエストのタイムアウトとリトライ: クライアントが504を受け取り再送した時点で、サーバー側はすでに処理を完了している可能性がある
・SQS at-least-once配送: メッセージは最低1回届くが、稀に2回以上届くことがある(Exactly-onceではない)
・Lambda関数の再実行: エラー時の自動リトライで同じイベントが複数回処理されることがある
決済・在庫引当・メール送信のような副作用を伴う処理が2回実行されると、ビジネス上の損害に直結する。冪等性設計はクラウド時代のシステム信頼性の基盤だ。
冪等性の基本概念と設計パターン
1. 冪等性(Idempotency)とは何か
数学的には「同じ操作を何度繰り返しても結果が変わらない性質」を冪等性という。システム設計では「同じリクエストを複数回受け取っても、最初の1回と同じ結果になること」を指す。
例として、「口座に1,000円を入金する」という操作を考えよう。
・冪等ではない設計: リクエストが3回届く → 3,000円入金されてしまう
・冪等な設計: リクエストが3回届く → 1,000円の入金(2回目以降は無視される)
HTTPメソッドの観点では、GETやPUTは本来冪等だが、POSTは冪等ではない。決済やオーダー作成のようなPOSTリクエストに冪等性を持たせるには、明示的な設計が必要だ。
2. 冪等キー(Idempotency Key)パターン
最も広く使われる手法が冪等キー(Idempotency Key)を使ったパターンだ。リクエスト送信側がUUID等の一意なキーを生成し、HTTPヘッダーやリクエストボディに含める。受信側はこのキーをもとに「すでに処理済みか」を確認する。
# HTTPリクエスト例(決済API) POST /payments Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Content-Type: application/json { "amount": 1000, "currency": "JPY", "customer_id": "cust_001" }
受信したAPIサーバーは、このIdempotency-Keyをストレージ(DynamoDBやRedisなど)に記録し、次回同じキーのリクエストが来た場合は保存済みのレスポンスを返す。これにより、リトライが発生してもユーザーへの影響を防げる。
3. 処理結果のキャッシュ設計と保存期間
冪等キーとセットで重要なのが「処理結果の保存期間」の設計だ。
・保存期間が短すぎる場合: クライアントのリトライが来た時にすでに期限切れで二重処理が発生する
・保存期間が長すぎる場合: ストレージコストが膨らみ、管理コストが上がる
一般的には24時間から7日間が実用的な範囲だ。決済系では数日、一般的なAPIでは24時間を目安にするとよい。DynamoDBのTTL機能を使えば期限切れレコードを自動削除できるため、管理の手間も最小限で済む。
AWSでの冪等性実装パターン
1. Amazon SQSのメッセージ重複排除機能
Amazon SQS FIFO(First-In-First-Out)キューには、メッセージ重複排除ID(MessageDeduplicationId)という仕組みが組み込まれている。
# AWS CLIでFIFOキューにメッセージを送信(重複排除ID付き) aws sqs send-message \ --queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/MyQueue.fifo \ --message-body '{"orderId": "order-001", "amount": 1000}' \ --message-group-id "order-group-001" \ --message-deduplication-id "order-001-payment-attempt-1"
同じMessageDeduplicationIdのメッセージが5分以内に再送された場合、SQSは自動的に重複を排除し2回目以降のメッセージをキューに追加しない。インフラレベルで重複を防げる強力な機能だ。
ただし注意点がある。FIFOキューは標準キューより料金が高く、スループットも100件/秒(高スループットモードで3,000件/秒)に制限される。大量メッセージ処理が必要な場合はアーキテクチャを慎重に設計する必要がある。
2. AWS Lambda + DynamoDBによる冪等キー管理
標準SQSキュー(at-least-once配送)をLambdaで処理する場合、アプリケーション側で冪等性を実装する必要がある。DynamoDBを使った方法が最もシンプルで堅牢だ。
# DynamoDBテーブル設計(冪等キー管理) # テーブル名: IdempotencyKeys # パーティションキー: idempotency_key (String) # TTL属性: expire_at (Number, Unixタイムスタンプ) # Lambda関数内の疑似コード(Python) import boto3, time from botocore.exceptions import ClientError dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('IdempotencyKeys') def handler(event, context): idempotency_key = event['idempotency_key'] # 条件付き書き込みで冪等性を保証(アトミックなチェック&書き込み) try: table.put_item( Item={ 'idempotency_key': idempotency_key, 'status': 'PROCESSING', 'expire_at': int(time.time()) + 86400 # 24時間 }, ConditionExpression='attribute_not_exists(idempotency_key)' ) except ClientError as e: if e.response['Error']['Code'] == 'ConditionalCheckFailedException': # 処理済み → 保存済みの結果を返す return get_cached_result(idempotency_key) raise # 実際の処理を実行 result = process_order(event) # 処理結果を保存 table.update_item( Key={'idempotency_key': idempotency_key}, UpdateExpression='SET #s = :s, result = :r', ExpressionAttributeNames={'#s': 'status'}, ExpressionAttributeValues={':s': 'COMPLETED', ':r': result} ) return result
DynamoDBの条件付き書き込み(Conditional Write)を使うことで、アトミックな「チェック&書き込み」が実現できる。「まずチェックしてから書き込む」という2段階操作では競合状態(Race Condition)が発生するが、条件付き書き込みではDynamoDB側で原子的に処理されるため安全だ。
3. AWS Powertools for Lambdaの冪等性ユーティリティ
AWS公式ライブラリ「AWS Lambda Powertools」には、冪等性を簡単に実装できるユーティリティが組み込まれている。
# AWS Lambda Powertools(Python)を使った実装例 from aws_lambda_powertools.utilities.idempotency import ( idempotent, DynamoDBPersistenceLayer, IdempotencyConfig ) persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyKeys") config = IdempotencyConfig( expires_after_seconds=86400, # 24時間で期限切れ event_key_jmespath="body.idempotency_key" # イベント内のキーフィールド指定 ) @idempotent(config=config, persistence_store=persistence_layer) def handler(event, context): # ここに通常の処理を書くだけでよい return process_payment(event)
デコレータを1つ追加するだけで冪等性が実装できる。内部的にはDynamoDB条件付き書き込みを使っており、前述の手動実装と同等の安全性が得られる。新規プロジェクトでは積極的に活用したい。
冪等性実装のコスト感覚
冪等性のためにかかる主なコストはDynamoDB書き込みだ。
| 用途 | DynamoDB操作 | コスト目安(東京リージョン・2026年3月時点) |
|---|---|---|
| 冪等キー新規登録 | 条件付き書き込み(1WCU相当) | $0.000735/1,000 WCU |
| 重複チェック(処理済みの場合) | GetItem(1RCU相当) | $0.0001469/1,000 RCU |
| TTLによる自動削除 | TTL削除(無料) | $0 |
月100万リクエストでも追加コストは数十円程度だ。重複処理によるビジネス損失(返金対応・カスタマーサポートコスト)と比較すれば、投資対効果は圧倒的に高い。
SQS FIFOキューの場合は標準キューより料金が若干高い。2026年3月時点で、FIFOキューの料金は$0.50/100万リクエスト(標準キューは$0.40/100万)。差は小さいため、重複排除が必要なワークロードでは迷わずFIFOを選ぶのが現実的だ。
応用・実務Tips
冪等キーの生成はクライアント側の責任にする
サーバー側でキーを生成すると、同一リクエストの2回目が「新規リクエスト」として扱われてしまう。UUIDv4のような衝突率が極めて低いキーをクライアント側が生成し、リトライ時は同じキーを再送するよう設計する。
部分的な処理完了の扱いを明確にする
「在庫引当は成功、メール送信は失敗」のような部分的な成功の場合、ステータスを「PARTIAL_SUCCESS」として保存し、リトライ時に未完了の処理だけ再実行する設計が実用的だ。完全な原子性が必要な処理ではサガパターンと組み合わせる。
冪等性の範囲はAPIのビジネス操作単位にする
「注文作成」を1つの冪等操作として扱い、その中の個別ステップ(在庫チェック・決済・メール)を別々に冪等化するのは複雑になりすぎる。まず外部に見えるビジネス操作の単位で冪等性を保証し、内部の実装は徐々に整備する順序がよい。
Sagaパターンと組み合わせる
分散トランザクションを管理するSagaパターンと冪等性は補完的な関係にある。Sagaの各ステップを冪等にすることで、補償トランザクション(ロールバック操作)の安全な再実行が保証される。姉妹サイトLinuxMaster.JPでLinuxサーバーの基礎を固め、クラウド上での安定運用につなげてほしい。
よくあるトラブルと対処法
Q: 冪等キーが同一なのに異なる内容のリクエストが来た場合はどうする?
セキュリティリスクとして扱い、最初のリクエスト内容を返す(または409 Conflictを返す)のが正解だ。冪等キーは「同一操作の再試行」にのみ使うものであり、異なる内容のリクエストに同じキーを使うのはクライアント側のバグとして扱う。
Q: Lambda関数がタイムアウトした場合、冪等キーが「処理中」のまま残る
DynamoDBのTTLを適切に設定することで解決できる。TTLをリクエストのタイムアウト時間(例: 30秒)より少し長めに設定しておけば、タイムアウト後に「処理中」ステータスが自動でクリアされ、次のリトライで再処理される。AWS Powertoolsは「INPROGRESS」ステータスを設定したタイムアウト後に自動リセットする機能がある。
Q: SQSで同じメッセージが2回届いた場合のLambdaの挙動は?
Lambda関数がSQSトリガーで起動する場合、1回目の処理が成功してキューからメッセージが削除されれば2回目は来ない。ただし可視性タイムアウト内に処理が完了しなかった場合、メッセージが再可視化されて2回目の起動が発生しうる。可視性タイムアウトをLambdaのタイムアウト設定の6倍以上に設定するのが推奨だ。
Q: DynamoDBの条件付き書き込みが高スループット時に失敗する
プロビジョニングモードではWCUを十分に確保する。オンデマンドモードなら自動でスケールするため、急激なトラフィックスパイクにも対応できる。冪等キーテーブルはアクセスパターンが単純(キー検索のみ)なのでオンデマンドモードがコスト効率よく機能することが多い。
本記事のまとめ
| 課題 | 解決アプローチ | AWSサービス |
|---|---|---|
| APIリクエストの重複処理 | 冪等キー(Idempotency Key)パターン | DynamoDB(条件付き書き込み) |
| SQSメッセージの重複配送 | FIFOキューの重複排除ID | Amazon SQS FIFO |
| Lambda関数リトライ時の二重実行 | 処理結果のキャッシュ(Lambda Powertools) | AWS Lambda Powertools + DynamoDB |
| 分散トランザクションの整合性 | SagaパターンのステップをStep Functionsで冪等化 | AWS Step Functions |
クラウド上の分散システムでは「少なくとも1回は処理される」ことは保証できても「ちょうど1回だけ処理される(Exactly-once)」を保証することは本質的に難しい。冪等性設計はその現実を受け入れた上で、「何回来ても同じ結果になる」という堅牢さをシステムに持たせる設計原則だ。
まずAPIの決済・注文など副作用の大きい操作から冪等化を進め、SQSトリガーのLambda関数にはAWS Lambda Powertoolsを導入するところから始めよう。
二重処理のトラブル、もう防ぎたいと思っていませんか?
クラウド実務に役立つ「Cloud Architecture」カテゴリの記事を他にもまとめています。あわせて読みたい関連記事はこちらからどうぞ。
