技術ブログ / 読了 約 10 分

Pega spin-off と split-for-each の使い分け|並列サブプロセス設計

「サブプロセスを並列で回したい」という要件は同じでも、親が完了を待つのか、可変個を待ち合わせるのか、固定の分岐を合流させるのかで選ぶべき仕組みは違う。spin-off・split-for-each・split-join を取り違えると、待つべき処理を待たずに先へ進んだり、大量並列で負荷やトランザクションの問題を起こしたりする。3つの違いと判断軸を解説する。

結論から:軸は「join(待ち合わせ)の有無と粒度」

Pega でサブプロセスやサブケースを並列・非同期に起動したい、という要件は頻出します。「複数の処理を同時に走らせたい」「重い処理を裏で回して先に進みたい」——言葉は同じでも、実装で選ぶべき仕組みは spin-off / split-for-each / split-join の 3 つに分かれ、挙動はまったく違います。

結論を先に言います。

選定の主軸は「親フローが結果を待ち合わせる(join する)か、待つならその粒度」。待たないなら spin-off、可変個を待つなら split-for-each、固定・少数の異なる分岐を待つなら split-join。

「並列で回す」という表層だけで選ぶと、待つべき処理を待たずに先へ進んでしまったり、件数が読めない処理を固定分岐で組んで破綻したりします。本記事では、この 3 つを join の有無と粒度で切り分け、大量並列時のトランザクション境界と部分失敗リカバリまで、LSA 目線で整理します。

spin-off・split-for-each・split-join の選定フロー 親フローが結果を待たないなら spin-off、待つ対象が可変個なら split-for-each、固定・少数の異なる分岐なら split-join を選ぶ判断フロー図。 並列でサブプロセスを起動したい ① 親フローは結果を待つ? (結果を join=待ち合わせる?) いいえ spin-off 非同期・待たない (fire-and-forget) はい ② 待つ対象は可変個? (リストの各要素に同じ処理) はい split-for-each 各要素を並列+join (同一処理×可変個) いいえ(固定・少数) split-join 異なる複数プロセスを並列→合流 (異なる処理×固定・少数の分岐) 各フロー図形の可用性・挙動は Pega の版により異なる ― 対象環境で確認する
図:親が結果を待たないなら spin-off、待つ対象が可変個なら split-for-each、固定・少数の異なる分岐なら split-join。分ける軸は join の有無と、その粒度。

3つの仕組みは何が違うのか

spin-off — 非同期で投げて待たない(fire-and-forget)

spin-off は、サブプロセスやサブケースを 非同期で開始し、親フローはその完了を待たずに次のステップへ進みます。いわゆる fire-and-forget です。起動した処理の結果を、親フローのその後の判断に使わない場合に向きます。

例:ケース確定時に「通知メールを送る」「外部システムへ実績を連携する」といった、成否や完了タイミングを親フローが気にしない付随処理。親は投げたら即座に本流を進めます。

注意点は「待たない=結果を当てにできない」ことです。spin-off で起動した処理が失敗しても、親フローはそれを知らずに完了まで進みます。結果を親の判断材料にしたい処理を spin-off にしてはいけません。

split-for-each — ページリストの各要素を並列処理し join で待つ

split-for-each は、ページリスト(可変個の要素)の各要素に対して同一のサブプロセスを起動し、join 条件で待ち合わせます。件数が実行時まで決まらない「N 件を同じ処理で回す」パターンの本命です。

join 条件は「全件の完了を待つ」「いずれか 1 件で先へ進む」「指定した件数・条件を満たしたら進む」といった粒度で指定できます。たとえば「申込に含まれる明細 N 行それぞれを審査サブプロセスにかけ、全件終わったら次へ」は split-for-each の典型です。要素数が 3 件でも 300 件でも、同じ定義で回せるのが強みです。

split-join — 固定・少数の異なるプロセスを並列起動して合流

split-join は、あらかじめ決まった複数の(異なる)サブプロセスを並列に起動し、join 条件で合流させます。分岐の数と中身が設計時に固定されているパターンに向きます。

例:融資審査で「反社チェック」「信用情報照会」「本人確認」の 3 つを同時に走らせ、すべてそろったら次へ進む。分岐は 3 つで固定、それぞれ中身が違う——これが split-join です。split-for-each が「同じ処理 × 可変個」なのに対し、split-join は「異なる処理 × 固定個」と覚えると混同しません。

選定の主軸:待ち合わせ(join)の有無と粒度

3 つの選択は、突き詰めると 2 つの問いに落ちます。

  1. 親フローは結果を待つ(join する)か? — 待たないなら spin-off で確定。
  2. (待つ場合)待つ対象は可変個か、固定・少数の異なる分岐か? — 可変個なら split-for-each、固定分岐なら split-join。

言い換えると、spin-off と(split-for-each / split-join)を分けるのは join の有無、split-for-each と split-join を分けるのは 並列の粒度(同じ処理 × 可変個か、異なる処理 × 固定個か)です。この 2 問の順で考えれば、3 つのどれを使うかは一意に決まります。

