アーキテクチャ (IT)システムアーキテクト

分散予約システムのためのフォールトトレラントなサーガオーケストレーションパターンをどのように設計しますか?独立したサブドメイン全体で長期間実行されるトランザクションを補償し、重複リクエストシナリオにおいて冪等性を確保するためには、どのようなことが必要ですか?

Hintsage AIアシスタントで面接を突破

質問への回答

TemporalまたはNetflix Conductorを使用して、PostgreSQLに耐久性のあるワークフローステートを保持し、ドメインサービスとのgRPC通信を行う中央集権的なサーガオーケストレーターを実装します。このパターンでは、ビジネス制約に一致するTTLウィンドウを持つ冪等性キーがRedis Clusterに保存される必要があり、Apache Kafkaは監査トレイルと補償トリガーのためのイベントバックボーンとして機能します。各サーガステップには、リバース操作を実行する補償トランザクションが含まれ、サーガステートマシンパターンを使用して明示的な状態(PENDING、SUCCEEDED、COMPENSATING、COMPENSATED)がetcdまたはZooKeeperでクラスターの調整のために追跡されます。

┌─────────────────┐     ┌──────────────┐     ┌─────────────────┐
│   API Gateway   │────▶│   Temporal   │────▶│   Inventory     │
└─────────────────┘     │  Orchestrator│     │   Service       │
                        └──────────────┘     └─────────────────┘
                               │                        │
                               ▼                        ▼
                        ┌──────────────┐          ┌─────────────────┐
                        │  PostgreSQL  │          │   PostgreSQL    │
                        │  State Store │          │   (Compensation │
                        └──────────────┘          │    Logic)       │
                                                  └─────────────────┘

実生活からの状況

あるグローバルホテル予約プラットフォームは、異なる地域にある3つの異なるKubernetesクラスター間で部屋の予約、支払い処理、ロイヤルティポイントの更新を調整する際にカスケード障害に苦しんでいました。彼らのレガシー実装は、REST APIを介して**Two-Phase Commit (2PC)**を使用しており、支払いゲートウェイが10秒を超える遅延スパイクを経験した during peak traffic では広範なデッドロックが発生しました。

チームは、各サービスが共有バスにドメインイベントを公開するChoreography-Based Sagaの使用を評価しました。このアプローチにより、単一障害点が排除され、インフラコストが40%削減されました。しかし、複雑なマルチルーム予約が成功したかを判断するには、17のマイクロサービス間でログを照会する必要があり、このことで深刻な可観測性の課題が発生しました。暗黙の依存関係があるため、一貫したタイムアウトポリシーを強制することが不可能になり、プロダクションの問題をデバッグすることは、複数のAWS CloudWatchダッシュボードにまたがる法医学的な作業になりました。

チームはAWS ECS上にデプロイされたカスタムNode.jsコーディネーターを用いたOrchestrated Sagaのプロトタイプを作成しました。これにより、ビジネスロジックが中央集約され、統一されたGrafanaダッシュボードを通じてモニタリングが簡素化されました。残念ながら、初期実装ではワークフローステートがメモリにのみ保存されていたため、デプロイメント中にコーディネーターが再起動した際に致命的なデータ損失が発生しました。30のトランザクションが不明な状態に入り、手動のデータベース調整が3日間かかり、二重課金された顧客からのかなりの収益損失が生じました。

採用されたソリューションは、Cassandra永続性を持つワークフローエンジンとしてTemporalをデプロイし、ポッド再起動およびAZ障害を通じてステートの耐久性を保証しました。アーキテクチャは、オーケストレーターとドメインサービス間の型安全な通信のためにProtobufスキーマを使用し、冪等性キーを管理するためにRedis Sentinelを使用しました。支払いサービスがus-east-1で地域的障害を経験したとき、サーガは自動的に補償ワークフローをトリガーし、200ms以内に部屋のホールドを解除し、ロイヤルティポイントを原子単位で取り消しました。

