代表的な面接トピック

フロントエンド面接:WebSocketStreamのバックプレッシャーとフォールバックをどのように評価するか?

フロントエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

共同編集ページが高頻度のイベントを受信し、編集内容をサーバーに送信します。WebSocketStreamが適しているかを評価し、Streamsのバックプレッシャー、close、cancellationがメッセージの蓄積をどのように防ぐかを説明し、非対応ブラウザ向けのフォールバックを設計してください。

プロンプトと範囲

共同編集ページが高頻度のイベントを受信し、編集内容をサーバーに送信します。WebSocketStreamが適しているかを評価し、Streamsのバックプレッシャー、close、cancellationがメッセージの蓄積をどのように防ぐかを説明し、非対応ブラウザ向けのフォールバックを設計してください。

WebSocketStreamは実験的で非標準のWeb APIです。接続をReadableStreamおよびWritableStreamオブジェクトとして公開し、Streamsのバックプレッシャーによって読み取りと書き込みを制御できるようにします。MDNでは、本番環境で使用する前にブラウザの互換性を確認することを明示的に推奨しています。この面接では、ストリームプロトコル設計とリスク判断について問われます。

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

ReadableStreamとWritableStreamのライフサイクル、バックプレッシャーが無制限のキューを防止する仕組み、readerとwriterのロック、abortとcloseの違い、メッセージの順序付けと冪等性、ハートビートと再接続、Workerの利用、機能検出(capability detection)、およびフォールバックをカバーします。

30秒での回答

「まずWebSocketStreamが広く標準化された機能ではないことを確認し、機能検出を行った後でのみ有効化します。readableとwritableを個別に管理し、無制限の配列をバッファリングする代わりにバックプレッシャーを伝播させ、close、キャンセル、ネットワークエラーを観測可能な状態にします。AbortSignalを用いてコンポーネントのライフタイムを非同期処理に引き継ぎます。本番環境では、同一のメッセージプロトコル、有界キュー、再接続バックオフ、重複抑制を備えたクラシックなWebSocketアダプターを維持します。」

ステップバイステップの解決策

ステップ 1: 機能と境界の確認

有効化する前に、ランタイムがWebSocketStreamを公開しているか確認します。これは実験的かつ非標準であるため、特定のChromeビルドでのサポートがすべてのブラウザ、WebView、またはエンタープライズ環境でのサポートを意味するわけではありません。機能チェックが失敗した場合は、直接安定した実装を選択します。

ステップ 2: 接続とストリームのオープン

WebSocketStreamを構築し、openedプロミスをawaitしてreadable、writable、プロトコル、拡張機能の情報を取得します。readerを通じてreadable側を消費し、writerを通じて書き込みを行います。接続の終了はclosedプロミスを通じて報告します。

js
const socket = new WebSocketStream(url);
const { readable, writable } = await socket.opened;
const reader = readable.getReader();
const writer = writable.getWriter();

ステップ 3: バックプレッシャーの活用

すべてのイベントを通常の配列にpushしないでください。下流のTransformStreamまたはWritableStreamのhighWaterMarkでキューを制限し、送信前にwriter.readyをawaitし、コンシューマーのペースに合わせてreadを呼び出します。バックプレッシャーはクライアントストリームを調整しますが、サーバーには依然としてプロトコルレベルのレート制限が必要です。

ステップ 4: メッセージ順序の定義

編集内容にシーケンス番号、ドキュメントバージョン、冪等性キーを付与します。再接続後、古いストリームの最後のメッセージが永続化されたと仮定してはなりません。新しいイベントを適用する前に、サーバーからギャップデータまたはバージョン付きスナップショットを要求し、レンダリング層とトランスポート層の両方で重複を認識できるようにします。

ステップ 5: キャンセルとcloseの処理

ユーザーがドキュメントを離れるか切り替えた場合、読み取りと書き込みを停止し、関連するストリームをcloseまたはcancelします。AbortSignalはコンポーネントのライフタイムを非同期処理に接続します。通常のclose、能動的なキャンセル、プロトコルエラー、ネットワーク切断を別々の状態として区別し、再接続ロジックが実際のネットワーク障害にのみ適用されるようにします。

ステップ 6: メモリとUIの保護

パース、バリデーション、レンダリングを分離し、必要に応じてデコードやバッチ処理をWorkerに移動します。最大メッセージサイズ、保留イベント数、バッチ処理時間を設定します。しきい値を超えた場合は、無制限に蓄積するのではなく、購読を一時停止するか、再構築可能な中間イベントをマージ/破棄するか、サーバースナップショットを要求します。

