サーガ パターン: マイクロサービス アーキテクチャにおける分散トランザクション

サーガ パターン: マイクロサービス アーキテクチャにおける分散トランザクション

従来のモノリシック アプリケーションでは、複数のエンティティ間でデータの一貫性を維持するのは簡単です。リレーショナル データベース エンジンは、ローカル SQL トランザクション内にラップされた ACID (原子性、一貫性、分離性、耐久性) 保証を提供します。注文の発注、支払いの差し引き、または在庫の予約が途中で失敗した場合、ROLLBACK を呼び出すと、データベースのすべての変更が即座に元に戻されます。

ただし、最新の マイクロサービス アーキテクチャ に移行すると、データ管理が根本的に変わります。ドメインの自律性と独立したスケーラビリティを確保するために、各マイクロサービスはプライベート データベースを所有します。電子商取引のチェックアウトの処理などの 1 つのビジネス操作が、複数のサービス境界とデータベース エンジン (注文の PostgreSQL、支払いの DynamoDB、在庫の Redis など) にまたがるようになりました。

分散マイクロサービスは単一のデータベース トランザクションに依存できないため、ネットワーク境界を越えてデータの一貫性を維持することは、分散システム エンジニアリングにおいて最も困難な問題の 1 つになります。

システムの可用性やパフォーマンスを犠牲にすることなくこれを解決するために、ソフトウェア アーキテクトは Saga パターン を利用します。

この詳細な説明では、従来の分散トランザクションが失敗する理由を調査し、Saga パターンの中核的な仕組みを分析し、コレオグラフィーとオーケストレーションを比較し、分離対策を分析し、GoJava での実稼働コードの実装を調査し、実際の障害ロールバックを安全に処理する方法を学びます。


根本的な問題: マイクロサービスで 2 フェーズ コミット (2PC) が失敗する理由

Saga パターンを採用する前に、エンジニアはよく次のような質問をします: マイクロサービス全体で従来の 2 フェーズ コミット (2PC / XA)​​ を使用できないのはなぜですか?

2PC の仕組み

2 フェーズ コミットは、中央のトランザクション マネージャーを使用して、複数のデータベース ノードにわたる分散トランザクションを 2 つのフェーズで調整します。

  1. 準備フェーズ: トランザクション マネージャーは、参加しているすべてのデータベース ノードに、必要な行を準備してロックするように要求します。参加者は YES または NO に投票します。
  2. コミット フェーズ: すべての参加者が YES に投票した場合、マネージャーはすべてのノードに COMMIT コマンドを発行します。いずれかのノードが NO に投票するか、タイムアウトになると、ROLLBACK が発行されます。
Client ----> Transaction Manager
                   |
     +-------------+-------------+
     | (Prepare)   | (Prepare)   | (Prepare)
     v             v             v
 Order DB     Payment DB    Inventory DB

2PC がマイクロサービスのアンチパターンである理由

2PC は強力な一貫性を保証しますが、クラウド ネイティブのマイクロサービス環境では、アーキテクチャ上のいくつかの欠陥により機能しません。

  1. ブロッキングとリソース競合: データベース行は、多段階のネットワーク ハンドシェイクを通じてロックされたままになります。ネットワークの遅延が増加したり、サービスの速度が低下したりすると、ロックが開いたままになり、接続プールが急速に消費され、連鎖的なシステム障害が発生します。
  2. 可用性のボトルネック: 2PC では、システムの可用性は、すべての参加者の可用性の によって制限されます ($A_{total} = A_1 \times A_2 \times \dots \times A_n$)。準備フェーズ中に単一のサービスまたはデータベース ノードがオフラインになると、グローバル トランザクション全体が無期限にブロックされます。
  3. サービス自律性の喪失: 2PC は、サービスにネットワーク API を介してデータベース レベルの XA プロトコルを公開し、データベース エンジンを直接結合することを強制します。
  4. ブローカーの制約: Apache Kafka や RabbitMQ などの高スループットのメッセージ ブローカーは、リレーショナル データベース全体にわたる従来の XA 2PC トランザクションにネイティブには参加しません。

