Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Ruby のメモリ リークの理解と修正:包括的なガイド

メモリ リークに関するこの 2 部構成のシリーズの最初の部分では、Ruby がメモリを管理する方法とガベージ コレクション (GC) がどのように機能するかを調べました。

より多くのメモリを搭載した強力なマシンを購入する余裕があり、ユーザーが気づかないほど頻繁にアプリが再起動されるかもしれませんが、メモリ使用量は重要です。

割り当てとガベージ コレクションは無料ではありません。リークが発生した場合、アプリを構築した目的を実行する代わりに、ガベージ コレクションにますます多くの時間を費やすことになります。

この投稿では、メモリ リークの発見と診断に使用できるツールを詳しく見ていきます。

続けましょう!

Ruby でのリークの発見

漏れの検出は非常に簡単です。 GC を使用できます。 、ObjectSpace 、APM ツールの RSS グラフを使用して、メモリ使用量の増加を監視します。しかし、水漏れがあることがわかっただけでは、それを解決するには十分ではありません。それがどこから来たのかを知る必要があります。生の数字だけではそれは分かりません。

幸いなことに、Ruby エコシステムには、数値にコンテキストを付加するための優れたツールがいくつかあります。 2 つは memory-profiler および derailed_benchmarks .

memory_profiler Ruby で

memory_profiler gem は、非常にシンプルな API と、割り当てられたオブジェクトのクラス、そのサイズ、割り当てられた場所を含む詳細な (少し膨大ではありますが) 割り当ておよび保持されたメモリ レポートを提供します。リーキーなプログラムに追加するのは簡単です。

 

これと同様のレポートを出力します。

 

ここには多くの情報がありますが、一般的にはallocated objects by locationretained objects by location セクションは、漏れを探すときに最も役立ちます。これらは、オブジェクトを割り当てるファイルの場所であり、割り当てられたオブジェクトの数順に並べられています。

  • allocated オブジェクトは、report 内で割り当てられた (作成された) すべてのオブジェクトです。 ブロックします。
  • retained オブジェクトは、report の終わりまでにガベージ コレクションが行われていないオブジェクトです。 ブロック。リークしたオブジェクトをより明確に確認できるように、ブロックの終了前に GC の実行を強制しました。

retained を信頼する場合は注意してください。 オブジェクトはカウントされます。これらは、リークしたコードのどの部分が report 内にあるかに大きく依存します。 ブロックします。

たとえば、an_array の宣言を移動すると、 report に入力します ブロックすると、私たちはだまされてコードに漏れがないと考えてしまう可能性があります。

 

結果として得られるレポートの上部には、多くの保持されたオブジェクトは報告されません (レポート自体のみ)。

 

derailed_benchmarks Ruby で

derailed_benchmarks gem は、主に Rails アプリを対象とした、あらゆる種類のパフォーマンス作業に非常に役立つツールのスイートです。リークを見つけるには、perf:mem_over_time を確認します。 、perf:objectsperf:heap_diff .

これらのタスクは、curl を送信することで機能します。 リクエストは実行中のアプリに送信されるため、漏れの多いプログラムにリクエストを追加することはできません。代わりに、メモリをリークするエンドポイントを備えた smallRails アプリをセットアップしてから、derailed_benchmarks をインストールする必要があります。 そのアプリで。

 
 

これで、bin/rails s でアプリを起動できるようになります。 。 curl ができるようになります リクエストごとにリークするエンドポイント。

 

derailed_benchmarks を使用できるようになりました。 リークの動作を確認してください。

perf:mem_over_time

これにより、時間の経過に伴うメモリ使用量が表示されます (watch を使用してリーク スクリプトのメモリ増加を観察した方法と同様) と ps ).

Derailed はアプリを運用モードで起動し、エンドポイント (/) に繰り返しヒットします。 デフォルトで)、メモリ使用量を報告します。成長が止まらない場合は、漏洩が発生します。

 

メモ :Derailed は、テストを実行するために実稼働モードで Rails アプリを起動します。デフォルトでは、require rails/all になります。 まず。このアプリにはデータベースがないため、この動作を DERAILED_SKIP_ACTIVE_RECORD=true でオーバーライドする必要があります。 .

このベンチマークをさまざまなエンドポイントに対して実行して、どのエンドポイント (ifany) がリークしているかを確認できます。

perf:objects

perf:objects タスクは memory_profiler を使用します

 

このレポートは、リークしたメモリが割り当てられている場所を絞り込むのに役立ちます。この例では、レポートの最後のセクション - Retained String Report — 私たちの問題が何であるかを正確に教えてくれます。

 

