ActiveRecord 不释放内存
ActiveRecord not releasing memory
我有一个导出作业,可以从我们的 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
- 以
76.93359375 MB
开头
- 加载 1 个 Mio 对象后,内存增加到
2436.3125 MB
- 垃圾收集将内存减少到
2421.046875 MB
,但我希望减少得更多!
- 有趣的是,第二个 运行,只会将内存增加到
2436.3828125 MB
- 最后一个
GC.start
以某种方式 增加了 内存到 2436.3984375 MB
所以我想知道这是怎么回事? ActiveRecord 中一定有我不知道的东西,我想了解这一切是如何工作的,以及为什么内存没有被释放。
按照这个逻辑,内存应该在每次读取数据的请求时增加,但我假设在请求-响应周期内使用时会有一些不同。
在 Ruby 中加载散布在内存各处的大对象(而不是像 String
这样位于连续内存中的对象)往往会产生这种效果,因为 Mark & Sweep 算法不能 return 将整个内存块返回到 OS。如果您开始解析大型 JSON 文件(如 10+MB),您将获得类似的效果,因为由大量其他对象组成的结果哈希将与其他对象一起放置在多个内存块中有一个有效的引用,因此 Ruby 无法释放该块。
我有一个导出作业,可以从我们的 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
- 以
76.93359375 MB
开头
- 加载 1 个 Mio 对象后,内存增加到
2436.3125 MB
- 垃圾收集将内存减少到
2421.046875 MB
,但我希望减少得更多! - 有趣的是,第二个 运行,只会将内存增加到
2436.3828125 MB
- 最后一个
GC.start
以某种方式 增加了 内存到2436.3984375 MB
所以我想知道这是怎么回事? ActiveRecord 中一定有我不知道的东西,我想了解这一切是如何工作的,以及为什么内存没有被释放。
按照这个逻辑,内存应该在每次读取数据的请求时增加,但我假设在请求-响应周期内使用时会有一些不同。
在 Ruby 中加载散布在内存各处的大对象(而不是像 String
这样位于连续内存中的对象)往往会产生这种效果,因为 Mark & Sweep 算法不能 return 将整个内存块返回到 OS。如果您开始解析大型 JSON 文件(如 10+MB),您将获得类似的效果,因为由大量其他对象组成的结果哈希将与其他对象一起放置在多个内存块中有一个有效的引用,因此 Ruby 无法释放该块。