CAP 定理 によると、分散システムはネットワーク パーティション (P) で強力な一貫性 (C) と高可用性 (A) のどちらかを選択する必要があります。最新のマイクロサービス システムは 可用性とパーティション耐性を優先し、即時整合性を優先して 最終整合性 (BASE: 基本的に利用可能、ソフト状態、結果整合性) を優先します。


サーガパターンとは何ですか?

Saga パターンは、もともと 1987 年に Hector Garcia-Molina と Kenneth Salem によって、データベース管理システムで存続期間の長いトランザクションを処理するためのメカニズムとして提案されました。最新のマイクロサービスでは、Saga は一連の個別のローカル トランザクションを表します。

マルチサービス フロー全体を単一のグローバル ロック内にラップする代わりに、Saga はビジネス プロセスを一連の独立したローカル データベース トランザクション ($T_1、T_2、\dots、T_n$) として実行します。

各ローカル トランザクションは、単一のマイクロサービスのデータベース内のデータを更新し、ドメイン イベントまたはメッセージを発行します。このイベントは、ダウンストリーム サービスの次のローカル トランザクション ($T_{i+1}$) をトリガーします。

[Order Service]         [Payment Service]       [Inventory Service]
  Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order)              (Process Payment)            (Reserve Stock)

フォワード約定とバックワード報酬

すべてのローカル トランザクションが成功すると、サーガは正常に完了します (順方向実行)。

ただし、ローカル トランザクションが途中で失敗した場合 (たとえば、商品の在庫切れにより $T_3$ が失敗した場合)、これらのローカル トランザクションはすでにそれぞれのデータベースにコミットされているため、Saga は前のステップ ($T_1、T_2$) でデータベース ROLLBACK を単純に呼び出すことはできません。

すでにコミットされたローカル トランザクションを元に戻すには、Saga は 補償トランザクション ($C_{n-1}, \dots, C_1$) を逆の順序で実行する必要があります。

Forward Flow:    T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
                                                                       |
Backward Rollback:  C1 (Cancel Order) <--- C2 (Refund Card) <----------+

佐賀取引の分類

堅牢な Saga ワークフローを設計するには、トランザクション シーケンスのすべてのステップを次の 3 つの構造タイプのいずれかに分類する必要があります。

取引タイプ 説明 べき等性とロールバックの要件
補償対象取引 復帰不能点の に実行されたステップ。下流のステップが失敗した場合は、元に戻したり、元に戻したりすることができます。 対応する補償トランザクション ($C_i$) が必要です。
ピボット トランザクション サーガの決定的な一歩。ピボットが成功すると、サーガは確実に終了します。失敗すると、Saga はロールバックします。 補償も再審も不可能です。これは、ロールバックとフォワード完了の間の境界を示します。
再試行可能なトランザクション ピボット トランザクションの「後」に実行されるステップ。最終的には成功することが保証されており、報酬は必要ありません。 成功するまで自動的に再試行されるため、厳密に冪等である必要があります

電子商取引チェックアウトの例の内訳

次の 4 つのステップで構成される電子商取引の注文チェックアウトを考えてみましょう。

  1. $T_1$: 保留中の注文を作成 (補償可能) $C_1$ 経由で $\to$ 元に戻す: 注文をキャンセル
  2. $T_2$: 支払いの承認 (補償可能) $C_2$ による $\to$ 元に戻す: 支払いの返金
  3. $T_3$: 在庫の確保 (ピボット トランザクション) $\to$ 在庫の割り当てが成功すると、注文が確定します。失敗した場合は、$C_2$ と $C_1$ をトリガーします。
  4. $T_4$: 配送リクエストのディスパッチ (再試行可能) $\to$ ピボット後に実行されます。配信されるまで再試行されます。

佐賀の建築様式: 振付 vs. オーケストレーション

