Sidekiq导致rails应用程序内存膨胀



我有一个rails应用程序,sidekiq工作人员在后台执行进程,最初有大约30个线程来执行任务。我们发现这导致了高内存使用率,减少了工作线程数也减少了内存膨胀,但我不明白为什么。谁能解释一下吗?

从快速谷歌上看,你似乎遇到了内存碎片,这对Sidekiq来说很正常。你在使用类变量吗?您的代码在执行期间是否需要类?您正在执行多少AR查询?许多AR查询创建了数千个(如果不是数百万的话(对象并将其丢弃

你的代码线程安全吗Ruby中的内存分配包括三层:解释器、操作系统内存分配器库和内核。Ruby在称为Ruby heap pages的内存领域中组织对象,并将Ruby堆页面划分为大小相等的槽,其中一个对象占据一个槽。这些插槽要么被占用,要么是空闲的,当Ruby分配一个新对象时,它会尝试占用一个空闲插槽。如果没有可用的插槽,它将分配一个新的堆页面。每个插槽都有一个字节限制,如果对象高于字节限制,则会在堆页中放置指向该对象的指针。

内存碎片是发生这些分配的时候,并且在高线程应用程序中非常频繁。当垃圾回收发生时,堆页面将清除的插槽标记为空闲,并允许重用该插槽。如果堆页面中的所有对象都标记为空闲,那么堆页面将被释放回内存分配器,并可能释放回内核。Ruby并没有承诺对所有对象进行垃圾收集,那么当不是所有空闲插槽都被释放,并且有大量堆页面被部分填充时,会发生什么呢?堆页面有可供Ruby分配的插槽,但内存分配器仍然认为它们已分配内存。内存分配器不会一次释放整个操作系统堆,并且可以释放任何单独的操作系统页面,只要为所述页面释放了所有分配即可。

因此,线程处理是一个问题,因为每个线程都试图同时从同一个操作系统堆中分配内存,并争夺访问权限。一次只能有一个线程执行分配,这会降低多线程内存分配性能。内存分配器试图通过创建多个操作系统堆来优化性能,并试图将不同的线程分配给自己的操作系统堆。

如果你可以访问ruby 2.7,你可以调用GC.compact来对抗它。它提供了一种方法来查找可以在Ruby中移动的对象,并对它们进行浓缩,减少了堆页面的使用量。在消耗的插槽之间通过GC释放的空插槽现在可以进行压缩。例如,您有一个有四个插槽的堆页面,而只有插槽一、插槽二和插槽四分配了对象。紧凑调用将评估对象四是否是可移动对象,并将其分配给槽三和与该对象相关的任何引用,然后重定向到槽三。插槽4现在放置有一个T_MOVED对象,最后的GC用T_EMPTY替换T_MOVED对象,准备进行分配。

就我个人而言,我不会只依赖GC.compact,你可以做简单的MALLOC_ARENA_MAX技巧,但阅读源文档,你应该找到合适的解决方案。

最新更新