システムは現在、毎日50,000件の複雑な予約を処理しており、99.99%の一貫性保証を持ち、ネットワークパーティション中の手動介入はゼロです。故障の検出平均時間(MTTD)は45分から8秒に短縮され、補償レイテンシはp99で500ms未満を維持しています。

候補者が見落としがちな点

補償トランザクション自体が失敗する場合、部分補償失敗をどのように処理しますか?その結果、システムが不整合な状態に留まる可能性があります。

Apache Kafkaを使用したイベントソーシングによる補償監査ログを実装し、すべての試みられた補償を不変イベントとして無限に保持します。システムは、指数バックオフで自動的に再試行する必要がある一時的なインフラの障害と、人間の介入を必要とするビジネスロジック違反を区別する必要があります。一時的な問題には、RabbitMQAmazon SQSデッドレターキュー(DLQ)を使用し、サービスが回復した後にジッターを持って補償を再処理します。ビジネスルール違反、例えばすでに精算されたトランザクションの払い戻しを試みる場合、サーガは「COMPENSATION_FAILED」状態に入り、PagerDutyアラートをトリガーし、コマンドモデルを介して集約ルートを凍結するためにCQRSパターンを適用します。常にデータベースの一意制約やRedisのSETNX操作を使用して、補償を冪等に設計し、再試行が副作用を生じないことを確実にします。

一時的な結合と「現在のトランザクション状態は何ですか?」というクエリに対する応答能力に関して、振り付けとオーケストレーションの根本的なアーキテクチャの違いは何ですか?

振り付けReactive Manifestoに従い、サービスが上流または下流の参加者の知識なしにイベントに反応することで時間的分離を生成しますが、複雑な分散トレースを実装しなしではサーガの状態をクエリする能力を犠牲にします。状態はイベントログからの抽出物となり、「予約は完了していますか?」という質問に答えるためにCQRS読み取りモデルのプロジェクションが必要になります。オーケストレーションは、コーディネーターとワーカー間の明示的な時間的結合を導入します。オーケストレーターは次のステップをトリガーするために利用可能である必要がありますが、状態ストア(PostgreSQL/CockroachDB)における単一の真実のソースを提供します。これにより、即時の状態クエリが可能になりますが、ネットワーク依存性を生じさせます。重要な洞察は、振り付けがすべての消費者に状態機械を実装することを必要とするのに対し、オーケストレーションはこの複雑性を中央集中化することです。強力な監査可能性とコンプライアンス(PCI-DSS)を必要とするシステムにおいては、オーケストレーションが好まれます。

Kafkaコンシューマのリバランス中やKubernetesポッドの再起動中に、メッセージブローカーで少なくとも1回の配信セマンティクスを使用して重複したサーガの実行をどのように防ぎますか?

RedisMemcachedを使用した冪等消費者パターンを実装し、処理されたメッセージIDを保存します。重複排除ウィンドウは、通常、金融システムにおいて24~48時間に一致します。サーガオーケストレーターがコマンドを受信すると、サイドエフェクトを引き起こす前にビジネスキー(顧客ID + 予約リファレンス)でコリレーションIDをハッシュ化して決定論的な冪等性キーを生成します。各ドメインサービスは、Idempotency Storeに対してこのキーを検証する必要があり、これは複合キーに一意制約を持つPostgreSQLテーブルとして実装されるか、メモリ効率の良いネガティブルックアップのためにRedisBloom Filtersが使用されます。長期間のサーガに対しては、分散ノード間の正確な1回処理セマンティクスを処理するためにetcdのバージョンベクトルを介した楽観的ロックを使用したサーガステートマシンを利用します。これにより、消費者グループがデプロイ中にリバランスする際や、KubernetesのlivenessProbeが再起動をトリガーするネットワークパーティション中にダブルブッキングシナリオが防止されます。