比較表

観点spin-offsplit-for-eachsplit-join
join(待ち合わせ)しないするする
親フローの継続即継続(待たない)join まで待つjoin まで待つ
並列の対象単一のサブプロセス/サブケースリストの各要素(同一処理)複数の異なるプロセス
件数1(投げっぱなし)可変(実行時に決まる)固定・少数
join 条件なし全件/いずれか/件数・条件全件/いずれか/条件
結果の利用親は使わない親が結果を使える親が結果を使える
向くケース通知・連携など付随処理N 件を同じ処理で並列固定分岐の同時実行

大量並列で必ず設計すること

3 つのどれを選ぶにせよ、並列度が上がるほど「起動の仕方」より「起動した先の負荷と失敗の扱い」が設計の本題になります。ここを詰めずに並列化すると、開発環境では動いても本番の件数で破綻します。

1. 「並列」は同一スレッド上の論理的な並列であることを理解する

まず押さえるべきは、spin-off / split-join / split-for-each の「並列」は、OS スレッドを分けた計算並列ではないという点です。Pega の公式ドキュメントでも、spin-off した子フローは「非同期かつ独立に実行されるが、当初は同一ノードの同一 Java スレッド上で動く」(execute asynchronously and independently, but initially within the same Java thread on the same node)と説明されています。split-join / split-for-each も同様に、同一リクエスタのスレッド内で各サブフローが処理され、アサインメント(人手の待ち)で制御が返ることで並行しているように見える——いわば アサインメント単位の論理的な並列 です。別ワーカーへ自動的に分散されるわけではありません。

この理解が抜けると、設計を次のように見誤ります。

  • split-for-each で数百件を一括起動しても、別スレッド/別ワーカーに分散されない。 各要素のフローは同じスレッドで処理されるため、1 回のインタラクションが「N 件ぶんの処理」になって重くなります。「1 件では速いが数百件で詰まる」のは、キューが渋滞するからではなく、単一スレッド上で回るからです。
  • 生成物(アサインメント・ケース行・ロック)が件数に比例して増える。 大量の子フローは work/assign テーブルの行数や、親ケースのロック競合を押し上げます。

本当にバックグラウンドで分散処理したい(別スレッド/別リクエスタで捌きたい)なら、フロー図形ではなく、子ケース化(必要に応じて「親をロックしない=Do Not Lock Parent」)や、Queue Processor / Job Scheduler などの非同期処理へ寄せます。キューやワーカーのスループット・同時実行数を見積もるべきなのは、この非同期処理を選んだときです。フロー図形の並列と、真の非同期バックグラウンド処理を混同しないでください。

2. トランザクション境界とコミットのタイミング

Pega のフロー処理は、フローが終了したとき、およびアサインメントが生成・完了したときに自動でコミットします(“Flow processing performs commits automatically when the flow ends and when an assignment is created or completed”)。並列サブプロセスは前述のとおり同一スレッド上で処理されますが、それぞれがアサインメント境界やフロー終了で順にコミットしていくため、どこまでが確定済みかは「起動の仕方」ではなく「アサインメント/フローの区切り」で決まります。ここを把握していないと、「一部の子だけ確定して一部は未確定」という中途半端な状態が生まれます。

特に spin-off は 親に合流しない独立したフロー実行 で、自身のアサインメント境界で勝手にコミットしていきます。つまり、いったん spin-off が走り出して確定した処理は、親をロールバックしても取り消せません。fire-and-forget は「投げたら取り消せない」前提で使ってください。なお、フロー関連のアクティビティで手動 Commit を挟むと自動コミットと干渉するため避けます。真に独立したトランザクション単位が必要なら、子ケース化や非同期処理で切り離します。

3. 部分失敗のリカバリ

可変個を並列処理する split-for-each で最も重要なのがこれです。300 件のうち 3 件が失敗したとき、それを「全体失敗」とみなすのか、「297 件成功・3 件は再処理待ち」とみなすのか。 join 条件の選び方(全件を待つのか、成功分だけで先へ進むのか)と、失敗要素の退避・再処理の仕組みをセットで決めておく必要があります。ここを決めずに「全件 join」にすると、1 件の失敗で全体が止まります。逆に無条件で先へ進めると、失敗が握りつぶされます。どこまでを成功扱いにするかは業務要件の判断であり、フロー設計の前に合意すべき事項です。

具体例で見る使い分け

  • ケース確定後の通知メール送信 → 結果を親が待つ必要がない。spin-off。
  • 外部システムへの実績連携(結果を親フローで使わない) → spin-off。
  • 申込明細 N 行をそれぞれ審査(件数は申込ごとに可変) → split-for-each、全件 join。
  • カート内の各商品の在庫引当(点数は可変) → split-for-each。
  • 融資審査で反社・信用・本人確認を同時実行(3 つで固定) → split-join、全件 join。
  • 複数の承認者のうち誰か 1 人が承認すれば進む → 対象が固定の少数なら split-join +「いずれか」、承認者リストが可変なら split-for-each +「いずれか」。