分散システムで Saga パターンを実装するには、Choreography (分散型) と Orchestration (集中型) という 2 つの主要なアーキテクチャ スタイルがあります。

Saga パターンの実装スタイル: コレオグラフィーとオーケストレーションのアーキテクチャ図

スタイル 1: コレオグラフィー (イベント駆動型の分散化)

コレオグラフィーベースのサーガ には、中央のコントローラーやコーディネーターは存在しません。代わりに、マイクロサービスは、中央のイベント バス (Apache Kafka、NATS、RabbitMQ など) に発行されたドメイン イベントをリッスンすることによって非同期通信します。

ワークフローの仕組み

  1. Order Service は $T_1$ を実行し (保留中の注文を作成)、OrderCreated イベントを Kafka に発行します。
  2. Payment ServiceOrderCreated をリッスンし、$T_2$ (クレジット カードへの請求) を実行し、PaymentCompleted イベント (または PaymentFailed) を発行します。
  3. Inventory ServicePaymentCompleted をリッスンし、$T_3$ を実行します (アイテムを予約します)。商品が在庫切れの場合は、InventoryReservationFailed が出力されます。
  4. Payment ServiceInventoryReservationFailed をリッスンし、$C_2$ を実行します (返金を発行します)。
  5. 注文サービスPaymentRefunded をリッスンし、$C_1$ を実行します (注文をキャンセル済みとしてマークします)。

コレオグラフィーの利点

  • 疎結合: サービスはイベント トピックのみをサブスクライブします。彼らは他のサービスの実装について知りません。
  • 高スループットと分散化: 直接イベント パブリッシュ/サブスクライブにより、中央のオーケストレーターのボトルネックが解消されます。
  • 短いワークフローのためのシンプルさ: シンプルな 2 ~ 3 ステップのワークフローを簡単にセットアップできます。

コレオグラフィーのデメリット

  • 循環依存関係のリスク: サービスが互いのイベントをリッスンすることになり、複雑な循環依存関係が作成される可能性があります。
  • 困難なコード トレーサビリティ: エンドツーエンドのビジネス プロセスを理解するには、複数のコードベース リポジトリにわたるロジックをトレースする必要があります。
  • イベント ストーミングと複雑さ: ステップの数が増えると (例: 10 以上のサービス)、エラーのエッジ ケースを管理するとイベント状態が爆発的に増加します。

スタイル 2: オーケストレーション (集中ワークフロー管理)

オーケストレーション ベースの Saga では、Saga Orchestrator として知られる専用のマイクロサービスが分散トランザクションのライフサイクル全体を制御します。オーケストレーターは中央コーディネーターとして機能し、参加しているマイクロサービスに明示的なコマンドを発行し、その応答イベントをリッスンします。

ワークフローの仕組み

  1. クライアントは、Saga Orchestrator に注文リクエストを送信します。
  2. Orchestrator は CreateOrder コマンドを Order Service に送信します。注文サービスは OrderCreated を返します。
  3. Orchestrator はステート マシンを更新し、ProcessPayment コマンドを Payment Service に送信します。支払いサービスは PaymentSuccessful を返します。
  4. Orchestrator は ReserveInventory コマンドを Inventory Service に送信します。インベントリ サービスは InventoryFailed (Out of Stock) を返します。
  5. Orchestrator が障害を検出し、補償フローを開始します。
    • RefundPayment コマンドを Payment Service に送信します。
    • CancelOrder コマンドを 注文サービス に送信します。
  6. Orchestrator は、Saga の実行を FAILED としてマークします。

オーケストレーションの利点

  • 一元化されたビジネス ロジック: ワークフローの状態とビジネス ロジックは、単一のオーケストレーター サービスまたはステート マシンにローカライズされます。
  • 循環依存関係なし: マイクロサービスはオーケストレーターからのコマンドに応答します。彼らは他のダウンストリーム サービスに依存したり、それについて認識したりしません。
  • 明確なモニタリングとデバッグ: エンドツーエンドのトランザクション状態は、オーケストレーターの状態ストア (PostgreSQL や Temporal / Camunda などのワークフロー エンジンなど) に明示的に保存されます。
  • 簡単なエラー処理: 新しいステップの追加またはロールバック ルールの変更は、オーケストレーター内で完全に管理されます。

