---
title: 分散トランザクション
url: https://doc.liz6.com/ja/distributed-systems/03-replication-and-consistency/03-distributed-transactions
locale: ja
area: distributed-systems
tags:
- 2PC
- XA
- Saga
- TCC
- compensating action
- idempotency
- outbox pattern
- distributed transaction
- distributed-systems
- replication-and-consistency
date: 2026-06-30
modified: 2026-07-16
description: 複数のサービスにまたがる1回の書き込み操作——グローバルなACIDは存在しない。2PCは強い整合性を追求するが、コーディネーターが障害発生すると参加者がすべてブロックされる。SagaやTCCは補償トランザクションで可用性と引き換えに、アプリケーション層でロールバックロジックを自前で実装する必要がある。outbox patternと冪等性により、実装上の「少なくとも1回」の配信が保証される。
---

# 分散トランザクション

> 複数のサービスにまたがる1回の書き込み操作——グローバルなACIDは存在しない。2PCは強い整合性を追求するが、コーディネーターが障害発生すると参加者がすべてブロックされる。SagaやTCCは補償トランザクションで可用性と引き換えに、アプリケーション層でロールバックロジックを自前で実装する必要がある。outbox patternと冪等性により、実装上の「少なくとも1回」の配信が保証される。

単一ノードのデータベースではトランザクションはACID準拠であり、BEGIN/COMMIT/ROLLBACKで制御できる。しかし分散システムでは複数のサービスが関与するため、グローバルなACIDトランザクションは存在しない。

## 2PC (Two-Phase Commit)

最も基本的な分散トランザクションプロトコル——ブロッキング型であり、コーディネーターの障害に対する耐性を持たない。

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="2PC 两阶段提交流程:PREPARE 投票阶段与 COMMIT/ABORT 决议阶段">
  <defs><marker id="m2pc" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">2PC 2フェーズコミット: PREPARE投票 → COMMIT/ABORT決定</text>

  <rect x="40" y="50" width="300" height="28" rx="6" fill="#4f46e5"/>
  <text x="190" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">① PREPAREフェーズ · コーディネーターが投票を開始</text>

  <rect x="52" y="90" width="276" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="109" font-size="12" font-weight="700" fill="#3730a3">コーディネーター → 各参加者</text>
  <text x="66" y="126" font-size="11" fill="#4f46e5">PREPAREリクエストを送信</text>

  <rect x="52" y="140" width="276" height="60" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="159" font-size="12" font-weight="700" fill="#3730a3">参加者</text>
  <text x="66" y="176" font-size="11" fill="#4f46e5">操作を実行し、WALに書き込むが、コミットはしない</text>
  <text x="66" y="192" font-size="11" fill="#4f46e5">YES(コミット可能)またはNOで応答</text>

  <rect x="52" y="206" width="276" height="32" rx="6" fill="#e0e7ff"/>
  <text x="190" y="227" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">すべての応答を収集し、すべてがYESかどうかを判断</text>

  <rect x="380" y="50" width="300" height="28" rx="6" fill="#0d9488"/>
  <text x="530" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">② 決定フェーズ · コーディネーターが結果をブロードキャスト</text>

  <rect x="392" y="90" width="276" height="54" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="406" y="112" font-size="12" font-weight="700" fill="#166534">すべてYES → COMMIT</text>
  <text x="406" y="132" font-size="11" fill="#15803d">コーディネーターがCOMMITをブロードキャストし、参加者がコミット</text>

  <rect x="392" y="154" width="276" height="54" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="406" y="176" font-size="12" font-weight="700" fill="#9a3412">いずれかがNO → ABORT</text>
  <text x="406" y="196" font-size="11" fill="#c2410c">コーディネーターがABORTをブロードキャストし、参加者がロールバック</text>

  <line x1="330" y1="172" x2="388" y2="172" stroke="#475569" stroke-width="1.8" marker-end="url(#m2pc)"/>

  <rect x="40" y="248" width="640" height="56" rx="8" fill="#ffedd5"/>
  <text x="56" y="270" font-size="12.5" fill="#9a3412">問題点: coordinatorがPREPARE後、COMMIT前にクラッシュした場合、participantは不確実な状態に陥る——</text>
  <text x="56" y="290" font-size="12.5" fill="#9a3412">単独でコミットすることも、単独でアボートすることもできず、コーディネーターの回復を待ってブロック状態になる。これが2PCのブロッキングプロトコルである。</text>
