📅 2025年12月26日 04:08
nextTickとsetImmediateが握るNode.js高速化の裏技 — 見落としがちな実行順序の威力
要約
process.nextTickとsetImmediateは似て非なる「予約命令」。イベントループ内での実行タイミングを制御することで、パフォーマンスや応答性、I/O公平性を実現できる。
この記事を読むべき理由
日本でもNode.jsはAPIサーバー、バッチ、サーバーレスで広く使われており、ちょっとした実行順序の理解でレイテンシ改善やデバッグが容易になる。特に非同期処理の微妙な順序がバグやパフォーマンス問題の原因になり得る現場に必須の知識。
詳細解説
Node.jsのイベントループは複数のフェーズから成り、各フェーズでコールバックが実行される。重要なのは「マイクロタスク(microtasks)」と「マクロタスク(macrotasks)」の扱い。
- process.nextTick: Node固有。次のイベントループフェーズに入る前に実行されるマイクロタスク。現在の操作の直後に優先して走るため、短時間に多用するとI/Oフェーズを後回しにしてしまう(starvation)。
- Promise.then / queueMicrotask: ECMAScriptのマイクロタスク。nextTickよりも後か同等のタイミングで処理されるが、Node実装依存の順序差あり。
- setImmediate: 次のイベントループの「チェック(check)」フェーズで実行されるマクロタスク。I/O後に走るため、I/O処理の完了を待ってからの処理に向く。
- setTimeout(fn, 0): タイマーキューに登録され、タイマーのフェーズで処理。典型的にはsetImmediateより遅くなる場合がある。
簡単な実行順序の例:
// javascript
console.log('start');
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('end');
一般的な出力順: start end nextTick promise immediate timeout
ポイントは nextTick が最優先で、次いで Promise 系のマイクロタスク、その後で setImmediate / タイマーフェーズと続くこと。
なぜこれが重要か:
- レイテンシ制御: 直ちに応答性を改善したい後処理はnextTickで短く処理する。
- I/O優先: I/Oを先に進めたい場合はsetImmediateを使うことでI/Oフェーズを邪魔しない。
- スタック深度・再帰回避: 長い同期ループを分割してイベントループに戻す際に有効。
- デバッグと追跡: 非同期バグの再現やコール順の理解に直結。
危険性:
- process.nextTickを無制限に使うとI/O処理が永遠に実行されない(starvation)。Promiseチェーンも同様に注意。
Nodeバージョンの話: V8やNodeの実装により微妙に挙動が変わる可能性があるため、主要なLTSで動作確認を行うこと。サーバーレス(AWS Lambda等)では実行環境の Node バージョン依存を意識すること。
実践ポイント
- 応答性を最優先する短い後処理は process.nextTick または queueMicrotask を検討。
- I/O後の処理や重いバッチは setImmediate を使い、I/Oをブロックしないようにする。
- 長時間実行するループは適宜 nextTick/setImmediate で分割し、イベントループに戻す(スタック深度とブロッキング回避)。
- nextTickの多用は避け、PromiseとsetImmediateの組合せで公平なスケジューリングを実現。
- 本番前に使用環境の Node バージョンで動作とパフォーマンスを測定する。
引用元
- タイトル: The Hidden Power of nextTick + setImmediate in Node.js
- URL: https://medium.com/@unclexo/the-hidden-power-of-nexttick-setimmediate-in-node-js-2bd5b5fb7e28