Node.jsのパフォーマンス監視は、従来のサーバー環境とは異なるアプローチが必要です。シングルスレッドのイベントループ、ガベージコレクションによるメモリ管理、非同期I/Oは、CPUやメモリの汎用的なメトリクスだけでは捉えきれない独自の障害モードを生み出します。本記事では、本番環境でNode.jsアプリケーションを安定稼働させるために必要な重要メトリクスとツールを解説します。
Node.jsパフォーマンス監視の特殊性
マルチスレッドサーバーでは1つの低速処理が1スレッドだけをブロックするのに対し、Node.jsでは1つのCPU負荷の高い処理がイベントループ全体をブロックし、すべての同時リクエストに影響を与えます。ガベージコレクションのポーズは予測不能なレイテンシスパイクを引き起こします。よくある障害モードとしては、イベントループ飢餓、クリーンアップされなかったクロージャによるメモリリーク、コールバックスラッシング、未処理のPromise拒否によるエラーの飲み込みなどがあります。
イベントループ遅延
イベントループ遅延は、タイマーコールバックのスケジュールから実際の実行までの遅延を測定します。これはNode.jsプロセスの最も重要な健全性指標です。
const { monitorEventLoopDelay } = require("perf_hooks");
const histogram = monitorEventLoopDelay();
histogram.enable();
setInterval(() => {
const lag = histogram.mean / 1e6;
histogram.reset();
if (lag > 50) logger.warn({ eventLoopLag: lag }, "イベントループ遅延を検出");
}, 10000);
| 遅延 | 解釈 |
|---|---|
| < 10 ms | 健全 |
| 10–50 ms | 要注意 — 調査推奨 |
| > 50 ms | クリティカル — 即時対応が必要 |
ガベージコレクションメトリクス
V8のガベージコレクタは高速なScavenge(若い世代)と低速なMark-Sweep-Compact(古い世代)の2フェーズで動作します。長いまたは頻繁なGCポーズはp99レイテンシに直接影響します。
const { PerformanceObserver } = require("perf_hooks");
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.detail.kind === "gc") {
logger.warn({ gcDuration: entry.duration }, "GCポーズ検出");
}
}
});
obs.observe({ entryTypes: ["gc"] });
高いアロケーションレートはGCサイクルを頻発させ、CPUオーバーヘッドとポーズ時間を増加させます。対策として、ホットパスでのオブジェクトプーリング、リクエストハンドラ内の一時オブジェクト削減、安全な場面でのBuffer.allocUnsafeの使用が有効です。
メモリヒープ分析
process.memoryUsage()でrss、heapTotal、heapUsed、external、arrayBuffersを取得できます。より深い分析にはヒープスナップショットが有効です。
const { writeHeapSnapshot } = require("v8");
const used = process.memoryUsage();
for (const [key, value] of Object.entries(used)) {
logger.info({ metric: `memory.${key}`, value: Math.round(value / 1024 / 1024) }, "メモリ使用量");
}
if (used.heapUsed / used.heapTotal > 0.8) {
writeHeapSnapshot(`/tmp/heap-${Date.now()}.heapsnapshot`);
}
スナップショットをChrome DevToolsで分析することで、イベントリスナーの増加、削除されないキャッシュ、未解放のタイマー、意図しないグローバル変数などのメモリリークパターンを特定できます。
CPUプロファイリング
Node.jsには--profフラグで有効になるビルトインプロファイラが含まれています。
node --prof app.js
# ロードテスト後:
node --prof-process isolate-*.log > processed.txt
Clinic.jsのFlameツールを使用すると、フレームグラフでCPU時間の使われ方を可視化できます。グラフ上部の広いブロックはホット関数を示し、深いスタックはリファクタリングの機会を示唆します。
OpenTelemetry統合
OpenTelemetryはトレース、メトリクス、ログを収集するベンダーニュートラルな標準を提供します。
const { NodeSDK } = require("@opentelemetry/sdk-node");
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http");
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({ url: "http://jaeger:4318/v1/traces" }),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
カスタムビジネスロジック用の手動スパンも作成できます:
const { trace } = require("@opentelemetry/api");
const tracer = trace.getTracer("payment-service");
await tracer.startActiveSpan("processPayment", async (span) => {
span.setAttribute("orderId", order.id);
try { /* 決済処理 */ } finally { span.end(); }
});
APMツール比較
| ツール | セットアップ | 強み | オーバーヘッド |
|---|---|---|---|
| Datadog APM | Agentベース | 深いNode.js統合、ランタイムメトリクス | 低 |
| New Relic | npm install | 自動計装、ブラウザ統合 | 中 |
| Elastic APM | Agentベース | オープンソース、ELKネイティブ | 低 |
| OpenTelemetry + SigNoz | 手動設定 | セルフホスト、OTLP対応 | 低 |
| PM2 + Keymetrics | ビルトイン | 軽量、プロセス管理 | 最小 |
実践的セットアップ
最小構成では、prom-clientを使用してプロセスメトリクスをPrometheusにエクスポートし、Grafanaで可視化します。
const client = require("prom-client");
const gcHistogram = new client.Histogram({
name: "nodejs_gc_duration_seconds",
help: "GC実行時間",
buckets: [0.001, 0.01, 0.1, 1],
});
監視は反復的に進めましょう。まずイベントループ遅延とメモリから始め、問題が表面化したらCPUプロファイリングとGC監視を追加し、アプリケーションが分散アーキテクチャにスケールするにつれてOpenTelemetryを導入します。
