2010-08-18 137 views
8

我打算使用延迟作业来运行一些背景分析。在我最初的测试中,我看到了大量的内存使用情况,所以我基本上创建了一个非常简单的任务,每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 

回答

18

ActiveRecord相当耗费内存 - 在进行选择时要非常小心,并且要注意Ruby会自动将块中的最后一条语句作为返回值返回,这可能意味着您要传回的记录数组作为结果保存在某个地方,因此不符合GC的条件。

此外,当您调用“Post.not_expired.each”时,您将加载全部 your 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开始扫描(你不想在实时应用程序中执行此操作,但由于这是后台工作,因此如果需要额外的300ms运行则可以)。如果返回nil,这也意味着该方法的结果是nil,这意味着我们不会意外挂在从查找程序返回的AR实例上。

使用类似这样的东西应该可以确保您不会以泄漏的AR对象为结束,并且应该极大地提高性能和内存使用率。你会想确保你的应用程序没有在其他地方泄漏(类变量,全局变量和类引用是最糟糕的违规者),但我怀疑这会解决你的问题。所有这一切说,这是一个克伦问题(定期复发的工作),而不是一个DJ问题,在我看来。您可以使用一次性分析解析器,该解析器每隔X分钟运行一次,分析内容为script/runner,由cron调用,非常巧妙地清除每次运行的潜在内存泄漏或误用(因为整个过程终止)

+0

我唯一要补充的是这个优秀的答案是任何Rails进程都会消耗很多内存 - 你的110mb并不少见。这并不代表你的代码中有内存泄漏,或者你做了多少处理。处理1000条记录或10M条记录将使用相同数量的内存(如果你已经做好了正确的处理)(克里斯解释过的方式)。 – wuputah 2010-08-28 01:52:04

0

这是一个事实,即红宝石消耗(和泄漏)的内存。我不知道你是否可以做很多事情,但至少我建议你看看Ruby Enterprise Edition

REE是一个开放源代码端口,承诺在所有其他优点中“内存减少33%”。我已经使用REE与Passenger合作了近两年,我感到非常高兴。

+0

嗯,我对RoR至今有一定的了解,但如果它不好,它真的会让人失望。我正在尝试REE,谢谢! – badnaam 2010-08-18 17:48:12

+0

REE的“减少33%内存使用量”的承诺是由于Rails框架本身加载后的进程分叉。在一个单独的过程中,它不会产生重大影响。 – 2010-08-27 06:23:56

1

如果遇到内存问题,一种解决方案是使用另一种后台处理技术,如resque。这是由github使用的BG处理。

由于Resque的父/子 架构,即在 完成使用太多 内存释放内存的工作。没有不必要的增长

如何?

在某些平台上,当Resque 工人保留工作立即 叉一个子进程。子 处理作业然后退出。当 孩子已成功退出时, 工作人员保留另一个工作,并且 重复此过程。

您可以在README中找到更多技术细节。

+0

谢谢。这个父/子架构在什么平台上工作? – badnaam 2010-08-26 07:59:50

+0

我知道它适用于Linux和OS X.它可能不适用于Windows? – wuputah 2010-08-27 18:09:45

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,我不明白为什么你会打扰。在托管服务提供商中,90MB的内存成本至多约为60美元/年,这通常比您的时间便宜得多。

Ruby on Rails拥抱更关心您的生产力和时间而不是内存使用的理念。如果你想修剪它,减少它的饮食,你可以做到这一点,但它会花费一点努力。

+0

好点!非常感谢你! – badnaam 2010-08-29 05:54:10