オーケストレーションの欠点

  • オーケストレーターの複雑さ: オーケストレーターにドメイン ロジックを組み込みすぎると、「スマート オーケストレーター、ダム サービス」のアンチパターンになってしまうリスクがあります。
  • 潜在的な単一障害点: オーケストレーターは高可用性かつステートフルである必要があります。

比較マトリックス: 振り付けとオーケストレーション

特集 振付 オーケストレーション
制御構造 分散型 (イベント Pub/Sub) 集中型 (Saga コーディネーター / ステート マシン)
カップリング 非常に低い (サービスはイベントを消費します) 中 (サービスはコーディネーターからのコマンドを受け入れます)
プロセスの可視化 低 (ログ ファイル全体に分散) 高 (単一状態ストアがワークフローを視覚化)
最適な用途 シンプルなワークフロー (2 ~ 4 つのサービス ステップ) 複雑なエンタープライズ ワークフロー (5 つ以上のステップ、分岐ロジック)
ツール/フレームワーク Kafka、RabbitMQ、NATS、AWS EventBridge Temporal.io、AWS Step Functions、Camunda、Axon

分離の課題と対策 (「ACID マイナス I」の処理)

Saga のローカル トランザクションはローカル データベースに即座にコミットするため、Saga パターンには従来の ACID 保証の 分離 (I) がありません。

Saga の実行中にクライアントが $T_1$ によって変更されたデータベース行を読み取る場合、コミットされていない中間状態を読み取ることになります。ダウンストリーム ステップが失敗し、補償 ($C_1$) がトリガーされた場合、クライアントは ダーティ リードを実行しています。

分離の欠如によって引き起こされる一般的な異常

  1. 更新の喪失: サーガ A がレコードを更新します。サーガ A が完了する前に、サーガ B が同じレコードを上書きします。サーガ A が失敗して補正を実行すると、サーガ B の更新が上書きされます。
  2. ダーティ リード: 顧客は、Saga A ($T_1$) によって更新された利用可能な在庫を読み取ります。 Saga A は下流で障害が発生し ($T_3$)、在庫を復元します ($C_1$) が、顧客はすでに古いデータに基づいて注文を行っています。
  3. 反復不可能な読み取り: サービスはステップ $T_1$ でデータを読み取り、ステップ $T_3$ で再度読み取りますが、その間に別の同時実行サーガがデータを変更しました。

対策と緩和戦略

分離の欠如にもかかわらずデータの整合性を維持するために、ソフトウェア アーキテクトは特定の分離設計パターンを実装します。

1. セマンティック ロック (保留中/フラグ付き状態)

ローカル トランザクション $T_1$ がデータベース レコードを更新すると、ステータス フィールドが PENDING または APPROVAL_REQUIRED (例: ORDER_PENDING_PAYMENT) に設定されます。

このレコードを読み取っている他の同時実行サーガは、セマンティック ロック フラグをチェックし、状態が COMMITTED または CANCELLED に変わるまで、動作をブロックまたは変更する必要があります。

2. 注文のコミット

高リスクまたは不可逆的な操作が Saga 実行の後半で発生し、脆弱性が発生する範囲を最小限に抑えるように、ローカル トランザクションのシーケンスを設計します。

3. 再読み取り検証 (オプティミスティック同時実行制御)

重要な手順または補正を実行する前に、ターゲット データベース レコードを再読み取り、バージョン タイムスタンプ (version_id) を確認して、同時変更が発生していないことを確認します。

4. 悲観的な見方

経済的エクスポージャーを最小限に抑えるために、Saga のステップを再順序付けします (たとえば、支払い承認をピボット トランザクションのできるだけ近くに配置します)。


