NodeJsアプリケーションのパフォーマンスの問題を見つける(例付き)

シングルスレッドのNode.jsアーキテクチャにより、アプリケーションの高いパフォーマンスに注意し、パフォーマンスの低下につながり、サーバーアプリケーションから貴重なCPUリソースを消費するコードのボトルネックを回避することが重要です。

この記事では、nodejsアプリケーションのCPU負荷を監視し、リソースを集中的に使用するコードのセクションを検出し、CPUコアの100%負荷で起こりうる問題を解決する方法について説明します。







画像

1.アプリケーションのCPUプロファイリング。 ツール



幸いなことに、開発者には、CPUホットスポットを検出して視覚化するための便利なツールがあります。







Chrome DevTools Inspector



まず、これはChrome DevToolsに組み込まれているプロファイラーで、WebSocket(標準ポート9229



)を介してNodeJsアプリケーションと通信します。







--inspectフラグを指定してnodejsアプリケーションを起動します







(デフォルトでは、標準ポート9229



が使用されますが、これは--inspect=<>



で変更できます)。







NodeJsサーバーがdockerコンテナーにある場合、-inspect --inspect=0.0.0.0:9229



ノードを起動し、 Dockerfile



またはDockerfile



docker-compose.yml



ポートを開く必要があります







Chromeを開く://ブラウザで検査する







画像



“Remote Target”



アプリケーションを見つけて、 “inspect”



をクリックします。

標準の「ブラウザ」Chrome DevToolsと同様に、インスペクターウィンドウが開きます。

アプリケーションの実行中にいつでもCPUプロファイルを記録できる[ “Profiler”



]タブに興味があります。







画像



記録後、収集された情報は便利な表形式のツリービューに表示され、各関数の時間はmsで、合計記録時間の%になります(以下を参照)。

サイクル ライブラリ (別の一般的なwinston v2.xライブラリで使用)のボトルネックを利用して、高いCPU負荷でJSコードをエミュレートする実験用の簡単なアプリケーション( ここからクローンできます )を見てみましょう。







元のサイクルの作業または私の修正バージョンを比較します。







アプリケーションをインストールし、 npm run inspect



ます。 インスペクターを開き、CPUプロファイルの記録を開始します。 開かれたhttp://localhost:5001/



ページで、CPUプロファイルの記録を完了(テキスト「ok」でアラート)した後、 "Run CPU intensive task"



クリックします。 その結果、最も食いしん坊なf-s(この場合はrunOrigDecycle()



runFixedDecycle()



を比較して、それらを比較します%)を示す写真を見ることができます:







画像



NodeJsプロファイラー



もう1つのオプションは、NodeJの組み込みプロファイラーを使用してCPUパフォーマンスをレポートすることです。 インスペクターとは異なり、アプリケーションの全期間のデータを表示します。







--profフラグを指定してnodejsアプリケーションを起動します







アプリケーションフォルダーに、 isolate-0xXXXXXXX-v8.log



という形式のファイルが作成され、そこにティックに関するデータが記録されます。







このファイルのデータは分析には不便ですが、コマンドを使用して人間が読めるレポートを生成できます。

node --prof-process < isolate-*-v8.log>









上記のテストアプリケーションのレポートの例を次に示します。

(自分で生成するには、 npm run prof









プロファイリング用のnpmパッケージもいくつかあります-v8-profiler

、V8プロファイラーAPIへのJSインターフェースと、ノードインスペクター(Chrome DevToolsベースのプロファイラーのリリース後に非推奨)を提供します。







2.インスペクターなしでJSコードをブロックする問題を解決する



無限ループまたはその他のエラーがコード内に忍び込み、サーバーでのJSコードの実行が完全にブロックされたと仮定します。 この場合、唯一のNodeJsスレッドがブロックされ、サーバーが応答を停止し、CPUコアの負荷が100%に達します。 インスペクターがまだ実行されていない場合、それを起動しても、コードの有罪な部分をキャッチするのに役立ちません。







この場合、 gdbデバッガーが助けになります。







ドッカーの場合は、使用する必要があります

--cap-add=SYS_PTRACE





パッケージのインストール

apt-get install libc6-dbg libc-dbg gdb valgrind





そのため、nodejsプロセスに接続する必要があります(そのpidを認識します)。

sudo gdb -p <pid>









接続後、次のコマンドを入力します。







 b v8::internal::Runtime_StackGuard p 'v8::Isolate::GetCurrent'() p 'v8::Isolate::TerminateExecution'($1) c p 'v8::internal::Runtime_DebugTrace'(0, 0, (void *)($1)) quit
      
      





各チームが何をするかについては詳しく説明しません。ここでは、 V8エンジンの一部の内部機能が使用されているとしか言えません。







その結果、現在の「ティック」での現在のブロッキングJSコードの実行は停止され、アプリケーションは動作し続け(Expressを使用すると、サーバーは着信リクエストをさらに処理できるようになります)、スタックトレースがNodeJsアプリケーションの標準出力ストリームに出力されます。







非常に長いですが、有用な情報、つまりJS関数の呼び出しのスタックを見つけることができます。







次のような行を見つけます。







 --------- sourcecode --------- function infLoopFunc() {\x0a //this will lock server\x0a while(1) {;}\x0a} -----------------------------------------
      
      





彼らは「有罪」コードを識別するのに役立つはずです。







便宜上、このプロセスを自動化するスクリプトを作成し、呼び出しスタックを別のログファイルに書き込みます: loop-terminator.sh







また、視覚的な用途のサンプルアプリケーションも参照してください。







3. NodeJ(およびnpmパッケージ)を更新する



時々あなたは非難することはありません:)







nodejs <v8.5.0(8.4.0、8.3.0で確認)で、特定の状況で1 CPUコアの100%の負荷が発生するという面白いバグに遭遇しました。

このバグを繰り返す単純なアプリケーションのコードはこちらです。







child_process.fork()



、アプリケーションがWebSocketサーバーを( socket-ioで )起動し、 child_process.fork()



介して1つの子プロセスをchild_process.fork()



です。 次の一連のアクションは、1 CPUコアの100%の負荷を引き起こすことが保証されています。







  1. クライアントはWS-Northに接続します
  2. 子プロセスは終了し、再作成されます。
  3. クライアントがWSから切断する


さらに、アプリケーションはまだ実行されており、Expressサーバーはリクエストに応答しています。

バグはおそらくlibuv



にあり、ノード自体にはありません。 私はこのバグの本当の原因とそれを修正するコミットを変更ログで見つけませんでした。 簡単な「グーグル」により、古いバージョンで同様のバグが発生しました。







https://github.com/joyent/libuv/issues/1099

https://github.com/nodejs/node-v0.x-archive/issues/6271







解決策は簡単です-ノードをv8.5.0 +に更新します。







4.子プロセスを使用する



サーバーアプリケーションにCPUにかなり負荷がかかるリソース集中型のコードがある場合は、別の子プロセスに配置することをお勧めします。 たとえば、Reactアプリケーションのサーバー側レンダリングです。







別のNodeJsアプリケーションを作成し、メインアプリケーションからchild_process.fork()



介して実行します。 IPC



チャネルを使用して、プロセス間で通信します。 ChildProcess



ChildProcess



の子孫であるため、プロセス間のメッセージングシステムの開発は非常に簡単EventEmitter





ただし、子NodeJsプロセスを多く作成することはお勧めできません。







パフォーマンスに関して言えば、もう1つの重要な指標はRAMの消費です。 メモリリークを見つけるためのツールとテクニックがありますが、これは別の記事のトピックです。








All Articles