分散トランザクション - 整合性を保つ設計パターン

15分 で読める | 2025.12.25

公式ドキュメント

分散トランザクションの課題

マイクロサービスでは、1つのビジネス操作が複数のサービスにまたがることがあります。各サービスは独自のデータベースを持つため、従来のACIDトランザクションが使えません。

flowchart LR
 Order["1. 注文サービス<br/>注文を作成"]
 Inventory["2. 在庫サービス<br/>在庫を減らす"]
 Payment["3. 決済サービス<br/>支払いを処理"]
 Shipping["4. 配送サービス<br/>配送を手配"]

 Order --> Inventory --> Payment --> Shipping

 Question["❓ どこかで失敗したら?<br/>全体をロールバック?"]

2相コミット(2PC)

すべての参加者が準備完了してから、一斉にコミットするプロトコルです。

sequenceDiagram
 participant C as コーディネーター
 participant A as 参加者A
 participant B as 参加者B

 C->>A: 1. Prepare
 C->>B: 1. Prepare
 A-->>C: 2. Ready
 B-->>C: 2. Ready
 C->>A: 3. Commit
 C->>B: 3. Commit
 A-->>C: 4. Done
 B-->>C: 4. Done

2PCの問題点

問題説明
ブロッキング参加者はコーディネーターの応答を待つ間ロック
単一障害点コーディネーターがダウンすると全体が停止
低パフォーマンス同期的な通信が必要
分散環境での限界ネットワーク分断時の対応が困難

現代のマイクロサービスでは2PCは推奨されません

ポイント: 2PCはコーディネータが単一障害点になります。マイクロサービスでは2PCよりSagaパターンが推奨されます。

Sagaパターン

長時間のトランザクションを、一連のローカルトランザクションに分割します。失敗時は補償トランザクションでロールバックします。

flowchart LR
 subgraph Normal["正常フロー"]
 T1["T1<br/>注文作成"] --> T2["T2<br/>在庫確保"] --> T3["T3<br/>決済"] --> T4["T4<br/>配送手配"]
 end
flowchart LR
 subgraph Failure["失敗時(T3で失敗)"]
 T1b["T1"] --> T2b["T2"] --> T3b["T3<br/>❌ 失敗"] --> C2["C2<br/>在庫戻し"] --> C1["C1<br/>注文キャンセル"]
 end

オーケストレーション方式

中央のオーケストレーターがSagaを制御します。

class OrderSaga {
 async execute(orderData) {
 const sagaId = uuid();
 let currentStep = 0;

 try {
 // Step 1: 注文作成
 const order = await orderService.create(orderData);
 currentStep = 1;

 // Step 2: 在庫確保
 await inventoryService.reserve(order.items);
 currentStep = 2;

 // Step 3: 決済
 await paymentService.process(order.total);
 currentStep = 3;

 // Step 4: 配送手配
 await shippingService.schedule(order);
 currentStep = 4;

 return { success: true, orderId: order.id };

 } catch (error) {
 // 補償トランザクションを実行
 await this.compensate(currentStep, order);
 throw error;
 }
 }

 async compensate(step, order) {
 if (step >= 3) await paymentService.refund(order.id);
 if (step >= 2) await inventoryService.release(order.items);
 if (step >= 1) await orderService.cancel(order.id);
 }
}

コレオグラフィ方式

各サービスがイベントを発行・購読して協調します。

flowchart TB
 subgraph Success["正常フロー"]
 OS1["注文サービス"] --> E1["OrderCreated"]
 E1 --> IS1["在庫サービス"] --> E2["InventoryReserved"]
 E2 --> PS1["決済サービス"] --> E3["PaymentProcessed"]
 E3 --> SS1["配送サービス"] --> E4["ShippingScheduled"]
 end
flowchart TB
 subgraph Failure["失敗時"]
 PS2["決済サービス"] --> EF1["PaymentFailed"]
 EF1 --> IS2["在庫サービス"] --> EF2["InventoryReleased<br/>(補償)"]
 EF2 --> OS2["注文サービス"] --> EF3["OrderCancelled<br/>(補償)"]
 end

比較

観点オーケストレーションコレオグラフィ
可視性フローが明確フローが分散
結合度オーケストレーターに依存サービス間が疎結合
複雑さ単純なロジックイベント設計が複雑
デバッグ容易困難

注意: 補償トランザクションは「取り消し」ではなく「逆の操作」です。完全に元に戻せない場合もあるため、ビジネスルールに基づいた設計が必要です

補償トランザクション

失敗時にロールバックする代わりに、逆の操作を実行します。

// 元の操作
async function reserveInventory(items) {
 for (const item of items) {
 await db.inventory.update({
 where: { productId: item.productId },
 data: { quantity: { decrement: item.quantity } }
 });
 }
}

// 補償トランザクション
async function releaseInventory(items) {
 for (const item of items) {
 await db.inventory.update({
 where: { productId: item.productId },
 data: { quantity: { increment: item.quantity } }
 });
 }
}

補償の考慮点

すべての操作が補償可能とは限らない:

操作補償方法
メール送信「取り消しメール」を送信
物理的な作業開始手動介入が必要
外部APIコール外部システムの補償APIが必要

Outboxパターン

データベース更新とイベント発行を確実に行います。

// 1. トランザクション内で両方を実行
async function createOrder(orderData) {
 await db.transaction(async (tx) => {
 // ビジネスデータを保存
 const order = await tx.orders.create(orderData);

 // outboxテーブルにイベントを保存
 await tx.outbox.create({
 eventType: 'OrderCreated',
 payload: JSON.stringify(order),
 status: 'pending'
 });
 });
}

// 2. 別プロセスでoutboxをポーリング
async function publishOutboxEvents() {
 const events = await db.outbox.findMany({
 where: { status: 'pending' }
 });

 for (const event of events) {
 await messageQueue.publish(event.eventType, event.payload);
 await db.outbox.update({
 where: { id: event.id },
 data: { status: 'published' }
 });
 }
}

結果整合性

即座の整合性ではなく、最終的に整合性が取れることを許容します。

整合性モデル説明
強い整合性書き込み直後に全員が同じデータを見る
結果整合性書き込み後、しばらくすると全員が同じデータを見る(一時的な不整合を許容)

結果整合性の対処

// UI側での対応例
async function placeOrder(orderData) {
 const result = await api.createOrder(orderData);

 // 即座に確定ではなく「処理中」として表示
 return {
 orderId: result.orderId,
 status: 'processing',
 message: 'ご注文を受け付けました。確定後にメールでお知らせします。'
 };
}

まとめ

分散トランザクションは、マイクロサービスアーキテクチャにおける難しい課題です。2PCは現代の分散システムには適さず、Sagaパターンと補償トランザクションが推奨されます。結果整合性を受け入れ、ビジネス要件に合った整合性モデルを選択することが重要です

参考リソース

← 一覧に戻る
PR
PR
PR
PR