Zenn
🔁

イベントループを止めるな

2025/03/25に公開
2

node.jsで動くサーバーが、何らかの重い処理を走らせている際、他のリクエストを受け付けなくなってしまうことがある(実際、大きなCSVファイルを処理している間、当該podの応答が止まってしまう事象が弊社製品であるHERP Hireのバックエンドで観測されていた)。

app.post("/load-csv", async (c) => {
    // 重い処理
    // このループが実行されている間、サーバーが応答しない!
    for await (const row of streamHugeCSV(c)){
        const obj = parseRow(row);
        logger.info(`Processing ${obj.id}`)
        processRow(obj);
    }
    return c.text("done");
} 

これは重い処理がthe Event Loopをブロックしてしまうことによって発生する問題である。the Event Loopはnode.jsにおける非同期IOの中枢であり、これを回す余地を与えなければ、アプリケーションが応答しなくなってしまう。
for awaitという字面からはあまり影響を受けなさそうに見えるが、async iteratorであったとしてもこの問題は発生する。

setImmediate()を処理の中に挿入することで、イベントループに優先権を渡すことができ、この問題を回避できる。

import { setImmediate } from 'timers/promises'; 

await setImmediate();
// await new Promise<void>((resolve) => setImmediate(resolve)); と等価
for await (const row of streamHugeCSV()){
    const obj = parseRow(row);
    logger.info(`Processing ${obj.id}`)
    processRow(obj);
    // 処理を譲る
    await setImmediate();
}

ちなみに、HaskellではControl.Concurrent.yieldsetImmediate()に相当する。Haskell (GHC)の場合、ほとんど全ての入出力やメモリアロケーションのタイミングで優先権を譲る(Haskell製のプログラムはほぼ常時アロケーションしている)ので、明示的にyieldを呼び出す必要がある場面はほとんどない。

サンプルコード

イベントループをブロックすると他の処理が走らなくなること、そしてsetImmediateを挿入すると解決することを検証してみよう。

import { setImmediate } from "timers/promises";

async function* busystream(){
  const t0 = performance.now();
  // 3秒経過するまでループを回し続ける
  for(let i=0;performance.now() - t0 < 3000;i++){
    yield i;
    // await setImmediate();
  }
}
const t0 = performance.now();

setTimeout(() => console.log("took", Math.floor(performance.now() - t0)), 500);

for await (const i of busystream()){

}

このコードは3秒間forループを回し続けるため、setTimeoutが500msに設定されているにもかかわらずtook 3000が表示されるが、setImmediateを入れればtook 500になる。

Alternative Solution

Worker threadsを使って、CPU-intensiveな処理をより低いレイヤーで分離する方法もあるが、JavaScriptのソースコードを切り出す必要があるので取り回しが悪い。普段使いには適さず、音声処理など特殊な用途に限られるだろう。

まとめ

Webサーバーのようなアプリケーションにおいて長時間かかる処理をしたい場合、要所にsetImmediateを挿入し、他の処理と協働できるようにすることを忘れないようにしたい。

See also

PR

株式会社HERPでは、他のチームと協働しながら改善のループを回せる人材を募集しています。

https://herp.careers/v1/herpinc/zVEuWwkjSuW5

2

Discussion

ログインするとコメントできます