【问题标题】:rails high memory usagerails高内存使用率
【发布时间】:2011-03-31 08:53:03
【问题描述】:

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

rails 真的这么糟糕还是我做错了什么?

 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

【问题讨论】:

    标签: ruby-on-rails performance delayed-job


    【解决方案1】:

    Ruby 消耗(和泄漏)内存是一个事实。我不知道你能不能做很多,但至少我建议你看看Ruby Enterprise Edition

    REE 是一个开源端口,它承诺“减少 33% 的内存”以及所有其他好处。我已经在生产中使用 REE 和Passenger 快两年了,我很高兴。

    【讨论】:

    • 嗯,到目前为止,我对 RoR 有一定的了解,但如果情况如此糟糕,那真的很令人失望。我现在正在尝试 REE,谢谢!
    • REE 承诺“减少 33% 的内存使用”是由于 Rails 框架本身加载后的进程分叉。在单个过程中,它不会产生显着影响。
    【解决方案2】:

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

    感谢 Resque 的父母/孩子 建筑,使用过多的工作 内存释放该内存 完成。没有不必要的增长

    怎么做?

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

    您可以在自述文件中找到更多技术细节。

    【讨论】:

    • 谢谢。这种父/子架构在哪些平台上工作?
    • 我知道它适用于 Linux 和 OS X。可能它不适用于 Windows?
    【解决方案3】:

    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 问题。您可以拥有一个一次性分析解析器,它使用 script/runner 每 X 分钟运行一次分析,由 cron 调用,它可以非常巧妙地清除任何潜在的内存泄漏或每次运行的误用(因为整个过程在最后终止)

    【讨论】:

    • 我要添加到这个优秀答案的唯一一点是,任何 Rails 进程都会消耗大量内存 - 你的 110mb 并不罕见。这并不表示您的代码中存在内存泄漏,或者您已经完成了多少处理。如果处理得当(Chris 解释的方式),处理 1000 条记录或 10M 条记录将使用相同数量的内存。
    【解决方案4】:

    按照 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。这可能是您执行此特定操作所需的全部内容。如果您主要执行 SQL 调用来重组记录或删除旧数据,我还发现使用像 Sequel 这样的轻量级 ORM 会使您的后台工作非常轻量级。但是,如果您需要访问模型及其方法,则需要使用 ActiveRecord。不过,出于性能和效率的考虑,有时值得在纯 SQL 中重新实现简单的逻辑。

    在测量内存使用时,唯一需要关注的数字是“真实”内存。虚拟量包含共享库,并且这些成本在使用它们的每个进程中分摊,即使每个进程都已全部计算在内。

    最后,如果运行重要的东西需要 100MB 的内存,但你可以通过三周的工作将它降低到 10MB,我不明白你为什么要费心。在托管提供商处,90MB 内存的成本最多约为 60 美元/年,这通常比您的时间便宜得多。

    Ruby on Rails 的理念是更关注您的生产力和时间,而不是内存使用。如果你想把它修剪回来,让它节食,你可以做到,但需要一些努力。

    【讨论】:

      猜你喜欢
      • 2017-09-14
      • 1970-01-01
      • 2012-05-31
      • 1970-01-01
      • 1970-01-01
      • 2011-09-19
      • 2021-11-11
      • 2018-07-01
      • 2014-02-26
      相关资源
      最近更新 更多