Share
Embed
Streams APIとTCPフロー制御 / Web Streams API and TCP flow control
Nihonbashi.js #9 のLT資料です。
イベント情報はこちら↓
https://nihonbashi-js.connpass.com/event/332328/
tasshi
October 31, 2024
Transcript
24 slides
-
Stream APIとTCPフロー制御 - Nihonbashi.js #9 はじめに ⽥代雅治 (@tasshi_me) • 仕事
• サイボウズ株式会社 • kintone拡張基盤🧩チーム EM • 好きなもの • ⾃転⾞ 🚲 • ゲーム 🎮 • ⽝ 🐶 • 2024年9⽉に結婚しました💍 ⾃⼰紹介 2 -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 はじめに • kintoneの拡張機能(プラグイン・連携サービス)向けの 開発基盤を開発・保守するチーム •
API開発 • SDK/CLIなどのOSS提供 など kintone拡張基盤🧩チーム 3 -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 Streams API • データ⼊出⼒を逐次的に、効率よく扱うためのAPI •
データを分割された断⽚(chunk)の連続した流れとして扱う • 概念⾃体はかなり古くからあり、多くの開発⾔語でストリーム操作はあります • 3種類のストリームオブジェクト(ReadableStream, WritableStream, TransformStream) • Stream同⼠をパイプ接続すると、chunkは流れるように終端まで処理される(パイプチェーン) Streams APIの概要 5 Transform Stream Readable Stream Writable Stream chunk データの読み込み データの変換 (読み込み+書き込み) データの書き込み chunk Source Sink -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 Streams API • ストリーム(オブジェクト)は未処理チャンクを保持する内部キューを持っている •
パイプチェーン上のストリーム間で処理速度に差がある場合、 内部キューにチャンクが溜まっていくことになる • => メモリを圧迫する︖ ストリームオブジェクト間の処理速度の違いとメモリ使⽤量 6 Transform Stream Readable Stream Writable Stream chunk chunk チャンクが溜まっていく︖ ここの処理が遅い場合 (DBアクセスなど) -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 Streams API • チャンクを受け⼊れられない場合、ストリームは上流に停⽌信号を出す •
停⽌信号を受けた上流のストリームはデータの送信を停⽌する • 下流のストリームが送信を指⽰(pull)すると再び処理が再開する => チャンクの流速が調整されてメモリを圧迫せずに処理できる 背圧 (Backpressure) 7 Transform Stream Readable Stream Writable Stream chunk chunk 内部キューが満杯 STOP! 内部キューが満杯 STOP! -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 Streams API • 背圧は内部キューの状態から、キューイング戦略に基づいて通知される •
現在は2種類のキューイング戦略が利⽤可能 キューイング戦略と最⾼⽔準点(highWaterMark) 8 キューに格納されたチャンク数で判定 キューに格納されたバイト数で判定 chunk chunk chunk chunk chunk chunk chunk chunk 3 最⾼⽔準点 (highWaterMark) 10KB CountQueuingStrategy ByteLengthQueuingStrategy -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 メモリリークと調査 • Webアプリケーションから全レコードを取得し、加⼯してDBに書き込んでいく • アプリケーションは全レコードをJSONL形式で返すAPIを持つ
• ストリームを使えばメモリに全レコードを載せずに効率よく処理できるはずだが、、、 全レコードへのバッチ処理 10 Text Decoder Stream Response JSON Parser Stream Line Splitter Stream ⾏ごとに 再分割 UTF-8 デコード DBに 書き込み レスポンスの 読み込み Writable Stream DBに 書き込み fetch DB -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 メモリリークと調査 • 終端ストリームを置き換え(DB => fs,
setTimeout)=> 効果なし • 中間ストリームを置き換え => 効果なし • 始端ストリームを置き換え(fetch => fs)=> 効果あり 原因箇所の特定 12 Response fetch Text Decoder Stream ここより前に原因がありそう -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 メモリリークと調査 • APIレスポンスはJSONL形式 (Content-Type: application/jsonl;
charset=utf-8) • HTTP/1.1 • Transfer-Encoding: chunked • ↑レスポンスはチャンク単位で送られてくる • => この通信に背圧が反映されないとResponse(ReadableStream)にチャンクが溜まるのでは︖ 通信の詳細 13 chunk Server chunk chunk Response STOP! STOP!…できてる︖ -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • Server: JSONLを70MB返却するAPI •
Batch Runner: • APIを呼び出し、レスポンスをストリーム処理する • 終端のWritableStreamはチャンクを受け取ると⼀定時間待機する(背圧を発⽣させる) 検証環境 15 chunk Server (Express) Batch Runner chunk chunk HTTP WireShark キャプチャ -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • ウィンドウサイズはある程度⼤きい値を維持 • 受信側のチャンクサイズも⼩さい(受信速度より終端の処理速度が速い)
終端ストリームの遅延時間なしの場合 16 受信側のチャンクサイズ -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • ⼀定期間ごとにウィンドウサイズが急激に⼩さくなっていることが確認できた • チャンクサイズは上限値に張り付いていた
• => 背圧の発⽣がウィンドウサイズに反映されていると考えられる 終端ストリームの遅延時間100msの場合 17 -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • Zero Windowが送信されていた •
ZeroWindow: ウィンドウサイズが0(通信⼀時停⽌) • ZeroWindowProveAck: 通信⼀時停⽌中の接続維持のためのACK • TCP Window Update: ウィンドウサイズが更新されて通信再開 • => よりキューの処理に時間がかかっていると考えられる 終端ストリームの遅延時間1000msの場合 18 -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • Zero Window送信後、⻑時間に渡って通信再開せず •
=> 内部キューが処理できていないと考えられる 終端ストリームの遅延時間10000msの場合 19 -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 BackpressureとTCP通信 • Streams APIの背圧はネットワークリクエストにも反映されると考えられる •
ウィンドウサイズが⼩さくなり、受信可能データ量を調整する • それでも処理しきれず受信を⽌める場合は、 サーバーにzero windowパケットが送信される • (HTTP/2での挙動も⾒てみたい) まとめ 20 chunk Server chunk chunk Response STOP! STOP! (zero window) -
Stream APIとTCPフロー制御 - Nihonbashi.js #9 メモリリークの原因 • Node.js v22.7.0 (undici
v6.19.7)で修正された (nodejs/undici#3445) • v22ではメモリ使⽤量が300MB付近で横ばい • LTSへのbackport状況 • v20.x => v20.18.0にbackport済み、だが計測結果は× • v18.x => backportの予定は⾒つけられず undiciがメモリリークしていた 23