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

惑星規模のステートフルストリーム処理プラットフォームのアーキテクチャについて詳しく説明してください。このプラットフォームは、無限のデータストリームに対してイベント時間のウィンドウ集約を正確に1回のみのセマンティクスで可能にし、トポロジーの変更中に自動的にリバランスを行い、サブ秒のチェックポイント復元を維持し、複数の処理言語とクロスリージョンのアクティブ・アクティブレプリケーションを共有ストレージ依存なしでサポートします。

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

質問の歴史

ストリーム処理アーキテクチャは、Apache Stormのレコードアットリーストオンス処理から、Apache FlinkSpark Structured Streamingによって導入された現代の正確な1回のみの保証へと進化しました。企業がバッチLambdaアーキテクチャから継続的なKappaストリームに移行する中で、複雑さは単純な変換からウィンドウ集約やセッション化のための分散状態の管理に移りました。データ主権要件や地域の待機時間制約の出現は、共有NFSSANストレージに依存しないアクティブ・アクティブ展開を必要とし、地理的フェイルオーバー中の状態の一貫性に新たな課題をもたらしました。

問題

ステートフルストリーム処理には、処理ノード上にギガバイトのオペレータ状態(キーウィンドウ、セッションストア)を保持しつつ、1秒間に数百万のイベントを取り込む必要があります。正確に1回のみのセマンティクスは、3つのコンポーネント間でアトミックコミットを要求します:ソースオフセットトラッキング、状態バックエンドの更新、シンク書き込み。共有ストレージなしのクロスリージョンのアクティブ・アクティブレプリケーションは、ネットワークパーティションが発生するときにスプリットブレインリスクをもたらし、オートスケーリングは、処理中のレコードを失うことなく、また処理時間保証を違反することなくライブ状態の移行を要求します。複数の言語(JavaPythonGo)をサポートするには、従来、シリアライゼーションオーバーヘッドや言語固有のランタイムロックインが強いられます。

解決策

アーキテクチャは、Apache KafkaまたはApache Pulsarを統一ログとして使用し、Kubernetes上で動作する処理ノードに言語非依存のgRPCサイドカーを持たせることで、非同期設計を採用しています。状態管理には、非同期インクリメンタルチェックポイントをS3互換オブジェクトストレージに保存するための組み込みRocksDBを使用し、軽量の分散コーディネーションサービス(etcdまたはZooKeeper)を介して調整されます。正確に1回のみのセマンティクスは、状態のChandy-Lamportスナップショットアルゴリズムとトランザクショナルシンクのための二相コミット(2PC)プロトコル(Kafkaトランザクションまたは冪等なJDBC書き込み)を通じて達成されます。クロスリージョンのレプリケーションには、Kafka MirrorMaker 2またはPulsar Geo-Replicationを介したログベースの状態輸送を利用し、集約のためのCRDTベースの可換カウンタや、キー付き状態のためのバージョン管理された主要な所有権を使用して競合解決を行います。

質問への回答

このプラットフォームは、取り込み、処理、状態管理、コーディネーションの4つの論理層から構成されます。

取り込み層

Apache Kafkaクラスタは、MirrorMaker 2を使用して双方向トピックレプリケーションを行う複数の地域で動作します。プロデューサの冪等性とトランザクショナルIDにより、地域間のプロデューサフェイルオーバー中も正確に1回のみの取り込みが保証されます。

処理層

Apache Flinkまたは同様のストリームプロセッサは、Kubernetesのステートフルセットとして動作します。各TaskManagerは、Protobufシリアライズされたタスクを受け入れるgRPCサイドカーを公開し、PythonおよびGoのユーザー定義関数(UDF)がgRPCコンテナ内で実行できるようにし、Javaランタイムが状態とチェックポイント管理を行います。JobManagerは、レコードキーに対して一貫したハッシュを使用してTaskManagers間でトポロジーを分割します。

状態管理

オペレーター状態バックエンドはRocksDBを使用し、インクリメンタルチェックポイントを有効にします。チェックポイントは、地域のS3バケットに非同期で15秒ごとにデルタ状態の変更を書き込みます。クロスリージョンの一貫性のために、アクティブ・アクティブ展開は、単調集計(カウント、合計)にLWW-Element-Set CRDTsを使用し、非可換オペレーションに対してプライマリキーの親和性を持たせます。地域のフェイルオーバー中は、待機中のTaskManagersがS3からSavepointsを使用して状態を復元します。

正確に1回のみの保証

システムは、以下を通じてエンドツーエンドの正確に1回のみを実装します:

  • 二相コミット:シンクはFlinkのTwoPhaseCommitSinkFunctionに参加し、チェックポイント中にKafkaまたはPostgreSQLに前コミットし、チェックポイント通知の成功時にコミットします。
  • 冪等プロデューサ:上流のKafkaプロデューサは、シーケンス番号を使用した冪等な配送を利用して再試行の重複を削除します。
  • トランザクションアイソレーション:チェックポイントはトランザクション境界として機能し、未コミットデータは下流の消費者には見えません。

実生活からの状況

