我有一个在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来解决这个问题,但是我没有太多查看指标的经验,所以我想了解发生了什么.
>如果我的工作在约30分钟内完成,那么内存使用量可能会持续增长的常见原因是什么?在工作之前它非常稳定
>有没有办法告诉哪些数据存储在内存中?尽管我不知道它是否会超过十六进制地址数据,但进行内存转储会很棒
>我可以使用哪些其他工具来更好地了解情况?我可以通过上传另一个大文件来收集更多数据来重现这种情况
只是有点迷失在哪里开始调查.
谢谢!
编辑: – 我们有Heroku New Relic插件,它也收集数据.令人讨厌的是,New Relic报告了同一时间段内不同/正常的内存使用价值.这是常见的吗?测量什么?
最佳答案 最有可能的原因是:
场景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.它维护了已知内存泄漏问题的宝石列表.如果您有此列表中的内容,我建议您快速更新.
现在解决你的其他问题.如果您在Heroku或任何其他提供商上拥有完全健康的应用程序,您将始终看到内存随着时间的推移而增加,但它应该在某个时候稳定下来. Heroku每天重启一次dynos.根据您的指标,您将看到突然下降和2天左右的缓慢增加.
默认情况下,New Relic显示所有实例的平均数据.您应该切换到仅显示来自工作人员dyno的数据,以查看正确的内存使用情况.
最后,我建议阅读this article关于Ruby如何使用内存.这里提到了许多有用的工具,特别是derailed_benchmarks.它是由Heroku(当时)的人创建的,它是许多与人们在Heroku上遇到的最常见问题相关的基准测试的集合.