Chuyển đến nội dung chính

レッスン 8: Saga パターンと分散トランザクション

ACID がマイクロサービスで機能しないのはなぜですか。サーガパターン: 振り付け vs オーケストレーション。トランザクション、冪等性、エラー処理の補償。実践例: 注文→支払い→在庫→発送のワークフロー。

🏗️ アーキテクチャ — レッスン 8 レッスン 8: サーガ パターンと分散 取引

マイクロサービスとマイクロ フロントエンドのシステム設計 — 基本から運用まで

パート 3: マイクロサービスのデータ アーキテクチャ

xdev.asia

はじめに

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

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 つ以上のステップ)、明確な可視性
  • べき等性が必要です - 常に重複メッセージを処理します

次の記事: レッスン 9: イベント ソーシングと CQRS — いつ必要で、いつ必要でしょうか?