はじめに
Monolith では、トランザクションは簡単です。 BEGIN → INSERT order → UPDATE inventory → COMMIT。マイクロサービスでは各サービスが独自のDBを持つ→分散ACIDトランザクションが使えない(2PCは遅すぎる、壊れやすい)。 Saga パターン が標準ソリューションです。

1. 問題: 分散トランザクション
1.1 2PC (2 フェーズ コミット) が適さないのはなぜですか?
- ブロック: コーディネーターが決定するまで、すべての参加者はロックされます。
- 単一障害点: コーディネーターのクラッシュ → デッドロック
- パフォーマンスの低下: 遅延が大幅に増加します
- スケールなし: 参加者を追加 = 指数関数的に複雑さを追加
1.2 最終的な一貫性を受け入れる
マイクロサービスでは、強整合性の代わりに 結果整合性 を受け入れます。データの一貫性は、すぐにではなく最終的になります。
2. サーガパターン
Saga = ローカル トランザクションの文字列。各トランザクションは 1 つのサービスを更新します。トランザクションが失敗した場合、補償トランザクションによって以前の変更が取り消されます。
2.1 コレオグラフィーベースのサーガ
各サービスがイベントを発行 → 次のサービスがリッスンして動作します。
Order Service Payment Service Inventory Service
│ │ │
│──OrderCreated───────►│ │
│ │──PaymentProcessed───►│
│ │ │──InventoryReserved──►
│ │ │
│ (nếu Inventory fail) │
│ │◄──InventoryFailed────│
│◄──PaymentRefunded────│ │
│──OrderCancelled │ │
利点: シンプル、疎結合、中央コーディネーターなし 欠点: 全体的なフローを追跡するのが難しく、循環依存関係が発生する可能性があります。
2.2 オーケストレーションベースのサーガ
Saga Orchestrator はフロー全体を調整します。
┌─────────────────┐
│ Order Saga │
│ Orchestrator │
└──┬──┬──┬───────┘
│ │ │
┌──────────┘ │ └──────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Payment │ │Inventory │ │ Shipping │
│ Service │ │ Service │ │ Service │
└──────────┘ └──────────┘ └──────────┘
Orchestrator:
1. Create Order (PENDING)
2. Command: ProcessPayment → Payment Service
3. If success: Command: ReserveInventory → Inventory Service
4. If success: Command: CreateShipment → Shipping Service
5. If any fail: Compensate previous steps in reverse
利点: 明確なフロー、理解しやすい、一元化されたエラー処理 欠点: オーケストレーターがボトルネックとなり、単一障害点になる可能性があります。
2.3 いつ何を使用するか?
| 基準 | 振付 | オーケストレーション |
|---|---|---|
| 複雑さ | 2~3ステップ | 4 ステップ以上 |
| 可視性 | 追跡が難しい | クリア |
| カップリング | ルース | 中 (オーケストレーター向け) |
| エラー処理 | 分散 | 集中型 |
3. 補償取引
補償トランザクション = ビジネス トランザクションの「元に戻す」(DB ロールバックではない):
Forward: Compensating:
CreateOrder → CancelOrder
ProcessPayment → RefundPayment
ReserveInventory → ReleaseInventory
CreateShipment → CancelShipment
注意: 補償トランザクションは 冪等 である必要があります。つまり、同じ結果で複数回呼び出される必要があります。
4. 冪等性 — 重複メッセージの処理
分散システムでは、メッセージを複数回送信できます (少なくとも 1 回の配信)。サービスは重複を処理する必要があります。
// Idempotent payment processing
async function processPayment(command) {
// Check idempotency key
const existing = await db.findPayment(command.idempotencyKey);
if (existing) return existing; // Already processed
// Process payment
const result = await stripe.charge(command.amount);
await db.savePayment({
idempotencyKey: command.idempotencyKey,
...result
});
return result;
}
5. ハンズオン: E コマース注文編
Create Order Saga (Orchestration):
Step 1: Order Service → CreateOrder(PENDING)
Step 2: Payment Service → ChargeCustomer(orderId, amount)
✅ → Step 3
❌ → Compensate: CancelOrder → DONE (Order: PAYMENT_FAILED)
Step 3: Inventory Service → ReserveItems(orderId, items)
✅ → Step 4
❌ → Compensate: RefundPayment → CancelOrder → DONE (Order: OUT_OF_STOCK)
Step 4: Order Service → ConfirmOrder(orderId)
✅ → DONE (Order: CONFIRMED)
Step 5 (async): Notification Service → SendConfirmationEmail
概要
- 2PC はマイクロサービスには適していません - 遅すぎて壊れやすい
- サガ パターン = 一連のローカル トランザクション + 補償トランザクション
- 振付: シンプルなフロー (2 ~ 3 ステップ)、疎結合
- オーケストレーション: 複雑なフロー (4 つ以上のステップ)、明確な可視性
- べき等性が必要です - 常に重複メッセージを処理します