apeescape2.com
  • メイン
  • プロセスとツール
  • 計画と予測
  • リモートの台頭
  • モバイル
バックエンド

Node.jsアプリケーションでのメモリリークのデバッグ

私はかつてV8ツインターボエンジンを搭載したアウディを運転しましたが、その性能は素晴らしかったです。道路に誰もいない午前3時にシカゴ近くのIL-80高速道路を約140MPHで運転していました。それ以来、「V8」という言葉は私にとって高性能に関連するようになりました。

冷戦中の技術
Node.jsは、ChromeのV8 JavaScriptエンジン上に構築されたプラットフォームであり、高速でスケーラブルなネットワークアプリケーションを簡単に構築できます。

アウディのV8は非常に強力ですが、それでもガソリンタンクの容量には制限があります。同じことがGoogleのV8(Node.jsの背後にあるJavaScriptエンジン)にも当てはまります。そのパフォーマンスは信じられないほどであり、Node.jsには多くの理由があります 多くのユースケースでうまく機能します 、ただし、ヒープサイズによって常に制限されます。 Node.jsアプリケーションでさらにリクエストを処理する必要がある場合は、垂直方向にスケーリングするか、水平方向にスケーリングするかの2つの選択肢があります。水平スケーリングは、より多くの同時アプリケーションインスタンスを実行する必要があることを意味します。正しく実行すると、より多くのリクエストを処理できるようになります。垂直スケーリングとは、アプリケーションのメモリ使用量とパフォーマンスを改善するか、アプリケーションインスタンスで利用可能なリソースを増やす必要があることを意味します。

Node.jsアプリケーションでのメモリリークのデバッグ



Node.jsアプリケーションでのメモリリークのデバッグ つぶやき

最近、メモリリークの問題を修正するために、ApeeScapeクライアントの1つでNode.jsアプリケーションを使用するように依頼されました。 APIサーバーであるこのアプリケーションは、毎分数十万のリクエストを処理できるようにすることを目的としていました。元のアプリケーションは約600MBのRAMを占有していたため、ホットAPIエンドポイントを取得して再実装することにしました。多くの要求に対応する必要がある場合、オーバーヘッドは非常に高価になります。

新しいAPIの場合、バックグラウンドジョブ用にネイティブMongoDBドライバーとKueを使用したrestifyを選択しました。非常に軽量なスタックのようですね。完全ではありません。ピーク負荷時には、新しいアプリケーションインスタンスが最大270MBのRAMを消費する可能性があります。したがって、1X HerokuDynoごとに2つのアプリケーションインスタンスを持つという私の夢は消えました。

Node.jsメモリリークデバッグアーセナル

Memwatch

「ノードのリークを見つける方法」を検索すると、おそらく最初に見つかるツールは memwatch 。元のパッケージはかなり前に放棄され、現在は維持されていません。ただし、GitHubで新しいバージョンを簡単に見つけることができます リポジトリのフォークリスト 。このモジュールは、ヒープが5つの連続したガベージコレクションを超えて増大するのを確認すると、リークイベントを発行する可能性があるため便利です。

ヒープダンプ

を可能にする素晴らしいツール Node.js開発者 ヒープスナップショットを取得し、後でChromeデベロッパーツールで検査します。

ノードインスペクター

実行中のアプリケーションに接続し、ヒープダンプを取得し、その場でデバッグして再コンパイルすることもできるため、ヒープダンプのさらに便利な代替手段です。

スピンのための「ノードインスペクター」の取得

残念ながら、Herokuで実行されている本番アプリケーションに接続することはできません。これは、実行中のプロセスにシグナルを送信できないためです。ただし、Herokuだけがホスティングプラットフォームではありません。

node-inspectorの動作を体験するために、restifyを使用して単純なNode.jsアプリケーションを作成し、その中にメモリリークの小さなソースを配置します。ここでのすべての実験は、V8v3.28.71.19に対してコンパイルされたNode.jsv0.12.7を使用して行われます。

