Amazon Connect の StartOutboundVoiceContact API で冪等性パラメータを使って二重発信を防ぐ考え方を整理してみた
はじめに
Amazon SQS の標準キュー、Amazon SNS の標準トピック、Amazon EventBridge などを起点に AWS Lambda(以下、Lambda)を起動する構成では、同じメッセージやイベントが複数回処理される可能性があります。
例えば、Lambda から Amazon Connect の StartOutboundVoiceContact API を呼び出して電話を発信する構成を考えます。今回は、同じ通知リクエストに対して、同じ相手へ複数回発信したくないものとします。
重複処理への対策として、Amazon DynamoDB(以下、DynamoDB)などで処理済みフラグを管理する方法があります。一方、後続の API が冪等性をサポートしていれば、API が用意しているパラメータを使って二重発信や重複作成を防げる場合があります。
StartOutboundVoiceContact API には、リクエストの冪等性を確保するための ClientToken パラメータがあります。
今回は、AWS API が提供する冪等性パラメータの一例として、StartOutboundVoiceContact API の ClientToken の使い方と、DynamoDB などによる重複管理との使い分けを整理します。
結論
同じ発信リクエストを再試行する場合は、同じ ClientToken を指定します。
トークンの有効期間内にコンタクトがすでに開始されている場合、StartOutboundVoiceContact API は新しいコンタクトを開始せず、既存の ContactId を返します。
次の条件を満たすのであれば、二重発信防止だけを目的として、DynamoDB に処理済みフラグを保存する必要は基本的にありません。
- 同じ論理リクエストに対して同じ
ClientTokenを指定できる - 重複処理がトークンの有効期間である7日以内に行われる
- 重複を防ぎたい対象が
StartOutboundVoiceContactAPI による電話発信である
一方、次のような場合は、DynamoDB などの外部ストアを検討します。
- 7日を超えて同じ通知の重複を防ぎたい
- 複数システムからの発信要求を一元的に管理したい
- 発信履歴や再架電状態を管理したい
- 電話発信以外の処理も含めて重複を制御したい
整理すると、以下のようになります。
| 目的 | 対応 |
|---|---|
| API の再試行による二重発信を防ぎたい | 同じ ClientToken を指定する |
| SQS や EventBridge の再処理を同じ発信として扱いたい | 通知 ID などから同じ ClientToken を生成する |
| 7日を超えて重複を防ぎたい | DynamoDB などで業務上の重複を管理する |
| 複数システムから同じ要求が来る可能性がある | 共通の ID 設計や共有ストアを検討する |
| 発信履歴や再架電状態を管理したい | DynamoDB などに業務データとして保存する |
ポイントは、ClientToken が防ぐのは Lambda の重複実行そのものではなく、StartOutboundVoiceContact API が再実行された場合の二重発信である点です。
同じメッセージやイベントが複数回処理される可能性がある
SQS 標準キュー、SNS、EventBridge などを起点に Lambda を実行する構成では、同じメッセージやイベントが複数回処理される可能性があります。
SQS、SNS、EventBridge
↓
Lambda
↓
StartOutboundVoiceContact
SQS を Lambda のイベントソースとして利用する場合、Lambda のイベントソースマッピングは各イベントを少なくとも1回処理します。そのため、同じレコードが重複して処理される可能性があります。
SNS から Lambda を起動する場合も、Lambda は非同期で呼び出されるため、同じイベントが複数回処理される可能性があります。
EventBridge がターゲットへのイベント配信に失敗した場合は、再試行ポリシーに従って配信が再試行されます。デフォルトでは、24時間、最大185回再試行されます。
また、EventBridge から Lambda にイベントが渡された後は、Lambda の非同期呼び出しによる再試行も関係します。関数がエラーを返した場合はデフォルトで追加の実行が2回行われ、エラーを返していない場合でも同じイベントを複数回受け取る可能性があります。
そのため、これらのサービスを起点に Lambda を実行する場合は、後続の API が複数回呼び出されても二重発信や重複作成が発生しないように設計する必要があります。
AWS API における冪等性パラメータの例
ClientToken のような冪等性パラメータは、Amazon Connect 固有のものではありません。
AWS Builders Library の「Making retries safe with idempotent APIs」では、安全に API を再試行する方法として、呼び出し元が一意のリクエスト識別子を指定する設計が説明されています。
AWS API における冪等性パラメータの例は、以下のとおりです。
| サービス | API 例 | パラメータ例 | 補足 |
|---|---|---|---|
| Amazon Connect | StartOutboundVoiceContact |
ClientToken |
トークンは7日間有効。開始済みの場合は既存の ContactId が返る |
| Amazon Connect | StartChatContact、StartTaskContact |
ClientToken |
コンタクト開始リクエストの冪等性に利用する |
| Amazon EC2 | RunInstances、CreateVolume など |
ClientToken |
一部の API がクライアントトークンをサポートする |
| Amazon ECS | RunTask、CreateService など |
clientToken |
同じトークンと同じリクエストパラメータによる再試行を冪等に扱う |
| Amazon DynamoDB | TransactWriteItems |
ClientRequestToken |
トークンは最初のリクエスト完了後10分間有効 |
| AWS Secrets Manager | PutSecretValue |
ClientRequestToken |
シークレットバージョンの識別子として利用する |
| AWS CloudFormation | CreateStack |
ClientRequestToken |
リクエストの識別やイベントの追跡に利用する |
| AWS Step Functions | StartExecution |
name と input |
STANDARD ワークフローでは、同じステートマシンに対する同じ name と input の実行が進行中の場合に、元のリクエストと同じレスポンスが返る |
API ごとに、パラメータ名、有効期間、適用範囲、同じトークンで異なるパラメータを指定した場合の挙動が異なります。
そのため、利用する API のドキュメントで個別に仕様を確認する必要があります。
参考資料:
- https://docs.aws.amazon.com/connect/latest/APIReference/API_StartChatContact.html
- https://docs.aws.amazon.com/connect/latest/APIReference/API_StartTaskContact.html
- https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-idempotency.html
- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_Idempotency.html
- https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
- https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_PutSecretValue.html
- https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html
- https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartExecution.html
StartOutboundVoiceContact API の ClientToken とは
StartOutboundVoiceContact API の ClientToken は、リクエストの冪等性を確保するために指定する識別子です。
| 項目 | 内容 |
|---|---|
| パラメータ名 | ClientToken |
| 用途 | リクエストの冪等性を確保する |
| 大文字小文字 | 区別される |
| 最大長 | 500文字 |
| 必須かどうか | 必須ではない |
| 未指定時 | AWS SDK が値を設定する |
| 有効期間 | 作成後7日間 |
| コンタクトが開始済みの場合 | 既存の ContactId が返される |
同じ ClientToken でリクエストを再試行し、コンタクトがすでに開始されている場合は、既存の ContactId が返されます。
一方、同じ ClientToken を使いながら、宛先電話番号やコンタクトフローなどのリクエストパラメータを変更した場合の挙動は、StartOutboundVoiceContact API の公式ドキュメントには明記されていません。
そのため、同じ ClientToken は、リクエストパラメータも同じである、同一の論理リクエストの再試行にだけ使用するのが安全です。
同じ論理リクエストの再試行
→ 同じ ClientToken と同じリクエストパラメータを使う
別の論理リクエスト
→ 別の ClientToken を使う
ClientToken の決め方
ClientToken は、再処理のたびに新しい値を生成するのではなく、通知 ID や受信者 ID などの業務 ID から決定します。
例えば、「同じ通知 ID、かつ同じ受信者 ID」を同じ発信要求とみなす場合は、次のような値を使用できます。
call-notification-12345-user-67890
SQS メッセージの送信側で clientToken を決め、メッセージ本文に含める方法もあります。
{
"notificationId": "notification-12345",
"recipientId": "user-67890",
"destinationPhoneNumber": "+8190xxxxxxxx",
"clientToken": "call-notification-12345-user-67890"
}
また、複数の ID を連結してハッシュ化する方法もあります。
ClientToken = SHA-256(
notificationId + "\n" +
recipientId
)
重要なのは、同じ論理リクエストからは同じ値が生成され、別の論理リクエストからは別の値が生成されることです。
なお、同じ通知、同じ相手への発信であっても、業務上の意図的な再架電は別の論理リクエストです。その場合は、再架電単位の ID を含めて別の ClientToken を生成します。
Lambda や API の再試行
→ 同じ ClientToken
業務上の意図的な再架電
→ 別の ClientToken
EventBridge の id を利用する場合
EventBridge のイベントには、トップレベルに id フィールドがあります。id はイベントごとに生成される一意な識別子で、イベントの追跡に利用できます。
EventBridge による同じイベントの再配信だけを重複とみなすのであれば、この id を ClientToken の元にできます。
ただし、同じ通知要求が送信元から複数回 PutEvents された場合、それぞれ別の EventBridge イベントとなり、異なる id が付与されます。
そのため、「同じ通知要求が複数回登録された場合も、1回の発信として扱いたい」という要件では、イベントの detail に通知 ID や受信者 ID を含め、その値から ClientToken を生成します。
{
"id": "11111111-1111-1111-1111-111111111111",
"detail": {
"notificationId": "notification-12345",
"recipientId": "user-67890",
"destinationPhoneNumber": "+8190xxxxxxxx"
}
}
| 重複とみなす範囲 | ClientToken の元にする値 |
|---|---|
| 同じ EventBridge イベントの再配信 | トップレベルの id |
| 同じ通知要求の複数回登録 | notificationId や recipientId などの業務 ID |
| 複数システムから送信された同じ通知 | システム間で共通化した通知 ID |
ClientToken を指定しない場合
StartOutboundVoiceContact API では、ClientToken を指定しなかった場合、AWS SDK が値を設定します。
ただし、SQS や EventBridge の再処理によって Lambda が改めて実行された場合、前回の Lambda 実行と同じ ClientToken が自動的に使われることは保証されません。
1回目の Lambda 実行
→ AWS SDK が ClientToken を自動設定
同じメッセージによる2回目の Lambda 実行
→ 前回と同じ ClientToken が設定される保証はない
異なる ClientToken が使われた場合、Amazon Connect からは別の発信リクエストとして扱われる可能性があります。
そのため、SQS や EventBridge の再処理を含めて二重発信を防ぎたい場合は、通知 ID などから決定した ClientToken をアプリケーション側で明示的に指定します。
DynamoDB を検討するケース
ClientToken は、StartOutboundVoiceContact API の再試行による二重発信を防ぐための仕組みです。通知単位の長期的な重複排除や、発信履歴の管理をすべて置き換えるものではありません。
7日を超えて重複を防ぎたい場合
ClientToken の有効期間は作成後7日間です。
例えば、次のような要件は業務ルールとしての重複排除であり、DynamoDB などで管理する必要があります。
同じ通知 ID と同じユーザー ID への発信は、
30日間に1回だけにしたい
次のような運用がある場合は、同じ通知が7日を超えて再処理される可能性があります。
- DLQ のメッセージを後日再投入する
- 障害復旧後に過去のイベントを再処理する
- EventBridge のアーカイブからイベントをリプレイする
- 過去の通知を手動で再実行する
DynamoDB を利用する場合でも、StartOutboundVoiceContact API の呼び出しには ClientToken を指定します。
DynamoDB
→ 業務上の通知単位で重複を管理する
ClientToken
→ API の再試行による二重発信を防ぐ
DynamoDB への書き込みと StartOutboundVoiceContact API の呼び出しは、1つのトランザクションとして実行できません。そのため、DynamoDB を利用する場合も、API を安全に再試行できるようにしておく必要があります。
複数システムから発信する場合
複数のシステムが別々のルールで ClientToken を生成すると、業務上は同じ通知でも、Amazon Connect からは別の発信要求として扱われる可能性があります。
複数システムをまたいで重複を防ぎたい場合は、次のような対応を検討します。
- システム間で共通の通知 ID を使用する
ClientTokenの生成ルールを共通化する- 発信要求を1つのサービスに集約する
- DynamoDB などの共有ストアで重複を管理する
発信履歴や再架電状態を管理する場合
ClientToken は、発信履歴や再架電状態を保存するための仕組みではありません。
次のような情報を管理したい場合は、DynamoDB などに業務データとして保存します。
- 通知 ID
- 受信者 ID
ClientTokenContactId- 発信日時
- 発信状態
- 再架電回数
- エラー内容
ClientToken
→ API 再試行時の二重発信防止
DynamoDB
→ 発信履歴、監査、再架電状態などの管理
また、Lambda 内でメール送信や他の API 呼び出しも行う場合、ClientToken によって冪等になるのは StartOutboundVoiceContact の呼び出しだけです。Lambda の処理全体については、別途冪等性を検討する必要があります。
まとめ
SQS、SNS、EventBridge などから Lambda を起動する構成では、同じメッセージやイベントが複数回処理される可能性があります。
Amazon Connect の StartOutboundVoiceContact API では、同じ論理リクエストに同じ ClientToken を指定することで、API の再試行による二重発信を防ぎやすくなります。
同じ論理リクエストの再試行
→ 同じ ClientToken
別の論理リクエストや意図的な再架電
→ 別の ClientToken
同じ論理リクエストの再処理が7日以内に行われ、再処理時にも同じ ClientToken を指定できるのであれば、二重発信防止だけを目的として DynamoDB に処理済みフラグを保存する必要は基本的にありません。
一方、次のような要件には DynamoDB などの外部ストアを利用します。
- 7日を超える重複を防ぐ
- 複数システムからの発信要求を一元管理する
- 発信履歴や再架電状態を管理する
- 電話発信以外の処理も含めて重複を制御する
ClientToken と DynamoDB は、次のように役割を分けて考えます。
ClientToken
→ StartOutboundVoiceContact API の再試行による二重発信を防ぐ
DynamoDB
→ 業務上の重複、長期間の重複、履歴や状態を管理する
まずは API が提供する冪等性の仕組みを利用し、それだけでは扱えない期間や範囲に対して、DynamoDB などの外部ストアを組み合わせるのがよさそうです。