生産シーケンス フロー: トランザクションの補正

以下は、順方向実行の失敗とその結果として生じる逆方向補正実行を示す完全なシーケンス フロー図です。

順方向実行の失敗とロールバックの補償を示す Saga パターンのシーケンス フロー図

実践的なコード実装

コレオグラフィー (Go の場合) とオーケストレーション (Java Spring Boot の場合) の両方について、本番環境に対応した実装例を見てみましょう。

実装 1: Go での振り付けベースのサーガ

この Go の例では、注文の作成を処理し、イベント ブローカーを介して支払い失敗イベントをリッスンして、補償ロールバック ロジックを実行する Order Service を示します。

package saga

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// Event definitions
type OrderCreatedEvent struct {
	OrderID   string  `json:"order_id"`
	CustomerID string `json:"customer_id"`
	Amount    float64 `json:"amount"`
}

type PaymentFailedEvent struct {
	OrderID string `json:"order_id"`
	Reason  string `json:"reason"`
}

// OrderRepository handles local DB operations
type OrderRepository interface {
	CreateOrder(ctx context.Context, orderID string, amount float64) error
	UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}

// EventBus abstraction for message broker (e.g., Kafka / NATS)
type EventBus interface {
	Publish(topic string, payload []byte) error
	Subscribe(topic string, handler func(payload []byte)) error
}

type OrderSagaChoreographer struct {
	repo     OrderRepository
	eventBus EventBus
}

func NewOrderSagaChoreographer(repo OrderRepository, bus EventBus) *OrderSagaChoreographer {
	c := &OrderSagaChoreographer{repo: repo, eventBus: bus}
	c.registerSubscriptions()
	return c
}

// Step 1: Forward Transaction (T1)
func (s *OrderSagaChoreographer) StartOrderSaga(ctx context.Context, orderID, customerID string, amount float64) error {
	// Execute local database transaction
	err := s.repo.CreateOrder(ctx, orderID, amount)
	if err != nil {
		return fmt.Errorf("failed local DB transaction T1: %w", err)
	}

	// Emit domain event for downstream Payment Service
	event := OrderCreatedEvent{OrderID: orderID, CustomerID: customerID, Amount: amount}
	bytes, _ := json.Marshal(event)
	
	log.Printf("[SAGA][T1] Order %s created. Publishing OrderCreatedEvent...", orderID)
	return s.eventBus.Publish("orders.created", bytes)
}

// Register subscription for compensating events
func (s *OrderSagaChoreographer) registerSubscriptions() {
	_ = s.eventBus.Subscribe("payments.failed", func(payload []byte) {
		var event PaymentFailedEvent
		if err := json.Unmarshal(payload, &event); err != nil {
			log.Printf("[ERROR] Corrupt payment event: %v", err)
			return
		}

		// Execute Compensating Transaction (C1)
		s.handlePaymentFailed(context.Background(), event)
	})
}

// Step C1: Compensating Transaction
func (s *OrderSagaChoreographer) handlePaymentFailed(ctx context.Context, event PaymentFailedEvent) {
	log.Printf("[SAGA][C1] Payment failed for Order %s (Reason: %s). Rolling back local order...", event.OrderID, event.Reason)
	
	// Revert order status to CANCELLED in local DB
	err := s.repo.UpdateOrderStatus(ctx, event.OrderID, "CANCELLED_PAYMENT_FAILED")
	if err != nil {
		log.Printf("[CRITICAL] Failed to execute compensating transaction C1 for Order %s: %v", event.OrderID, err)
		// Trigger alert or write to Dead Letter Queue (DLQ)
		return
	}
	
	log.Printf("[SAGA][SUCCESS] Order %s successfully compensated and cancelled.", event.OrderID)
}

実装 2: Java でのオーケストレーションベースのサーガ (Spring Boot)

