1. 導入:なぜByteLengthQueuingStrategyが必要なのか
フロントエンド開発において、Fetch APIやReadableStreamを使用して巨大なデータを扱う際、メモリを浪費せずに効率的にデータを処理することは極めて重要です。デフォルトのストリーム設定では、チャンク(データの塊)の個数のみでバックプレッシャー(読み込み速度の制御)を判断してしまいます。しかし、サイズが不均一なデータを扱う場合、これではメモリ不足やパフォーマンス低下を招くことがあります。ByteLengthQueuingStrategyは、データの「バイトサイズ」に基づいてストリームのバッファを制御することで、メモリ使用量を正確に管理し、アプリケーションの安定性を高めるための重要なツールです。
2. 基礎知識:ストリームとバックプレッシャー
Streams APIにおいて「バックプレッシャー」とは、データの供給側(Source)に対して、消費側(Sink)が「これ以上送らないでほしい」と信号を送る仕組みです。
通常、ストリームには「高水位標(highWaterMark)」という閾値が設定されています。この値を超えると、ストリームは一時的に停止します。ByteLengthQueuingStrategyを使用すると、この高水位標を「要素数」ではなく「合計バイト数」として解釈させることが可能になります。これにより、例えば数KBのチャンクも数MBのチャンクも、バイトサイズに基づいて適切にフロー制御できるようになります。
3. 実装/解決策:メモリ制限を考慮したストリーム構築
ByteLengthQueuingStrategyを実装するには、コンストラクタにhighWaterMarkを指定するだけです。この値が「何バイトまでならバッファに溜めても良いか」の境界線となります。この戦略をReadableStreamのコンストラクタに渡すことで、メモリ消費量を意識したストリーム処理が完成します。
4. サンプルプログラム:バイトサイズによる制御の実装
以下は、1MB(1,048,576バイト)を上限としたストリーム制御の例です。
// 1MBを上限とする戦略を定義
const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 1024 1024 });
// ReadableStreamの作成
const stream = new ReadableStream({
pull(controller) {
// 実際にはFetch APIやファイル読み込みなどを想定
const data = new Uint8Array(500 1024); // 500KBのデータを生成
controller.enqueue(data);
// バッファがhighWaterMarkを超えると、バックプレッシャーが働き
// このpullメソッドは一時的に呼び出されなくなります
}
}, strategy);
// ストリームの消費(リーダーの生成)
const reader = stream.getReader();
async function processStream() {
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(`受信したデータサイズ: ${value.byteLength} bytes`);
}
}
processStream();
5. 応用・注意点:現場での運用におけるポイント
実務でこの戦略を利用する際は、以下の点に注意してください。
・highWaterMarkの設定値の選定
値を大きくしすぎるとメモリを圧迫し、小さすぎると頻繁にストリームが停止して処理効率が落ちます。ターゲットとするユーザーのデバイススペック(特にモバイル端末)を考慮し、数MB単位で調整するのが一般的です。
・チャンクの型
ByteLengthQueuingStrategyは、基本的に「サイズ(byteLength)」を持つオブジェクト(ArrayBufferやTypedArrayなど)に対して機能します。文字列などを流す場合は、正しくエンコードされているか確認が必要です。
・他の戦略との比較
もし「バイトサイズ」ではなく「処理する要素の個数」で管理したい場合は、CountQueuingStrategyを使用してください。用途に合わせて使い分けることが、メモリ効率の良いフロントエンド設計の第一歩です。