ホームページ > ウェブフロントエンド > jsチュートリアル > Node.js での高いメモリ使用量を追跡する

Node.js での高いメモリ使用量を追跡する

Patricia Arquette
リリース: 2024-12-17 00:40:25
オリジナル
821 人が閲覧しました

この記事では、Node.js での高いメモリ使用量を追跡して修正する私のアプローチを共有します。

コンテンツ

  • コンテキスト
  • アプローチ
    • コードを理解する
    • 問題を個別に再現します
    • ステージング サービスからプロファイルをキャプチャする
    • 修正を確認します
  • 結果
  • 結論

コンテクスト

最近、「ライブラリ x のメモリ リーク問題を修正する」というタイトルのチケットを受け取りました。説明には、高メモリ使用量に悩まされ、最終的には OOM (メモリ不足) エラーでクラッシュする十数のサービスを示す Datadog ダッシュボードが含まれており、それらはすべて共通の x ライブラリを持っていました。

私がコードベースを知ったのはつい最近 (2 週間以内) で、それがこのタスクをやりがいのあるものにし、共有する価値のあるものでもありました。

私は 2 つの情報をもとに作業を開始しました:

  • すべてのサービスで使用されているライブラリがあり、メモリ使用率が高くなります。これには redis が関係しています (ライブラリの名前には redis が含まれています)。
  • 影響を受けたサービスのリスト。

以下はチケットにリンクされたダッシュボードです:

Tracking down high memory usage in Node.js

サービスは Kubernetes 上で実行されており、サービスがメモリ制限に達し、クラッシュ (メモリを再利用) して再び開始するまで、時間の経過とともにメモリが蓄積されていることは明らかでした。

アプローチ

このセクションでは、私が目の前のタスクにどのように取り組み、メモリ使用率が高い原因を特定し、後でそれを修正したかを共有します。

コードを理解する

私はコードベースにあまり慣れていなかったので、まずコード、問題のライブラリが何をするのか、どのように使用されるのかを理解したいと思いました。このプロセスにより問題を特定しやすくなるだろうと期待していました。残念ながら、適切なドキュメントはありませんでしたが、コードを読み、サービスがライブラリをどのように利用しているかを検索することで、その要点を理解することができました。これは、Redis ストリームをラップし、イベントの生成と消費に便利なインターフェイスを公開するライブラリでした。コードを読むのに 1 日半かかりましたが、コードの構造と複雑さ (多くのクラス継承と rxjs が不慣れでした) のため、すべての詳細とデータの流れを把握することはできませんでした。

そこで、読むのを一時停止し、実際のコードを観察しながら問題を特定し、テレメトリ データを収集することにしました。

問題を個別に再現する

さらなる調査に役立つ利用可能なプロファイリング データ (連続プロファイリングなど) がなかったため、問題をローカルで再現し、メモリ プロファイルのキャプチャを試みることにしました。

Node.js でメモリ プロファイルをキャプチャする方法をいくつか見つけました。

  • ヒープ スナップショットの使用
  • ヒープ プロファイラーの使用
  • パフォーマンスプロファイリング JavaScript
  • クリニック.js

どこを見ればよいのか手がかりがなかったので、ライブラリの中で最も「データ集約型」と思われる部分、つまり Redis ストリームのプロデューサーとコンシューマーを実行することにしました。 Redis ストリームからデータを生成および消費する 2 つの単純なサービスを構築し、メモリ プロファイルをキャプチャして結果を経時的に比較する作業を進めました。残念ながら、サービスに負荷をかけてプロファイルを比較してから数時間後、2 つのサービスのいずれでもメモリ消費量の違いを見つけることができず、すべてが正常に見えました。このライブラリは、Redis ストリームと対話するためのさまざまなインターフェイスと方法を多数公開していました。特に実際のサービスに関するドメイン固有の知識が限られていると、問題を再現するのは予想よりも複雑になることが明らかになりました。

そこで問題は、メモリ リークを捕捉する適切な瞬間と条件をどのように見つけられるかということでした。

ステージング サービスからプロファイルをキャプチャする

前述したように、メモリ プロファイルをキャプチャする最も簡単で便利な方法は、影響を受ける実際のサービスに対して継続的にプロファイリングを行うことですが、私にはこのオプションはありませんでした。私は、追加の労力を必要とせずに必要なデータをキャプチャできるよう、少なくともステージング サービスを活用する方法 (同じように大量のメモリ消費に直面していました) を調査し始めました。

私は、Chrome DevTools を実行中のポッドの 1 つに接続し、経時的にヒープ スナップショットをキャプチャする方法を探し始めました。メモリ リークがステージングで発生していることはわかっていたので、そのデータをキャプチャできれば、少なくともいくつかのホットスポットを特定できるだろうと期待していました。驚いたことに、まさにそれを行う方法があります。

これを行うためのプロセス

  • ポッド上のノード プロセスに SIGUSR1 シグナルを送信して、ポッド上の Node.js デバッガーを有効にします。
