分散トランザクションパターンまとめ¶
用途: マイクロサービス / 複数DB / 複数サービス間で整合性を保つための設計パターン。面接深掘り対策と実務メモを兼ねる。
1. なぜ分散トランザクションが問題になるか¶
単一DB内のトランザクション (ローカルトランザクション) は ACID で守られる。 しかし、以下のいずれかが発生すると ACID が単純には効かない:
- 複数DB (例: マッチング結果 → 残高DB + 台帳DB + 通知系)
- 複数サービス (例: 注文サービス → 残高サービス → 清算サービス)
- 外部システム連携 (例: カバー取引で外部LP API呼び出し)
このとき、A の更新は成功したが B が失敗した という部分的失敗 (partial failure) が起こる。
これを整合的に扱うのが分散トランザクションパターンの目的。
2. 主要パターン早見表¶
| パターン | 整合性 | レイテンシ | 実装難度 | 主な用途 |
|---|---|---|---|---|
| 2PC (Two-Phase Commit) | 強整合 | 低速 (ブロッキング) | 中 | 同一信頼境界内の複数RDB |
| Saga (Orchestration) | 結果整合 | 中速 | 中〜高 | 長期ワークフロー、補償可能な処理 |
| Saga (Choreography) | 結果整合 | 中速 | 高 (追跡困難) | 疎結合な小規模サービス連携 |
| TCC (Try-Confirm-Cancel) | 結果整合 (短時間) | 中速 | 高 | 在庫予約・席予約など短期ロック型 |
| Outbox Pattern | 結果整合 | 中速 | 低〜中 | DB更新 + メッセージ送信のアトミック化 |
| Event Sourcing + CQRS | 結果整合 | 中速 | 高 | 監査ログ・状態復元・ドメインイベント |
3. 2PC (Two-Phase Commit)¶
概要¶
調停者 (Coordinator) がすべての参加者に「準備OK?」を聞き、全員OKなら「コミット」、誰かNGなら「ロールバック」を指示する。
Phase 1 (Prepare):
Coordinator → A: PREPARE → A: 「OK」 (ロック取得、ログ書き込み)
Coordinator → B: PREPARE → B: 「OK」
Coordinator → C: PREPARE → C: 「OK」
Phase 2 (Commit):
Coordinator → A: COMMIT → A: 「Done」
Coordinator → B: COMMIT → B: 「Done」
Coordinator → C: COMMIT → C: 「Done」
保証¶
- 強整合性: ACID を分散環境でも維持
- アトミック: 全員成功 or 全員失敗
問題点¶
- ブロッキング: Phase 1 でロック取得 → Phase 2 完了まで他トランザクションを待たせる
- Coordinator 単一障害点 (SPOF): Coordinator が Phase 2 の途中で落ちると参加者が宙ぶらりんに
- ネットワーク分断に弱い: Phase 2 のメッセージが届かないと参加者が判断できない
- パフォーマンス: 2ラウンドトリップ + ロック時間が長い → 高スループット領域では非現実的
いつ使うか¶
- 同一データセンター内の複数RDB
- XA トランザクションをサポートするRDB (PostgreSQL, MySQL の XA, Oracle)
- スループットよりも整合性が圧倒的に優先される少数のシナリオ
いつ使わないか¶
- マイクロサービス間のトランザクション (推奨されない)
- 外部API呼び出しを含む処理 (外部APIが XA 対応していない)
- レイテンシ要求が厳しい処理 (取引マッチングなど)
4. Saga パターン¶
概要¶
長期トランザクションを複数のローカルトランザクションに分割し、失敗時は補償トランザクション (Compensating Transaction) で取り消す。
バリエーション¶
(a) Orchestration (オーケストレーション)¶
中央の Orchestrator が各ステップを順に呼ぶ。
- 長所: フローが1箇所に集約され、追跡しやすい / 状態管理が容易
- 短所: Orchestrator が中央集権的になる / Orchestrator のロジックが肥大化しやすい
(b) Choreography (コレオグラフィー)¶
各サービスがイベントを発行し、他サービスがそれに反応して動く。
注文サービス → "OrderCreated" イベント
↓
残高サービス: 引落 → "BalanceDeducted" イベント
↓
在庫サービス: 確保 → 失敗 → "InventoryFailed"
↓
残高サービス: 戻し
注文サービス: 取消
- 長所: サービス間が疎結合 / 中央点なし
- 短所: フロー全体の追跡が困難 / デバッグしづらい / 暗黙の依存関係が増える
補償トランザクション (Compensating Transaction) の設計¶
- 必須要件: ビジネスロジックで「巻き戻し可能」であること
- 冪等性 (Idempotency) が重要 — 同じ補償を複数回実行しても結果が変わらない
- 意味的補償: 「キャンセル」「返金」「在庫戻し」 — 完全に元に戻すのではなく、ビジネス的に等価な状態に持っていく
- 例: メール送信は取り消せないので、「お詫びメール送信」で補償
- 例: 印刷ジョブは取り消せないので、「破棄指示」で補償
問題点¶
- 結果整合性のみ — 一時的に不整合な状態を許容する必要がある
- 隔離性なし — 他のトランザクションが中間状態を読む可能性 → アンチパターン回避策が必要
- 補償できないステップ — 完全に巻き戻せない処理 (外部送金、通知送信) は意味的補償で対応
- Saga の途中失敗時の追跡 — Orchestrator か Saga Log で状態管理必須
隔離性問題の回避策¶
- Semantic Lock: 中間状態に「pending」フラグを付け、他トランザクションから見えないようにする
- Commutative Updates: 順序に依存しない更新 (incrementなど) を使う
- Pessimistic View: 結果が確定するまで読まれないようにする
- Reread Value: 補償時に値を再読み込みして整合性チェック
いつ使うか¶
- マイクロサービス間の長期トランザクション
- 外部API呼び出しを含むワークフロー
- enechain の Process Manager パターンはこれに該当
5. TCC (Try-Confirm-Cancel)¶
概要¶
Saga の特殊形。各ステップが Try / Confirm / Cancel の3操作をサポートする。
Try Phase: 全サービスでリソースを予約 (まだ確定しない)
注文サービス.try() → 仮注文作成
残高サービス.try() → 残高を「予約済み」状態に
在庫サービス.try() → 在庫を「確保中」状態に
Confirm Phase (全 Try 成功):
全サービスで Confirm → 予約を確定
Cancel Phase (どれか Try 失敗):
全サービスで Cancel → 予約解放
2PC との違い¶
- 2PC: DB レベルのロック
- TCC: アプリケーションレベルでの予約状態
長所¶
- DB ロックを長時間掴まない (2PC より高スループット)
- 補償ロジックが「Cancel」として明示的にあるため Saga より分かりやすい
短所¶
- 各サービスに Try / Confirm / Cancel の3操作を実装する必要がある
- アプリケーションロジックが複雑化
- Try 状態のタイムアウト管理が必要
いつ使うか¶
- 在庫予約、座席予約、リソース確保系
- 短期間で確定/破棄が決まる処理
6. Outbox Pattern¶
概要¶
「DB更新 + メッセージ送信」をアトミックに実現するパターン。
従来のNG例:
1. DBに注文を書き込む
2. メッセージキューに「OrderCreated」を送る
→ 1は成功したが2が失敗すると、メッセージが永久に失われる
Outbox Pattern:
1. 同一トランザクションで:
- orders テーブルに注文を書き込む
- outbox テーブルに「OrderCreated」イベントを書き込む
2. 別プロセス (Relay/Polling) が outbox を読んで MQ に送信
3. 送信成功後、outbox から削除 (or flag更新)
保証¶
- DB更新とイベント発行のアトミック性
- At-least-once 配信 (重複あり得るので消費側で冪等性必要)
実装方式¶
(a) Polling Publisher¶
- 別プロセスが定期的に outbox テーブルを SELECT してメッセージ送信
- シンプルだが、ポーリング間隔の分のレイテンシ
(b) Transaction Log Tailing (CDC)¶
- Debezium / Datastream で DB の WAL/binlog を読み、変更を MQ に流す
- レイテンシ低 / アプリケーション側の改修不要
- enechain も Datastream で CDC を使っている (テックブログ記載)
いつ使うか¶
- マイクロサービス × イベント駆動アーキテクチャ
- 「DB更新したらイベントを必ず発行したい」場面すべて
7. Event Sourcing + CQRS¶
概要¶
状態をイベントの追記ログとして保存し、現在状態はイベントを再生して構築する。
従来:
accounts テーブル: id=1, balance=10000
Event Sourcing:
events テーブル:
1. AccountCreated(id=1, initial=0)
2. Deposited(id=1, amount=15000)
3. Withdrawn(id=1, amount=5000)
→ 再生すると balance=10000
保証¶
- 任意時点の状態復元: 監査要件・取引ログ保全に直結
- イベントは Immutable: 追記のみ、更新削除なし
CQRS との組み合わせ¶
- Command 側: イベントを書く (Event Store)
- Query 側: イベントから構築した Read Model を別DBに保持
- 読み書きの責務を分離 → 高スループット & 強監査性
問題点¶
- イベントスキーマ進化 — イベントは Immutable なので、過去イベントを後から変更できない → versioning が必要
- プロジェクション再構築 — Read Model を作り直すのに時間がかかる (イベント全再生)
- スナップショット戦略 — 全イベント再生は重いので、定期的にスナップショットを取る
- 学習コスト — チームに浸透するまで時間がかかる
いつ使うか¶
- 監査要件・状態復元要件が強い (金融・取引・規制産業)
- イベント駆動アーキテクチャの中核
- enechain の eClear / eSquare で採用
8. 冪等性 (Idempotency) の重要性¶
分散トランザクションのほぼ全パターンで冪等性が前提になる。
冪等性とは¶
- 同じ操作を複数回実行しても結果が変わらない性質
- 例:
set balance = 100は冪等 /balance += 100は非冪等
実装パターン¶
(a) Idempotency Key¶
- リクエストに一意のキーを含める
- サーバー側で「このキーは既に処理済み」を記録 → 同じキーの再送は無視 or 同じ結果を返す
- Stripe API などが採用
(b) Versioning / Optimistic Locking¶
- 楽観ロック:
WHERE version = ?で更新失敗を検知
(c) State Machine¶
- 状態遷移を明示的にし、同じ遷移は1回しか起きないようにする
9. パターン選択フロー¶
Q1. 同一信頼境界内 (同じチーム・同じデータセンター・短期) か?
YES → 2PC (XAトランザクション) を検討
NO → 次へ
Q2. 全ステップがビジネス的に補償可能か?
YES → Saga を検討
NO → 補償できないステップを最後に置く / 設計見直し
Q3. ステップが「予約 → 確定/解放」型か?
YES → TCC を検討
NO → Saga
Q4. DB更新 + イベント発行のアトミック性が欲しいだけ?
YES → Outbox Pattern (単独で使う)
NO → 上記と組み合わせ
Q5. 監査要件・任意時点状態復元が必要か?
YES → Event Sourcing + CQRS を検討
NO → 通常のSagaやOutboxで十分
10. コインチェック・enechain 文脈での整理¶
コインチェックでの実例 (本人経験)¶
| 処理 | 採用パターン |
|---|---|
| マッチング → 残高更新 → 台帳記帳 | 同一DBトランザクション (モノリス内) で完結。マイクロサービス化していない |
| 取引ログ・約定ログ | 性能経路と監査経路の 別系統保全 (=実質的に Outbox 的発想) |
| カバー取引 (外部LP呼出含む) | Aggregator経由でラップ。失敗時は手動 or 業務フローで補償 (= 簡易 Saga) |
enechain での想定 (テックブログから推測)¶
| 処理 | 採用パターン |
|---|---|
| eSquare 注文掲載プロセス | Process Manager (Saga Orchestration の体系的実装) |
| 清算・決済 (eClear) | Event Sourcing + CQRS |
| DB更新 → 通知配信 | PostgreSQL LISTEN/NOTIFY (Outbox の軽量版に近い) |
| BigQuery集計 | Datastream (CDC) = Transaction Log Tailing 方式の Outbox |
11. 面接で語る時のスタンス¶
Why層で評価される回答の型¶
- 「銀の弾丸はない」を最初に言う — マイクロサービスもSagaも万能ではない
- トレードオフを明示する — 整合性 vs スループット vs 実装複雑度
- ドメイン制約から導く — 「金融取引は監査要件があるので Saga の中間状態は許容できない」など
- 既存アーキとの接続 — 「コインチェックではモノリスで強整合を維持した」「enechain は Process Manager で長期ワークフローを分離している」と具体例で語る
一次面接で深掘りされた時の応答ストック¶
| 質問 | 回答の核 |
|---|---|
| 「マイクロサービス化したらどんな問題?」 | 強整合の喪失 / レイテンシ増 / 順序保証困難 / Saga中間状態の監査リスク |
| 「Sagaで補償できないステップは?」 | 意味的補償 (お詫びメール、破棄指示) で代替する |
| 「2PCはなぜ使わない?」 | ブロッキング / SPOF / マイクロサービス連携で非現実的 |
| 「Event Sourcing の運用課題は?」 | スキーマ進化 / プロジェクション再構築時間 / スナップショット戦略 |
| 「冪等性をどう担保?」 | Idempotency Key / Versioning / State Machine |
Sources / 参考¶
- Chris Richardson『マイクロサービスパターン』
- Martin Fowler "Saga" (martinfowler.com)
- enechain TechBlog: Process Manager パターン記事 / LISTEN-NOTIFY 記事
- AWS Database Blog: Saga / TCC 解説
- Microsoft Cloud Design Patterns: Compensating Transaction, Outbox
最終更新: 2026-05-12
作成: 2026-05-12 / 最終更新: 2026-06-16