如何分析Ruby on Rails内存泄漏?

Ran*_*anH 5 ruby memory-leaks rubygems ruby-on-rails puma

我正在处理一个遗留系统(Ruby 2.7.6),该系统存在内存泄漏问题,这导致以前的开发人员使用puma worker Killer,通过每 30 分钟重新启动一次进程来克服内存问题。随着流量的增加,我们现在需要增加实例数量,将 30 分钟的杀掉率降低到 20 分钟。

我们想要调查此内存泄漏的根源,这显然源于我们的许多 Gem 依赖项之一(以前的开发人员提供的信息)。

该系统位于AWS(Elastic Beanstalk)上,但也可以在docker上运行。谁能推荐一个好的工具并指导如何找到内存泄漏的根源?谢谢

** 更新:我使用了迷你分析器,并拍摄了一些内存快照来查看服务器上大约 100 个请求的影响,[之前、期间、之后]

从输出来看,Ruby中似乎没有内存泄漏,但内存使用量确实增加并保持不变,尽管我们似乎没有使用......

KiB Mem:总计 2007248,免费 628156,使用 766956,612136 buff/cache KiB 交换:总计 2097148,免费 2049276,使用 47872。1064852 可用内存

分配总量:115227 字节(1433 个对象) 保留总量:21036 字节(147 个对象)

gem 分配的内存

 33121  activesupport-6.0.4.7
 21687  actionpack-6.0.4.7
 14484  activerecord-6.0.4.7
 12582  var/app
  9904  ipaddr
  6957  rack-2.2.4
  3512  actionview-6.0.4.7
  2680  mysql2-0.5.3
  1813  rack-mini-profiler-3.0.0
  1696  audited-5.0.2
  1552  concurrent-ruby-1.1.10
Run Code Online (Sandbox Code Playgroud)

期间

KiB Mem:总计 2007248,免费 65068,使用 1800424,141756 buff/cache KiB 交换:总计 2097148,免费 2047228,使用 49920。
58376 可用内存

分配总量:225272583 字节(942506 个对象) 保留总量:1732241 字节(12035 个对象)

gem 分配的内存

106497060 maxmind-db-1.0.0
58308032 psych
38857594 user_agent_parser-2.7.0
4949108 activesupport-6.0.4.7
3967930 其他
3229962 activerecord-6.0.4.7
2154670rack-2.2.4
1467383 操作pack-6.0.4.7
1336204 activemodel-6.0.4.7

后:

KiB Mem:总计 2007248,免费 73760,使用 1817688,115800 buff/cache KiB 交换:总计 2097148,免费 2032636,使用 64512。
54448 可用内存

分配总量:109563 字节(1398 个对象) 保留总量:14988 字节(110 个对象)

gem 分配的内存

 29745  activesupport-6.0.4.7
 21495  actionpack-6.0.4.7
 13452  activerecord-6.0.4.7
 12502  var/app
  9904  ipaddr
  7237  rack-2.2.4
  3128  actionview-6.0.4.7
  2488  mysql2-0.5.3
  1813  rack-mini-profiler-3.0.0
  1360  audited-5.0.2
  1360  concurrent-ruby-1.1.10
Run Code Online (Sandbox Code Playgroud)

那么泄漏可能在哪里呢?是彪马吗?

Mys*_*yst 3

从问题中的统计数据来看,大多数对象都被内存分配器正确释放。

然而,当您进行大量重复分配时,系统malloc有时(并且经常)可以保留内存而不将其释放给系统(Ruby 不知道该内存被视为“空闲”)。

这样做有两个主要原因:

  1. 最重要的是:堆碎片(分配器无法释放内存,也无法将其中的一部分用于将来的分配)。

  2. 系统的内存分配器知道它可能很快会再次需要该内存(这与可以释放且不会受到碎片影响的内存部分有关)。

这可以通过尝试将系统的内存分配器替换为适合您的特定需求的分配器来解决(即,例如jamalloc此处和此处建议的以及此处询问分配器)。

在使用 C 扩展时,您还可以尝试使用具有自定义内存分配器的 gem(iodinegem 可以这样做,但您也可以让其他 gem 也这样做)。

这种方法应该有助于缓解这个问题,但事实是你的一些宝石看起来内存不足......我的意思是......:

  • gem是否maxmind-db使用了 106,497,060 字节 (106MB) 的内存,或者它是否分配了那么多的对象?

  • 为什么psych这么饿?数据和 YAML 之间是否存在可以跳过的往返?

  • 似乎有很多用户代理字符串同时存储......(宝石user_agent_parser)......也许你可以缓存这些字符串而不是有很多重复项。例如,您可以创建一个Set这些字符串,并将每个 String 对象替换为 Set 中的对象。这样,相等的字符串将指向同一个对象(防止某些对象重复并释放一些内存)。

是彪马吗

可能不会。

虽然我是 Web 服务器的作者iodine,但我真的很喜欢 Puma 团队多年来所做的工作,并认为它是一个超级可靠的服务器。我真的怀疑泄漏是来自服务器,但你可以随时切换并看看会发生什么。


回复:Linux 报告和 Ruby 分析器之间的区别

区别在于malloc“空闲”内存所持有的内存不会返回到系统,但 Ruby 并不知道。

Ruby 分析器测试 Ruby 分配的内存(“实时”内存,如果您愿意的话)。他们可以访问分配的对象数量以及这些对象所持有的内存。

malloc库不是 Ruby 的一部分。它是 Ruby 所在的 C 运行时库的一部分。

mallocRuby 没有使用为进程分配的内存。该内存要么等待使用(保留以供malloc将来使用),要么等待释放回系统(或者暂时碎片化并丢失)。

Ruby 使用的内容和持有的内容之间的差异malloc应该可以解释 Linux 报告和 Ruby 分析报告之间的差异。

有些 gem 可能会使用自己定制的内存分配器(即iodine这样做)。malloc它们的行为与它们所持有的内存不会显示在 Ruby 分析器中(至少不完全显示)的意义上相同。