var restify = require('restify'); var server = restify.createServer(); var tasks = []; server.pre(function(req, res, next) { tasks.push(function() { return req.headers; }); // Synchronously get user from session, maybe jwt token req.user = { id: 1, username: 'Leaky Master', }; return next(); }); server.get('/', function(req, res, next) { res.send('Hi ' + req.user.username); return next(); }); server.listen(3000, function() { console.log('%s listening at %s', server.name, server.url); });

ここでのアプリケーションは非常に単純で、非常に明白なリークがあります。配列 タスク アプリケーションの存続期間中に成長し、速度が低下し、最終的にはクラッシュします。問題は、クロージャーだけでなく、リクエストオブジェクト全体もリークしていることです。

V8のGCは、ストップザワールド戦略を採用しているため、メモリ内にあるオブジェクトの数が多いほど、ガベージの収集にかかる時間が長くなります。以下のログを見ると、アプリケーションの寿命の初めにゴミを収集するのに平均20ミリ秒かかることがはっきりとわかりますが、数十万のリクエストの後は約230ミリ秒かかります。私たちのアプリケーションにアクセスしようとしている人は待たなければならないでしょう 230ms GCのおかげで今は長くなっています。また、GCが数秒ごとに呼び出されることがわかります。これは、数秒ごとにユーザーがアプリケーションへのアクセスで問題が発生することを意味します。また、アプリケーションがクラッシュするまで遅延が大きくなります。

[28093] 7644 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 25.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7717 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 18.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7866 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 23.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 8001 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 18.4 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. ... [28093] 633891 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.3 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 635672 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 331.5 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 637508 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].

これらのログ行は、Node.jsアプリケーションが –trace_gc 国旗:

node --trace_gc app.js

このフラグを使用してNode.jsアプリケーションを既に開始していると仮定します。アプリケーションをnode-inspectorに接続する前に、実行中のプロセスにSIGUSR1シグナルを送信する必要があります。クラスターでNode.jsを実行する場合は、必ずスレーブプロセスの1つに接続してください。

kill -SIGUSR1 $pid # Replace $pid with the actual process ID

これにより、Node.jsアプリケーション(正確にはV8)をデバッグモードにします。このモードでは、アプリケーションは自動的にポート5858を開きます。 V8デバッグプロトコル 。

次のステップは、実行中のアプリケーションのデバッグインターフェイスに接続し、ポート8080で別のWebインターフェイスを開くnode-inspectorを実行することです。

$ node-inspector Node Inspector v0.12.2 Visit http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 to start debugging.

アプリケーションが本番環境で実行されていて、ファイアウォールが設定されている場合は、リモートポート8080をローカルホストにトンネリングできます。

ssh -L 8080:localhost:8080 [email protected]

これで、Chrome Webブラウザーを開いて、リモートの本番アプリケーションに接続されているChrome開発ツールにフルアクセスできます。残念ながら、Chromeデベロッパーツールは他のブラウザでは機能しません。

漏れを見つけよう!

V8でのメモリリークは、C / C ++アプリケーションからわかっているため、実際のメモリリークではありません。 JavaScriptでは、変数は空白に消えることはなく、単に「忘れられる」だけです。私たちの目標は、これらの忘れられた変数を見つけて、ドビーが無料であることを彼らに思い出させることです。

Chromeデベロッパーツール内では、複数のプロファイラーにアクセスできます。特に興味があります ヒープ割り当てを記録する これは実行され、時間の経過とともに複数のヒープスナップショットを取得します。これにより、どのオブジェクトがリークしているかを明確に確認できます。

ヒープ割り当ての記録を開始し、ApacheBenchmarkを使用してホームページで50人の同時ユーザーをシミュレートしましょう。

スクリーンショット

ab -c 50 -n 1000000 -k http://example.com/

新しいスナップショットを作成する前に、V8はマークスイープガベージコレクションを実行するため、スナップショットに古いガベージがないことは間違いありません。

その場でリークを修正する

の期間にわたってヒープ割り当てスナップショットを収集した後 3分 最終的には次のようになります。

ブートストラップの操作方法

スクリーンショット

いくつかの巨大な配列、多くのIncomingMessage、ReadableState、ServerResponse、およびDomainオブジェクトもヒープ内にあることがはっきりとわかります。リークの原因を分析してみましょう。

チャートで20秒から40秒までのヒープ差分を選択すると、プロファイラーを開始してから20秒後に追加されたオブジェクトのみが表示されます。このようにして、すべての正規データを除外できます。

システム内にある各タイプのオブジェクトの数に注意して、フィルターを20秒から1分に拡張します。すでにかなり巨大なアレイが成長し続けていることがわかります。 「(配列)」の下に、等距離のオブジェクト「(オブジェクトプロパティ)」がたくさんあることがわかります。これらのオブジェクトは、メモリリークの原因です。

また、「(closure)」オブジェクトも急速に成長していることがわかります。

文字列も見ると便利かもしれません。文字列リストの下には、「HiLeakyMaster」というフレーズがたくさんあります。それらは私たちにもいくつかの手がかりを与えるかもしれません。

私たちの場合、文字列「HiLeakyMaster」は「GET /」ルートでしか組み立てられないことがわかっています。

リテーナパスを開くと、この文字列が何らかの形で参照されていることがわかります。 必須 、次にコンテキストが作成され、これらすべてがクロージャの巨大な配列に追加されます。

スクリーンショット

したがって、この時点で、ある種の巨大なクロージャの配列があることがわかります。実際に行って、[ソース]タブですべてのクロージャにリアルタイムで名前を付けましょう。

スクリーンショット

コードの編集が完了したら、CTRL + Sを押して、コードをその場で保存および再コンパイルできます。

では、別の録音をしましょう ヒープ割り当てスナップショット どのクロージャがメモリを占有しているかを確認します。

それは明らかです SomeKindOfClojure() 私たちの悪役です。今、私たちはそれを見ることができます SomeKindOfClojure() クロージャは、という名前の配列に追加されています タスク グローバル空間で。

この配列が役に立たないことは簡単にわかります。コメントアウトできます。しかし、どのようにしてすでに占有されているメモリを解放するのでしょうか?非常に簡単です。空の配列をに割り当てるだけです。 タスク 次のリクエストでオーバーライドされ、次のGCイベント後にメモリが解放されます。

スクリーンショット

ドビーは無料です!

V8でのごみの寿命

ええと、V8 JSにはメモリリークがなく、変数を忘れただけです。

ええと、V8 JSにはメモリリークがなく、変数を忘れただけです。 つぶやき

V8ヒープは、いくつかの異なるスペースに分割されています。

  • 新しいスペース :このスペースは比較的小さく、サイズは1MBから8MBの間です。ほとんどのオブジェクトはここに割り当てられます。
  • オールドポインタースペース :他のオブジェクトへのポインタを持つ可能性のあるオブジェクトがあります。オブジェクトが新しいスペースで十分長く存続する場合、オブジェクトは古いポインタスペースに昇格します。
  • 古いデータスペース :文字列、ボックス化された数値、ボックス化されていないdoubleの配列などの生データのみが含まれます。新しいスペースでGCを十分長く生き残ったオブジェクトも、ここに移動されます。
  • 大きなオブジェクトスペース :大きすぎて他のスペースに収まらないオブジェクトがこのスペースに作成されます。各オブジェクトには、メモリ内に独自のmmap ‘ed領域があります
  • コードスペース :JITコンパイラによって生成されたアセンブリコードが含まれています。
  • セルスペース、プロパティセルスペース、マップスペース :このスペースには、Cell s、PropertyCell s、およびMap sが含まれます。これは、ガベージコレクションを簡素化するために使用されます。

各スペースはページで構成されています。ページは、mmapを使用してオペレーティングシステムから割り当てられたメモリの領域です。大きなオブジェクトスペースのページを除いて、各ページのサイズは常に1MBです。

モノのインターネットのセキュリティ問題

V8には、Scavenge、Mark-Sweep、Mark-Compactの2つのガベージコレクションメカニズムが組み込まれています。

スカベンジは非常に高速なガベージコレクション手法であり、 新しいスペース 。スカベンジはの実装です チェイニーのアルゴリズム 。アイデアはとてもシンプルです、 新しいスペース To-SpaceとFrom-Spaceの2つの等しい半空間に分割されます。スカベンジGCは、To-Spaceがいっぱいになると発生します。 ToスペースとFromスペースを交換し、すべてのライブオブジェクトをTo-Spaceにコピーするか、2回の清掃を生き延びた場合は古いスペースのいずれかにプロモートし、スペースから完全に消去します。スカベンジは非常に高速ですが、2倍のサイズのヒープを保持し、メモリ内のオブジェクトを常にコピーするというオーバーヘッドがあります。スカベンジを使用する理由は、ほとんどのオブジェクトが若くして死ぬためです。

Mark-Sweep&Mark-Compactは、V8で使用される別のタイプのガベージコレクターです。もう1つの名前はフルガベージコレクターです。すべてのライブノードにマークを付けてから、すべてのデッドノードをスイープし、メモリをデフラグします。

GCのパフォーマンスとデバッグのヒント

Webアプリケーションの場合、高性能はそれほど大きな問題ではないかもしれませんが、それでもリークを絶対に避けたいと思うでしょう。フルGCのマークフェーズ中、ガベージコレクションが完了するまでアプリケーションは実際に一時停止されます。つまり、ヒープ内のオブジェクトが多いほど、GCの実行にかかる時間が長くなり、ユーザーが待機する時間が長くなります。

クロージャと関数には常に名前を付けてください

すべてのクロージャと関数に名前が付いていると、スタックトレースとヒープを調べるのがはるかに簡単になります。

db.query('GIVE THEM ALL', function GiveThemAllAName(error, data) { ... })

ホット機能で大きなオブジェクトを避ける

理想的には、すべてのデータが収まるように、ホット関数内の大きなオブジェクトを避けたいと考えています 新しいスペース 。 CPUとメモリにバインドされたすべての操作は、バックグラウンドで実行する必要があります。また、ホット関数の最適化解除トリガーを回避します。最適化されたホット関数は、最適化されていない関数よりも少ないメモリを使用します。

ホット機能を最適化する必要があります

実行速度は速いがメモリ消費量が少ないホット関数は、GCの実行頻度を減らします。 V8は、最適化されていない関数または最適化されていない関数を見つけるための便利なデバッグツールをいくつか提供します。

ホット機能におけるICの多態性を回避する

インラインキャッシュ(IC)は、オブジェクトプロパティアクセスをキャッシュすることにより、コードの一部のチャンクの実行を高速化するために使用されますobj.keyまたはいくつかの単純な関数。

function x(a, b) { return a + b; } x(1, 2); // monomorphic x(1, “string”); // polymorphic, level 2 x(3.14, 1); // polymorphic, level 3

いつ x(a、b) 初めて実行されると、V8は単形ICを作成します。電話をかけるときx 2回目は、V8が古いICを消去し、整数と文字列の両方のタイプのオペランドをサポートする新しいポリモーフィックICを作成します。 3回目にICを呼び出すと、V8は同じ手順を繰り返し、レベル3の別の多形ICを作成します。

ただし、制限があります。 ICレベルが5に達した後(で変更可能 –max_inlining_levels フラグ)関数はメガモーフィックになり、最適化可能とは見なされなくなります。

単相関数が最も高速に実行され、メモリフットプリントも小さいことは直感的に理解できます。

大きなファイルをメモリに追加しないでください

これは明白でよく知られています。大きなCSVファイルなど、処理する大きなファイルがある場合は、ファイル全体をメモリにロードするのではなく、1行ずつ読み取り、小さなチャンクで処理します。 csvの1行が1MBを超えるという非常にまれなケースがあります。そのため、これを収めることができます。 新しいスペース 。

メインサーバースレッドをブロックしないでください

画像のサイズを変更するAPIなど、処理に時間がかかるホットAPIがある場合は、別のスレッドに移動するか、バックグラウンドジョブに変換します。 CPUを集中的に使用する操作では、メインスレッドがブロックされ、他のすべての顧客が待機して要求を送信し続ける必要があります。未処理のリクエストデータはメモリにスタックされるため、完全なGCが完了するまでに長い時間がかかります。

不要なデータを作成しないでください

私はかつてrestifyで奇妙な経験をしました。無効なURLに数十万のリクエストを送信すると、アプリケーションのメモリは最大100メガバイトで急速に増加し、数秒後に完全なGCが開始されます。これにより、すべてが正常に戻ります。無効なURLごとに、restifyは長いスタックトレースを含む新しいエラーオブジェクトを生成することがわかりました。これにより、新しく作成されたオブジェクトがに割り当てられました 大きなオブジェクトスペース ではなく 新しいスペース 。

このようなデータにアクセスできることは、開発中に非常に役立つ可能性がありますが、本番環境では明らかに必要ありません。したがって、ルールは単純です。確かに必要でない限り、データを生成しないでください。

あなたのツールを知っている

最後に、もちろん重要なことですが、ツールを知ることです。さまざまなデバッガー、リークキャザー、および使用状況グラフジェネレーターがあります。これらのツールはすべて、ソフトウェアをより高速かつ効率的にするのに役立ちます。

結論

V8のガベージコレクションとコードオプティマイザーがどのように機能するかを理解することは、アプリケーションのパフォーマンスの鍵です。 V8はJavaScriptをネイティブアセンブリにコンパイルし、場合によっては、適切に記述されたコードがGCCコンパイル済みアプリケーションと同等のパフォーマンスを達成する可能性があります。

ご参考までに、私のApeeScapeクライアント用の新しいAPIアプリケーションは、改善の余地はありますが、非常にうまく機能しています。

Joyentは最近、V8の最新バージョンの1つを使用するNode.jsの新しいバージョンをリリースしました。 Node.js v0.12.x用に作成された一部のアプリケーションは、新しいv4.xリリースと互換性がない場合があります。ただし、アプリケーションでは、新しいバージョンのNode.js内でパフォーマンスとメモリ使用量が大幅に向上します。

.NET単体テスト:前払いで後で節約する

技術

.NET単体テスト:前払いで後で節約する
TopTrackerがフリーランサー向けの支払いソリューションをリリースし、無料のグローバル支払いを可能にする

TopTrackerがフリーランサー向けの支払いソリューションをリリースし、無料のグローバル支払いを可能にする

その他

人気の投稿
テクノロジーの冷戦:まだここにあり、まだ使用されている
テクノロジーの冷戦:まだここにあり、まだ使用されている
デザインにおけるAIの現在と未来(インフォグラフィック付き)
デザインにおけるAIの現在と未来(インフォグラフィック付き)
拡張現実vs.仮想現実vs.複合現実–入門ガイド
拡張現実vs.仮想現実vs.複合現実–入門ガイド
ApeeScape Financeは、金融の専門知識をオンデマンドで提供するための仕事の未来を支援します
ApeeScape Financeは、金融の専門知識をオンデマンドで提供するための仕事の未来を支援します
アマゾンウェブサービスで生産性を高める
アマゾンウェブサービスで生産性を高める
 
ソフトウェアデプロイメントの強化-DockerSwarmチュートリアル
ソフトウェアデプロイメントの強化-DockerSwarmチュートリアル
Android TVの開発–大画面が登場します、準備をしてください!
Android TVの開発–大画面が登場します、準備をしてください!
退屈なアイコンをオリジナルの傑作にすばやく変換する方法
退屈なアイコンをオリジナルの傑作にすばやく変換する方法
COVID-19のスモールビジネスリソース:ローン、助成金、クレジット
COVID-19のスモールビジネスリソース:ローン、助成金、クレジット
ApeeScapeがLauraGarcíaCasadiegoとGlòriaMaciàMuñozに女性のためのSTEM奨学金を授与
ApeeScapeがLauraGarcíaCasadiegoとGlòriaMaciàMuñozに女性のためのSTEM奨学金を授与
人気の投稿
  • a ++プログラミング言語
  • APIを設計する方法
  • 開発者がアプリを作成するために使用するツールのグループを最もよく表す用語は次のうちどれですか?
  • 人々のクレジットカード番号とセキュリティコード
  • ギリシャの金融危機の説明
  • ゲシュタルト心理学の基本原則
カテゴリー
ヒントとツール 財務プロセス ブランドデザイン ライフスタイル 技術 人とチーム Uiデザイン アジャイル Uxデザイン トレンド

© 2021 | 全著作権所有

apeescape2.com