rails高内存使用率

bad*_*aam 9 performance ruby-on-rails delayed-job

我打算使用延迟工作来运行一些后台分析.在我的初始测试中,我看到了大量的内存使用,所以我基本上创建了一个非常简单的任务,每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
Run Code Online (Sandbox Code Playgroud)

发布模型

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
Run Code Online (Sandbox Code Playgroud)

Chr*_*ald 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
Run Code Online (Sandbox Code Playgroud)

这里发生了一些事情.首先,整个事物的作用域是一个函数,以防止变量冲突保持块迭代器的引用.接下来,find_in_batches batch_size一次从数据库中检索对象,并且只要您不构建对它们的引用,就可以在每次迭代运行后获得垃圾收集资格,从而减少总内存使用量.最后,我们GC.start在方法结束时调用; 这会强制GC开始扫描(你不想在实时应用程序中进行扫描,但由于这是一个后台工作,如果需要额外的300ms才能运行).如果返回它也有非常明显的好处nil,这意味着方法的结果是nil,这意味着我们不会意外地挂起从查找器返回的AR实例.

使用这样的东西应该确保你不会得到泄漏的AR对象,并且应该大大提高性能和内存使用.你需要确保你没有在你的应用程序的其他地方泄漏(类变量,全局变量和类引用是最严重的违规者),但我怀疑这将解决你的问题.

总而言之,在我看来,这是一个cron问题(定期重复工作),而不是DJ问题.您可以拥有一个一次性分析解析器,每隔X分钟运行一次分析script/runner,由cron调用,它可以非常巧妙地清除任何潜在的内存泄漏或每次运行的误用(因为整个过程在结束时终止)


tad*_*man 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)
Run Code Online (Sandbox Code Playgroud)

这些框架中的每一个都只是坐在那里等待永远不会被发送的电子邮件,或者永远不会被调用的控制器.加载它们毫无意义.调整你的database.yml文件,设置你的后台工作在production_bg环境中运行,你会有一个更清洁的平板开始.

您可以做的另一件事是直接使用ActiveRecord而根本不加载Rails.这可能是您执行此特定操作所需的全部内容.我还发现使用像Sequel这样的轻量级ORM,如果你主要进行SQL调用来重组记录或删除旧数据,那么你的后台工作会非常轻量级.如果您需要访问模型及其方法,则需要使用ActiveRecord.但是,出于性能和效率的原因,有时值得在纯SQL中重新实现简单逻辑.

在测量内存使用时,唯一需要关注的是"真实"内存.虚拟数量包含共享库,并且它们的成本分散在使用它们的每个进程中,即使它们被完全计入每个进程.

最后,如果运行一些重要的东西需要100MB的内存,但你可以通过三周的工作将其降低到10MB,我不明白为什么你会打扰.托管服务提供商每年最多60美元的内存成本约60美元,这通常比您的时间便宜得多.

Ruby on Rails采用的理念是更关注您的工作效率和时间,而不是内存使用.如果你想修剪它,节省它,你可以做,但需要一点努力.