AIワークフローで重複実行を防ぐ方法|IDと状態を持つ

AI Workflowで同じメールが2回送られたり、CRM Recordが重複したりする原因は、AIの回答精度よりRetry・Webhook再送・Timeout時のState管理にあることが多いです。

AIものさし編集部

AIワークフローの重複実行は、AIの回答精度だけでは防げません。同じWebhookの再送、実行のRetry、タイムアウト後の再送で、メールやレコード作成などの副作用が二度起きることがあります。対策は、イベントを識別するID、処理状態、Action結果を保存し、再受信時に状態を確認することです。

大切なのは「同じTriggerを二度受けない」だけでなく、「同じ業務Actionを二度実行しない」ことです。受信元の重複排除と、下流での二重作成・二重送信は別の境界なので、両方を確認します。完全に一度だけ実行されると仮定せず、再送されても結果が壊れにくい設計を目指します。

まず重複が起きる境界を分ける

表は横にスクロールできます

境界例確認すること
イベント受信Webhook送信元が同じEventを再送元システムの安定したEvent IDがあるか
Workflow起動PollingやTriggerが同じ項目を再検出どの範囲で既処理IDを記録するか
AI Step再実行前段の失敗後に分類・抽出を再度実行同じ入力・版・出力を追跡できるか
外部ActionCRM作成、メール送信、決済など相手側が冪等性キーや照会を支援するか
Workflowの自分への再Trigger処理結果が再び起動条件になるループを識別して止められるか

同じイベントを識別できても、処理途中でサーバーが停止すると、イベント記録と外部Actionの結果が別々の状態になります。例えば、Action成功後にcompleted保存前で落ちると、再実行側からは未完了に見えます。この区間を設計せずに「IDとStateを入れたから安全」とは言えません。

Stable IDとAction単位のKey

最優先で使うのは、Webhookや元データが提供する不変で一意なEvent IDです。注文番号のような業務IDでも、同じ注文で複数の正当な更新イベントが起きるならEvent IDの代わりにはなりません。日時だけをIDにすると、同時刻の別イベントを区別できない可能性があります。

Workflow内に複数の副作用がある場合は、Event全体のIDにAction名を加え、個別の処理状態を追います。例として、event_42:crm_upsertとevent_42:notify_ownerを別の処理として記録します。どの値が重複判定の正本なのか、イベントの再送期間、記録を保持する期間も仕様として決めます。


event_id       = 送信元が付与した安定ID

action_key     = event_id + 対象Actionの識別子

correlation_id = 関連する一連のWorkflowを追跡するID

この文字列は説明用の例です。実際のIDは衝突しない範囲とActionの単位に合わせて設計します。個人名、メールアドレス、本文などをIDへ直接埋め込まず、個人を特定しない参照値を使います。

状態遷移を設計する

表は横にスクロールできます

State意味再受信・再実行時の判断
receivedイベントとActionを受け付けた未着手なら処理開始を試みる
processing担当Workerが処理中同時実行を防ぎ、完了を確認する
action_pending外部Actionを呼び出す直前Actionの状態を確認してから開始
completedAction結果を確認して記録済み同じActionを繰り返さず結果を返す
failed_retryable再試行が安全と分かっている失敗回数・間隔・期限内で再試行
failed_terminal入力や権限など修正が必要な失敗Holdして人へ通知
unknown_outcome送信後に応答が失われ、結果未確認相手側を照会するまで再送しない

processingは永遠に残さないように、開始時刻、担当、期限またはLeaseの扱いを決めます。実行が停止したとき、期限切れのStateをどう回収するか、別Workerが処理を引き継いでも二重処理にならないか確認します。状態名は実装に合わせて変えられますが、成功・失敗・結果不明を同じ値へまとめないことが重要です。

受信から完了までの処理


1. Eventを受信し、event_idとpayloadの参照を検証

2. action_keyを作り、永続Storeで原子的に処理権を確保

3. 既にcompletedなら保存済みの結果を返して終了

4. processingなら待機・重複終了・レビューの規則へ進む

5. Actionを実行し、下流IDまたは結果を安全に記録

6. 完了確認後にcompletedへ遷移

7. Retry時は最新Stateと下流の実在状態を再確認

重要なのは、確認してから書き込む二段階が、同時実行時にも一つの実行だけを通すことです。単純に「Storeを読む→空なら書く」だけだと、同時に届いた2つのRunが両方とも空を見て処理を進める競合が起きます。データベースの一意制約、原子的な挿入、ロック、Queue等、利用する環境で保証できる方法を使います。

状態の保存先に実行履歴だけを使えるか、別の永続Storeが必要かは製品と設定に依存します。履歴を消すと重複判定も消える設計なら、保持期間と再送期間を比較してください。状態の保管・削除仕様が分からない場合は、運用前に公式資料と環境を確認します。

失敗時に二重実行を作らない

表は横にスクロールできます

失敗地点起きた可能性Retry前にすること
Action呼び出し前副作用はまだ起きていない入力と失敗原因を確認し、再試行条件を満たせば処理
Action送信中相手側で処理されたか不明unknown_outcomeとして状態照会または手動確認
Action成功後・完了記録前副作用は起きたがStoreは未完了の可能性外部Recordや冪等性Keyで既存結果を確認
Validation / 認証失敗仕様や資格情報の問題Retryを繰り返さず設定を修正
Rate limit / 一時障害短時間で復旧する可能性公式の待機・Retry指示、上限、期限を確認

