
本番環境でNode.js APIをデバッグする方法
再現可能な本番環境の症状から始める

本番環境のデバッグは、どの関数が壊れているかという推測ではなく、症状を正確に把握することから始まります。影響を受けているエンドポイント、HTTPメソッド、おおよその発生時刻、ステータスコード、レスポンスのレイテンシ、デプロイバージョン、そして問題が全ユーザーに影響しているのか、特定のセグメントだけに影響しているのかを記録します。たとえば、「リリース2025.03.18以降、ヨーロッパの顧客に対してPOST /api/ordersが500を返す」という情報は、「APIが不安定である」という説明よりはるかに有用です。
失敗するリクエストを、類似した入力を使用する成功リクエストと比較します。問題が常に発生するのか、断続的なのか、特定のリージョンに関連しているのか、あるいは特定のアカウント種別に限定されているのかを確認します。簡潔なインシデントのタイムラインを作成すると、症状と、デプロイ、データベース移行、環境変数の更新、トラフィック増加、サードパーティサービスの障害などの変更との関連を把握しやすくなります。この証拠により、アプリケーションコードを調査する前に検索範囲を絞り込めます。
Node.jsで構造化ログを使用する

複数の本番インスタンスが同時にリクエストを処理している場合、単純なコンソールメッセージは検索が困難です。timestamp、level、service、environment、requestId、route、statusCode、durationMs、エラー名などのフィールドを持つ構造化ログを使用します。PinoのようなロガーはJSONを効率的に出力できるため、ログプラットフォームでは文章を手作業で解析することなく、すべての500レスポンスを絞り込んだり、レイテンシ順にリクエストを並べ替えたりできます。
障害の説明に役立つコンテキストを含めますが、パスワード、アクセストークン、完全な決済情報、または制限のないリクエストボディをログに記録してはいけません。注文処理の失敗であれば、orderId、顧客セグメント、データベース操作名、依存サービスのステータスを記録するだけで十分な場合があります。ログレベルは一貫して使用します。重要なライフサイクルイベントにはinfo、復旧可能な異常にはwarn、調査が必要な障害にはerrorを使用します。スタックトレースは通常、正確なソース行と呼び出し経路を特定するため、内部ログには必ず元のエラースタックを含めてください。
サービスをまたいで1つのリクエストをトレースする

リクエストIDを使うと、1つの処理をミドルウェア、コントローラー、データベース呼び出し、キュー、外部サービスにまたがって追跡できます。クライアントからIDが提供されていない場合はエッジでIDを生成し、適切な場合にはレスポンスヘッダーに付加して、下流の呼び出しにも引き継ぎます。Expressアプリケーションでは、リクエストミドルウェアによって識別子をリクエストオブジェクトまたは非同期コンテキストに格納できるため、後続のログ出力でも同じ値を使用できます。
より広範なシステムでは、OpenTelemetryを使用した分散トレーシングを導入し、HTTPリクエストを通じてトレースコンテキストを伝播させます。トレースを確認すると、900ミリ秒のAPIレスポンスの内訳が、Node.jsで40ミリ秒、データベースクエリの待機に700ミリ秒、レコメンデーションサービスの呼び出しに160ミリ秒だったことが分かります。この内訳により、実際の遅延が依存先にあるにもかかわらず、アプリケーションチームがJavaScriptコードの最適化に注力してしまう事態を防げます。トレースは、プライバシー要件とコスト要件に従ってサンプリングおよびスクラブ処理されていることを確認してください。
サービスを停止させずにエラーを調査する

エラーがエラートラッカーに到達したら、完全なスタックトレース、エラーの種類、リクエストコンテキスト、リリース識別子を確認します。TypeError、JSON解析エラー、データベース制約違反、タイムアウト例外などのエラーは、それぞれ異なる調査方針を示します。繰り返し発生するエラーは、固有のリクエストデータではなく、正規化したスタックトレースを基準にグループ化してください。そうしないと、1つのバグが互いに無関係な何千もの問題として現れる可能性があります。
プラットフォームが制御された低オーバーヘッドのデバッグに対応していない限り、負荷の高い本番プロセスにライブデバッガーを接続することは避けてください。ブレークポイントによってイベントループの処理が一時停止し、無関係なユーザーのレイテンシが増加する可能性があります。スタックトレース、承認済みの手順で取得したヒープスナップショット、CPUプロファイル、終了時刻を明確に定めた一時的なデバッグログなど、安全な診断手段を優先してください。追加のログを有効にする必要がある場合は、設定によって制御し、対象範囲を限定するとともに、その設定をいつ無効にしたかを正確に記録してください。
イベントループとリソースの健全性を確認する

