Node.js: 遅いエンドポイントは I/O とは限らない。monitorEventLoopDelay でイベントループの詰まりを測る
巨大な JSON.parse や crypto.pbkdf2Sync のような同期処理が 1 回走るだけで、その間ほかの全リクエストが止まる。perf_hooks.monitorEventLoopDelay() でイベントループの遅延を測って p99 を監視し、重い CPU 処理は worker_threads に逃がす。
症状
- 特定のエンドポイントではなく、無関係なリクエストまでときどき遅くなる。
- DB や外部 API のレイテンシは正常なのに、アプリのレスポンスタイムだけ跳ねる。
Node はシングルスレッドのイベントループで動くので、同期処理が占有している間は他のコールバックが実行されない。
測り方
js
import { monitorEventLoopDelay } from "node:perf_hooks";
const h = monitorEventLoopDelay({ resolution: 20 }); // ms
h.enable();
setInterval(() => {
// 値はナノ秒
const p99ms = h.percentile(99) / 1e6;
metrics.gauge("nodejs.eventloop.delay.p99_ms", p99ms);
h.reset();
}, 10_000).unref();p99 が数十 ms〜数百 ms に跳ねるなら、どこかで同期処理がループを塞いでいる。原因の特定は node --cpu-prof や --inspect のプロファイラで。
対処
- パスワードハッシュは
crypto.pbkdf2/crypto.scryptの非同期版を使う(libuv のスレッドプールで動く)。 - 巨大な JSON の parse/stringify、画像処理、圧縮などは
worker_threads(Piscina などのプール)に移す。 - そもそも巨大なボディを受けないよう上限を付ける。
注意点
monitorEventLoopDelayは Node 11.10 以降。OpenTelemetry の Node ランタイム計測やprom-clientの既定メトリクスにも同等の値がある。
出典
- @amitsinha__07 · 2026-10-24
- Node.js ドキュメント
perf_hooks.monitorEventLoopDelay: nodejs.org