我有一个在Heroku Dyno上托管的网站,允许最大512MB的内存。
我的网站允许用户以CSV格式上传原始时间序列数据,我想加载测试上传一行~100万行(3.2 MB大小)的CSV的性能。用户界面允许用户上传文件,该文件依次启动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报告了同一时间段内不同/正常的内存使用价值。这是常见的吗?它测量的是什么?
答案 0 :(得分:2)
最可能的原因是:
场景1 。您处理整个文件,首先将每条记录从CSV加载到内存,进行一些处理,然后迭代并存储到数据库中。
如果是这种情况,那么您需要更改实施以批量处理此文件。加载100条记录,处理它们,存储在数据库中,重复。您还可以查看activerecord-import
gem以加快插入速度。
场景2 。脚本中有内存泄漏。也许您是批量处理,但是您持有对未使用对象的引用,并且它们不是垃圾回收。
您可以使用ObjectSpace
模块找到答案。它有一些非常有用的方法。
count_objects
将返回散列,其中包含当前在堆上创建的不同对象的计数:
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 }
或者你可以迭代一个类的对象:
ObjectSpace.each_object(String) { |o| puts o.inspect }
场景3 。宝石或系统库中存在内存泄漏。
这与之前的方案类似,但问题不在于您的代码。您也可以使用ObjectSpace
找到此信息。如果在调用库方法后看到有一些对象被保留,则该库可能会有内存泄漏。解决方案是更新这样的库。
看看这个repo。它维护具有已知内存泄漏问题的gem列表。如果您有此列表中的内容,我建议您快速更新。
现在解决您的其他问题。如果您在Heroku或任何其他提供商上拥有完全健康的应用程序,您将始终看到内存随着时间的推移而增加,但它应该在某个时候稳定下来。 Heroku每天重启一次dynos。根据您的指标,您将看到突然下降和2天左右的缓慢增长。
默认情况下,New Relic显示所有实例的平均数据。您应该切换到仅显示来自工作人员dyno的数据,以查看正确的内存使用情况。
最后,我建议阅读有关Ruby如何使用内存的this article。这里提到了许多有用的工具,特别是derailed_benchmarks。它是由Heroku的人(当时)创建的,它是许多与人们在Heroku上遇到的最常见问题相关的基准测试的集合。