下流APIが冪等性Keyを提供する場合は、公式仕様が許す形で同じActionの再試行に同じKeyを使います。Keyの有効期限、同じKeyで異なるpayloadを送った時の挙動、エラー後の結果照会などはAPIごとに違うため、実装前に確認します。機能が存在しない場合、検索してから作成するfind-or-createも候補ですが、検索と作成が原子的でなければ同時実行による競合は残ります。

架空ケース:CRMへ問い合わせを登録する

次の例は設計を説明するための架空ケースです。実サービスでの動作検証ではありません。Webhookがevent_id=evt_42を送るとし、ActionをCRM登録と担当者通知へ分けます。

表は横にスクロールできます

Action処理Key成功時に保存する値再実行時
CRM登録evt_42:crm_upsertCRM record ID、完了時刻既存Recordを照会し再作成を避ける
担当者通知evt_42:notify_owner通知Message ID、送信状態同一Notificationの扱いを確認

CRM登録が成功し、通知だけ失敗した場合、Run全体を最初からやり直すとCRMへ再登録してしまうことがあります。Actionごとに結果を記録すれば、完了済みのCRM処理を飛ばし、失敗した通知だけ再開できます。ただし、通知先が同じMessage IDや冪等Keyを扱うかは別途確認が必要です。

Find-or-CreateとLoopの注意

一意な業務Keyで既存Recordを検索し、あれば更新またはSkip、なければ作成する方法は重複を減らすのに役立ちます。しかし、曖昧な検索Keyで既存データへMergeすると、別顧客のRecordを誤更新する危険があります。また、同時処理が検索と作成の間に割り込むと二重作成が残り得ます。Keyの一意性と競合対策を確認してください。

Workflowの更新が同じWorkflowのTrigger条件に戻る場合は、処理済みMarker、対象Fieldの変更Filter、発生元の識別などを使い、再入を止めます。例えばCRMを更新した結果のWebhookが同じ処理を再起動するなら、更新者や変更Fieldだけでなく、Action単位のEvent IDと処理Stateも確認します。

Retryルールを分ける

Retryは、エラーが起きたら何度でも実行する仕組みではありません。入力が不正、権限不足、存在しないRecordなど、条件を直さなければ成功しないエラーは、RetryではなくHoldや修正担当へ送ります。一時的なネットワーク障害や利用制限は、公式資料にある待機条件、最大試行数、全体期限を確認してから再試行します。

Retry前に、今回の処理が外部へ何をしたかを記録します。成功の応答がなかったことと、処理が起きていないことは同じではありません。下流の結果照会、冪等性Key、または人による確認のどれも用意できない場合は、繰り返し自動再送するよりunknown_outcomeで止める方が安全です。

検証ケースと運用QA

  • 同じEventを2回順に送る → 外部Actionは重複せず、保存済み状態が返る

  • 同じEventをほぼ同時に送る → 原子的な処理権の確保が機能する

  • Action成功後、completed保存前に止める → 下流を照会し重複Actionを避ける

  • Action送信後にTimeoutさせる → unknown_outcomeへ進み、無条件Retryしない

  • Validation / 認証Error → 指定されたRetryへ流れずHoldする

  • Actionが複数あるEventで一つだけ失敗 → 完了Actionを再実行しない

  • Trigger更新が自分へ戻る → Loopを検出して停止する

各テストでEvent ID、Run ID、Action Key、実行回数、結果Record ID、State遷移を確認します。正常時だけのテストでは、タイムアウト・競合・途中停止が作る重複を見つけられません。検証環境で意図的な失敗を作り、再実行後の副作用数まで照合します。

ログにはEvent ID、Run ID、Action名、State、attempt、時刻、結果参照を残すと調査しやすくなります。payload全文、Secret、個人情報を重複調査のために無制限保存する必要はありません。入力を開ける権限、記録を保持する期間、削除後の再送リスクを、業務上の必要性と合わせて決めます。

次にすること

まずワークフローから外部副作用のあるActionをすべて列挙し、それぞれについて安定ID、状態の保存先、下流照会手段、Retry可否を記入します。次に、正常な再送、同時再送、Action後Timeoutの三ケースを検証環境で試し、下流の作成数とStateを比較します。

既存Workflowに同じEvent IDがない場合は、Triggerデータに安定IDがあるか確認します。なければ、生成方法と衝突・保持期間を設計します。状態Store、原子的な処理権、下流の照会・冪等性のいずれも確保できないActionは、少なくとも自動Retryを止め、担当者確認へ回すことを次の選択肢にします。

AIものさしの判断

重複実行対策はPromptの工夫ではなく、Event ID・Action単位State・副作用の結果照会を組み合わせる設計課題です。特にタイムアウト後は「失敗」と決めつけず、処理済みか分からないStateを扱います。同じEventを何度受けても処理権と結果を一貫して確認し、確認できないActionを安全に止めることが完成条件です。