[ODC]WorkflowのParallel Flowsでバッチ高速化の可能性を探る

ODCのWorkflowに、2025/04/16付で新機能「Parallel Flows」が来た。
Workflow内で、フローそのものを分岐し、各分岐を並行で動作させるもの。
普通に複数の担当者それぞれの業務が並行で動くのを（Human Activityで）表現するのに使ってもいいが、それよりも、バッチ高速化のために処理の並列化に使えるかを確認したい。

# 環境情報
ODC Studio(Version 1.5.17)

# リソース
## Product Releases and Updates
[Workflows for ODC: Parallel Flows](https://www.outsystems.com/product-updates/odc-parallel-flows/)

## Release Notes
[OutSystems Developer Cloud 2025-04-15](https://success.outsystems.com/support/release_notes/outsystems_developer_cloud_releases/outsystems_developer_cloud/#outsystems_developer_cloud_2025-04-15)

## ドキュメント
[Implement parallel paths](https://success.outsystems.com/documentation/outsystems_developer_cloud/building_apps/about_business_processes/implement_workflow/implement_parallel_paths/)

# 2025/04/16付新機能のRelease Notesを確認
箇条書きで5つあったので順に見ていこう。
> In the Workflow Editor, is now possible to add Parallel paths.

Workflow Editor (Workflow開発用のWebインターフェース）でParallel pathを追加できるようになった。

> Adding a parallel is now an option in the menu to add flow steps.

Workflowのステップ追加のオプションとして「Parallel」を選べるようになった。

> Each parallel has by default two paths, but the user can add more paths to run simultaneously.

Parallelはデフォルトで2分岐だが、分岐を追加できる。

> It's possible to create a maximum of two nested parallel flows within a parallel path.

Parallelは入れ子にできるが、最大で2階層まで。

> The execution of an workflow automatically proceeds to the subsequent activity, once all the paths of a parallel are completed.

Parallel内の全ての分岐が終了すると、Workflowの次のステップに進む。

# 基本：Parallel Flowsの使い方
Flowの中に他のStep（Human ActivityやWaitなど）を追加する時と同じく、Flow上の「＋」アイコンクリック→「Parallel」を選択することで、該当箇所に配置できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/7afab900-92ed-4bae-aace-e7bb5b807558.png)

追加されたParallelのプロパティ（フロー中のParallelを選択すると画面右に表示される）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/32f7c7ea-3e86-4b3e-ac2c-8365f8e0622b.png)
①：Desctiption。説明文を入力する場所
②：分岐の数を増やすアイコン。デフォルトでは、Path1とPath2の2分岐。この例は1つ増やしてある
③：追加した３番目の分岐名を「追加した分岐」に変更したところ

分岐内に追加できるStepは、Human Activity, Automatic Activity, Decision, Parallel (2階層まで）, Waitの5つ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/814e22b2-131a-489b-9219-e04151944f99.png)

# 動作確認
シンプルな実装で動作確認してみた。
Parallel内に3つのパスを用意し、Automatic Acitivityを置く。それぞれシンプルなService Actionを呼ぶ。
Service Actionは処理開始・終了時点でログ出力し、決まった時間Sleepする。
Sleepする時間をパスごとに変更することで、Parallelが全ての終了を待ってから次のステップへ行くことを確認する。

Parallelを抜けたら、Automatic Activityで終了したことを示すログのみ出力。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/d4301ca5-4906-45f5-be98-4c82e56cdcd1.png)

実行後のログ。ODC PortalのLogsなので降順（新しい順）である点に注意。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/39f65a22-6986-462b-bf41-5c4526bb5bf4.png)
①Parallel内の全てのパス終了後にAutomatic Activityが動いてログ出力している
②Parallel内の各パスの実行ログ
③Workflowの起点となるEventが動いたことを示すログ
以上から、Parallel内の全てのパスが終了してから後続処理が動いたことを確認できた。

# バッチ処理に使う場合の注意事項
- ログを見ると、Eventが発生してから対応するWorkflowインスタンス開始まで30秒くらいかかった（この遅延を待てないような処理であれば、Workflowで高速化は難しいかもしれない）
- 最近、AO数に加えて、Compute Instance数でも課金されることになった。Workflowの場合、Revisionごとに１インスタンスとしてカウントされる（[Monitor ODC resource capacity > Resource limits](https://success.outsystems.com/documentation/outsystems_developer_cloud/getting_started/monitor_odc_resource_capacity/#resource-limits))
- Parallel内でDecisionを使って分岐すると、「Go to a previous step」で同じパス内の前のステップまでは戻れるようだ（つまり大量レコードを処理するときは、同じService Actionを繰り返し呼んで、レコードを分割しつつ処理できる）
- Workflowから呼ぶService Actionがエラー終了すると最大10回までリトライするので注意（[Troubleshooting workflows](https://success.outsystems.com/documentation/outsystems_developer_cloud/building_apps/about_business_processes/troubleshooting_workflows/#troubleshooting-instance-errors))
- Parallelは最大2階層まで（Parallel内に別のParallelを置ける。ただし、その内側のParallel内に別のParallelを配置することはできない）

# 大量レコードの設計について
レコードを分割する処理方式として、以下のような方法が考えられそう
1. レコードを一定のルールで分割し、分割単位ごとにParallel内のパスを割り当てる（当然動的に分割の数は増やせない。例えば、Entityのある属性が1-4の数値を取り、それぞれ似たようなレコード数になる場合、4つのパスでそれぞれ1-4の値のレコードのみを処理する。シャーディングなどもこの方式）
1. あるEntityの処理は1つのパスで完結するが、「Go to a previous step」を使って繰り返し同じService Actionを読んで一定サイズの塊ずつレコードを処理していく