我有一个 elixir/OTP 应用程序由于内存不足问题而在生产中崩溃。导致崩溃的函数每 6 小时在一个专用进程中调用一次。运行需要几分钟 (~30),如下所示:
def entry_point do
get_jobs_to_scrape()
|> Task.async_stream(&scrape/1)
|> Stream.map(&persist/1)
|> Stream.run()
end
在我的本地机器上,当函数运行时,我看到大型二进制文件内存消耗不断增长:
请注意,当我在运行该函数的进程上手动触发垃圾收集时,内存消耗会显着下降,因此对于几个不同的进程无法进行 GC 肯定不是问题,但只有一个进程无法正确 GC。此外,重要的是要说每隔几分钟进程 设法GC,但有时还不够。生产服务器只有 1GB RAM,并且在 GC 启动之前就崩溃了。
试图解决我遇到的问题Erlang in Anger (见第 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
以及由此产生的内存负载:
我不太明白书中的其他建议如何解决这个问题。
在这种情况下你会推荐什么?保留hacky解决方案或有更好的选择。
最佳答案
erlang 虚拟机具有垃圾收集机制,默认情况下,该机制针对短期数据进行了优化。一个短暂的进程在死亡之前可能根本不会被垃圾收集,并且大多数垃圾收集运行只检查新添加的项目。在 GC 运行中幸存下来的项目将不会被再次检查,直到完成完整的扫描。
我建议您尝试调整 fullsweep_after 标志。可以通过 :erlang.system_flag(:fullsweep_after, value)
全局设置或使用 :erlang.spawn_opt/4
用于您的特定过程.
从文档:
The Erlang runtime system uses a generational garbage collection scheme, using an "old heap" for data that has survived at least one garbage collection. When there is no more room on the old heap, a fullsweep garbage collection is done.
Option fullsweep_after makes it possible to specify the maximum number of generational collections before forcing a fullsweep, even if there is room on the old heap. Setting the number to zero disables the general collection algorithm, that is, all live data is copied at every garbage collection.
A few cases when it can be useful to change fullsweep_after:
- If binaries that are no longer used are to be thrown away as soon as possible. (Set Number to zero.)
- A process that mostly have short-lived data is fullsweeped seldom or never, that is, the old heap contains mostly garbage. To ensure a fullsweep occasionally, set Number to a suitable value, such as 10 or 20.
- In embedded systems with a limited amount of RAM and no virtual memory, you might want to preserve memory by setting Number to zero. (The value can be set globally, see erlang:system_flag/2.)
默认值为 65535(除非您已经通过环境变量
ERL_FULLSWEEP_AFTER
对其进行了更改),因此任何较低的值都会使垃圾收集更加激进。这是一个很好的阅读主题:https://www.erlang-solutions.com/blog/erlang-19-0-garbage-collector.html
关于memory-management - 解决大型二进制文件泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43613027/