Nag*_*i45 12 erlang garbage-collection memory-management elixir
我有一个elixir/OTP应用程序由于内存不足问题而在生产中崩溃.导致崩溃的功能在专用进程中每6小时调用一次.运行需要几分钟(~30),看起来像这样:
def entry_point do
get_jobs_to_scrape()
|> Task.async_stream(&scrape/1)
|> Stream.map(&persist/1)
|> Stream.run()
end
Run Code Online (Sandbox Code Playgroud)
在我的本地机器上,当函数运行时,我看到大型二进制文件内存消耗不断增长:
请注意,当我在运行该函数的进程上手动触发垃圾收集时,内存消耗会显着下降,因此对于无法使用GC的几个不同进程来说肯定不是问题,而只有一个不能正常运行GC.此外,它说,每隔几分钟的过程中是非常重要的并设法GC,但有时还不够.生产服务器只有1GB内存,在GC启动之前就崩溃了.
试图解决我在愤怒中遇到Erlang的问题(参见第66-67页).一个建议是将所有大型二进制文件操作放在一次性过程中.scrape函数的返回值是包含大二进制文件的映射.因此,它们在Task.async_stream"worker"和运行该函数的进程之间共享.因此,从理论上讲,我可以把persist与一起scrape内Task.async_stream.我不想这样做,并persist通过这个过程保持呼叫同步.
另一个建议是:erlang.garbage_collect定期打电话.看起来它解决了这个问题,但感觉太过于hacky.作者也不建议这样做.这是我目前的解决方案:
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
Run Code Online (Sandbox Code Playgroud)
结果内存负载:
我不太明白书中的其他建议如何适应这个问题.
那个案子你会推荐什么?保持hacky解决方案或有更好的选择.
erlang虚拟机具有垃圾收集机制,默认情况下,该机制针对短期数据进行了优化.一个短暂的进程可能根本不会被垃圾收集,直到它死亡,并且大多数垃圾收集运行只检查新添加的项目.在完全扫描完成之前,不会再次检查在GC运行中幸存的项目.
我建议您尝试调整fullsweep_after标志.它可以通过:erlang.system_flag(:fullsweep_after, value)或使用特定过程进行全局设置:erlang.spawn_opt/4.
来自文档:
Erlang运行时系统使用分代垃圾收集方案,使用"旧堆"来存储至少一个垃圾收集的数据.当旧堆上没有更多空间时,将完成全扫描垃圾收集.
选项fullsweep_after使得可以在强制完全扫描之前指定世代集合的最大数量,即使旧堆上有空间也是如此.将数字设置为零会禁用常规收集算法,即在每个垃圾收集中复制所有实时数据.
更改fullsweep_after可能有用的几种情况:
- 如果不再使用的二进制文件将尽快丢弃.(将Number设置为零.)
- 一个主要具有短期数据的进程很少或永远不会被填满,也就是说,旧堆主要包含垃圾.要确保偶尔进行完全扫描,请将Number设置为合适的值,例如10或20.
- 在具有有限RAM和无虚拟内存的嵌入式系统中,您可能希望通过将Number设置为零来保留内存.(可以全局设置该值,请参阅erlang:system_flag/2.)
默认值为65535(除非您已通过环境变量更改了它ERL_FULLSWEEP_AFTER),因此任何较低的值都会使垃圾收集更具侵略性.
这是一个很好的阅读主题:https://www.erlang-solutions.com/blog/erlang-19-0-garbage-collector.html
| 归档时间: |
|
| 查看次数: |
876 次 |
| 最近记录: |