Pega 連携の冪等性設計|リトライで二重処理を起こさないための判断
「リトライしたら二重に登録された」——連携で最も痛い事故のひとつです。キュープロセッサやネットワークのリトライは基本 at-least-once で、重複実行は前提として設計すべきものです。本記事では、冪等キー・重複検知・副作用の分離といった手法で、何度呼ばれても安全な連携をどう組むかを整理します。
結論から:リトライは前提、冪等性はこちら側で作る
Pega で外部システムとの連携を設計するとき、遅かれ早かれ必ず直面するのが「リトライしたら二重に処理された」という事故です。二重課金、二重登録、在庫の二重引当——いずれも本番で起きると顧客影響が大きく、原因調査も面倒な、連携で最も痛いクラスの障害です。
結論を先に言います。
キュープロセッサやネットワーク層のリトライは基本 at-least-once(最低1回配信)で動く。だから「同じ処理が複数回実行される」ことは前提として設計する。安全に再実行できる状態(=冪等性)を先に作ってから、リトライの回数と間隔を決める。
冪等性を確保する手段は、突き詰めると 2 つに集約されます。相手システムに冪等キー/相関 ID を渡して相手側で重複を弾かせるか、それができないなら Pega 側で「処理済みか」を判定してから副作用を実行するか。本記事では、この分岐を実案件での判断基準まで落とし込みます。
なぜ「一度きり」を前提にできないのか
まず、設計の土台になる事実を正確に押さえます。キュープロセッサ(Queue Processor)やネットワーク層のリトライは、基本的に at-least-once(最低1回配信)で動作します。 つまり、メッセージや処理は「少なくとも1回は必ず実行される」ことは保証されますが、「ちょうど1回だけ」は保証されません。
これは Pega の実装からも裏付けられます。Pega のキュープロセッサは内部的に Stream サービス(Kafka)上で動作し、処理に失敗したりコミットできなかったメッセージは「壊れた(broken)」項目として退避され、遅延キュー側で再試行戦略に従って再処理されます。この「取りこぼさず必ず再処理する」設計こそが at-least-once の実体であり、裏返せば 同じメッセージが複数回処理されうるということです。リトライ回数や保持の細部はバージョンにより異なるため、実装時は公式ドキュメントで挙動を確認してください。
なぜ二重実行が起きるのか。典型的なのは次のような状況です。
- 相手システムの処理は成功したのに、応答(レスポンス)がタイムアウトやネットワーク断で戻ってこなかった。Pega 側は「失敗した」と判断してリトライする。結果、相手側では同じ処理が2回走る。
- キュープロセッサがメッセージを処理し終えた直後・コミット前にワーカーが落ちた。メッセージは未確定として残り、別のワーカーが再度拾って処理する。
ここで重要なのは、「成功したかどうか分からない」ときにリトライするのは、信頼性設計として正しいという点です。at-least-once は欠陥ではなく、メッセージを取りこぼさないための正しい選択です。問題は、その正しさが「同じ処理が複数回走りうる」という副作用とセットだということです。
だからこそ、exactly-once(ちょうど1回)を前提にした設計は破綻しやすいのです。「リトライは滅多に起きないから大丈夫」「タイムアウトはレアケース」という前提で組むと、負荷が上がった本番でネットワークが不安定になった瞬間に、二重処理が一気に顕在化します。正しい向き合い方は、exactly-once を祈るのではなく、二重に呼ばれても壊れないようにこちら側を設計することです。
冪等性とは「何度呼んでも最終結果が同じ」であること
冪等(idempotent)な連携とは、「同じ入力で何度実行しても、システムの最終結果が同じになる」性質を指します。1回呼んでも、同じ入力で3回呼んでも、最終的なデータの状態が変わらないなら、その処理は冪等です。
冪等性が確保できていれば、リトライは怖くありません。「成功したか分からないからもう一度呼ぶ」を安心して繰り返せます。逆に冪等でない処理をリトライ対象にすると、リトライのたびに副作用が積み上がっていきます。
冪等性を作る基本形は、相手システムに冪等キー(冪等性キー)や相関 ID を渡し、相手側で「このキーはもう処理済み」と重複を弾かせることです。同じ論理的な操作には同じキーを付与し、相手はそのキーを見て「初めてなら処理する、既に見たキーなら以前の結果を返すだけで副作用は起こさない」と振る舞います。これにより、通信路の途中で何回配信されても、実際の副作用は1回に収束します。
基本形:冪等キー/相関 ID を相手に渡す
もっとも堅牢なのは、相手システムが冪等キーに対応しているケースです。Pega 側は、その連携1件を一意に識別する ID(ケース ID や、業務的に一意な注文番号など)を冪等キーとして生成し、リクエストに載せて送ります。
設計上のポイントは次のとおりです。
- キーは「論理的な操作」に対して一意にする。 リトライで同じ操作を再送するときは、同じキーを再利用します。リトライのたびに新しいキーを振ってしまうと、相手からは別々の操作に見え、重複排除が効きません。ここを取り違えると冪等キーを渡している意味がなくなります。
- キーは決定論的に導出する。 ランダム生成した値をどこにも保存せずに使うと、リトライ時に同じキーを再現できません。ケースのプロパティなど、再実行しても同じ値が得られる場所から導出・保存します。
- 相手の重複排除の窓(保持期間)を確認する。 相手が冪等キーを覚えている期間には上限があることが多く、その窓を越えて遅延リトライが来ると重複排除が効かない場合があります。リトライ間隔を設計するときに合わせて確認します。
HTTP メソッドの性質を利用する
REST 連携では、HTTP メソッドの性質そのものが冪等性の助けになります。
| メソッド | 本来の冪等性 | 連携設計での扱い |
|---|---|---|
| GET | 冪等(参照のみ) | 何度呼んでも安全。リトライを気にしなくてよい |
| PUT | 冪等(同じ状態に上書き) | 「この ID をこの内容にする」なら二重実行しても結果は同じ |
| DELETE | 冪等(消えた状態は同じ) | 2回消しても「無い」状態に収束する(応答コードの扱いは要確認) |
| POST | 非冪等(都度新規作成) | 登録系。冪等キー等の重複防止策を必ず併用する |
GET / PUT / DELETE は本来冪等なので、リトライしても最終結果は変わりません。問題は POST(新規登録) です。POST は呼ぶたびに新しいリソースを作るのが本来の意味なので、素朴にリトライすると二重登録になります。登録系の連携には、冪等キーや業務的な一意キーによる重複防止を必ず併用する——これが原則です。
ここで実務的な設計判断が出てきます。「新規登録」を、相手が対応しているなら PUT(一意キーを指定した upsert 的な更新)に寄せることで、そもそも冪等なメソッドの土俵に持ち込めないか。API 仕様が許すなら、POST + 冪等キーより PUT のほうが素直に冪等性を確保できることがあります。
相手が冪等キーに対応しないとき — Pega 側で処理済み判定
現実には、相手システムが冪等キーに対応していない連携先は珍しくありません。レガシーな基幹システム、古い SOAP サービス、単純な受信 API——こうした相手には「重複を弾いてくれ」と期待できません。
この場合は、Pega 側で「この操作は処理済みか」を判定してから、副作用のある呼び出しを実行する設計にします。具体的な手段は次の3つが基本です。
- 一意制約(unique constraint) — 業務的に一意なキー(注文番号など)にデータベースの一意制約をかけ、2件目の登録を物理的に弾く。最も堅牢で、競合状態にも強い。
- 重複検知テーブル — 「処理済みのキー」を記録する専用テーブルを持ち、副作用の実行前にキーの有無を確認する。存在すればスキップ、なければ記録してから実行する。
- ステータス確認 — 副作用を起こす前に、対象ケースやレコードの状態を読み、「すでに送信済み/登録済み」なら再実行しない。
判定の設計で外してはいけないのが、「判定」と「副作用」の間に隙間を作らないことです。「処理済みか確認 → 未処理なら送信」の2ステップの間に、別のリトライが割り込んで両方とも「未処理」と判定すると、二重に送信されます。可能な限り一意制約のようなアトミックな仕組みで守り、チェックだけに頼らない設計にします。
副作用とべき等な参照更新を切り分ける
冪等性設計の実務でいちばん効くのは、「リトライで危険なのはどこか」を最初に見極めることです。すべての処理を厳密に守ろうとすると設計が過剰になります。危険な箇所を絞り込めば、対策を集中できます。
処理を「副作用(外部に不可逆な影響を与える)」と「べき等な参照更新(何度やっても同じ状態になる)」に分けて考えます。
| 処理の種類 | 例 | リトライで危険か | 対策 |
|---|---|---|---|
| 課金・決済 | 決済 API への請求 | 危険(二重課金) | 冪等キー必須、または Pega 側で処理済み判定 |
| 新規登録 | 外部システムへのレコード作成 | 危険(二重登録) | 冪等キー/一意キー、PUT 化を検討 |
| 通知・送信 | メール・SMS・Webhook 送信 | 危険(多重送信) | 送信済みフラグ、重複検知 |
| 在庫引当 | 在庫の確保・減算 | 危険(二重引当) | 一意制約、処理済み判定 |
| 状態の上書き | 「この ID を状態 X にする」 | 安全(べき等) | 対策不要。PUT 的に設計 |
| 参照・取得 | マスタの GET | 安全 | 対策不要 |
この表の使い方はシンプルです。「危険」の行だけに冪等性の作り込みを集中させる。参照や、状態を特定の値に収束させる更新(べき等な更新)は、リトライしても結果が変わらないので、そもそも守る必要がありません。
設計をひとつ進めるなら、危険な副作用と、べき等な更新を、処理として分離するのが有効です。ひとつの処理の中に「べき等な準備処理」と「一度きりであるべき課金」が混在していると、リトライしたときにどこまでが安全でどこからが危険なのかが曖昧になります。副作用を末端に寄せ、それ単体を冪等キーや処理済み判定で守る構造にしておくと、リトライ設計が一気に楽になります。
冪等性とリトライ設計はセットで初めて成立する
最後に、いちばん誤解されやすい点を強調します。冪等性はリトライ設計とセットで初めて意味を持ちます。 どちらか片方だけでは不十分です。
- 冪等性がないのにリトライする → 二重処理を量産する
- 冪等性はあるがリトライ設計が雑 → 無限リトライで相手を叩き続ける、あるいは諦めが早すぎて処理が欠落する
正しい順序は、まず「安全に再実行できる」ことを保証し、それができてからリトライの回数・間隔・上限を決めることです。冪等性という土台がある前提で、リトライ回数の上限、バックオフ(間隔を空けて再試行する)、上限を超えたときの退避先(デッドレター相当の受け皿)を設計します。Pega では、この「退避先」に相当するのが処理不能なメッセージを溜める broken(壊れた項目)キューで、再試行自体は遅延キューを介して行われます。連携コネクタ側にも、一時的なエラーに対する自動リトライやエラーハンドラーフローといった仕組みが用意されており、これらを土台の冪等性とセットで設計します。土台がないままリトライ回数だけ増やすのは、事故を増やす行為でしかありません。
よくある失敗
exactly-once を前提に組んでしまう。 最も多い失敗です。「リトライは滅多に起きない」という楽観のもとで重複対策を省くと、負荷が上がった本番で一斉に二重処理が出ます。at-least-once は前提であって例外ではありません。
リトライのたびに新しい冪等キーを振ってしまう。 冪等キーを使っているつもりでも、再送のたびにキーを再生成していては、相手からは毎回別の操作に見え、重複排除がまったく効きません。リトライでは同じキーを再利用する——ここが冪等キー設計の心臓部です。
「確認してから実行」の隙間を突かれる。 処理済みチェックと副作用の実行が別ステップだと、その間に別のリトライが割り込み、両方とも「未処理」と判定して二重実行します。チェックだけに頼らず、一意制約のようなアトミックな仕組みで守ります。
POST を素朴にリトライする。 POST は非冪等です。登録系にリトライをかけるなら、冪等キーの併用か、可能なら PUT 化を検討します。メソッドの性質を無視したリトライは二重登録の直行便です。
すべてを一律に厳重化する。 逆方向の失敗もあります。参照系やべき等な更新まで重い重複対策で固めると、設計が複雑になり保守しづらくなります。守るべきは「危険な副作用」だけ、と切り分けることが大切です。
まとめ
- リトライは at-least-once が前提。 「ちょうど1回」は保証されない。exactly-once を前提にした設計は破綻する
- 冪等性 = 同じ入力で何度実行しても最終結果が同じ。 これを先に保証してからリトライを設計する
- 基本形は冪等キー/相関 ID を相手に渡し、相手側で重複を弾かせる。 リトライでは同じキーを再利用する
- HTTP メソッドの性質を使う。 GET / PUT / DELETE は冪等、POST は非冪等。登録系には重複防止を必ず併用する
- 相手が冪等キー非対応なら、Pega 側で処理済み判定(一意制約・重複検知テーブル・ステータス確認)してから副作用を実行する
- 副作用とべき等な参照更新を切り分け、「リトライで危険なのはどこか」を明確にする。 危険な箇所に対策を集中させる
「リトライしたら二重に登録された」という事故は、設計時に「この処理は何度呼ばれても大丈夫か?」という一問を通しておくだけで、その大半を未然に防げます。連携設計の初期にこの型をチームで共有しておくことが、後工程での障害対応コストを大きく下げます。
関連リンク
関連記事
Pega 宣言的処理のコスト設計|前方連鎖と後方連鎖の使い分け
Declare Expression は便利だが、前方連鎖のカスケードは1つのプロパティ変更が広範な再計算を引き起こす。宣言的処理は「書けば動く」半面、実行コストが見えにくい。本記事は前方連鎖と後方連鎖、宣言的と手続き的の境界線を、性能と保守性の観点で整理する。
記事を読むPega Insights と Report Definition の使い分け|セルフサービス分析と運用レポートの線引き
「レポートは Report Definition で作るべきか、Insights でユーザーに任せるべきか」——ダッシュボード設計で必ず出る分岐です。統制と再利用が要る定型レポートはルール化し、アドホックな探索はセルフサービスに開く、が基本方針。本記事では両者のガバナンス差とデータソースの前提から、線引きの判断軸を示します。
記事を読むPega 監査証跡設計:フィールド監査・履歴・セキュリティ監査の使い分け
「何を、どこまで監査するか」は Pega のセキュリティ設計で最も判断が割れるテーマです。フィールド監査・ケース履歴・セキュリティイベントの使い分けと、監査のかけ過ぎが招く性能・容量問題の回避策を、LSA の視点で解説します。
記事を読む