2017-04-25 2 views
12

メモリ不足の問題が原因で本番でクラッシュするエリクシール/ OTPアプリケーションがあります。クラッシュの原因となる機能は、専用のプロセスで6時間ごとに呼び出されます。これは、実行に数分(〜30)を取り、次のようになります。私のローカルマシン上大きなバイナリのリークを解決する

def entry_point do 
    get_jobs_to_scrape() 
    |> Task.async_stream(&scrape/1) 
    |> Stream.map(&persist/1) 
    |> Stream.run() 
end 

を機能の実行時に、私は大きなバイナリメモリ消費量の一定の成長を参照してください。

observer memory usage shows constant memory growth of large binaries

を関数を実行するプロセスでガベージコレクションを手動で起動すると、メモリ消費量が大幅に減少するため、GCには対応していない複数のプロセスでは問題はありませんが、GCが正しくないプロセスでは問題はありません。さらに、数分おきにプロセスがGCに管理されますが、時にはそれだけでは不十分だと言うことは重要です。プロダクションサーバーには1GBのRAMしかなく、GCが起動する前にクラッシュします。

問題を解決しようとしましたErlang in Anger(66-67ページを参照)に出くわしました。 1つの提案は、大きなバイナリ操作のすべてを一度のプロセスで行うことです。 scrape関数の戻り値は、大きなバイナリを含むマップです。したがって、それらは "Task.async_stream"ワーカー "とその機能を実行するプロセスの間で共有されます。だから理論的にはpersistscrapeと一緒にTask.async_streamに入れることができます。私はそうしないことを好むし、persistへの呼び出しをプロセスを通して同期させておくことを望む。

もう1つの提案は、:erlang.garbage_collectを定期的に呼び出すことです。それは問題を解決するように見えるが、あまりにもハッキリと感じる。著者はまたそれをお勧めしません。

def entry_point do 
    my_pid = self() 
    Task.async(fn -> periodically_gc(my_pid) end) 
    # The rest of the function as before... 
end 

defp periodically_gc(pid) do 
    Process.sleep(30_000) 
    if Process.alive?(pid) do 
    :erlang.garbage_collect(pid) 
    periodically_gc(pid) 
    end 
end 

、得られたメモリ負荷:私はかなりの本の中で他の提案は、問題をどのように適合するか理解していない

observer memory usage after GC hack

ここに私の現在のソリューションです。

この場合、あなたは何をお勧めしますか?ハッキーな解決策を保つか、より良い選択肢があります。

+0

あなたはETSでバイナリを保存することを考えましたか?それらを確実にリリースできる場合は、効果的に手作業による割り当てを行い、BEAMのGCの不快さを回避します。 OTOH、これが適切なスノーフレークであれば、手動コールGCソリューションで十分でしょうか? – cdegroot

+0

興味深いアイデア!私が理解しているかどうかを見てみましょう:スクレーパー機能を使ってデータをETSに入れ、テーブル内のデータに 'persist'関数をマップするのは正しいのですか?それでも、 'Task.async_stream'ワーカーは大きなバイナリへの参照を持ち、メインプロセスは' persist'関数の中でETSからフェッチするための同じ参照を持ち、問題は残っています。 – Nagasaki45

+0

まあ、 "各列挙可能なアイテムは、関数の引数として渡され、独自のタスクで処理されます"ということは、 'Task.async_stream'のドキュメントにあるので、' scrape'コールのそれぞれがバイナリをビルドするだけであれば、ETS 、参照を返すと、あなたはビジネスにいるかもしれません。 – cdegroot

答えて

6

erlang仮想マシンには、デフォルトでは短命のデータ用に最適化されたガベージコレクションメカニズムがあります。短命のプロセスは、死ぬまでガベージコレクションされないことがあります。ほとんどのガベージコレクション実行では、新しく追加された項目だけがチェックされます。 GC実行から生き残ったアイテムは、完全なスイープが完了するまで再びチェックされません。

fullsweep_afterフラグを調整してみることをお勧めします。グローバルには:erlang.system_flag(:fullsweep_after, value)、または特定のプロセスに対しては:erlang.spawn_opt/4を使用して設定できます。ドキュメントから

Erlangのランタイムシステムは、少なくとも1つのガベージコレクションを生き抜いたデータのための「古いヒープ」を使用して、世代別ガベージコレクションスキームを使用しています。古いヒープ上に余分なスペースがないときは、ガーベジコレクションが一杯です。

オプションfullsweep_afterを指定すると、古いヒープに空きがある場合でも、フルスウィープを強制する前に世代別コレクションの最大数を指定できます。数字をゼロに設定すると、一般的な収集アルゴリズムが無効になります。つまり、すべてのライブデータがすべてのガベージコレクションにコピーされます。 fullsweep_after変更すると便利です

いくつかの例:

  • もはや使用されているバイナリができるだけ早く捨てることになっている場合

    。 (数字をゼロに設定)
  • ほとんどの場合、短命のデータが残っているプロセスは、ほとんどまたはまったくフルスワップされません。古いヒープにはほとんどがゴミが含まれています。フルスウィープを確実に行うには、Numberを10または20などの適切な値に設定します。
  • 制限されたRAM容量と仮想メモリを持たない組み込みシステムでは、Numberをゼロに設定してメモリを保持します。 (値をグローバルに設定することができ、参照のerlang:system_flag/2。)
  • デフォルト値は(すでに環境変数 ERL_FULLSWEEP_AFTERを通してそれを変更しない限り)65535、そのいずれかの低い値は、ゴミを行いますです

より積極的なコレクション。

これは、件名に良い読み物です:https://www.erlang-solutions.com/blog/erlang-19-0-garbage-collector.html

関連する問題