代表的な面接トピック

Kubernetes面接:大規模なLISTレスポンスにおけるストリーミングエンコーディングをどう設計するか?

システム設計難しい
Offer.cc 編集チーム公開日 更新日

質問

レスポンス形式、ページネーションの一貫性、および診断可能な障害処理を維持しながら、Kubernetes APIサーバーが数万個のオブジェクトに対するLISTレスポンスをストリーミングエンコードするにはどうすればよいですか?

問題とスコープ

あるクラスターには数万個のPodとカスタムリソースが存在し、コントローラーは再起動後に完全なLISTリクエストを発行します。従来のエンコーダーはitems配列全体を単一の連続バッファにシリアライズするため、APIサーバーのメモリ急増(スパイク)を引き起こします。ストリーミングエンコーディングを設計し、そのJSONおよびKubernetes Protobufのスコープ、limit/continueページネーションやgzipとの関係、クライアントのリカバリ、バックプレッシャー、可観測性、およびロールバック条件を説明してください。

面接官がテストしていること

  • 問題をネットワーク帯域幅に矮小化せず、エンコーディングバッファにおけるピークを特定できているか。
  • ストリーミングエンコーディング、ページネーション、圧縮、およびwatchのセマンティクスを明確に分離できているか。
  • 1つのLISTレスポンスに対するJSON/Protobufの構造、リソースバージョン、およびエラーセマンティクスを維持できているか。
  • 低速クライアント、切断、CPU増加、プロキシのバッファリング、および古いクライアントとの互換性を処理できるか。
  • 再現可能なベンチマーク、メトリクス、カナリアリリース、およびロールバックゲートを定義できるか。

明確化のための質問

  1. リクエストは名前空間スコープですか、それともクラスター全体ですか?オブジェクトのサイズ、数、および同時実行されるLISTの数がメモリ予算を決定します。
  2. クライアントは1つの完全なレスポンスを受け取る必要がありますか、それともlimit/continueを使用できますか?ページネーションは結果のサイズを制限しますが、サーバー側のエンコーディング最適化に取って代わるものではありません。
  3. 経路上にHTTP/2、gzip、またはリバースプロキシが存在しますか?プロキシによるバッファリングは、エンコード中にメモリを解放することによるエンドツーエンドのメリットを変化させます。
  4. JSON、Kubernetes Protobuf、またはその両方が必要ですか?エンコーダーの対象範囲によってロールアウトの順序が変わります。

30秒の回答フレームワーク

私ならまず、メモリスパイクの原因をitems配列全体のシリアライズにあると特定し、エンコーダーをインクリメンタルに書き込むように変更します。つまり、コレクションのプレフィックスを出力し、各アイテムをエンコードして書き込み、サフィックスを出力します。JSONとKubernetes Protobufはリソースセマンティクスを維持し、ページネーションは結果サイズを制御し、圧縮は帯域幅を制御します。制限付きバッファと書き込みバックプレッシャーで低速クライアントを処理します。RSSピーク、エンコーディングCPU、最初のバイトまでの時間(TTFB)、完了レイテンシ、および切断数を測定し、まずJSONをカナリアリリースし、その後Protobuf、プロキシ、および古いクライアントを検証してからロールアウトを拡大します。ゲートを超過した場合はロールバックをトリガーします。

ディープダイブ回答

1. メモリピークの分解

従来のパスでは、オブジェクトリスト、エンコーディングの中間状態、および完全なレスポンスバッファを同時に保持する可能性があります。オブジェクト数が増えると、レスポンスのバイト数はitemsペイロードの合計とともに増加し、同時実行されるLISTによってそれぞれのピークが加算されます。ストリーミングが削減を保証するのは、エンコーディング段階の一時メモリのみです。読み取り、キャッシュ、ソート、または認可フィルタリングに必要なメモリが削除されるわけではありません。エンコーダーを変更したりレート制限を追加したりする前に、ヒーププロファイルと同時実行負荷を用いてボトルネックを確認してください。

2. アイテム単位のエンコーダーの設計

固定のコレクションフィールドを最初に書き込みます。itemsの開始部分の後、1つのオブジェクトをシリアライズして書き込み、次のオブジェクトを処理する前にその一時バッファを解放します。無制限のバイト文字列にアイテムを追加してはいけません。Kubernetesの実装はJSONおよびProtobufコレクションエンコーダーを対象とし、APIオブジェクトの形状を変更することなく、大容量のitemsフィールドに焦点を当てています。

text
write(listPrefix)
for item in items:
    encoded = encodeOne(item)
    writeWithBackpressure(encoded)
    release(encoded)
write(listSuffix)

ストリーミングはメモリのライフタイムを変更するものであり、オブジェクトの順序、メタデータ、リソースバージョン、またはエラー定義を変更するものではありません。レスポンスの途中でクライアントが切断された場合は、エンコーディングを停止して現在のアイテムを解放します。不完全なボディは成功したLISTとはみなされません。

3. ページネーション、圧縮、watchの分離

limit/continueは1つのコレクションを一貫したページに分割し、1リクエストのサイズを削減してクライアントが段階的に処理できるようにします。これらはトークンの有効期限切れ、再開、およびクライアントループを導入します。ストリーミングはサーバー側のエンコーディングピークを制御するため、1ページが大きい場合でも依然として重要です。gzipはネットワークバイト数を削減しますが、コンプレッサーがバッファリングを追加する可能性があるため、そのフラッシュポリシーを測定してください。watchは異なるリカバリセマンティクスを持つ継続的なイベントストリームであり、一貫した1つのLISTに代わることはできません。