Node.jsは多数の同時リクエストを処理できますが、イベントループをブロックするJavaScript処理があると、そのプロセスが処理するすべてのリクエストに遅延が生じます。同期的なファイル操作、負荷の高いJSON処理、大規模な正規表現、画像変換、または想定外に大きな配列に対するループを確認してください。イベントループ遅延、CPU使用率、リクエスト時間、アクティブハンドル数を組み合わせると、CPU使用率だけの場合よりも確かな証拠が得られます。コールバックがブロッキング処理の後ろで待機している場合、プロセスのCPU使用率が中程度であっても、レスポンスタイムが悪化する可能性があります。
メモリの問題には、別のアプローチが必要です。ガベージコレクション後のヒープ使用量、常駐セットサイズ、再起動頻度、アクティブな接続数を比較してください。ヒープが継続的に増加している場合は、解放されないオブジェクト、上限のないキャッシュ、削除されないリスナー、またはライフタイムを超えて保持されるリクエストデータが原因である可能性があります。コンテナの上限を変更する前に、メモリ増加の原因がアプリケーションにあるのか、依存関係にあるのかを確認してください。上限を引き上げても、メモリリークが解消されるわけではなく、メモリ不足による障害を先送りできるだけです。
データベースと外部依存先を検証する

多くのAPIインシデントは、ルートハンドラーの外部で発生します。データベースのコネクションプール使用状況、クエリ実行時間、ロック待機、トランザクションの失敗、スロークエリログ、最近のスキーマ変更を確認してください。長時間実行されるクエリによってプールが枯渇すると、新しいリクエストがハングしているように見え、最終的にタイムアウトエラーが発生する可能性があります。可能な場合は、診断出力に個人情報や機密データが含まれないよう保護しながら、正確なクエリパラメーターと実行計画を比較してください。
外部サービスは、明示的なタイムアウト、リトライ回数の上限、必要に応じたサーキットブレーカー動作を備えた、可観測な依存関係として扱ってください。上限のないリトライは、障害発生中にトラフィックを増幅させ、利用可能な接続をすべて消費する可能性があります。依存先の名前、操作、タイムアウト値、試行回数、発生したエラーのカテゴリをログに記録してください。決済プロバイダーが429レスポンスを返す場合、適切な対応は、すべてのAPIワーカーから直ちにリクエストを繰り返すことではなく、制御されたバックオフとキューへの操作登録である可能性があります。
安全な修正を適用し、復旧を検証する

証拠によって裏付けられる、最小限で元に戻せる対応を選択してください。原因に応じて、リリースのロールバック、機能フラグの無効化、環境変数の修正、停止したワーカーの終了、一時的なレート制限の追加などが考えられます。変更を加える前に、失敗したリクエストの形式と関連ログを保存してください。ロールバックによって、後の根本原因分析に必要な証拠が失われる可能性があるためです。診断用エンドポイントやスタックトレースを、認証されていないユーザーに直接公開してはなりません。
検証には、手動リクエストが成功したかどうかだけでなく、本番環境のシグナルを使用してください。エラー率、p95またはp99レイテンシー、スループット、イベントループ遅延、メモリ使用量、依存先の障害、完了した注文数などのビジネスレベルの成果を監視します。修正後は、以前失敗していたケースと通常のケースの両方をテストしてください。インシデントが安定したら、発生のトリガー、検知の抜け、寄与した条件、是正措置、そして次回より早く問題を検知するモニターまたはテストについて説明する、短いタイムラインを作成してください。
関連記事
参考情報
タグ :
- Web開発