kubectl exec -it <nodejs-pod-name> -- kill -SIGUSR1 <node-process-id>
ログイン後にコピー

Node.js シグナルの詳細については、シグナル イベントを参照してください

成功した場合は、サービスからのログが表示されるはずです。

Debugger listening on ws://127.0.0.1:9229/....
For help, see: https://nodejs.org/en/docs/inspector
ログイン後にコピー
  • 次のコマンドを実行して、デバッガがリッスンしているポートをローカルで公開します。
kubectl port-forward <nodejs-pod-name> 9229
ログイン後にコピー
  • 前の手順で有効にしたデバッガーに Chrome Devtools を接続します。 chrome://inspect/ にアクセスすると、ターゲットのリストに Node.js プロセスが表示されるはずです。

Tracking down high memory usage in Node.js

そうでない場合は、ターゲット検出設定が適切に設定されていることを確認してください

Tracking down high memory usage in Node.js

これで、時間をかけてスナップショットのキャプチャを開始し (期間はメモリ リークが発生するまでに必要な時間によって異なります)、比較できるようになります。 Chrome DevTools は、これを行うための非常に便利な方法を提供します。

メモリ スナップショットと Chrome 開発ツールの詳細については、ヒープ スナップショットの記録

をご覧ください。

スナップショットを作成すると、メインスレッドの他のすべての作業が停止します。ヒープの内容によっては、1 分以上かかる場合もあります。スナップショットはメモリに組み込まれるため、ヒープ サイズが 2 倍になり、メモリ全体がいっぱいになってアプリがクラッシュする可能性があります。

運用環境でヒープ スナップショットを取得する場合は、アプリケーションの可用性に影響を与えることなく、取得元のプロセスがクラッシュしても問題がないことを確認してください。

Node.js ドキュメントより

私の場合に戻り、比較するために 2 つのスナップショットを選択し、デルタで並べ替えると、以下に示すような結果が得られました。

Tracking down high memory usage in Node.js

最大のプラスの差分が文字列コンストラクターで発生していることがわかります。これは、サービスが 2 つのスナップショットの間に大量の文字列を作成したが、それらはまだ使用されていることを意味します。ここで問題となるのは、それらがどこで作成され、誰が参照しているのかということでした。キャプチャされたスナップショットにリテイナーと呼ばれるこの情報が含まれていて良かったです。

Tracking down high memory usage in Node.js

スナップショットと縮小することのない文字列リストを調べていると、ID に似ている文字列のパターンに気づきました。それらをクリックすると、それらを参照しているチェーン オブジェクト (別名リテイナー) が表示されます。ライブラリコードから認識できるクラス名からsentEventsという配列でした。さあ、犯人が決まりました。この時点では公開されていないと私が推測していた ID のリストは増え続けるばかりです。時間をかけて大量のスナップショットを撮影しましたが、大きなプラスのデルタを持つホットスポットとして繰り返し出現したのは、この 1 つの場所でした。

修正を確認する

この情報により、コード全体を理解しようとするのではなく、配列の目的、いつデータが設定され、いつクリアされるかに焦点を当てる必要がありました。コードが項目を配列にプッシュしていた場所が 1 か所あり、コードが項目をポップアウトしていた場所が 1 か所あり、修正の範囲が狭まっています。

配列が空になるべきときに空になっていなかったと考えても問題ありません。コードの詳細は省略しますが、基本的に何が起こっているかは次のとおりです:

  • ライブラリは、イベントの消費、生成、またはイベントの生成と消費のためのインターフェイスを公開していました。
  • イベントの消費と生成の両方を行っていた場合、イベントをスキップして再消費しないように、プロセス自体が生成したイベントを追跡する必要がありました。 sendEvents は生成時に設定され、消費時にメッセージがスキップされるとクリアされます。

これがどこへ向かうかわかりますか? ?サービスがイベントを生成するためだけにライブラリを使用していた場合でも、sentEvents にはすべてのイベントが設定されますが、それをクリアするためのコード パス (コンシューマ) がありませんでした。

プロデューサー、コンシューマー モードでのみイベントを追跡するようにコードにパッチを適用し、ステージングにデプロイしました。ステージング負荷があったとしても、このパッチが高いメモリ使用量の削減に役立ち、いかなるリグレッションも引き起こさないことは明らかでした。

結果

パッチが運用環境にデプロイされると、メモリ使用量が大幅に削減され、サービスの信頼性が向上しました (OOM がなくなりました)。

Tracking down high memory usage in Node.js

嬉しい副作用として、同じトラフィックを処理するのに必要なポッドの数が 50% 削減されました。

Tracking down high memory usage in Node.js

結論

これは、Node.js でのメモリ問題の追跡と、利用可能なツールにさらに慣れることに関して、私にとって素晴らしい学習機会でした。

各ツールの詳細については別の投稿に値するため、ここでは触れない方がよいと考えましたが、このトピックについて詳しく知りたい人や、同様の問題に直面している人にとって、これが良い出発点となることを願っています。

以上がNode.js での高いメモリ使用量を追跡するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート