我打算使用延迟作业来运行一些后台分析。在我的初始测试中,我看到了大量的内存使用,所以我基本上创建了一个非常简单的任务,每2分钟运行一次,只是为了观察正在使用多少内存。
任务很简单,analytics_eligbile?在给定数据的位置的情况下,方法总是返回false,所以基本上没有一个重击代码被调用。我在开发中的示例数据中有大约200个帖子。发布has_one analytics_facet。
无论此处的内部逻辑/业务如何,此任务唯一要做的就是调用analytics_eligible?方法每2分钟200次。在4小时内,我的物理内存使用量为110MB,虚拟内存为200MB。只是为了做这么简单的事情!我甚至无法想象,如果用真实的生产数据对10,000个帖子进行真正的分析,它会占用多少内存!当然,它可能无法运行2分钟,更像每30分钟,但我认为它不会飞。
这是在Ubuntu 10.x 64位上运行ruby 1.9.7,rails 2.3.5。我的笔记本电脑有4GB内存,双核CPU。
轨道真的很糟糕,还是我做错了什么?
Delayed::Worker.logger.info('RAM USAGE Job Start: ' + `pmap #{Process.pid} | tail -1`[10,40].strip)
Post.not_expired.each do |p|
if p.analytics_eligible?
#this method is never called
Post.find_for_analytics_update(p.id).update_analytics
end
end
Delayed::Worker.logger.info('RAM USAGE Job End: ' + `pmap #{Process.pid} | tail -1`[10,40].strip)
Delayed::Job.enqueue PeriodicAnalyticsJob.new(), 0, 2.minutes.from_now
def analytics_eligible?
vf = self.analytics_facet
if self.total_ratings > 0 && vf.nil?
return true
elsif !vf.nil? && vf.last_update_tv > 0
ratio = self.total_ratings / vf.last_update_tv
if (ratio - 1) >= Constants::FACET_UPDATE_ELIGIBILITY_DELTA
return true
end
end
return false
end
答案 0 :(得分:19)
ActiveRecord相当需要内存 - 在选择时要非常小心,并注意Ruby会自动返回块中的最后一个语句作为返回值,这可能意味着你要传回一大堆已保存的记录结果在某处,因此不符合GC的条件。
此外,当您调用“Post.not_expired.each”时,您将所有的not_expired帖子加载到RAM中。更好的解决方案是find_in_batches,它一次只能将X记录加载到RAM中。
修复它可能很简单:
def do_analytics
Post.not_expired.find_in_batches(:batch_size => 100) do |batch|
batch.each do |post|
if post.analytics_eligible?
#this method is never called
Post.find_for_analytics_update(post.id).update_analytics
end
end
end
GC.start
end
do_analytics
这里发生了一些事情。首先,整个事物的作用域是一个函数,以防止变量冲突保持块迭代器的引用。接下来,find_in_batches一次从数据库中检索batch_size
个对象,只要您没有构建对它们的引用,就可以在每次迭代运行后获得垃圾收集资格,从而减少总内存使用量。最后,我们在方法结束时调用GC.start
;这会强制GC开始扫描(你不想在实时应用程序中进行扫描,但由于这是一个后台工作,如果需要额外的300毫秒才能运行)。如果返回nil
,它也有非常明显的好处,这意味着该方法的结果是nil
,这意味着我们不会意外地挂起从查找器返回的AR实例。
使用这样的东西应该确保你最终不会泄露AR对象,并且应该大大提高性能和内存使用率。你需要确保你没有在你的应用程序的其他地方泄漏(类变量,全局变量和类引用是最严重的违规者),但我怀疑这将解决你的问题。
所有这一切,在我看来,这是一个cron问题(定期重复工作),而不是DJ问题。您可以使用一次性分析解析器,每隔X分钟使用script/runner
运行您的分析,由cron调用,它可以非常巧妙地清除任何潜在的内存泄漏或每次运行的误用(因为整个过程终止于最后) )
答案 1 :(得分:6)
批量加载数据并积极使用垃圾收集器,正如Chris Heald建议的那样会给你一些非常大的收益,但人们常常忽略的另一个领域是他们正在加载的框架。
加载默认的Rails堆栈将同时为您提供ActionController,ActionMailer,ActiveRecord和ActiveResource。如果您正在构建Web应用程序,则可能没有使用所有这些,但您可能使用最多。
当您构建后台作业时,可以通过为其创建自定义环境来避免加载您不需要的内容:
# config/environments/production_bg.rb
config.frameworks -= [ :action_controller, :active_resource, :action_mailer ]
# (Also include config directives from production.rb that apply)
这些框架中的每一个都只是等待永远不会被发送的电子邮件,或者永远不会被调用的控制器。加载它们毫无意义。调整您的database.yml
文件,将您的后台作业设置为在production_bg
环境中运行,并且您可以开始使用更清晰的文件。
您可以做的另一件事是直接使用ActiveRecord而根本不加载Rails。这可能是您执行此特定操作所需的全部内容。我还发现使用像Sequel这样的轻量级ORM会使你的后台工作变得非常轻,如果你主要进行SQL调用来重新组织记录或删除旧数据。如果您需要访问模型及其方法,则需要使用ActiveRecord。但是,出于性能和效率的原因,有时值得在纯SQL中重新实现简单逻辑。
在测量内存使用时,唯一需要关注的是“真实”内存。虚拟金额包含共享库,并且这些库的成本在使用它们的每个进程中传播,即使每个进程都计算在内。
最后,如果运行一些重要的东西需要100MB的内存但你可以在三周的工作时将其降低到10MB,我不明白为什么你会烦恼。托管服务提供商每年最多60美元的内存成本约60美元,通常远比您的时间便宜。
Ruby on Rails拥抱的理念是更关注你的工作效率和时间,而不是内存使用。如果你想修剪它,节省它,你可以做,但需要一点努力。
答案 2 :(得分:1)
如果您遇到内存问题,一种解决方案是使用其他后台处理技术,例如resque。这是github使用的BG处理。
感谢Resque的父母/孩子 建筑,使用太多的工作 内存释放那记忆 完成。没有不必要的增长
如何?
在某些平台上,当一个Resque 工人立即保留工作 分叉儿童过程。孩子 处理工作然后退出。当。。。的时候 孩子已经成功退出了 工人保留另一份工作 重复这个过程。
您可以在README中找到更多技术细节。
答案 3 :(得分:0)
Ruby消耗(和泄漏)内存是事实。我不知道你是否可以做很多事情,但至少我建议你看看Ruby Enterprise Edition。
REE是一个开源端口,承诺在所有其他好东西中“减少33%的内存”。我已经在生产中使用REE和Passenger近两年了,我很高兴。