Ruby-on-Rails 3.2:导出具有大数据集的CSV(100,000条记录)

时间:2012-04-20 11:46:02

标签: ruby-on-rails csv scalability delayed-job ruby-on-rails-3.2

简介

我有一个有多个表的应用程序,有些有一些表,有些没有关联。

一些表需要容纳大约100,000个条目。

该应用程序在Ruby 1.9上使用Rails 3.2并在Heroku上托管。如果需要,我可以接触工人。

问题中的要求

该应用程序的一个重要要求是允许用户将数据导出为CSV - 这样做的一个要求是允许用户过滤他们想要导出的数据,但我现在并不担心这一点我将从下面的数据中看到,我已经硬编码了要导出的数据,但这确实排除了创建一个rake任务来导出整个表格。

此外,必须考虑实现的方法,以允许多个表使用,以避免重复执行代码。

当前解决方案

我正在我的应用中实施delayed_job并在作业中执行CSV生成。在这样做时,我正在遵循http://www.ayokasystems.com/blog/delegating-long-running-jobs-in-rails/来自'abdullah'的解决方案。

我们的想法是以CSV格式生成数据并将其保存在UserJobs表的LONGTEXT字段中,以允许用户在完成和将来一次下载。

问题

上面教程中使用的方法在我的应用程序中工作正常,直到我一次运行100,000个记录的作业。为了解决这个问题,我尝试将很酷的find_each函数添加到perform方法中,但是每次尝试处理它时,延迟的作业工作者都会报告错误:

[Worker(host:*** pid:18637)] ReportJob failed with NoMethodError: undefined method `each' for #<Title:0x007ff20c1ec1b0> - 0 failed attempts
[Worker(host:*** pid:18637)] ReportJob failed with NoMethodError: undefined method `each' for #<Title:0x007ff20ec47f18> - 1 failed attempts
[Worker(host:*** pid:18637)] 2 jobs processed at 10.5219 j/s, 2 failed ... 

我执行方法的代码是:

def perform
  Title.find_each do |titles|
    csv_data = CSV.generate do |csv|
      titles.each do |t|
        csv << t.to_csv
      end
    end
    user_job = UserJob.find(user_job_id)
    user_job.update_attribute :data, csv_data
  end
end

任何人都可以看到问题可能是什么,我想我刚刚犯了一个愚蠢的错误,我是如何循环的。

我对如何完成有关要求的任何其他建议持开放态度,但请记住我对Heroku的限制。

2 个答案:

答案 0 :(得分:3)

你试图用每个进行迭代,但在这种情况下,标题是标题的实例(不是数组)。

csv_vals = []
columns = [:name, :release_date, :studio]

Title.find_each(:select => columns) do |title| 
  columns.each {|value| csv_vals << "#{title[value]}"}
end

# comma separated string 
csv_string = csv_vals.join(',')

有更优雅的方式来制定CSV字符串,但我实在太懒了。

重要的是,您只在所需的列上执行SELECT。对于100 000条记录,增加了大量带宽较少的DB通信。只需 find_each 即可获得每行的所有列,而您不需要它们。

答案 1 :(得分:1)

find_each为块提供单个记录,而不是集合,因此您在单个记录上调用each时出错。看看find_in_batches,或修改您的代码以使用单个记录:

Title.find_each do |title|
  CSV.generate do |csv|
    csv << title.to_csv
  end
  user_job = UserJob.find(user_job_id)
  user_job.update_attribute :data, csv_data
end