判断の入口はいつも同じです。「親はこの結果を待つ?」→ No なら spin-off。Yes なら「待つ対象は同じ処理 × 可変個か、異なる処理 × 固定個か」で split-for-each か split-join を選びます。

よくある失敗

待つべき処理を spin-off にしてしまう。 「非同期で速くなりそう」と、付随処理でないものまで spin-off にすると、親フローが結果を待たずに進み、後続ステップが未完了の前提データを掴みます。結果を親の判断に使うなら、それは join が要る=spin-off ではありません。

可変個を split-join で組んでしまう。 「今は 3 件だから」と固定分岐で並べた結果、件数が変わるたびにフローを直す羽目になります。実行時に件数が決まる/増減するなら split-for-each 一択です。固定分岐は「数と中身が設計時に確定している」ときだけ使います。

部分失敗を設計しないまま大量並列にする。 前述のとおり、300 件中の数件失敗をどう扱うかを決めずに「全件 join」にすると、1 件の失敗で全体が止まります。並列度を上げる前に、join 条件と失敗要素の再処理方針を必ず言語化してください。

トランザクション境界を意識せず spin-off を多用する。 親と切り離されて走ることを忘れ、「親をロールバックしたのに子の処理は走ってしまった」という不整合を招きます。fire-and-forget は「投げたら取り消せない」前提で使います。

モダンなケースライフサイクルでの表現と、版依存の注意

ここまでフロー図形(spin-off / split-for-each / split-join)を軸に説明しましたが、モダンなケースライフサイクルでは、ステージ内の並列プロセスや**ケースの分割(子ケース)**で同等の要件を表現できる場合があります。「N 件を並列で処理して合流」を、フロー図形ではなくケース構造で組む選択肢もある、ということです。どちらで組むかは、可視性・再利用性・運用のしやすさで判断します。

重要なのは、各フロー図形の可用性や挙動は Pega の版(Constellation 世代を含む)によって異なるという点です。ある版で使えた図形が別の版では推奨されない、あるいは代替手段が用意されている、ということが起こります。設計を確定する前に、必ず対象環境の公式ドキュメントで、利用可能な構成と挙動を確認してください。本記事の判断軸(join の有無と粒度)は版に依らず有効ですが、それを実装に落とす手段は環境で確認するのが鉄則です。

まとめ

  • 選定の主軸は join(待ち合わせ)の有無と粒度。表層の「並列で回したい」で選ばない
  • 待たないなら spin-off(fire-and-forget/付随処理)、可変個を待つなら split-for-each(同じ処理 × 可変個)、固定・少数の異なる分岐を待つなら split-join(異なる処理 × 固定個)
  • 大量並列では キュー/バックグラウンドの負荷・トランザクション境界・部分失敗のリカバリを必ずセットで設計する
  • 実装手段(フロー図形かケース構造か、各図形の可用性)は 対象環境の版で確認する

「親はこの結果を待つか、待つなら何を待つか」——この 1 問を設計の最初に言語化しておくだけで、並列処理の破綻の大半は避けられます。並列化は「起動」より「待ち合わせと失敗の扱い」で決まる、と捉えておくと設計を外しません。

Pega の並列・非同期処理やケースライフサイクルの設計でお困りの際は、Pega Platform 開発支援 や 無料相談 をご利用ください。Pega 認定資格の最高位 LSA(Lead System Architect)を保有するコンサルタントが、設計判断からご支援します。

関連リンク

RELATED

関連記事

Pega 宣言的処理のコスト設計|前方連鎖と後方連鎖の使い分け

Declare Expression は便利だが、前方連鎖のカスケードは1つのプロパティ変更が広範な再計算を引き起こす。宣言的処理は「書けば動く」半面、実行コストが見えにくい。本記事は前方連鎖と後方連鎖、宣言的と手続き的の境界線を、性能と保守性の観点で整理する。

記事を読む

Pega Insights と Report Definition の使い分け|セルフサービス分析と運用レポートの線引き

「レポートは Report Definition で作るべきか、Insights でユーザーに任せるべきか」——ダッシュボード設計で必ず出る分岐です。統制と再利用が要る定型レポートはルール化し、アドホックな探索はセルフサービスに開く、が基本方針。本記事では両者のガバナンス差とデータソースの前提から、線引きの判断軸を示します。

記事を読む

Pega 監査証跡設計:フィールド監査・履歴・セキュリティ監査の使い分け

「何を、どこまで監査するか」は Pega のセキュリティ設計で最も判断が割れるテーマです。フィールド監査・ケース履歴・セキュリティイベントの使い分けと、監査のかけ過ぎが招く性能・容量問題の回避策を、LSA の視点で解説します。

記事を読む

Pega 導入のご相談はお気軽に。

Pega Customer Service / Pega Constellation UI / Pega Platform の各ソリューションについて、無料でご相談を承ります。