【发布时间】:2017-09-22 14:34:06
【问题描述】:
我有一个 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 内存,在 GC 启动之前就崩溃了。
试图解决我遇到的问题Erlang in Anger(见第 66-67 页)。一个建议是将所有大型二进制文件操作放在一次性过程中。 scrape 函数的返回值是一个包含大型二进制文件的映射。因此,它们在Task.async_stream“workers”和运行该函数的进程之间共享。所以,理论上,我可以将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
以及由此产生的内存负载:
我不太明白书中的其他建议如何解决问题。
在这种情况下你会推荐什么?保留老套的解决方案,否则会有更好的选择。
【问题讨论】:
-
您是否考虑将二进制文件保存在 ETS 中?如果您可以可靠地释放它们,那么您就可以有效地进行手动分配并规避 BEAM GC 中的任何麻烦。 OTOH,如果这是一个合适的雪花,也许手动调用GC解决方案就足够了?
-
有趣的想法!让我们看看我是否理解:让scraper函数将数据放入ETS,然后将
persist函数映射到表中的数据上,对吗?尽管如此,Task.async_stream工作进程将引用大型二进制文件,而主进程将具有相同的引用,用于从persist函数内的 ETS 中获取数据,问题仍然存在。 -
好吧,根据
Task.async_stream文档,“每个可枚举项都作为参数传递给函数并由其自己的任务处理”,因此如果您的每个scrape调用只需构建二进制文件,将其存储在 ETS 中,并返回一个参考,然后你可能会做生意。 -
是的,但是我需要持久化数据,并且需要同步进行。所以我需要在
Task.async_stream以外的某个地方从 ETS 读取它。
标签: memory-management garbage-collection erlang elixir