この Java の例では、ステート マシン パターンを使用して Saga Orchestrator を構築し、前方コマンドを調整し、ダウンストリーム ステップが失敗したときに補償ロールバックを実行します。

package com.ghaznix.saga.orchestrator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.UUID;

public enum SagaState {
    STARTED,
    ORDER_CREATED,
    PAYMENT_PROCESSED,
    INVENTORY_RESERVED,
    COMPLETED,
    COMPENSATING_PAYMENT,
    COMPENSATING_ORDER,
    FAILED
}

@Service
public class OrderSagaOrchestrator {

    private static final Logger log = LoggerFactory.getLogger(OrderSagaOrchestrator.class);

    private final OrderServiceClient orderClient;
    private final PaymentServiceClient paymentClient;
    private final InventoryServiceClient inventoryClient;

    public OrderSagaOrchestrator(OrderServiceClient orderClient,
                                PaymentServiceClient paymentClient,
                                InventoryServiceClient inventoryClient) {
        this.orderClient = orderClient;
        this.paymentClient = paymentClient;
        this.inventoryClient = inventoryClient;
    }

    public boolean executeOrderSaga(String customerId, String productId, double amount, int quantity) {
        String sagaId = UUID.randomUUID().toString();
        log.info("[SAGA {}] Starting Order Saga Workflow...", sagaId);
        
        SagaState currentState = SagaState.STARTED;
        String orderId = null;
        String paymentId = null;

        try {
            // Step 1: Forward Local Tx T1 - Create Order
            log.info("[SAGA {}][T1] Sending CreateOrder command...", sagaId);
            orderId = orderClient.createOrder(customerId, productId, amount);
            currentState = SagaState.ORDER_CREATED;

            // Step 2: Forward Local Tx T2 - Process Payment
            log.info("[SAGA {}][T2] Sending ProcessPayment command for Order {}...", sagaId, orderId);
            paymentId = paymentClient.chargePayment(orderId, customerId, amount);
            currentState = SagaState.PAYMENT_PROCESSED;

            // Step 3: Forward Local Tx T3 (Pivot Step) - Reserve Inventory
            log.info("[SAGA {}][T3] Sending ReserveInventory command for Product {}...", sagaId, productId);
            boolean stockReserved = inventoryClient.reserveStock(productId, quantity);

            if (!stockReserved) {
                throw new InventoryAllocationException("Stock allocation failed: Item out of stock.");
            }

            currentState = SagaState.INVENTORY_RESERVED;
            log.info("[SAGA {}][SUCCESS] Saga completed successfully!", sagaId);
            return true;

        } catch (Exception ex) {
            log.error("[SAGA {}][FAILURE] Step failed during state {}. Triggering Compensation...", sagaId, currentState, ex);
            rollbackSaga(sagaId, currentState, orderId, paymentId);
            return false;
        }
    }

