【问题标题】:Solving large binaries leak解决大型二进制文件泄漏
【发布时间】: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”和运行该函数的进程之间共享。所以,理论上,我可以将persistscrape 放在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


【解决方案1】:

erlang 虚拟机具有垃圾收集机制,默认情况下,该机制针对短期数据进行了优化。一个短暂的进程在死亡之前可能根本不会被垃圾收集,并且大多数垃圾收集运行只检查新添加的项目。在完全扫描完成之前,不会再次检查在 GC 运行中幸存的项目。

我建议您尝试调整 fullsweep_after 标志。它可以通过:erlang.system_flag(:fullsweep_after, value) 全局设置或使用:erlang.spawn_opt/4 为您的特定进程设置。

来自文档:

Erlang 运行时系统使用分代垃圾收集方案,使用“旧堆”存储至少在一次垃圾收集中幸存下来的数据。当旧堆上没有更多空间时,将完成一次全扫描垃圾回收。

选项 fullsweep_after 可以在强制进行全扫描之前指定代收集的最大数量,即使旧堆上有空间。将数字设置为零会禁用一般收集算法,即在每次垃圾收集时复制所有实时数据。

更改 fullsweep_after 可能有用的几种情况:

  • 如果不再使用的二进制文件要尽快丢弃。 (将数字设置为零。)
  • 一个主要有短期数据的进程很少或从不完全扫描,也就是说,旧堆主要包含垃圾。为确保偶尔进行全扫描,请将 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

【讨论】:

    猜你喜欢
    • 2018-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-02
    • 1970-01-01
    • 2016-08-19
    相关资源
    最近更新 更多