优化rails循环中的内存使用

时间:2014-01-28 00:45:21

标签: ruby-on-rails ruby optimization heroku

我在雪松堆上开发了一个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在一小时后进入睡眠时死亡。

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

2 个答案:

答案 0 :(得分:3)

您可以将其拆分为批次,以便以合理的方式完成工作。

尝试这样的事情:

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记录方法直接从DB中撤回您想要的数据,而不是使用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

答案 1 :(得分:2)

使用

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

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

而不是CSV.generate

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