</svg>

問題点: coordinatorがPREPARE後、COMMIT前にクラッシュした場合 → participantは不確実な状態(uncertain state)に陥る——他のparticipantがYESかどうか分からないため単独でコミットできず、他がすでにコミットしている可能性もあるため単独でアボートもできない。**参加者がブロック**され、coordinatorの回復を待つ——これが「ブロッキングプロトコル」である。

実務では: XAトランザクション（2PCの実装）はデータベースのシャーディング間トランザクションで使われるが、coordinatorのクラッシュへの対応には管理者によるヒューリスティックなコミット/アボートが必要になる。

## Saga

長時間のトランザクションを複数のローカルトランザクションステップに分割し、各ステップには独自の補償操作(compensating action)を持たせる。あるステップで失敗した場合、完了したステップを逆順で補償実行する。

<svg viewBox="0 0 720 330" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Saga 分布式事务:四个本地事务步骤顺序执行,失败时按相反顺序执行补偿">
  <defs><marker id="msaga" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="330" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Saga: book_trip()をローカルトランザクションステップに分割し、失敗時は逆順で補償を実行</text>

  <rect x="20" y="60" width="140" height="50" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="90" y="80" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">Step 1</text>
  <text x="90" y="98" text-anchor="middle" font-size="11" fill="#4f46e5">book_flight()</text>

  <rect x="200" y="60" width="140" height="50" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="270" y="80" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">Step 2</text>
  <text x="270" y="98" text-anchor="middle" font-size="11" fill="#4f46e5">book_hotel()</text>

  <rect x="380" y="60" width="140" height="50" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="450" y="80" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">Step 3</text>
  <text x="450" y="98" text-anchor="middle" font-size="11" fill="#4f46e5">book_car()</text>

  <rect x="560" y="60" width="140" height="50" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="630" y="80" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">Step 4</text>
  <text x="630" y="98" text-anchor="middle" font-size="9.5" fill="#c2410c">process_payment() 失敗</text>

  <line x1="160" y1="85" x2="200" y2="85" stroke="#475569" stroke-width="1.6" marker-end="url(#msaga)"/>
  <line x1="340" y1="85" x2="380" y2="85" stroke="#475569" stroke-width="1.6" marker-end="url(#msaga)"/>
  <line x1="520" y1="85" x2="560" y2="85" stroke="#475569" stroke-width="1.6" marker-end="url(#msaga)"/>

  <line x1="90" y1="110" x2="90" y2="138" stroke="#475569" stroke-width="1.4" marker-end="url(#msaga)"/>
  <line x1="270" y1="110" x2="270" y2="138" stroke="#475569" stroke-width="1.4" marker-end="url(#msaga)"/>
  <line x1="450" y1="110" x2="450" y2="138" stroke="#475569" stroke-width="1.4" marker-end="url(#msaga)"/>

  <rect x="20" y="140" width="140" height="40" rx="6" fill="#ffedd5"/>
  <text x="90" y="164" text-anchor="middle" font-size="10.5" font-weight="700" fill="#9a3412">補償: cancel_flight()</text>

  <rect x="200" y="140" width="140" height="40" rx="6" fill="#ffedd5"/>
  <text x="270" y="164" text-anchor="middle" font-size="10.5" font-weight="700" fill="#9a3412">補償: cancel_hotel()</text>

  <rect x="380" y="140" width="140" height="40" rx="6" fill="#ffedd5"/>
  <text x="450" y="164" text-anchor="middle" font-size="10.5" font-weight="700" fill="#9a3412">補償: cancel_car()</text>

  <text x="20" y="207" font-size="12" font-weight="700" fill="#1f2933">process_payment() 失敗 → 完了したステップを逆順で補償実行:</text>

  <rect x="20" y="216" width="200" height="34" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="120" y="238" text-anchor="middle" font-size="11" font-weight="700" fill="#9a3412">process_payment() 失敗</text>

  <line x1="220" y1="233" x2="260" y2="233" stroke="#475569" stroke-width="1.6" marker-end="url(#msaga)"/>

  <rect x="260" y="216" width="200" height="34" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="360" y="238" text-anchor="middle" font-size="11" font-weight="700" fill="#9a3412">① cancel_hotel()</text>

  <line x1="460" y1="233" x2="500" y2="233" stroke="#475569" stroke-width="1.6" marker-end="url(#msaga)"/>

  <rect x="500" y="216" width="200" height="34" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="600" y="238" text-anchor="middle" font-size="11" font-weight="700" fill="#9a3412">② cancel_flight()</text>

  <rect x="20" y="258" width="680" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="36" y="280" font-size="12.5" fill="#3730a3">Sagaには分離性がない——他のトランザクションが中間状態を見ることができるが、ブロックしない。いつでもシステムは進行中(progressing)である。</text>
  <text x="36" y="300" font-size="12.5" fill="#3730a3">2つのオーケストレーションモード: Choreography(分散型、イベント駆動) vs Orchestration(中央コーディネーター、ロジックが集中して管理しやすい)。</text>