ステップ 7: 再接続とリカバリの設計

ジッター付きの指数バックオフを使用し、最後に確認されたドキュメントバージョンを含めます。サーバーが不足しているイベントまたはスナップショットを返した後、ライブストリームを再開します。クライアント側で生成された冪等性IDにより、切断された書き込みが二重に送信されるのを防ぎます。

ステップ 8: フォールバックとオブザーバビリティの実装

同一のメッセージエンベロープとステートマシンを備えたクラシックなWebSocketをデフォルトのフォールバックとして使用します。機能判定結果、openedおよびclosedのレイテンシ、キュー深度、バックプレッシャー待機時間、再接続回数、破棄されたイベント数、スナップショットリカバリ率を記録します。実験を拡大する前に、ブラウザ、ネットワークタイプ、ページバージョンごとにこれらを分析します。

トレードオフと境界

WebSocketStreamかWebSocketか

WebSocketStreamはストリームのバックプレッシャーとPromiseベースのライフサイクルシグナルを提供しますが、サポートと標準化が限定的です。クラシックなWebSocketは広く利用可能ですが、アプリケーション側でonmessageの処理と送信キューを制限する必要があります。1つのプロトコル抽象化で両方のトランスポートをサポートできます。

イベント破棄かスナップショット要求か

カーソル移動などの再構築可能なイベントは、キューが一杯のときにマージまたは破棄できます。ドキュメントの編集は暗黙的に破棄できません。消費を一時停止し、バージョン付きスナップショットを要求します。この選択は、可換性、順序性、サーバーのリプレイサポートに依存します。

メインスレッドかWorkerか

小さなメッセージと低頻度のレンダリングはメインスレッドに適しています。高頻度のバイナリデコード、圧縮、バッチマージはWorkerに移動できます。Workerを使用しても全体的なメモリ制限が解除されるわけではないため、トランスポートキューとキャンセルには依然として明示的な制限が必要です。

障害訓練と発展

ブラウザがAPIをサポートしていない

機能分岐をオフにし、クラシックなWebSocketが接続し、同一のメッセージを消費し、互換性メトリクスを報告することを確認します。

コンシューマーの処理速度が低下する

レンダリングを意図的に遅延させ、バックプレッシャーの待機時間が増加し、キューが制限内に維持され、クラッシュする代わりにしきい値でスナップショットリカバリパスがトリガーされることを確認します。

書き込みの途中で接続が切断される

編集内容を送信した直後に切断します。冪等性ID、最後に確認されたバージョン、再接続バックオフによって、編集が二重に適用されることなく状態が復元されることを確認します。

よくある間違いとフォローアップ

間違い 1: Streamsがサーバーのスロットリングを解決すると想定する

フォローアップ: バックプレッシャーは何を制御しますか? クライアントストリーム内の生成と消費を制御します。サーバーのクォータ、接続制限、またはビジネスロジックのバッチ処理を置き換えるものではありません。

間違い 2: close、cancel、abortを同一のものとして扱う

フォローアップ: コンポーネントのアンマウント時に何が起きますか? 通常のclose、読み取りのcancel、非同期のabortを個別に記録し、再接続ロジックが実際のネットワーク障害を対象とするようにします。

間違い 3: WebSocketStreamのみを提供・リリースする

フォローアップ: WebViewや古いブラウザではどうなりますか? 機能チェックに失敗するとクラシックなWebSocketアダプターが選択され、メッセージプロトコルとステートマシンはそのまま維持されます。

さらに深いフォローアップと模範解答

なぜwriter.readyをawaitするのか?

送信者がバックプレッシャーの下で無制限のプロミスや配列を作成する代わりに、WritableStreamの容量を待つことができるようにするためです。サーバーのレート制限やメッセージサイズ制限は依然として必要です。

クライアントはいつスナップショットを要求すべきか?

イベントキューがその制限を超えた場合、シーケンスの欠落が発生した場合、またはクライアントが連続したバージョンを確認できない場合、イベントの順序を推測するよりもバージョン付きスナップショットを使用する方が安全です。

実験的なAPIをどのように展開(ロールアウト)するか?

機能、ブラウザのバージョン、ページのバージョンごとにバケット化し、少割合で有効化します。キュー深度、切断からのリカバリ、エラー率、レンダリングレイテンシを比較し、メトリクスが悪化した場合はフラグを無効にしてクラシックなWebSocketに戻します。

公開情報ソース

関連する質問