ActiveRecord 不释放内存

ActiveRecord not releasing memory

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

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

我在 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. 开头
  3. 加载 1 个 Mio 对象后,内存增加到 2436.3125 MB
  4. 垃圾收集将内存减少到 2421.046875 MB但我希望减少得更多!
  5. 有趣的是,第二个 运行,只会将内存增加到 2436.3828125 MB
  6. 最后一个 GC.start 以某种方式 增加了 内存到 2436.3984375 MB

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

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

在 Ruby 中加载散布在内存各处的大对象(而不是像 String 这样位于连续内存中的对象)往往会产生这种效果,因为 Mark & Sweep 算法不能 return 将整个内存块返回到 OS。如果您开始解析大型 JSON 文件(如 10+MB),您将获得类似的效果,因为由大量其他对象组成的结果哈希将与其他对象一起放置在多个内存块中有一个有效的引用,因此 Ruby 无法释放该块。