</svg>

Sagaには分離性がない（他のトランザクションが中間状態を見ることができる）が、ブロックしない——いつでもシステムは進行中(progressing)である。

### オーケストレーションモード

- **Choreography**: 各サービスが前のステップのイベントを消費し、自身の完了/失敗イベントを公開する——中央のオーケストレーターは存在しない
- **Orchestration**: 中央のSagaコーディネーターが各サービスに「ステップを実行」または「補償を実行」を指示する——ロジックが集中しており、管理が容易

## TCC (Try-Confirm-Cancel)

Sagaのバリエーションで、より「厳格な」リソース予約に適している。

```
Try:     リソースを予約 (在庫確認、残高凍結) — リソースは**消費されていない**、単に「使用待ち」マーク
Confirm: リソースを実際に消費 (在庫減算、入金処理)
Cancel:  予約を解放 (在庫解放、残高解除)
```

Sagaとの違い: Sagaのステップはデータを直接変更する（book_flightは実際にチケットを予約する）、補償はundoである。TCCのTryは予約のみを行い、Confirmが実際に変更を行う——**いつでもCancelでき、後でundoを実行する必要がない**。

## 冪等性

分散システムにおいて最も重要な非機能的要件: `f(x) = f(f(x))`——再試行操作が結果に影響を与えない。

```
実装方法:
  1. unique idempotency key: クライアントがUUIDを生成し、サーバーが保存 ((key) → (処理済み/未処理))
     → 重複するkey → キャッシュされた結果を返す、再実行しない
  2. database constraints: INSERT INTO orders VALUES (order_id=123, ...)
     → 再試行 → UNIQUE制約違反 → 無視または既存レコードを読み取る
  3. token-based: サーバーがクライアントにトークンを発行し、クライアントがリクエストにトークンを付与
     → 重複するtoken → 拒否

冪等でない例:
  UPDATE users SET balance = balance + 100  -- 再試行 = 2回加算される!
  修正: UPDATE users SET balance = ? WHERE version = ? (楽観的ロック)
```

## Outbox Pattern

トランザクションとしてデータベースに書き込み + メッセージキューにメッセージを送信——両者を原子性で保証するには？DBに書き込んでからメッセージを送信するのは不可（DB書き込みは成功したがメッセージ送信が失敗 → 不整合）。

```
[ローカルトランザクション]:
  INSERT INTO orders (...);
  INSERT INTO outbox (event_type, payload, created_at) VALUES ('order_created', '...', NOW());
  COMMIT;  ← 両方が同じローカルトランザクション内にある!

[Outbox Poller]:
  SELECT * FROM outbox ORDER BY id → Kafka/MQに送信 → 成功 → outboxから削除
```

保証: orderの書き込み + eventの生成は原子性（同じトランザクション内）。Outbox pollerがat-least-once配信を行う。

## 参考

- **論文**: "Sagas" (Garcia-Molina & Salem, 1987)
- **マイクロサービストランザクション**: microservices.io/patterns/data/saga.html
- **冪等性**: stripe.com/docs/idempotency (本番環境レベルの実装)

*Keywords: 2PC, XA, Saga, TCC, compensating action, idempotency, outbox pattern, distributed transaction*
