在后台作业中完成后生成CSV并将其上载到S3

时间:2012-08-28 18:40:34

标签: ruby-on-rails csv heroku paperclip sidekiq

我正在为用户提供通过CSV下载大量数据的功能。要做到这一点,我正在使用Sidekiq,并在启动它后将任务放入后台工作。我在后台工作中所做的是生成一个包含所有正确数据的csv,将其存储在/tmp中,然后调用save!在我的模型上,将文件的位置传递给paperclip属性,该属性然后关闭并存储在S3中。

所有这一切在当地完美无缺。我现在的问题在于Heroku,它能够根据您所在的节点短时间存储文件。由于Heroku如何处理这些文件,我的后台作业无法找到保存的tmp文件。我想我正在寻找一种更好的方法来做到这一点。如果有一些方法可以在内存中完成所有事情,那将是非常棒的。唯一的问题是,当您保存模型时,paperclip需要将实际文件对象作为属性。这是我的后台工作的样子:

class CsvWorker
  include Sidekiq::Worker

  def perform(report_id)
    puts "Starting the jobz!"
    report = Report.find(report_id)
    items = query_ranged_downloads(report.start_date, report.end_date)

    csv = compile_csv(items)

    update_report(report.id, csv)
  end

  def update_report(report_id, csv)
    report = Report.find(report_id)
    report.update_attributes(csv: csv, status: true)
    report.save!
  end

  def compile_csv(items)
    clean_items = items.compact
    path = File.new("#{Rails.root}/tmp/uploads/downloads_by_title_#{Process.pid}.csv", "w")
    csv_string = CSV.open(path, "w") do |csv|
      csv << ["Item Name", "Parent", "Download Count"]
      clean_items.each do |row|
        if !row.item.nil? && !row.item.parent.nil?
        csv << [
          row.item.name,
          row.item.parent.name,
          row.download_count
          ]
        end
      end
    end

    return path
  end
end

为了便于阅读,我省略了查询方法。

1 个答案:

答案 0 :(得分:1)

我认为Heroku的临时文件存储不是问题所在。围绕这一点的警告主要集中在这样的事实:a)dynos是短暂的,所以你写的任何东西都可以在没有通知的情况下消失;和b)dynos是可以互换的,因此当你有多个web dyno运行时,inter-request tempfiles的存在是一个好运。但是,在您的工作人员运行时,临时文件不会消失。

我注意到的一件事是你实际上正在创建两个具有相同名称的临时文件:

> path = File.new("/tmp/filename", "w")
 => #<File:/tmp/filename> 
> path.fileno
 => 3 
> CSV.open(path, "w") do |csv| csv << %w(foo bar baz); puts csv.fileno end
4
 => nil 

您可以将path =行更改为仅设置文件名(而不是将其打开以进行写入),然后使update_report打开文件名以供阅读。当你给它一个空的,已经被覆盖的,打开写入的文件句柄时,我还没有深入研究Paperclip所做的事情,但改变这个流程可能很好地解决了这个问题。

或者,您可以在内存中执行此操作:将CSV生成为字符串,并将其作为StringIO提供给Paperclip。 (Paperclip使用例如Paperclip::StringioAdapter支持某些非文件对象,包括StringIO。)尝试类似:

# returns a CSV as a string
def compile_csv(items)
  CSV.generate do |csv|
     # ...
  end
end

def update_report(report_id, csv)
  report = Report.find(report_id)
  report.update_attributes(csv: StringIO.new(csv), status: true)
  report.save!
end