在 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 来解决这个问题,但我在查看指标方面没有太多经验,所以我想了解发生了什么。
- 如果我的工作在大约 30 分钟内完成,内存使用量可能持续增长的一些常见原因是什么?工作之前很稳定
- 有没有办法知道哪些数据存储在内存中?做一个内存转储会很棒,虽然我不知道它是否会比十六进制地址数据更多
- 我可以使用哪些其他工具来更好地了解情况?我可以通过上传另一个大文件来收集更多数据来重现这种情况
有点不知道从哪里开始调查。
谢谢!
编辑: - 我们有 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 上遇到的最常见问题相关的许多基准的集合。
我有一个网站托管在 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 来解决这个问题,但我在查看指标方面没有太多经验,所以我想了解发生了什么。
- 如果我的工作在大约 30 分钟内完成,内存使用量可能持续增长的一些常见原因是什么?工作之前很稳定
- 有没有办法知道哪些数据存储在内存中?做一个内存转储会很棒,虽然我不知道它是否会比十六进制地址数据更多
- 我可以使用哪些其他工具来更好地了解情况?我可以通过上传另一个大文件来收集更多数据来重现这种情况
有点不知道从哪里开始调查。
谢谢!
编辑: - 我们有 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 上遇到的最常见问题相关的许多基准的集合。