問題と適用シナリオ
複数のプロダクトチームで共有される分散メッセージキューを設計します。定常時の取り込みは平均1 KBのメッセージが毎秒100万件で、トラフィックのピーク時には毎秒300万件を15分間維持できます。メッセージはデフォルトで24時間保持されます。プロデューサーはバッチでパブリッシュします。コンシューマーはグループとしてプルし、オフセットをコミットし、保持期間内であれば再生(リプレイ)できます。同一のビジネスキーを持つメッセージはローカルな順序保証が必要ですが、異なるキーは並行して処理できます。通常のメッセージは少なくとも1回の配信(at-least-once)を使用し、パブリッシュの確認応答(Ack)のp99ターゲットは50ミリ秒未満です。各パーティションは3つのアベイラビリティゾーンにわたって3つのレプリカを持ち、1つのゾーンが失われても確認応答済みのメッセージが失われてはなりません。
大半のメッセージは小容量ですが、APIでは最大100 MBのビジネスペイロードを許容します。大容量ペイロードがブローカーのログ、レプリカ転送、およびコンシューマーのバッファを繰り返し通過することは避けるべきです。この設計では、大容量ペイロードはまずオブジェクトストレージに入り、キューにはイミュータブルな参照、サイズ、およびチェックサムのみが格納されます。スループット、レイテンシ、保持期間、しきい値、およびレプリカ数は面接用の前提条件であり、ターゲットハードウェアでのベンチマークが必要です。これらは製品の仕様保証ではありません。
2026年5月に公開された中国のシステム設計記事では、1日あたり数百億件のメッセージを処理し、ピーク時に数百万QPSに達するキューについて直接論じられています。2026年6月に更新されたPracHubのプロンプトでは、候補者にAPI、コンシューマーグループ、オフセット、パーティション、レプリカ、大容量ペイロード、およびマルチテナント分離を網羅することが求められています。これらを合わせることで、現在検証可能なシステム設計プロンプトが構成されます。特定の企業への帰属を主張しているのは1つのページのみであるため、この記事では企業名を未設定としています。
面接官が評価するポイント
第1に、候補者が配信コントラクト(配信規約)を定義しているかどうかです。パブリッシュの成功、ブローカーの永続ストレージへの書き込み、コンシューマーへのメッセージ配信、ビジネス上の副作用(処理)の完了、およびオフセットのコミットは、5つの独立した境界です。これらすべてを単に「メッセージ成功」と呼ぶと、2つの障害ウィンドウが見落とされます。プロデューサーが確認応答をロストした後にパブリッシュを再試行する場合と、コンシューマーが副作用を完了したもののオフセットコミット前にクラッシュして処理を重複実行する場合です。
第2に、パーティショニング、順序保証、およびスケーリングが一貫した論理に従っているかどうかです。1つのキーを1つのパーティションに送信することで、そのキーのログ順序が維持されます。同じパーティションが書き込みスループットとコンシューマーグループの並列度の両方を制限します。パーティションを増やすと並列スロットは増えますが、継続的に過負荷なホットキーを分割したり、グローバルな順序を作成したりすることはできません。
第3に、レプリカの確認応答ルールが障害に耐えられるかどうかです。優れた回答では、プロデューサーにいつ確認応答を返せるか、どのレプリカが新しいリーダーになる資格を持つか、そしてエポック(epoch)によって回復した古いリーダーがどのようにフェンシング(隔離)されるかを述べる必要があります。「3つのレプリカが自動的にフェイルオーバーする」と述べるだけでは、確認応答済みのレコードが確実に残る証明にはなりません。
第4に、コンシューマーオフセットがビジネス結果と結び付けられているかどうかです。処理前にコミットするとビジネスの副作用をスキップするリスクがあります。コミット前に処理するとクラッシュ後に再実行(リプレイ)される可能性があります。at-least-onceは2番目のウィンドウを選択し、安定したメッセージID、ビジネスべき等性キー、ユニーク制約、またはバージョン条件によって重複を吸収します。ブローカーのトランザクションはそのトランザクションに参加しているリソースのみを対象とし、外部の決済サービス、メールプロバイダー、またはデータベースに対して自動的にexactly-onceの効果を付与するわけではありません。
最後に、キューが高負荷下でもこれらの境界を維持できるかどうかです。候補者は24時間のストレージとバースト時のバックログを計算し、大容量メッセージ、ホットパーティション、遅延コンシューマー、ポイズンメッセージ、ディスクフル、ノイズの多いテナント(Noisy Neighbor)に対処した上で、障害を注入して確認応答済みメッセージが失われないこと、未コミットの作業がスキップされないこと、古い所有者がオフセットを進められないことを証明する必要があります。
回答前の明確化のための質問
- これは保持型のログ(Retained log)ですか、それとも取得して削除するワークキュー(Claim-and-delete work queue)ですか? この設計では複数のコンシューマーグループとリプレイが必要なため、保持型ログを選択します。1つのジョブを厳密に1つのワーカーが取得し、過去のリプレイが不要であれば、可視性タイムアウトを備えたリースベースのキューの方がシンプルです。
- 順序保証のスコープはどうなりますか? 順序保証は1つのトピックパーティション内でのアペンド順を対象とし、1つのビジネスキーが1つのパーティションに固定されます。キー、パーティション、またはトピックをまたいだグローバルな順序保証はありません。グローバルな順序を保証しようとすると、スループットが単一のシリアルログに制限されてしまいます。
- パブリッシュの確認応答(Ack)は何を意味しますか? 3つのレプリカのうち2つがレコードを永続的に保存し、コントロールプレーンが現在のリーダーエポックを引き続き認識している状態を意味します。強耐障害性を持つトピックでは、利用可能なレプリカが2つ未満の場合、書き込みを拒否します。
- 消費セマンティクスは何ですか? デフォルトは at-least-once(少なくとも1回)です。正常に処理してから次のオフセットをコミットします。外部への副作用にはべき等性またはリコンシリエーション(データ照合)が必要です。損失を許容し重複を禁止する低価値のワークロードのみが、先にコミットを行います。
- コンシューマーには任意の時点へのリプレイが必要ですか? グループは24時間の保持ウィンドウ内であれば、オフセットまたはタイムスタンプによってリセットできます。保持期間を超えた場合はアーカイブからリストアするか、アーカイブが存在しない場合は明示的に失敗する必要があります。
- ポイズンメッセージはスキップしてよいですか? 通常のトピックでは、制限付きのリトライ後にメッセージをデッドレタートピックに移動できます。厳格にキーの順序が保証されたトピックでは、無償でスキップすることはできません。キーまたはパーティションを一時停止して修復しないと、後続のメッセージが失敗したメッセージを追い越してしまいます。
- 100 MBのペイロードはインラインである必要がありますか? いいえ。この設計では最大256 KiBまでのインラインペイロードを想定し、そのしきい値を超える場合はオブジェクト参照を使用します。しきい値はベンチマークとコストによって決定されます。100 MBはオブジェクトペイロードの上限です。
- クロスリージョンの要件はどうなっていますか? プライマリ設計は、3つのアベイラビリティゾーンにまたがる単一リージョンです。非同期のクロスリージョンディザスタリカバリでは、データ損失ゼロとローカル書き込みレイテンシの両立を保証できません。RPOゼロのクロスリージョン要件がある場合、確認応答パスとレイテンシバジェットが変化します。
- テナントの分離強度はどの程度ですか? ブローカーはデフォルトで共有され、テナントごとに取り込み、エグレス、ストレージ、パーティション、および接続の制限が設けられます。非常に大規模なテナントや規制対象のテナントには、同一のコントロールプレーンとプロトコルのもとで専用のブローカープールを使用できます。
30秒の回答フレームワーク
「これを保持型で再生可能なパーティション化ログとしてモデル化します。プロデューサーはビジネスキーによってパーティションリーダーにルーティングし、書き込みをバッチ処理して、異なるアベイラビリティゾーンにある2つのレプリカがバッチを永続化した後にのみ確認応答(Ack)を受け取ります。これにより、グローバルな順序ではなくキーごとの順序が提供されます。コンシューマーグループはパーティションを排他的に所有し、べき等なビジネス書き込みを完了してから次のオフセットをコミットするため、配信は at-least-once となり、外部の副作用はメッセージIDによって重複排除されます。定常時の取り込みは論理的に約1 GB/s、1日あたり86.4 TBであり、3つのレプリカ構成では259.2 TBとなります。15分間の3倍のピークでは、コンシューマーが定常レートを維持した場合、約1.8 TBの追加バックログが発生します。256 KiBを超えるペイロードはオブジェクトストレージに送られ、キューには参照とチェックサムが保持されます。テナントクォータ、公平なスケジューリング、ラグのアラート、リーダー障害、確認応答のロスト、およびコンシューマークラッシュのテストを通じて、システムの境界を実証します。」
ステップバイステップの詳細設計
ステップ1:コンポーネントの前にAPIと不変条件(Invariants)を記述する。
基本的なサーフェスは、トピック、パブリッシュ、フェッチ、コミット、およびオフセットのリセットをカバーします。
POST /v1/topics
POST /v1/topics/{topic}/messages:publish
POST /v1/groups/{group}/messages:fetch
POST /v1/groups/{group}/offsets:commit
POST /v1/groups/{group}/offsets:resetパブリッシュリクエストには、認証されたテナントコンテキスト、トピック、オプションの message_key、安定した message_id、ペイロードまたはオブジェクト参照、プロデューサーエポック、およびパーティションごとのシーケンスが含まれます。バッチレスポンスは、各メッセージのパーティション、オフセット、およびコミットステータスを返します。フェッチには、グループ、パーティション所有権のジェネレーション、開始オフセット、最大バイト数、およびロングポーリング期間が含まれます。コミットは、次に読み取るレコードのオフセットを書き込みます。
この設計では、4つの不変条件を維持します。1つのアベイラビリティゾーン障害後も確認応答済みレコードが読み取り可能であること、コンシューマーにはコミット済みプレフィックスのみが見えること、リーダーエポック内でオフセットが単調増加すること、および期限切れのジェネレーションを持つコンシューマーはオフセットをコミットしたり結果を書き込み続けたりできないことです。APIレベルの message_id はビジネス上の重複排除をサポートします。producer_id + epoch + sequence により、ブローカーは同じパブリッシュのリトライを認識できます。
ステップ2:コントロールプレーンとデータプレーンを分離する。
Control plane: tenants and ACLs, topic configuration, partition placement,
replica membership, leader epochs, quotas
Data plane:
Producer -> metadata cache -> partition leader -> follower replicas
Consumer group -> group coordinator -> partition leaders -> business sinkコンセンサスアルゴリズムに基づく小規模なクラスタがトピックとパーティションのメタデータを保存し、各リーダーの任期に単調増加するエポックを割り当てます。メッセージ本文は保持しません。ブローカーはレコードのアペンド、レプリケーション、読み取り、および保持を行います。クライアントはパーティションリーダーをキャッシュし、古いエポックやnot-leaderレスポンスを受け取った後にメタデータを更新します。メッセージスループットは中央プロキシを経由せず、既存のパーティションはコントロールプレーンの停止中も制限付きのリースの間は処理を継続できます。トピックの作成やパーティションの移動は一時停止する場合があります。古いメタデータによって勝手なリーダーが選出されてはなりません。
ステップ3:スループット、リプレイ、およびローカルな順序保証のためにパーティション化ログを使用する。
各パーティションはアペンドオンリー(追記専用)セグメントの集合であり、そのレコードには以下が含まれます。
MessageEnvelope {
tenant_id, topic, partition, offset
message_id, message_key, producer_id, producer_epoch, sequence
created_at, headers, payload_or_ref, payload_size, checksum
}アクティブなセグメントは順次アペンドを受け取ります。スパース(疎)オフセットインデックスが読み取り位置を特定し、クローズされたセグメントは時間またはサイズによってローテーションされます。コンシューマーはオフセット単位でバッチをフェッチするため、シーケンシャルI/O、ページキャッシュの利用、およびバッチネットワーク転送が可能になります。保持期間管理では、24時間後にセグメント全体を削除します。有効なリプレイ中または階層化ストレージへのアップロード中にあるセグメントは参照を保持するため、削除処理がリーダー(読み取り処理)と競合することはありません。
ルーティングハッシュには、信頼されたテナント、トピック、およびビジネスキーが含まれます。同じキーを持つレコードは同一パーティションにとどまります。キーのないレコードはラウンドロビンまたはスティッキーバッチ割り当てを使用できます。テナントのみによるルーティングは大規模テナントをホット(過負荷)にし、完全なランダムルーティングはキーの順序を失わせます。パーティションの追加は将来のレコードに影響します。剰余算(Modulo)の変更により、1つのキーが新旧両方のパーティションに配置される可能性があります。安定した順序が重要な場合は、仮想シャードを物理パーティションにマッピングします。移動中は仮想シャードを一時停止し、カットオーバーオフセットを記録し、古い所有者のデータをドレイン(処理完了)させてから、新しいエポックの下で再開します。
ステップ4:レプリケーションとリーダー選出にコミットの単一の定義を与える。
各パーティションは3つのアベイラビリティゾーンに3つのレプリカを持ちます。リーダーはオフセットを割り当て、バッチを永続的にアペンドし、並行してレプリケーションを行います。任意の2つのレプリカがバッチを永続化すると、commit_watermark が進み、プロデューサーに確認応答が返されます。コンシューマーはそのウォーターマークより下のオフセットのみを読み取ります。リーダー障害時、コントロールプレーンはコミット済みプレフィックスを含むレプリカのみを選択し、エポックをインクリメントします。回復した古いリーダーは、未コミットの末尾(Tail)を切り捨てて追いついてからサービスを再開します。古いエポックを持つリクエストは拒否されます。
このポリシーは1つのアベイラビリティゾーンの障害を許容します。レプリカが2つ残っている場合、両方が確認応答を返す必要があるため、レイテンシとキャパシティは低下します。その後、残りのレプリカのいずれかが失敗した場合、強耐障害性を持つトピックはレプリケーションが復旧するまで書き込みの確認応答を停止します。可用性を向上させるために古いレプリカを選出することは、データ損失ゼロの保証に違反します。ネットワーク分断(スプリットブレイン)中、コミットの過半数を持つ側のみが書き込み可能となり、反対側はフェンシングされます。
プロデューサーは、レコードがコミットされたものの確認応答レスポンスが失われた場合にリトライします。ブローカーは、プロデューサーエポックと単調増加シーケンスを使用してパーティション内で重複排除を行い、元のオフセットを返します。古いエポックを持つゾンビプロデューサーは拒否されます。これにより、パブリッシュのリトライによって引き起こされるログの重複が排除されます。ただし、異なるIDを使用した2つの個別のビジネスリクエストを結合したり、コンシューマーの外部への副作用を重複排除したりするわけではありません。
ステップ5:グループの所有権とオフセットをビジネス結果に結び付ける。
コンシューマーグループ内では、一度に1つのメンバーが1つのパーティションを所有します。グループコーディネーターは、メンバー、リース、ジェネレーション、および割り当てを管理します。タイムアウトやスケーリングイベントが発生すると新しいジェネレーションが作成され、古いメンバーからのフェッチやコミットは拒否されます。インクリメンタル(増分)リバランスにより、必要なパーティションのみを移動してグループ全体の一時停止を短縮しますが、コンシューマーは所有権が取り消される前にフェッチを停止し、完了した作業をコミットする必要があります。
デフォルトの順序は、バッチのフェッチ、べき等なビジネス書き込みの実行、次のオフセットのコミットです。ビジネスコミットの後、オフセットコミットの前にコンシューマーがクラッシュした場合、新しい所有者が完了したレコードをリプレイするため、at-least-onceの配信が成立します。安定した message_id は、ユニーク制約や処理済みメッセージレコードの裏付けとすることができ、またビジネス状態と同じデータベーストランザクション内でコミットすることもできます。出力が同じメッセージングシステムに戻る場合、出力レコードと入力オフセットはブローカーのトランザクションを共有できます。外部のデータベース、決済、またはメールプロバイダーには、依然としてべき等性、ステータス確認、またはリコンシリエーションが必要です。
オフセットは (tenant, group, topic, partition) の下の複製されたメタデータログに保存され、ジェネレーションを保持します。モニタリングには、log_end_offset - committed_offset と最も古い未処理メッセージの経過時間の両方が含まれます。メッセージサイズが可変の場合、メッセージ数だけではバックログを正確に把握できないため、システムはラグのバイト数と現在の正味の消費レートにおける追いつき時間(Catch-up time)もレポートします。
ステップ6:リトライ、デッドレター、および順序保証の間のトレードオフを明示する。
一時的なネットワークやスロットリングの障害は、ジッター付きの遅延リトライトピックに入ります。決定論的なスキーマ、権限、またはビジネスバリデーションの失敗は、やみくもにリトライするべきではありません。リトライでは、元の message_id、ソースのトピック、パーティション、オフセット、初回検出時刻、試行回数、およびエラークラスが保持されます。試行回数またはビジネスデッドラインの上限に達した後は、デッドレタートピックに移動され、アラートが発生し、制御されたリドライブが可能になります。リドライブでも元のIDが維持されるため、べき等性をバイパスすることはありません。
失敗したレコードを脇に移動すると、後続のレコードが先に完了できるようになりますが、これはキーごとの厳格な順序保証と衝突します。順序の状態を厳格に進化させる必要がある場合は、そのキーを一時停止し、その後続レコードを別の順序付きレーンにバッファリングして、修復後に失敗したオフセットから再開します。パーティション全体を一時停止する方が簡単ですが、影響範囲が広くなります。ビジネスがバージョンによる結果整合を受け入れる場合は、後続のレコードを進行させ、シンク側で古いバージョンを拒否できます。トピックのコントラクトはどちらかを選択しなければなりません。「ポイズンメッセージが絶対にブロックしない」と「メッセージが絶対に追い越さない」の両方を保証することはできません。
ステップ7:大容量メッセージをオブジェクト参照の背後に置き、ガベージコレクションの競合を解消する。
この設計では、インラインのしきい値を256 KiBに設定します。これより大きなペイロードは、有効期間の短いアップロード認証情報を使用して、サイズ、コンテンツハッシュ、および暗号化メタデータを含むイミュータブルなオブジェクトを書き込みます。アップロードが成功した後にのみ、プロデューサーは参照をパブリッシュします。コンシューマーはオブジェクトを読み取り、ハッシュを検証します。ブローカーのレプリカは小さなエンベロープのみをコピーするため、100 MBのレコードがネットワークバッファ、レプリケーションバッチ、またはコンシューマーメモリを独占することはありません。
アップロードは成功したが参照がパブリッシュされなかったオブジェクトは孤立(Orphan)となり、アップロードセッションのTTLで期限切れになります。参照がコミットされると、オブジェクトの保持期間はメッセージの保持期間、有効なリプレイ、デッドレター、および安全マージンをカバーする必要があります。削除ジョブはまず保護された参照を確認し、猶予期間の後に削除します。オブジェクトの読み取りに失敗した場合、消費は未コミットのままリトライされます。先に確認応答してしまうと、本文が永久に失われる恐れがあります。大容量ペイロードには、メッセージ数のスロットリングでは適切にコストを測れないため、テナントごとに個別のバイトレート、同時ダウンロード数、およびストレージクォータが割り当てられます。
ステップ8:キャパシティからパーティション数、ディスク、および追いつき余力を導出する。
10進数の1 KBを使用すると、定常時の論理取り込み量は次のようになります。
1,000,000 messages/s × 1,000 bytes = 1 GB/s
1 GB/s × 86,400 s = 86.4 TB/day
Lower bound for three replica writes = 86.4 × 3 = 259.2 TB/day15分間のピーク時の総取り込み量は 3 GB/s × 900 = 2.7 TB です。コンシューマーが定常の1 GB/sしか維持できない場合、余分なバックログは次のようになります。
(3 GB/s - 1 GB/s) × 900 s = 1.8 TBピーク後、新しい取り込みが1 GB/sにとどまる一方で、コンシューマーが1.5 GB/sを維持できると仮定します。正味の追いつきレートは0.5 GB/sであるため、1.8 TBをドレインするには理論上3,600秒(約1時間)かかります。レプリカのリカバリ、バッチオーバーヘッド、圧縮、インデックス、ファイルシステムの予備領域、および大容量メッセージのオブジェクトストレージによってキャパシティが追加されるため、これらは下限値となります。
パーティション数はバイト数とメッセージ数の両方によって制限されます。3つのレプリカと目標p99を用いたベンチマークにより、1つのパーティションが40 MB/sおよび毎秒40,000メッセージを維持できるとします。ピーク時の両方のディメンションにおいて、少なくとも75のパーティションが必要です。障害やリバランスのための50%のヘッドルーム(余力)を追加すると約113となり、実用的な選択肢としては128となります。このパーティションあたりの結果は面接用のベンチマークの前提条件です。ハードウェア、バッチ、確認応答、またはレコードサイズが異なれば新たなテストが必要です。128は普遍的な答えではありません。
ステップ9:バックプレッシャー、テナント分離、および検証可能な運用を実装する。
コンシューマーはロングポーリングを使用し、max_bytes と処理中(in-flight)バッチ制限によってレートを制御します。ブローカーのディスクウォーターマークが上昇すると、システムはまず新しいパーティションの作成を停止し、低優先度テナントのバースト許容量を減らし、クォータを超過したパブリッシュをリトライ可能なシグナルで拒否します。無制限のメモリキューとリトライは、輻輳をプロセス障害に変えてしまいます。プロデューサーは制限付きのバッチバッファ、デッドライン、およびジッター付きバックオフを使用して、ブローカーの障害が同期的なリトライストームを引き起こさないようにします。
テナントIDはメッセージ本文ではなく認証情報から取得します。取り込みはメッセージレートとバイトレートで制限されます。エグレスはフェッチバイト数とリクエストCPUによって公平にスケジュールされます。ストレージ、パーティション、コンシューマーグループ、接続、インフライトリクエスト、および大容量オブジェクトにも制限があります。配置にあたっては、1つのテナントのレプリカやホットパーティションが少数のブローカーに集中しないようにします。非常に大規模なテナントは専用プールに移行し、共有プールでもテナントごとの測定と拒否数のレポートを継続します。
主要なメトリクスには、パブリッシュ確認応答のp50/p95/p99、エラーおよび不明な結果、パーティションごとの取り込みバイト数、リーダーとフォロワーのラグ、commit_watermark、ディスクウォーターマーク、ホットキー、コンシューマーグループのコミット済みオフセット、コンシューマーラグ、最古の経過時間、リバランス、リトライ、デッドレター、オブジェクトの孤立と読み取り失敗、テナントごとのスロットリングと公平性が含まれます。エンドツーエンドのカナリアは、安定したIDをパブリッシュし、べき等なビジネス結果をコミットしてからオフセットをコミットし、ブローカー、グループ、およびビジネスの状態をリコンシリエーションします。
障害マトリクスには、レプリケーション前、コミット後、確認応答返却前のリーダーのクラッシュ、1つのアベイラビリティゾーンの喪失、ネットワーク分断、ディスクフル、古いリーダーの回復、ホットキー、15分間の3倍のピーク、ビジネスコミットの前後のコンシューマークラッシュ、リバランス中の古いコミット、ポイズンメッセージ、アップロード成功後のパブリッシュ失敗、オブジェクト読み取り失敗、およびデッドレターのリドライブが含まれます。受け入れ基準として、確認応答済みレコードが生存すること、未コミットの作業がスキップされないこと、キーの順序がトピックコントラクトに従うこと、古いジェネレーションがオフセットを進められないこと、そしてすべての重複、拒否、またはドロップに対応するメトリクスが存在することを検証します。
質の高い模範回答
「まず、これが複数のコンシューマーグループと24時間のリプレイを必要とする保持型ログサービスであることを確認します。トピックはパーティションに分割され、同じビジネスキーは同一のパーティションにとどまります。したがって、順序保証はキーとパーティションを対象とし、異なるパーティションは並行して実行されます。プロデューサーはコントロールプレーンからリーダーのメタデータを取得し、バッチを直接書き込みます。各パーティションはアベイラビリティゾーンにまたがって3つのレプリカを持ち、2つの永続化されたレプリカのみが commit_watermark を進めてプロデューサーに確認応答(Ack)を返します。新しいリーダーはコミット済みプレフィックスを含んでいる必要があり、エポックによって古いリーダーやプロデューサーをフェンシングします。
ストレージはアペンドオンリーセグメントとスパースオフセットインデックスを使用します。グループコンシューマーはパーティションを排他的に所有し、ロングポーリングを行います。コンシューマーはべき等なビジネス結果をコミットしてから、次のオフセットをコミットします。クラッシュによって処理がリプレイされることはあっても、黙ってスキップされることはありません。プロデューサー側では、プロデューサーエポックとシーケンスによって、確認応答のロストによるリトライを排除します。コンシューマー側では、安定したメッセージID、ユニーク制約、またはバージョン条件によって重複した処理を吸収します。エンドツーエンドのブローカートランザクションを主張できるのは、入力オフセットと出力の両方がそのブローカートランザクション内にある場合のみです。外部システムには依然としてべき等性またはリコンシリエーションが必要です。
定常容量は論理データで1 GB/s、1日あたり86.4 TBであり、3レプリカの書き込みでは下限値が259.2 TBとなります。コンシューマーのキャパシティが定常状態のままである場合、15分間の3倍のピークにより1.8 TBの追加バックログが発生します。ピーク後の正味の追いつきレートが0.5 GB/sであれば、理論上ドレインには約1時間かかります。パーティション数はメッセージレートとバイトレートの計算の大きい方を使用し、障害時のヘッドルームを追加した上で、実際のハードウェアでキャリブレーションします。
256 KiBを超えるペイロード本文は、まずオブジェクトストレージに入ります。キューはイミュータブルな参照、サイズ、およびチェックサムを保持します。アップロードセッションのTTLにより孤立したオブジェクトが削除され、コミットされた参照はそのオブジェクトを保持期間、リプレイ、およびデッドレターウィンドウの間保護します。リトライでは元のメッセージIDが保持されます。厳格に順序保証されたトピックでは、ポイズンメッセージに対してキーまたはパーティションを一時停止します。直ちにデッドレター化すると後続のメッセージが追い越してしまうためです。
最後に、テナントごとに取り込み、エグレス、ストレージ、パーティション、および大容量オブジェクトを制限し、ホットなテナントは専用プールに分離し、確認応答レイテンシ、不明なパブリッシュ、レプリカラグ、ディスクウォーターマーク、コンシューマーの最古経過時間、ホットキー、デッドレターを監視します。障害テストは、確認応答のロスト、コミット前後のリーダークラッシュ、ゾーン障害、古いオフセットコミット、ビジネス書き込み後のコンシューマークラッシュ、オブジェクトストレージ障害を対象とします。各テストは特定の確認応答境界をチェックします。」
よくある間違い
- 間違い:Producer、Kafka、Consumerだけを描く → 破綻理由:コンポーネント名だけでは確認応答、オフセット、順序、障害の境界が定義されない → 修正策:配信コントラクトと4つの不変条件を述べ、すべてのコンポーネントをそのいずれかにマッピングする。
- 間違い:水平スケーリングしながらグローバルな順序を約束する → 破綻理由:グローバルな順序には単一のシリアルな決定ポイントが必要だが、パーティションの並列化はその順序を失わせる → 修正策:順序のスコープをビジネスキーとパーティションに限定し、ホットキーの限界を明記する。
- 間違い:リーダーのローカルディスク書き込み後に確認応答を返す → 破綻理由:リーダーのアベイラビリティゾーンが失われると、唯一の永続コピーが失われる可能性がある → 修正策:クロスゾーンのコミット過半数の後に確認応答を返し、コミット済みプレフィックスを持つレプリカのみを選出する。
- 間違い:フェッチ直後にオフセットをコミットする → 破綻理由:そのコミットの後にクラッシュすると、ビジネス結果が永久にスキップされる → 修正策:べき等なビジネス結果を先にコミットし、その後に次のオフセットをコミットして、制御されたリプレイを受け入れる。
- 間違い:ブローカーのexactly-onceと外部の副作用のexactly-onceを同等とみなす → 破綻理由:外部システムはブローカーのトランザクションに参加しないため、確認応答のロストによって依然として重複ウィンドウが残る → 修正策:ビジネスべき等性キー、ユニーク制約、バージョン条件、またはリコンシリエーションを使用する。
- 間違い:失敗したすべてのメッセージを即座にデッドレター化する → 破綻理由:同じキーの後続レコードがそれを追い越してしまい、状態の順序が崩れる → 修正策:トピックコントラクトでキーの一時停止、パーティションの一時停止、またはバージョンベースの結果整合のいずれかを選択できるようにする。
- 間違い:100 MBのペイロードをブローカーログに直接書き込む → 破綻理由:少数のレコードがレプリケーション、バッファ、フェッチバッチを独占する → 修正策:本文をオブジェクトストレージに保存し、その参照、サイズ、チェックサムをログに記録する。
- 間違い:メッセージ数だけでクォータとキャパシティを計画する → 破綻理由:1 KBのレコードと100 MBのレコードではネットワーク、ディスク、メモリのコストが根本的に異なる → 修正策:件数、バイト数、インフライトバッチ、オブジェクトの同時実行数を計測する。
- 間違い:すべてのラグを解消するために単にコンシューマーを追加する → 破綻理由:1つのグループメンバーが一度に1つのパーティションを所有し、ホットキーはシリアルなパーティションパスによって制限されたままである → 修正策:パーティションを追加したり、ビジネスキーを分割したり、スロットリングを行ったりする前に、パーティションとキーの分散を調査する。
- 間違い:ブローカーの稼働時間(Uptime)のみを監視する → 破綻理由:クラスタが稼働していても、遅延するレプリカ、枯渇したディスク、古いオフセット、増加するデッドレターが存在する可能性がある → 修正策:セグメント化されたレイテンシ、コミット済みプレフィックス、最古メッセージの経過時間、追いつき時間、およびエンドツーエンドのカナリアを監視する。
フォローアップの質問と回答
フォローアップ1:パブリッシュのp99を50ミリ秒未満に維持しながら、クロスリージョンでのデータ損失ゼロをどのように実現しますか?
同期的なクロスリージョン確認応答は、パブリッシュパスに広域ネットワーク(WAN)のラウンドトリップ時間を追加します。50ミリ秒が可能かどうかは、リージョン間の距離とテイルネットワークレイテンシに依存します。ビジネスとして、RPOゼロとローカルレイテンシのどちらを優先するかを決定する必要があります。RPOゼロが優先される場合、書き込みはリモートのコミット過半数を待つ必要があり、レイテンシSLOを再設定しなければなりません。レイテンシが優先される場合、レプリケーションはリージョン内では同期的、リージョン間では非同期的となり、複製されていない末尾(Tail)に明示的なリスクが残ります。アクティブ-アクティブ構成には各キーの単一所有者または競合解決ルールも必要です。トピックまたはキー範囲ごとに単一のプライマリリージョンを設ける方が、通常は順序をより明確に維持できます。
フォローアップ2:あるテナントが単一のビジネスキーで毎秒20万メッセージを送信しています。なぜ128個のパーティションがあっても役に立たないのですか?
順序を維持するために同じキーは1つのパーティションにとどまらなければならないため、ベンチマークされた単一パーティションのレート(約40,000メッセージ/秒)によって依然として制限されます。選択肢としては、シリアルパスを最適化するか、そのテナントをスロットリングするか、あるいは互いに影響しないサブエンティティキーなど独立した順序ドメインを再定義することです。ビジネスがそのキーに対する完全な順序を要求する場合、サービス側はシリアルキャパシティを超える保証を拒否する必要があります。キーをランダムに分散させることは、キャパシティの障害を順序保証の障害に置き換えているにすぎません。
フォローアップ3:コンシューマーが決済を請求した直後、オフセットをコミットする前にクラッシュしました。2回目の請求をどのように防ぎますか?
決済のべき等性キーとして message_id またはビジネスオペレーションIDを使用します。決済プロバイダーがべき等APIをサポートしている場合、リプレイ時に同じキーを使用して元の結果を照会します。ローカルデータベースのみを制御できる場合は、1つのトランザクション内でビジネス状態と処理済みメッセージのユニークレコードをコミットし、外部ステップにはアウトボックスパターンを使用します。外部システムにべき等性もステータス確認もない場合は、UNKNOWN状態を記録してリコンシリエーションを行い、手動で補償トランザクションを実行します。オフセットを早くコミットすることは、損失を受け入れることで不確実性をごまかしているだけです。
フォローアップ4:ポイズンメッセージを安全にリドライブするにはどうすればよいですか?
まずコンシューマーまたはデータを修正し、リドライブのスコープを固定し、元のメッセージID、ソースオフセット、初回検出時刻、および試行履歴を保持します。シャドウ消費(Shadow consumption)で新しいバージョンを検証し、シンク側のべき等性をアクティブに保ったまま、テナントごとおよびパーティションごとのレートでリプレイします。厳格に順序保証されたトピックでは、そのキーの後続レコードも一時停止し、失敗したオフセットから順番に再開します。コントラクトで順序の変更が許容されている場合、シンク側は古いビジネスバージョンを拒否します。リドライブによって新しいIDを発行して重複排除をバイパスしたり、通常のトラフィックピーク時にメイントピックをあふれさせたりしてはなりません。
フォローアップ5:保持期間が24時間から30日に増えた場合、最初に何が変わりますか?
定常状態において、30日は論理データで約 86.4 × 30 = 2.592 PB になります。3つの完全なローカルレプリカを保持すると下限が約7.776 PBとなり、コストとリカバリ時間が支配的な要因になります。アクティブなセグメントと最近のセグメントをブローカー上に保持し、クローズされ検証されたセグメントをオブジェクトストレージにアップロードします。メタデータはオブジェクトの場所とチェックサムを記録し、過去のフェッチはキャッシュまたは読み取りプロキシを使用します。ローカルセグメントがリモートオブジェクトから読み取り可能になる前に削除されることがないよう、削除、リプレイ、コンパクション、およびオブジェクトのライフサイクルは単一の保持ステートマシンを共有しなければなりません。