Saga パターン (オーケストレーション)

分散トランザクションを複数のローカルトランザクションに分割し、中央のオーケストレーターが制御するパターン

分散システムトランザクション

Saga オーケストレーションとは

Saga パターンのオーケストレーション方式は、中央のオーケストレーター (指揮者) が各サービスのローカルトランザクションを順番に呼び出し、失敗時には補償トランザクションを逆順に実行する方式である。AWS Step Functions が代表的なオーケストレーターだ。

Saga は ACID の分離性 (Isolation) を保証しない。処理の途中では「在庫は引き当て済みだが決済は未完了」という中間状態が他の処理から見えてしまう。そのため、引き当て済みを表すステータスを持たせて確定前の在庫を売らないようにするなど、業務データ側で意味的なロックを設計する必要がある。

処理の流れ

オーケストレーション方式では、Step Functions などの中央コーディネーターが各サービスの呼び出し順序を制御する。途中で失敗した場合は、完了済みのステップに対して補償トランザクション (逆操作) を実行してロールバックする。

  1. 注文サービス: 注文作成 → 成功
  2. 在庫サービス: 在庫引き当て → 成功
  3. 決済サービス: 支払い処理 → 失敗!
  4. 補償: 在庫サービス: 在庫を戻す
  5. 補償: 注文サービス: 注文をキャンセル

コレオグラフィとの比較

選択の分かれ目は、フローの手順を 1 か所に集めて追跡しやすさを取るか、サービス間の依存を減らして各サービスの独立性を取るかである。

観点オーケストレーションコレオグラフィ
制御中央 (Step Functions)分散 (各サービスが自律)
可視性高い (フロー全体が可視化)低い (フローが見えにくい)
結合度中程度 (オーケストレーターに依存)低い (イベントのみ)
複雑なフロー得意 (条件分岐、並列実行)苦手
適するケース条件分岐や並列を含む多段のフロー手順が固定した短いフロー

Step Functions の利点

  • フロー全体を Amazon States Language (JSON) で宣言し、コンソールのワークフロー図で構造を確認できる。CloudFormation や SAM のテンプレートに書く場合は YAML でも表現できる
  • 各ステップのリトライ、タイムアウト、エラーハンドリング (Retry / Catch) が宣言的に設定できる
  • Standard ワークフローは実行履歴が Step Functions 側に記録され、直近 90 日分をコンソールから追える。Express ワークフローは実行履歴を保持しないため、後から追うには CloudWatch Logs へのログ出力設定が必要になる
  • 並列実行 (Parallel ステート) や条件分岐 (Choice ステート) をサポート

補償トランザクションの設計

補償トランザクションはべき等に実装する。同じ補償が複数回実行されても安全であること。

// ❌ 非べき等: 2 回実行すると在庫が 2 回戻る
async function releaseInventory(reservationId: string) {
  const r = await db.reservation.findUnique({ where: { id: reservationId } });
  await db.inventory.increment(r.productId, r.quantity);
}

// ✅ べき等: 引き当て済みの予約を解放できたときだけ在庫を戻す
async function releaseInventory(reservationId: string) {
  const released = await db.reservation.updateMany({
    where: { id: reservationId, status: 'reserved' },
    data: { status: 'released' },
  });
  if (released.count === 0) return; // 既に解放済み

  const r = await db.reservation.findUnique({ where: { id: reservationId } });
  await db.inventory.increment(r.productId, r.quantity);
}

実務で最初に決めること

オーケストレーション型を選ぶときに効いてくるのは、フロー全体が図で見えることよりも「打ち消せない工程をどこに置くか」である。在庫の引き当てや仮売上は補償で戻せるが、送信済みの通知や外部倉庫へ渡した出荷指示は戻せない。戻せない工程を手順の後ろへ寄せ、それより前を補償可能な範囲として設計しておけば、失敗した時に取れる手が残る。

もう 1 つは、補償側のコードを本流と同じ重みでテストすることである。補償は正常系では一度も通らないため、書いたまま実行されずに本番へ出やすい。途中の工程を意図的に失敗させる試験を用意しておかないと、いざ動いた時に補償自体が失敗し、中途半端な状態がそのまま残る。

この記事は役に立ちましたか?

関連用語

関連する記事