LeaksController から「ABC」を含む 10,000 個の文字列が漏洩しました オンライン 3. 重要なアプリでは、このレポートは大幅に大きくなり、保持したい保持文字列 (クエリ キャッシュなど) が含まれます。ただし、このセクションと他の「場所別」セクションは、リークを絞り込むのに役立ちます。

perf:heap_diff

perf:heap_diff perf:objects からのレポートがベンチマークに役立つ場合があります。 複雑すぎて、リークの原因がわかりません。

名前が示すように、perf:heap_diff 3 つのヒープ ダンプを生成し、それらの差を計算します。ダンプ間で保持されるオブジェクトのタイプとそれらを割り当てた場所を含むレポートを作成します。

 

また、2021 年の Ruby メモリ リークの追跡も読むことができます。 何が起こっているのかをよりよく理解するために。

このレポートは、漏れやすい赤ちゃん向けアプリがどこに進むべきかを正確に示しています。 diff の上部には、LeaksController から割り当てられた 999991 個の保持された文字列オブジェクトが表示されます。 3 行目。

実際の Ruby および Rails アプリのリーク

これまでに使用した例が実際のアプリに組み込まれていないことを願います。誰もメモリ リークを意図していないことを願っています。

重要なアプリでは、メモリリークを追跡するのがはるかに困難になる可能性があります。保持されたオブジェクトが必ずしも悪いわけではありません。ガベージ コレクションされたアイテムを含むキャッシュはあまり役に立ちません。

ただし、すべてのリークには共通点があります。ルートレベルのオブジェクト (クラス/グローバルなど) のどこかに、オブジェクトへの参照が保持されます。

一般的な例の 1 つは、制限やエビクション ポリシーのないキャッシュです。定義上、キャッシュに置かれたすべてのオブジェクトは永久に残るため、これによりメモリ リークが発生します。時間の経過とともに、このキャッシュがアプリのメモリを占める割合はますます増え、実際に使用される割合はますます少なくなります。

ゲームのハイスコアを取得する次のコードを考えてみましょう。それは私が過去に見たものと似ています。これは高価なリクエストであり、キャッシュが変更されると簡単に無効になる可能性があるため、キャッシュする必要があります。

 

@scores ハッシュは完全にチェックされていません。すべてのユーザーのハイスコアをすべて保持できるように成長していきますが、どちらかが多い場合は理想的ではありません。

Rails アプリでは、おそらく Rails.cache を使用することになります。 代わりに、適切な有効期限を設定します (Redis でのメモリ リークは依然としてメモリ リークです!)。

Rails 以外のアプリでは、ハッシュ サイズを制限して、最も古いアイテムや最も最近使用されていないアイテムを削除したいと考えています。 LruRedux これは素晴らしい実装です。

このリークのより巧妙なバージョンは、制限付きのキャッシュですが、そのキーのサイズは任意です。キー自体が増加すると、キャッシュも増加します。通常、これには当たりません。ただし、オブジェクトを JSON としてシリアル化し、それをキーとして使用する場合は、ユーザーの既読メッセージのリストなど、使用量に応じて増大するものをシリアル化していないかを再確認してください。

循環参照

循環参照は可能です。 ガベージコレクションされる。 Ruby のガベージ コレクションは、「マーク アンド スイープ」アルゴリズムを使用します。 Peter Zhu と Matt Valentine-House は、可変幅割り当てを紹介するプレゼンテーションで、このアルゴリズムがどのように機能するかについて優れた説明を行いました。

基本的に、マーキングとスイープの 2 つのフェーズがあります。

  • マーキングに フェーズでは、ガベージ コレクターはルート オブジェクト (クラス、グローバルなど) から開始し、それらにマークを付けてから、それらの参照オブジェクトを調べます。

    次に、参照されているすべてのオブジェクトにマークを付けます。すでにマークされている参照オブジェクトは再度参照されません。これは、参照するオブジェクトがなくなるまで続きます。つまり、参照されているすべてのオブジェクトがマークされます。

  • 次にガベージコレクターは掃除に移ります。 相。マークされていないオブジェクトはすべてクリーンアップされます。

したがって、ライブ参照を持つオブジェクトも引き続きクリーンアップできます。ルート オブジェクトが最終的にオブジェクトを参照しない限り、そのオブジェクトは収集されます。このようにして、循環参照を持つオブジェクトのクラスターも引き続きガベージ コレクションできます。

アプリケーション パフォーマンスの監視:イベント タイムラインと割り当てられたオブジェクトのグラフ