4. バックプレッシャーとプロキシの処理

ソケットが低速な場合、エンコーダーはブロックされた書き込みとキャンセルを尊重し、接続ごとのバッファリングを制限する必要があります。そうしないと、「ストリーミング」エンコーダーであってもプロキシやゲートウェイによって再度組み立てられてしまう可能性があります。最初のバイトと最後のバイトの時間を記録し、エンコーディングの待機、ネットワークバックプレッシャー、プロキシのバッファリング、および低速なクライアント読み取りを区別します。HTTP/2のフレーム分割は、アプリケーションが完全なレスポンスバッファを解放したことを証明するものではありません。サーバーのヒープと書き込みパスを検証してください。

5. 互換性、カナリアリリース、およびロールバック

continue、リソースバージョン、エラーを含め、JSON/Protobufのワイヤー構造とコンテンツネゴシエーションを変更せずに維持します。最初はオブジェクトが多く同時実行数が少ないリソースタイプに対して機能を有効にし、RSSピーク、CPU、最初のバイトのレイテンシ、完了レイテンシ、切断数、およびAPIエラー率を比較してから拡大します。古いプロキシがチャンク化されたレスポンスや圧縮されたレスポンスを受け入れられない場合は、フィーチャーゲートを維持するか、クライアントの能力に応じて従来のエンコーダーを選択します。平均スループットだけでなく、メモリピーク、P99レイテンシ、またはエラー率の悪化に基づいてロールバックします。

質の高い模範解答

私なら問題をAPIサーバーのエンコーディングピークに限定して捉えます。同時LIST負荷でそれを再現した上で、エンコーダーがコレクションのプレフィックスを出力し、各アイテムをシリアライズして書き込み、その一時バッファを解放して、サフィックスを出力するようにします。これによりエンコーディング段階の一時メモリが削減されますが、オブジェクトの読み取り、ソート、または認可のコストがなくなるわけではありません。JSONとKubernetes Protobufはそのワイヤー形状を維持し、limit/continueはページネーションの役割を維持し、gzipは帯域幅制御の役割を維持し、watchは異なるイベント契約のままとなります。書き込みはソケットのバックプレッシャーとキャンセルに従い、プロキシのバッファリングは個別に測定されます。まずJSONをカナリアリリースし、次にProtobufと主要なプロキシを検証し、RSSピーク、エンコーディングCPU、最初のバイトまでの時間、完了レイテンシ、および切断数を比較します。悪化がベースラインを超えた場合、ゲートによって機能が無効化されるか、従来のエンコーダーが選択されます。テストには、低速クライアント、切断、空のリスト、大きなアイテム、期限切れのcontinueトークン、および古いクライアントが含まれます。

よくある間違い

  • ページネーションのみを追加する → 1ページが依然として巨大で完全にバッファリングされる可能性がある → ページネーションとアイテムのストリーミングを合わせて検証する。
  • gzipをメモリの解決策として扱う → 圧縮によってバッファが保持される可能性がある → エンコーディング、圧縮、およびソケットの各段階を個別に測定する。
  • HTTP/2のフレーム分割をサーバーストリーミングとして扱う → アプリケーションが依然としてフルボディを構築している可能性がある → APIサーバーのヒープと書き込みパスを検査する。
  • 低速クライアントに対する無制限のバッファリングを許容する → 同時実行される低速接続がメモリを増幅させる → 接続数、キャンセル、およびバックプレッシャーを制限する。
  • LISTの形状やリソースバージョンを変更する → クライアントと一貫性のセマンティクスが壊れる → エンコーディングのライフタイムのみを置き換え、ワイヤーのレグレッションテストを実行する。

フォローアップの質問と回答

3,000個目のオブジェクトの後にクライアントが切断された場合はどうなりますか?

エンコーディングコンテキストをキャンセルし、以降のオブジェクトの読み取りを停止し、現在のバッファを解放して、その理由を記録します。クライアントは不完全なボディを成功したLISTとして扱うことはできません。完全なセットが必要な場合は、元のページネーション境界から再開します。

ページネーションによって1ページが500オブジェクトに制限されている場合、なぜストリーミングも行うのですか?

500個のオブジェクトであっても、特にカスタムリソースの場合は依然としてサイズが大きくなる可能性があります。ページネーションは結果セットを制限し、ストリーミングはエンコード中のサーバーの一時メモリを制限します。これらは異なる段階に対処するものであり、組み合わせることができます。

プロキシが転送前にレスポンスを完全にバッファリングしてしまう場合、この設計は失敗しますか?

サーバーは依然として自身のエンコーディングピークを削減できますが、クライアント側の最初のバイトまでの時間やエンドツーエンドのメモリ解放のメリットは失われます。プロキシのバッファリングをデプロイの前提条件として扱い、ルートごとに最初のバイトまでの時間を記録し、プロキシがストリーミングできない場合はカナリアリリースを無効化するか範囲を狭めてください。

公開情報ソース

関連する質問

関連面接ツール

システム設計の回答には「回答する」を使用

まず要件を明確にし、スケール、アーキテクチャ、コンポーネント選定、トレードオフの順に進めます。

ツールを見る