在 Heroku Dyno 上增加总内存

Rising Total Memory on Heroku Dyno

我有一个网站托管在 Heroku Dyno 上,允许最大 512MB 内存。

我的网站允许用户上传 CSV 格式的原始时间序列数据,我想加载测试上传约 10 万行(大小为 3.2 MB)的 CSV 的性能。 UI 允许用户上传文件,这反过来会启动 Sidekiq 作业,将文件中的每一行导入我的数据库。它将上传的文件存储在 dyno 的 /tmp 存储下,我相信它会在每次定期重启 dyno 时被清除。

实际上一切都没有错误地完成了,并且插入了所有 100k 行。但几个小时后,我发现我的网站几乎没有响应,于是我检查了 Heroku 指标。

就在我开始上传的确切时间,内存使用量开始增长并迅速超过最大 512MB。

日志证实了这一事实 -

# At the start of the job
Aug 22 14:45:51 gb-staging heroku/web.1:  source=web.1 dyno=heroku.31750439.f813c7e7-0328-48f8-89d5-db79783b3024 sample#memory_total=412.68MB sample#memory_rss=398.33MB sample#memory_cache=14.36MB sample#memory_swap=0.00MB sample#memory_pgpgin=317194pages sample#memory_pgpgout=211547pages sample#memory_quota=512.00MB 

# ~1 hour later
Aug 22 15:53:24 gb-staging heroku/web.1:  source=web.1 dyno=heroku.31750439.f813c7e7-0328-48f8-89d5-db79783b3024 sample#memory_total=624.80MB sample#memory_rss=493.34MB sample#memory_cache=0.00MB sample#memory_swap=131.45MB sample#memory_pgpgin=441565pages sample#memory_pgpgout=315269pages sample#memory_quota=512.00MB 
Aug 22 15:53:24 gb-staging heroku/web.1:  Process running mem=624M(122.0%) 

我可以重新启动 Dyno 来解决这个问题,但我在查看指标方面没有太多经验,所以我想了解发生了什么。

有点不知道从哪里开始调查。

谢谢!

编辑: - 我们有 Heroku New Relic 插件,它也可以收集数据。令人恼火的是,New Relic 报告了同一时间段的 different/normal 内存使用值。这很常见吗?它在测量什么?

最可能的原因是:

场景 1。您处理整个文件,首先将每条记录从 CSV 加载到内存,进行一些处理,然后迭代它并存储到数据库中。

如果是这种情况,那么您需要更改实施以批量处理此文件。加载 100 条记录,处理它们,存储在数据库中,重复。您还可以查看 activerecord-import gem 以加快插入速度。

场景 2。您的脚本中有内存泄漏。也许您分批处理,但您持有对未使用对象的引用并且它们没有被垃圾收集。

您可以使用ObjectSpace模块来查找。它有一些非常有用的方法。

count_objects 将 return 散列当前在堆上创建的不同对象的计数:

ObjectSpace.count_objects
=> {:TOTAL=>30162, :FREE=>11991, :T_OBJECT=>223, :T_CLASS=>884, :T_MODULE=>30, :T_FLOAT=>4, :T_STRING=>12747, :T_REGEXP=>165, :T_ARRAY=>1675, :T_HASH=>221, :T_STRUCT=>2, :T_BIGNUM=>2, :T_FILE=>5, :T_DATA=>1232, :T_MATCH=>105, :T_COMPLEX=>1, :T_NODE=>838, :T_ICLASS=>37}

这只是一个散列,因此您可以查找特定类型的对象:

ObjectSpace.count_objects[:T_STRING]
=> 13089

您可以将此代码段插入脚本的不同点,以查看在特定时间堆上有多少对象。为了获得一致的结果,您应该在检查计数之前手动触发垃圾收集器。它将确保您只会看到活动对象。

GC.start
ObjectSpace.count_objects[:T_STRING]

另一个有用的方法是each_object,它遍历实际在堆上的所有对象:

ObjectSpace.each_object { |o| puts o.inspect }

或者你可以遍历一个 class:

的对象
ObjectSpace.each_object(String) { |o| puts o.inspect }

场景 3。 gem 或系统库中有内存泄漏。

这与之前的情况类似,但问题不在于您的代码。您也可以使用 ObjectSpace 找到它。如果在调用库方法后看到有一些对象被保留,那么这个库可能存在内存泄漏。解决方案是更新此类库。

看看这个repo。它维护具有已知内存泄漏问题的 gem 列表。如果您有此列表中的内容,我建议您尽快更新它。

现在解决您的其他问题。如果您在 Heroku 或任何其他提供商上拥有非常健康的应用程序,您将始终看到内存随着时间的推移而增加,但它应该在某个时候稳定下来。 Heroku 大约每天重启一次测功机。在您的指标上,您会看到突然下降并在 2 天左右的时间里缓慢增加。

并且 New Relic 默认显示所有实例的平均数据。您可能应该切换到仅显示来自您的 worker dyno 的数据以查看正确的内存使用情况。

最后我建议特别阅读this article about how Ruby uses memory. There are many useful tools mentioned there, derailed_benchmarks。它是由来自 Heroku 的人(当时)创建的,它是与人们在 Heroku 上遇到的最常见问题相关的许多基准的集合。