このシリーズの最初の部分で述べたように、本番レベルのアプリは、何らかの形式の Application PerformanceMonitoring (APM) を使用する必要があります。

自分でロールするなど、多くのオプションが利用可能です (大規模なチームにのみ推奨)。 APM から得るべき重要な機能の 1 つは、アクション (またはバックグラウンド ジョブ) が行う割り当ての数を確認できることです。優れた APMtools はこれを分析し、割り当てがどこから来たのか (コントローラー、ビューなど) を洞察します。

これは、「イベント タイムライン」などと呼ばれることがよくあります。 APM でタイムラインをさらに細分化するカスタム コードを作成できる場合はボーナス ポイント。

Rails コントローラの次のコードを考えてみましょう。

 

APM によって報告されると、「イベント タイムライン」は次の AppSignal のスクリーンショットのようになります。

Ruby のメモリ リークの理解と修正:包括的なガイド

これをインストルメント化することで、コードのどの部分がタイムラインで割り当てを行っているかを確認できるようになります。実際のアプリでは、おそらくコードからはそれほど明らかではないでしょう 😅

 

これも AppSignal からの、計測されたイベント タイムラインの例です。

Ruby のメモリ リークの理解と修正:包括的なガイド

どこに計測器を設置するかを知るのは、多くの場合、把握するのが難しい場合があります。アプリケーションのコードを実際に理解することに代わるものはありませんが、「匂い」として機能するシグナルがいくつかあります。

APM で時間の経過とともに GC の実行または割り当てが明らかになった場合は、スパイクを探して、ヒットしている特定のエンドポイントまたは実行中の特定のバックグラウンド ジョブと一致するかどうかを確認できます。 AppSignal の Ruby VM マジック ダッシュボードの別の例を次に示します。

Ruby のメモリ リークの理解と修正:包括的なガイド

このように割り当てを確認することで、メモリの問題を調べるときに検索対象を絞り込むことができます。これにより、memory_profiler などのツールの使用がはるかに簡単になります。 および derailed_benchmarks

割り当てや GC 統計追跡など、AppSignal の Ruby gem への最新の追加機能についてお読みください。

まとめ

この投稿では、memory_profiler などのメモリ リークの発見と修正に役立ついくつかのツールについて詳しく説明しました。 、derailed_benchmarksperf:mem_over_timeperf:objectsperf:heap_diff 、AppSignal のイベント タイムラインと割り当てられたオブジェクトのグラフ。

この投稿とパート 1 が、Ruby アプリのメモリ リークの診断と整理に役立つと思っていただければ幸いです。

使用したツールについて詳しくは、こちらをご覧ください。

  • memory_profiler
  • derailed_benchmarks
  • 漏洩のある Rails アプリ

追加の詳細な資料:

  • GC モジュールのドキュメント
  • ObjectSpace モジュールのドキュメント
  • ガベージ コレクションの詳細
  • 可変幅の割り当て

コーディングを楽しんでください!

追記Ruby Magic の投稿を報道後すぐに読みたい場合は、Ruby Magic ニュースレターを購読して、投稿を 1 つも見逃さないようにしてください。


  1. システム負荷をマスターする:負荷平均を読み取って解釈する方法

    2024 年 2 月 25 日に更新され、スクリーンショットの例と古いリンクが更新されました。 システムの負荷を監視することは、アプリをスムーズに実行し続けるために不可欠です。 AppSignal のホスト メトリクスは、 システムの負荷平均に関する洞察を提供します。 、複数の時間枠にわたってシステムにどれだけの負荷がかかっているかを示します。 top などのツールを使用してこのメトリクスを確認することもできます。 、uptime 、および w : しかし、「負荷平均」とは正確には何を意味し、これらの数値をどのように解釈すればよいのでしょうか?この投稿では、システム負荷を分析し、負荷平均

  2. すべてのWeb開発者が機械学習を検討する必要がある理由

    私にはまだ子供がいませんが、子供がいるときは、2つのことを子供に学んでもらいたいです。 パーソナルファイナンス 機械学習 特異点が近いと信じているかどうかにかかわらず、世界がデータで実行されていることは否定できません。そのデータがどのように知識に変換されるかを理解することは、最近の成人にとって重要であり、開発者にとってはさらに重要です。 これは、フルスタックのRuby開発者が機械学習(ML)にアクセスできるようにするシリーズの最初の記事です。自由に使えるMLツールを理解することで、利害関係者がより良い意思決定を行えるように支援できます。今後の記事では、個々のテクニックと実際の例に焦点