【问题标题】:optimize memory usage in rails loop优化 Rails 循环中的内存使用
【发布时间】:2014-01-28 00:45:21
【问题描述】:

我在 cedar 堆栈上开发了一个 heroku rails 应用程序,这就是瓶颈。

def self.to_csvAlt(options = {})
  CSV.generate(options) do |csv|     
    column_headers = ["user_id", "session_id", "survey_id"]
    pages = PageEvent.order(:page).select(:page).map(&:page).uniq
    page_attributes = ["a", "b", "c", "d", "e"]
    pages.each do |p|
      page_attributes.each do |pa|
        column_headers << p + "_" + pa
      end
    end
    csv << column_headers
    session_ids = PageEvent.order(:session_id).select(:session_id).map(&:session_id).uniq
    session_ids.each do |si|
        session_user = PageEvent.find(:first, :conditions => ["session_id = ? AND page != ?", si, 'none']);
        if session_user.nil?
            row = [si, nil, nil, nil]
        else
            row = [session_user.username, si, session_user.survey_name]
        end
        pages.each do |p|
          a = 0
          b = 0
          c = 0
          d = 0
          e = 0
          allpages = PageEvent.where(:page => p, :session_id => si)
          allpages.each do |ap|
            a += ap.a
            b += ap.b
            c += ap.c
            d += ap.d
            e += ap.e
          end
          index = pages.index p
          end_index = (index + 1)*5 + 2
          if !p.nil?
            row[end_index] = a
            row[end_index-1] = b
            row[end_index-2] = c
            row[end_index-3] = d
            row[end_index-4] = e
          else
            row[end_index] = nil
            row[end_index-1] = nil
            row[end_index-2] = nil
            row[end_index-3] = nil
            row[end_index-4] = nil
          end
        end
      csv << row
    end
  end
end

如您所见,它会从一个表格中生成一个 csv 文件,该表格包含从一组调查中获取的每个单独页面上的数据。问题是表格中有大约 50,000 个单独的页面,heroku 应用程序继续给我 R14 错误(内存不足 512MB),并最终在 dyno 在一小时后进入睡眠状态时死亡。

话虽如此,我真的不在乎运行需要多长时间,我只需要它完成。我正在等待批准添加一个工人测功机来运行 csv 生成,我知道这会有所帮助,但与此同时我仍然想优化此代码。有可能一次处理超过 100,000 个页面,我意识到这是非常庞大的内存,并且确实需要尽可能减少其内存使用量。谢谢你的时间。

【问题讨论】:

    标签: ruby-on-rails ruby optimization heroku


    【解决方案1】:

    您可以将它分成多个批次,以便在合理的块中完成工作。

    试试这样的:

    def self.to_csvAlt(options = {})
    
      # ...
    
      pages = PageEvent.order(:page).select(:page).map(&:page).uniq
    
      pages.find_each(:batch_size => 5000) do |p|
        # ...
    

    将 find_each 与 batch_size 一起使用,您不会对循环进行大量查找。相反,它会获取 5000 行,运行你的循环,获取另一个,再次循环......等等,直到你没有更多的记录返回。

    这里要注意的另一件事是,rails 不会尝试同时实例化从数据库返回的所有对象,它只会实例化当前批处理中返回的对象。如果你有一个巨大的数据集,这可以节省大量的内存开销。

    更新:

    使用#map 将结果限制为模型的单个属性是非常低效的。您应该改用pluck Active record 方法直接从数据库中提取您想要的数据,而不是使用 Ruby 操作结果,如下所示:

    # Instead of this:
    pages = PageEvent.order(:page).select(:page).map(&:page).uniq
    
    # Use this:
    pages = PageEvent.order(:page).pluck(:page).uniq
    

    我个人也更喜欢使用.distinct 而不是别名.uniq,因为我觉得它更符合数据库查询,而不是与看起来更像数组函数的东西混淆:

    pages = PageEvent.order(:page).pluck(:page).distinct
    

    【讨论】:

    • 获取NoMethodError (undefined method 'find_each' for #(Array:0x...))
    • 您需要在 Arel 对象上使用 find_each。这将是 .map(&amp;:page) 的一部分。将其替换为 .pluck(:page) ... 我会为您更新我的答案。
    • 没有与pluckuniqdistinct 一起工作......不知道为什么。我想出了另一种获得session_ids 的方法,find_each 产生了巨大的变化。谢谢!
    • find_each 大大减少了内存使用和运行时间。标记为答案,即使上面的代码(pluckuniqdistinct)没有工作..
    • 同一个NoMethodError (undefined method 'find_each' for #(Array...))
    【解决方案2】:

    使用

    CSV.open("path/to/file.csv", "wb")
    

    这会将 CSV 流式传输到文件中。

    而不是CSV.generate

    generate 将创建一个巨大的字符串,如果它变得太大,最终会耗尽内存。

    【讨论】:

    • 使用 ruby​​ 2,CSV::Writer 在 1.9 之后被弃用,见 here
    • 谢谢,更新了我的答案以使用推荐的方法。
    猜你喜欢
    • 2022-12-04
    • 2014-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多