    private void rollbackSaga(String sagaId, SagaState failedState, String orderId, String paymentId) {
        log.info("[SAGA {}] Initiating backward compensating transactions from state: {}", sagaId, failedState);

        // Compensate Step 2 if payment was processed
        if (failedState == SagaState.PAYMENT_PROCESSED || failedState == SagaState.INVENTORY_RESERVED) {
            try {
                log.info("[SAGA {}][C2] Executing Payment Refund compensation for Payment {}...", sagaId, paymentId);
                paymentClient.refundPayment(paymentId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Payment refund C2 failed! Manual intervention or DLQ required.", sagaId, e);
            }
        }

        // Compensate Step 1 if order was created
        if (failedState != SagaState.STARTED && orderId != null) {
            try {
                log.info("[SAGA {}][C1] Executing Order Cancel compensation for Order {}...", sagaId, orderId);
                orderClient.cancelOrder(orderId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Order cancellation C1 failed!", sagaId, e);
            }
        }

        log.info("[SAGA {}] Saga compensation workflow finished. Final State: FAILED.", sagaId);
    }
}

プロダクションの要点: Saga とトランザクション送信ボックス パターンの組み合わせ

コレオグラフィーとオーケストレーションの両方で、ローカル トランザクション ($T_i$) を実行するには、ネットワーク経由でドメイン イベントまたはコマンドを発行する必要があります。

サービスが SQL データベースを更新してから Kafka にメッセージをパブリッシュすると、データベースのコミット後のネットワーク障害により サイレント イベント損失が発生します。逆に、データベースのコミット前にメッセージを公開すると、ファントム イベント処理が発生します。

これを解決するには、Saga を トランザクション送信トレイ パターン と組み合わせる必要があります。

[Service Database Transaction Boundary]
+-----------------------------------------------------+
| 1. INSERT INTO business_table (orders/payments)    |
| 2. INSERT INTO outbox_table (event_payload)        |
+-----------------------------------------------------+
                          |
             (CDC / Polling Message Relay)
                          |
                          v
               [Message Broker / Kafka]

イベント ペイロードを 同じローカル データベース トランザクション 内の outbox テーブルに永続化することで、アトミック性が保証されます。非同期バックグラウンド プロセス (Debezium やポーリング リレーなど) は送信トレイ テーブルから読み取り、イベントを Kafka に確実に発行します。

さらに、すべてのダウンストリーム サービス コンシューマは、再試行中の重複したメッセージ配信によって重複した請求や在庫割り当てがトリガーされないように、冪等性 (固有の idempotency_key またはメッセージ重複排除ヘッダーを使用) を実装する必要があります。


アーキテクチャ上の決定チェックリスト

マイクロサービス アプリケーションの分散トランザクションを設計する場合は、次の実用的な意思決定マトリックスを使用してください。

                  Do you need cross-service data consistency?
                                     |
                    +----------------+----------------+
                    | No                              | Yes
                    v                                 v
         Standard Single Service            Can you accept Eventual
            Local Database                    Consistency (BASE)?
                                                      |
                                     +----------------+----------------+
                                     | No                              | Yes
                                     v                                 v
                          Use Monolithic Core            Adopt Saga Pattern
                          with Single ACID DB                 |
                                                              |
                                             How complex is the workflow?
                                                              |
                                            +-----------------+-----------------+
                                            | Simple (2-3 steps)                | Complex (4+ steps/branches)
                                            v                                   v
                                   Choreography Saga                   Orchestration Saga
                                   (Event-Driven Bus)                  (Temporal/Custom State Machine)

結論と重要なポイント

Saga パターン は、リソースをロックしたりシステムの可用性を犠牲にしたりすることなく、マイクロサービスの境界を越えて分散トランザクションを管理するために不可欠なアーキテクチャ パターンです。

要約チェックリスト:

  1. クラウドネイティブ マイクロサービスで 2PC/XA を放棄する: 2 フェーズ コミットは、厳密なロック、高いレイテンシ、および深刻な可用性ボトルネックを引き起こします。
  2. トランザクションをローカル ステップに分割: グローバル操作を、逆補償トランザクション ($C_1 \dots C_{n-1}$) と組み合わせたローカル トランザクション ($T_1 \dots T_n$) に分割します。
  3. 適切なアーキテクチャ スタイルを選択:
    • 疎結合の単純な 2 ~ 3 ステップのイベント駆動型フローには、Choreography を使用します。
    • 一元的な可視性、分岐、およびステート マシンの追跡を必要とする複雑なビジネス ワークフローには、オーケストレーション を使用します。
  4. 分離対策の実装: セマンティック ロック (PENDING フラグ) と再読み取りオプティミスティック ロックを使用して、ダーティ リードと更新の喪失から保護します。
  5. 信頼性の高いメッセージングの保証:常に Saga 実装を トランザクション送信ボックス パターンと組み合わせ、再試行を安全に処理するために 冪等コンシューマを強制します。

Saga パターンを慎重に実装することで、ネットワークに障害が発生した場合でも回復力と一貫性を維持できる、可用性が高く、スケーラブルなマイクロサービスを構築できます。