如何追踪 rails 应用程序中的内存泄漏?
How can I track down a memory leak in a rails app?
我的 Rails 应用程序中有一个 class 可以评估应用程序在业务流程中的位置。为此,我有一个任务模型的概念,它使用单个 table 继承来提供多态性。 Task 的每个子class 可以通过执行自己的数据库查询来确定它是否完成,通常通过多态关联在关联模型上。
每次此求值器 运行 都会消耗更多内存,即使在手动调用 GC.start
后也不会释放内存。使用 ObjectSpace.object_counts
我制作了一个图表,显示了随着 TOTAL
计数增长的最密切相关的对象类型。 T_IMEMO
增长最快,T_STRING
位居第二。
内存分析器告诉我大部分内存都分配给了 ActiveRecord。但是在迭代之间,我没有保留新的 ActiveRecord 对象。代码大致是:
CustomerApplication.all.in_batches(of: 10).each do |batch|
batch.each do |customer_application|
Evaluator.new(customer_application).evaluate!
GC.start
# collect statistics
end
end
据我所知,我没有使用任何可以在迭代之间存储数据的 class 属性或全局变量。
我还尝试在每次迭代时禁用 ActiveRecord 日志记录并清除 ActiveRecord 缓存。
Evaluator
代码的核心如下所示:
class Evaluator
# ...
def stage(name, &block)
stage = Stage.new(@customer_application)
stage.instance_eval(&block) if block_given?
@stages << stage
end
def evaluate!
@stages = []
stage :first_stage do
step :first_step do
task(Task::SpecificTask)
phantom_task(Task::ReportTask, reference: ..., key: 'report_a')
end
end
stage :second_stage do
step :first_step do
task(Task::ReportTask, reference: ..., key: 'report_a')
end
step :second_step do
task(Task::ReportTask, reference: ..., key: 'report_b')
end
end
# A ton more stages/steps/tasks
Task.transaction do
self.tasks.each do |task|
task.enabled = true
task.evaluate_completion!
task.evaluate_optional!
task.save!
end
# Disable all other tasks
end
end
end
class Stage
# ...
private
def step(name, reference: nil, deps: [], &block)
step = Step.new(name, @customer_application, reference, deps)
step.instance_eval(&block) if block_given?
@steps << step
step
end
end
class Step
# ...
private
def task(klass, reference: nil, key: nil)
existing_task = @customer_application.tasks.find_by(type: klass.name, reference: reference, key: key)
task = (existing_task || klass.new(customer_application: @customer_application, reference: reference, key: key))
@tasks << task
task
end
def phantom_task(klass, reference: nil, key: nil)
readonly_klass =
Class.new(klass) do
def self.name
superclass.name
end
def readonly?
true
end
def optional?
true
end
def phantom_task?
true
end
end
task = readonly_klass.new(customer_application: @customer_application, reference: reference, key: key)
@tasks << task
task
end
end
我设法弄明白了。我在每次迭代中实例化运行时 类,这显然没有得到 GC。重构为不使用 Class.new
解决了问题。
所以如果有人用谷歌搜索这个,Class.new
会造成内存泄漏。
我的 Rails 应用程序中有一个 class 可以评估应用程序在业务流程中的位置。为此,我有一个任务模型的概念,它使用单个 table 继承来提供多态性。 Task 的每个子class 可以通过执行自己的数据库查询来确定它是否完成,通常通过多态关联在关联模型上。
每次此求值器 运行 都会消耗更多内存,即使在手动调用 GC.start
后也不会释放内存。使用 ObjectSpace.object_counts
我制作了一个图表,显示了随着 TOTAL
计数增长的最密切相关的对象类型。 T_IMEMO
增长最快,T_STRING
位居第二。
内存分析器告诉我大部分内存都分配给了 ActiveRecord。但是在迭代之间,我没有保留新的 ActiveRecord 对象。代码大致是:
CustomerApplication.all.in_batches(of: 10).each do |batch|
batch.each do |customer_application|
Evaluator.new(customer_application).evaluate!
GC.start
# collect statistics
end
end
据我所知,我没有使用任何可以在迭代之间存储数据的 class 属性或全局变量。
我还尝试在每次迭代时禁用 ActiveRecord 日志记录并清除 ActiveRecord 缓存。
Evaluator
代码的核心如下所示:
class Evaluator
# ...
def stage(name, &block)
stage = Stage.new(@customer_application)
stage.instance_eval(&block) if block_given?
@stages << stage
end
def evaluate!
@stages = []
stage :first_stage do
step :first_step do
task(Task::SpecificTask)
phantom_task(Task::ReportTask, reference: ..., key: 'report_a')
end
end
stage :second_stage do
step :first_step do
task(Task::ReportTask, reference: ..., key: 'report_a')
end
step :second_step do
task(Task::ReportTask, reference: ..., key: 'report_b')
end
end
# A ton more stages/steps/tasks
Task.transaction do
self.tasks.each do |task|
task.enabled = true
task.evaluate_completion!
task.evaluate_optional!
task.save!
end
# Disable all other tasks
end
end
end
class Stage
# ...
private
def step(name, reference: nil, deps: [], &block)
step = Step.new(name, @customer_application, reference, deps)
step.instance_eval(&block) if block_given?
@steps << step
step
end
end
class Step
# ...
private
def task(klass, reference: nil, key: nil)
existing_task = @customer_application.tasks.find_by(type: klass.name, reference: reference, key: key)
task = (existing_task || klass.new(customer_application: @customer_application, reference: reference, key: key))
@tasks << task
task
end
def phantom_task(klass, reference: nil, key: nil)
readonly_klass =
Class.new(klass) do
def self.name
superclass.name
end
def readonly?
true
end
def optional?
true
end
def phantom_task?
true
end
end
task = readonly_klass.new(customer_application: @customer_application, reference: reference, key: key)
@tasks << task
task
end
end
我设法弄明白了。我在每次迭代中实例化运行时 类,这显然没有得到 GC。重构为不使用 Class.new
解决了问题。
所以如果有人用谷歌搜索这个,Class.new
会造成内存泄漏。