Slide 1

Slide 1 text

1 @sapuri 2026/04/27 Background Job Talk 内製ワークフローエンジンの設計と メルカリでの活用事例

Slide 2

Slide 2 text

2 株式会社メルペイ ソフトウェアエンジニア 2019年に株式会社メルカリにソフトウェアエンジニア として入社。メルペイのオンライン決済機能やメル カード、社内向け決済ソリューションの開発を経て、 現在は主にメルコインの決済基盤の開発・運用を担 当している。 南 祐希 / @sapuri

Slide 3

Slide 3 text

3 分散トランザクション管理 今日の内容 ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01

Slide 4

Slide 4 text

4 分散トランザクション管理 今日の内容 ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01

Slide 5

Slide 5 text

5 ● メルカリはマイクロサービスアーキテクチャを採用している ● お客さまがアプリで何か1つ操作をすると、そのリクエストは基本的に 複数のサービスをまたいで処理されることになる 分散トランザクション管理

Slide 6

Slide 6 text

6 分散トランザクションの例 : 暗号資産購入

Slide 7

Slide 7 text

7 ● 各サービスが独自のDBを持つため、単純なロールバックができない ● 起きうる不整合の例: ○ 決済が失敗したのにメルコインの JPY残高が減っている ○ 残高は減ったがBTCが加算されない ○ BTCと交換できているのに取引が完了扱いになっていない ● Two-Phase Commitは長期間リソースをロックするためサービスの可用性を 下げる可能性がある ● → 結果整合性で解決する 分散トランザクション管理

Slide 8

Slide 8 text

8 ● 結果整合性を実現するアーキテクチャの 1 つ ● トランザクションを複数の小さなトランザクションに分割して順次実行 ○ 長時間のロックが不要 ● 途中でリトライ不可能なエラーが出たら、成功済み処理の補償トランザクション を逆順で実行 Saga

Slide 9

Slide 9 text

9 Sagaによる分散トランザクション管理 (暗号資産購入 ) ● 途中でリトライ不可能なエラーが出たら、成功済み処理の補償トランザクション を逆順で実行 ● どこで失敗しても結果整合性を保って完了できる ● 参考: メルコイン決済基盤における分散トランザクション管理 | メルカリエンジニアリン グ

Slide 10

Slide 10 text

10 ワークフローエンジンの検討 ● GCP Workflows ○ 各処理をHTTPエンドポイント化する必要がある。ユニットテストが難しい ○ YAMLではなくGoのコードでワークフローを記述したい ● Cadence / Temporal ○ Cloud Spannerに対応していなかった ○ システムの規模が大きく、運用のために専門家が必要になりそう

Slide 11

Slide 11 text

11 ● 要件を満たすものがなかったため自社で開発した ○ Cadence / Temporalのインターフェースの良さを取り入れる ○ メルペイのPayment Serviceで既に実績があった「DBへの実行状態の永続化 x インメモリキュー x Workerでの実行管理 」のアーキテクチャを再利用 ○ Go専用、必要な機能のみに絞って小規模に ■ 数人の兼務メンテナーで運用できている ワークフローエンジンの検討

Slide 12

Slide 12 text

12 分散トランザクション管理 今日の内容 ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01

Slide 13

Slide 13 text

13 アーキテクチャ ● アプリケーションサーバーと同じPodでデプロイされることを想定 ● Go runtimeで動作するSDKとして提供

Slide 14

Slide 14 text

14 ● manager.RegisterWorkflow() ○ ワークフローとして実行する関数を事前に Registryに登録 アーキテクチャ

Slide 15

Slide 15 text

15 アーキテクチャ ● manager.Workflow().Execute() ○ Engine ServerにWorkflowの作成をリクエスト ■ Workflowの関数名と引数をDBに保存 ○ ChannelにWorkflowStartedイベントをpublish

Slide 16

Slide 16 text

16 ● manager.Workflow().Execute() ○ WorkerがWorkflowStartedイベントをsubscribe ■ Registryから実行する関数を取得し、reflect.ValueOf(fn).Call(args) で実行 ■ Engine ServerにWorkflowの完了をリクエスト ● Workflowの実行結果を保存し、完了状態に遷移 ● ChannelにWorkflowCompletedイベントをpublish アーキテクチャ

Slide 17

Slide 17 text

17 アーキテクチャ ● manager.Workflow().Execute().Get() ○ WorkerがWorkflowCompletedイベントをsubscribe ■ アプリケーションに結果を返却 ■ エラーの場合はErrorMarshaler (後述) でWorkflowを完了するかどうか判定

Slide 18

Slide 18 text

18 アーキテクチャ ● Manager: SDKのエントリーポイント ○ アプリケーションは Workflow() / Activity() / RegisterWorkflows() などを呼び出す ○ リクエストに応じてEngine Serverと通信し、Channelへ開始イベントを発行 ● Engine Server: Create / Complete / List などのgRPC APIを提供するサーバー ○ DBにWorkflow / ActivityのI/Oと状態を保存 ○ 完了済のWorkflow / Activityの結果をそのまま返すので冪等にリトライされる ● Channel: Workflow / Activityの状態遷移イベントのハブとなるインメモリキュー

Slide 19

Slide 19 text

