導入:ストリーム処理における「溢れ」を防ぐ
Web APIからのデータ取得やファイルの読み込みなどで使用する「Streams API」を扱う際、データが生成される速度に対して処理が追いつかないという問題が発生することがあります。これを放置すると、メモリ消費が急激に増大し、ブラウザのクラッシュやパフォーマンス低下を招きます。今回解説するCountQueuingStrategyは、キューに蓄積されるデータの「数」を基準にバックプレッシャー(負荷制御)をかけるための重要な仕組みです。
基礎知識:CountQueuingStrategyとは?
Streams APIにおいて、データを一時的に保持する領域を「キュー」と呼びます。CountQueuingStrategyは、このキューの中に「何個のチャンク(データの塊)が存在するか」を監視し、あらかじめ設定した上限値(highWaterMark)を超えそうになった時に、データの読み込み側(Source)へ「これ以上送らないでください」という信号を送るための戦略オブジェクトです。
実装:どのように制御するか
実装は非常にシンプルです。ReadableStreamを作成する際、戦略オブジェクトとしてインスタンスを渡すだけです。
1. highWaterMark: キューに溜めておけるチャンクの最大数を指定します。
2. size関数: 各チャンクをどの程度の重さとみなすかを定義します(CountQueuingStrategyの場合はデフォルトで1とみなされます)。
この設定により、例えばネットワークが不安定で処理が滞った際、自動的にストリームの読み込みを一時停止させることが可能になります。
サンプルプログラム:安全なストリーム構築
以下は、1秒ごとにデータを生成するストリームに対して、キューの最大数を「3」に制限する例です。
// 3つ以上のデータが溜まると自動的に一時停止する戦略を作成
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 3 });
const stream = new ReadableStream({
start(controller) {
let count = 0;
// データを生成するタイマー
const interval = setInterval(() => {
count++;
controller.enqueue(`データ ${count}`); // データをキューに追加
console.log(`生成: データ ${count} (現在のキューサイズ: ${controller.desiredSize})`);
// 10個生成したら終了
if (count >= 10) {
clearInterval(interval);
controller.close();
}
}, 1000);
}
}, queuingStrategy);
// ストリームを消費する(処理をわざと遅くする)
const reader = stream.getReader();
async function process() {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 処理に2秒かける(生成より遅いため、キューが溜まっていく)
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`処理完了: ${value}`);
}
}
process();
応用・注意点:現場での使い分け
CountQueuingStrategyは「個数」で管理するため、すべてのデータが均一なサイズである場合に適しています。もし、データごとにサイズが大きく異なる場合(例:テキストの長さがバラバラな場合)は、ByteLengthQueuingStrategyを使用するほうがメモリ管理としては正確です。
また、highWaterMarkを小さく設定しすぎると、パフォーマンスの低下(頻繁な停止・再開によるオーバーヘッド)を招くことがあります。現場では、実際の処理速度とメモリ許容量を計測しながら、最適な値をチューニングすることが重要です。この戦略を適切に設定することで、大規模なデータ処理でも安定したWebアプリケーションを実現できます。