Web Streams APIのbackpressureでproducerを調整する
desiredSizeとwriter.readyがconsumerの需要を上流へ伝える仕組みと、queue戦略やTransformStreamの注意点を解説します。
発行・訂正窓口:infostoa · 編集基準
この記事の個別の執筆者・専門査読者の情報は提供されていません。
Web Streams APIのbackpressureは、速いproducerが遅いconsumerを追い越し続けないよう需要を伝える信号です。データを捨てたり処理を速くしたりする機能ではなく、queueが増える前に上流の生成を待たせます。responseやfileを一括でメモリへ集めずchunkで処理する場合に重要です。
desiredSizeはqueueの余裕を表す
WritableStreamのwriter.desiredSizeは、strategyのhighWaterMarkから現在queueにあるsizeを引いた値です。正数は設定値より下、0以下は次のchunkを作り続けず待つ合図になります。これは需要の目安であり、process全体のmemory上限やconsumerがchunkの処理を完了した証明ではありません。queueのsizeはstrategyによって意味が変わるため、chunk数だけでは実際のbyte量を判断できません。
producer loopで書き込みを待つ
producer loopを直接書くなら、writer.readyが解決するまで待って次の作業を準備し、順番に生成する必要がある場合はwriter.write(chunk)もawaitします。これらを無視して非同期taskを無制限に開始すると、stream外に処理が積み上がります。read、transform、writeの同時数も制限してください。backpressureを受けても、アプリがすでに作成した大きなbufferが自動で解放されるわけではありません。
writer.readyが解決するのは、queue strategy上で次のwriteを受け入れる余裕が戻ったという意味であり、下流の業務処理が完了した合図ではありません。writer.write()のPromiseもstreamのwritable側でchunkが処理されたことを示すもので、database commitやremote serviceの完了までは保証しません。その保証が必要ならアプリ独自のacknowledgementを待ちます。同じwriterを共有するproducerが複数ある場合、各loopが個別に待っていても合計の同時生成数が意図した上限を超えないか確認してください。
pipeとTransformStreamが需要を上流へ伝える
ReadableStream.pipeTo(WritableStream)はpipe内の需要をつなぎ、errorやcancelの流れも扱います。途中にTransformStreamがあれば、readable側の消費が遅いときwritable側の入力も抑制できます。長いchainでは各段階を確認しましょう。独自queueや切り離されたtaskが入力を受け続けると、下流streamが正しくbackpressureを適用していても増大します。
highWaterMarkはmemory全体の上限ではない
既定のCountQueuingStrategyはchunk数を数えるため、1KBと20MBのchunkが同じ1個として扱われる場合があります。通常のReadableStreamでは、独自のsize関数でchunkをbyte数や用途に合った単位で数えられます。一方、underlying sourceにtype: "bytes"を指定したbyte streamはchunkをbyte単位で数えるため、strategyにsizeを渡すと生成時にRangeErrorになります。この場合はhighWaterMarkを直接指定し、別途ByteLengthQueuingStrategyを渡さないでください。巨大な1chunkやqueue外に作った処理は閾値を超えるmemoryを必要とし得るため、最大chunkサイズと同時処理数も別に制限してください。
専門家の見解: 境界ごとに圧力を伝える
MDN contributorsはpipe chainを通じてconsumerの需要が上流へ伝わると説明しています。Google Chrome Developersの実装案内はdesiredSizeとwriter.readyを待機信号として使い、chunkのsizeやqueue管理も確認するよう示します。実務では1つのstreamだけにhighWaterMarkを設定して終わりにせず、producer、transform、consumerの全区間にある独自bufferとcancel経路を点検します。APIがprocess全体のmemory上限を保証するわけではありません。