グローバルなライドシェアプラットフォームは、AWS us-east-1およびAWS eu-west-1全体でドライバーの可用性とライドの需要を集約するリアルタイムのサージ価格計算を必要としました。以前のアーキテクチャは、レプリケーションラグのある単一プライマリのRedisクラスターを使用しており、2秒のフェイルオーバーウィンドウが発生し、価格計算中に地域の障害時に古いまたは重複したサージ乗数が生成され、不正確な運賃計算と顧客の苦情を引き起こしていました。

解決策 1: アクティブ-パッシブと共有ストレージ

チームは、状態ストレージ用に地域間でEFS(共有NFS)をマウントすることを検討しました。利点:単一ライターセマンティクスで簡素化されたフェイルオーバー、強い一貫性。欠点EFSの待機時間が地域間アクセスで100msを超え、50msの処理SLAを違反;さらに、NFSの書き込みの一貫性の問題が、ネットワークパーティション中にチェックポイントの破損を引き起こしました。

解決策 2: Lambda アーキテクチャ

Kafka Streamsを使ったスピードレイヤーと、修正のためのSparkを使ったバッチレイヤーを実装します。利点:不変ログによるフォールトトレランス、シンプルな復元。欠点:二重のコードパスの維持による運用の複雑さ;バッチ修正はサージ価格計算に必要なサブ秒の正確さのために遅れすぎました。

解決策 3: アクティブ・アクティブストリーム処理とCRDTs

Apache Flinkを両地域にデプロイし、RocksDB状態、インクリメンタルS3チェックポイント、およびライドカウントのためのCRDTベースのカウンタを使用します。利点:ローカル処理での待機時間が20ms未満、自動競合解決、ゼロダウンタイムのフェイルオーバー。欠点:集約を可換にするためにリファクタリングが必要(G-CountersおよびPN-Countersを使用)、二地域のチェックポイントのためのストレージコストが増加しました。

チームは、99.99%の可用性でサブ秒のフェイルオーバーというビジネス要件が、解決策1の2秒のウィンドウや共有ストレージの待機時間に耐えられないため、解決策3を選択しました。彼らは、ドライバー数のためのG-Countersと最新の価格乗数のためのLWW-Registersを実装しました。

結果

システムは、両地域で15msのp99待機時間で正確に1回のみのサージ価格計算を達成しました。シミュレーションされたus-east-1の障害中、eu-west-1は重複した運賃計算なしでローカルに複製された状態を使用して処理をシームレスに続行しました。チェックポイント復元時間は平均800msで、サブ秒の要件を十分に満たしています。

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

チェックポイントのインターバル調整は、ステートフルストリームプロセッサのバックプレッシャーメカニズムとどのように相互作用しますか?

多くの候補者は、復元時間のためにチェックポイントインターバルを最適化しますが、バックプレッシャーの伝播を考慮しません。チェックポイントバリアがバックプレッシャーのために遅れると、Chandy-Lamportアルゴリズムはパイプラインの実行を一時停止し、カスケードタイムアウトを引き起こす可能性があります。適切なアプローチは、チェックポイントのタイムアウトをバックプレッシャーの閾値と一致させることであり、高負荷時には未整列のチェックポイント(バリアがバッファを追い越す)を使用し、同期と非同期のチェックポイントフェーズを分ける必要があります。RocksDBのインクリメンタルチェックポイントは、ディスクI/Oを圧倒し、バックプレッシャーを悪化させることを防ぐためにRateLimiter設定を使用して制限する必要があります。

冪等なシンクとの組み合わせでの少なくとも1回の配送と、真の正確に1回の処理セマンティクスの根本的な違いは何ですか?

冪等なシンクは、重複処理が同じ出力状態(例:PostgreSQLHBaseUPSERT操作)を生成することを保証しますが、再試行中に中間状態を露出します。シンクがA、Bというレコードを書き込み、その後クラッシュしてA、B、Cを書き込む再試行を行うと、下流の観察者は一時的にA、B、A、B、Cを見てから重複削除されます。真の正確に1回のみ(実質的に1回)は、トランザクションアイソレーションを使用して、前コミットデータがチェックポイント完了まで見えないようにします。これには、シンクがトランザクションをサポートする必要があり(例:Kafkaトランザクションでisolation.level=read_committed)、二相コミットプロトコルが必要です。候補者は、冪等性は正確性の問題を解決しますが、復元中の一貫性/可視性の問題は解決しないことを見落とすことが多いです。

イベント時間のウィンドウ処理は、クロスリージョンのフェイルオーバーシナリオ中の遅れて到着するデータをどのように処理すべきですか?

リージョンAからリージョンBへのフェイルオーバーが発生すると、リージョンAのネットワークバッファ内の処理中のレコードが失われたり、水準が超えて遅延することがあります。候補者はしばしば、水準を無制限に延長することを提案しますが、これによりウィンドウの完全性保証が破られます。適切なアーキテクチャは、遅れて到着するデータキャプチャのためにSide OutputsFlink用語)を使用し、Allowed Lateness仕様を組み合わせることです。フェイルオーバー中、システムはタイムスタンプのあるS3 Savepointsからウィンドウを水和させ、その後、故障した地域のデッドレターキューから遅れて到着するレコードを次のウィンドウに統合するか、特定の遅延データハンドラをトリガーする必要があります。さらに、水準生成は地域間で冪等でなければならず、水時計時間を水準に使用するとフェイルオーバー中に乖離が生じるため、水準は両方のアクティブ地域の単調イベント時間抽出から派生しなければなりません。