活动记录不释放内存



我有一个导出作业,从我们的MySQL数据库导出大量数据。随着数据的增长,我注意到用于此的 sidekiq 作业占用了太多内存。服务器有32GB,导出完成后需要28GB。当我停止 sidekiq 进程时,内存使用量下降到 8GB。

我已经按照这里的指南 https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting

  • 通过使用MALLOC_ARENA_MAX=2防止内存碎片
  • 清除查询缓存ActiveRecord::Base.connection.clear_query_cache

我使用的是Ruby 2.6.5p114,并试图通过在生产环境中创建一个新的 rails 应用程序并使用我的数据库作为后端来隔离问题:

gem install rails --version 5.2.4.3
rails new debug -d mysql

我创建了一个空模型,以避免代码中可能导致问题的自定义方法:

class Variant < ApplicationRecord
end

此脚本仅从数据库加载 1 个 Mio 对象并打印内存使用情况:

# memory.rb
def memory
(`ps -o rss= -p #{Process.pid}`.to_i.to_f / 1024).to_s + " MB"
end
def load_variants
puts "load_variants..."
Variant.uncached do
variants = Variant.limit(1_000_000).to_a
puts "variant.count: #{variants.count}"
end
end
puts memory
load_variants
puts memory
puts "GC.start..."
GC.start
puts memory
# second run
load_variants
puts memory
puts "GC.start..."
GC.start
puts memory

这是输出:

root@6e79d7a97d9c:/usr/src/debug# rails r memory.rb
76.93359375 MB
load_variants...
variant.count: 1000000
2436.3125 MB
GC.start...
2421.046875 MB
load_variants...
variant.count: 1000000
2436.3828125 MB
GC.start...
2436.3984375 MB
  1. 它始于76.93359375 MB
  2. 加载 1 个 Mio 对象后,内存增加到2436.3125 MB
  3. 垃圾回收将内存减少到2421.046875 MB但我预计下降幅度要高得多!
  4. 有趣的是,第二次运行只会增加内存2436.3828125 MB
  5. 最后一个GC.start以某种方式将内存增加一点以2436.3984375 MB

所以我想知道这是怎么回事?ActiveRecord 中一定有一些我不知道的东西,我想了解这一切是如何工作的,以及为什么没有释放内存。

按照这个逻辑,每个读取数据的请求都应该增加内存,但我假设在请求-响应周期中使用时会有所不同。

在Ruby中加载分布在内存周围的大型对象(而不是像String这样的连续内存中的对象(往往会产生这样的效果,因为Mark&Sweep算法无法将整个内存块返回操作系统。如果您开始解析大型 JSON 文件(如 10+MB(,您将获得类似的效果,因为生成的 Hash(由大量其他对象组成(将被放置在多个内存块中,与其他仍然具有活动引用的对象一起,因此 Ruby 无法释放该块。