19 ● Workers: Workflow / Activityを実行するgoroutine群 ○ Channelから状態遷移イベントを購読し、イベントの種別に応じた処理を実行 ○ Registryから対象の関数を取り出し、リフレクションで動的呼び出し ● Registry: Registerされた関数をインメモリで保持 ○ ManagerがRegister、WorkersがGet アーキテクチャ

Slide 20

Slide 20 text

20 ● Recovery Worker: 未完了のWorkflow / Activityを定期的にリトライ ○ Engine ServerからListして、イベントをChannelに再投入 アーキテクチャ

Slide 21

Slide 21 text

21 コードサンプル

Slide 22

Slide 22 text

22 3種類のエラーを定義している エラーハンドリング エラー種別 例 ワークフローエンジンの挙動 Completable Error 失敗として完了させて良い 想定されたエラー 残高不足 / 利用制限 Workflowを完了 Retryable Error 一時的なエラー 即時リトライ Incompletable Error (default) 一時的なエラー / 予期しないエ ラー Workflowを停止 Recovery Workerが後で リトライ

Slide 23

Slide 23 text

23 Completable Error ● Completable Errorはクライアント側でErrorMarshalerを実装したエラーと して定義される ● 該当しないエラーは未完了として実行停止 → Recovery Worker によってリトライされる ● 明示的にCompletable Errorを返さない限りWorkflowは完了しない ○ 異常な状態でWorkflowが完了することがなくなる設計

Slide 24

Slide 24 text

24 ● ドメインのカスタムエラー型に Completable() を定義して ErrorMarshalerを実装 ● ビジネスロジックで特定のエラー コードを含むエラー返すとワークフ ローエンジンで完了可能なエラー として処理される エラー実装例

Slide 25

Slide 25 text

25 分散トランザクション管理 今日の内容 ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01

Slide 26

Slide 26 text

26 ● 例: 回線開通フロー ● Workflow + Child Workflowで同期レスポンスと非同期処理を分離 ○ Workflow: 回線開通リクエストを DBに保存 → Child Workflowをfire-and-forgetで起動 → レスポンス返却 ○ Child Workflow (非同期): 回線開通PubSubイベントを発行 ○ 失敗時はRecovery WorkerがChildWorkflowを復旧 (PubSub発行まで保証) 事例1: メルカリモバイル

Slide 27

Slide 27 text

27 ● SpannerではなくPostgreSQLを採用しているプロジェクト ● 購買代行パートナーを経由して海外のお客さまが日本のメルカリの商品を購入する ● 例: チェックアウト確定フロー ○ クーポン消費 → 注文作成 → 購買代行パートナーへ注文連携 → 注文確定通知送信 ○ 失敗時はSagaによる補償トランザクションでキャンセル ● なぜTemporalではなく内製のワークフローエンジンを採用した? ○ 社内に既にある類似実装や運用基盤を活用したい ○ メンテナーが社内にいるので直接サポートを受けられる。必要な機能を柔軟に追加できて最適 化しやすい 事例2: メルカリ グローバル EC基盤

Slide 28

Slide 28 text

28 ● 例: お客さま本人確認フロー ○ 書類の自動検証 → [審査待ち - 数時間〜数日] → 結果に応じて承認 or 拒否 ● 人による審査待ちを含むlong-runningワークフロー ● Signal (Temporalと同様のインターフェース) で外部からWorkflowに 干渉できる ○ Workflow中断 → 外部からWorkflowに対して情報を送って再開 ○ 長期フローを分割せず1本のWorkflowとして表現できる 事例3: eKYC

Slide 29

Slide 29 text

29 Signal

Slide 30

Slide 30 text

30 分散トランザクション管理 今日の内容 ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01

Slide 31

Slide 31 text

31 内製フレームワークの開発体験問題 ● インターネットに情報がないので類似事例を探すのが難しい ● エラーで雑にググれないのでハマりがち

Slide 32

Slide 32 text

32 ● フレームワークのドキュメントが充実しているに越したことはないが、 AIは内部の実装をすぐに理解できる ● インターネットに情報が無くても昔ほど利用のハードルが高くないかも 最近の運用 課題 対策 コミュニティ情報の不足 (類似事例・Q&Aが探せない) 社内コードの横断検索 + AI (Sourcegraph MCPなど) でドキュメントや社内実装から類似 事例を発見できる 間違った使い方に気付けない 専用のLinterを提供する LLM Analyzer?

Slide 33

Slide 33 text

33 ● 内製ワークフローエンジン専用のLinter ● 予想される実装のミスパターンごとに専用のAnalyzerを用意している ● 静的解析では問題の検出が難しいパターンがある ○ 例: Activityには引数として非決定的な値 (実行ごとに変わる値 ) は使えないという制約がある のでこれを検出したい ● → Analyzerの中でLLMを使って検出できないか実験してみた Linter

Slide 34

Slide 34 text

34 LLM内蔵Linter

Slide 35

Slide 35 text

35 LLM内蔵Linter - 仕組み 1. AST を走査して wm.Activity() を含む関数を収集する 2. 呼び出し元 (1階層上のcaller) も収集し、引数の由来を追跡する 3. 関数のソースコードをLLMに送信し、非決定的値の有無を分析する (任意のモデルを利用できる) 4. Structured Outputsで結果をパース、pass.Reportf() で診断を報告する

Slide 36

Slide 36 text

36 分散トランザクション管理 まとめ ワークフローエンジンの設計 活用事例 内製ワークフローエンジンの運用 02 03 04 01