3

我在将用户提供的 excel 文件中的大量记录导入数据库时​​遇到问题。这样做的逻辑运行良好,我正在使用 ActiveRecord-import 来减少数据库调用的数量。但是,当文件太大时,处理可能会花费很长时间,Heroku 将返回超时。解决方案:重新排序并将处理移动到后台作业。

到目前为止,一切都很好。我需要添加 CarrierWave 以将文件上传到 S3,因为我不能只将文件保存在内存中用于后台作业。上传部分也工作正常,我为他们创建了一个模型,并将 ID 传递给排队的作业,以便稍后检索文件,因为我知道我无法将整个 ActiveRecord 对象传递给作业。

我已经在本地安装了 Resque 和 Redis,在这方面似乎一切都设置正确。我可以看到我正在创建的作业正在排队,然后运行而不会失败。该作业似乎运行良好,但没有记录添加到数据库中。如果我在控制台中逐行运行工作中的代码,记录会按照我的预期添加到数据库中。但是当我正在创建的排队作业运行时,什么也没有发生。

我无法完全确定问题可能出在哪里。

这是我的上传控制器的创建操作:

def create
  @upload = Upload.new(upload_params)
  if @upload.save
    Resque.enqueue(ExcelImportJob, @upload.id)
    flash[:info] = 'File uploaded.
        Data will be processed and added to the database.'
    redirect_to root_path
  else
    flash[:warning] = 'Upload failed. Please try again.'
    render :new
  end
end

这是作业的简化版本,为了清楚起见,工作表列更少:

class ExcelImportJob < ApplicationJob
  @queue = :default

  def perform(upload_id)
    file = Upload.find(upload_id).file.file.file
    data = parse_excel(file)
    if header_matches? data
      # Create a database entry for each row, ignoring the first header row
      # using activerecord-import
      sales = []
      data.drop(1).each_with_index do |row, index|
        sales << Sale.new(row)
        if index % 2500 == 0
          Sale.import sales
          sales = []
        end
      end
      Sale.import sales
    end

    def parse_excel(upload)
      # Open the uploaded excel document
      doc = Creek::Book.new upload

      # Map rows to the hash keys from the database
      doc.sheets.first.rows.map do |row|
        { date: row.values[0],
          title: row.values[1],
          author: row.values[2],
          isbn: row.values[3],
          release_date: row.values[5],
          units_sold: row.values[6],
          units_refunded: row.values[7],
          net_units_sold: row.values[8],
          payment_amount: row.values[9],
          payment_amount_currency: row.values[10] }
      end
    end

    # Returns true if header matches the expected format
    def header_matches?(data)
      data.first == {:date => 'Date',
                     :title => 'Title',
                     :author => 'Author',
                     :isbn => 'ISBN',
                     :release_date => 'Release Date',
                     :units_sold => 'Units Sold',
                     :units_refunded => 'Units Refunded',
                     :net_units_sold => 'Net Units Sold',
                     :payment_amount => 'Payment Amount',
                     :payment_amount_currency => 'Payment Amount Currency'}
    end
  end
end

无论如何,我可能有一些改进的逻辑,因为现在我将整个文件保存在内存中,但这不是我遇到的问题——即使是一个只有 500 行左右的小文件,这项工作也不会t 向数据库中添加任何内容。

就像我说的那样,当我不使用后台作业时,我的代码运行良好,如果我在控制台中运行它仍然可以运行。但由于某种原因,这项工作什么也没做。

这是我第一次使用 Resque,所以我不知道我是否遗漏了一些明显的东西?我确实创建了一个工人,正如我所说,它似乎确实在运行这项工作。这是 Resque 的详细格式化程序的输出:

*** resque-1.27.4: Waiting for default
*** Checking default
*** Found job on default
*** resque-1.27.4: Processing default since 1508342426 [ExcelImportJob]
*** got: (Job{default} | ExcelImportJob | [15])
*** Running before_fork hooks with [(Job{default} | ExcelImportJob | [15])]
*** resque-1.27.4: Forked 63706 at 1508342426
*** Running after_fork hooks with [(Job{default} | ExcelImportJob | [15])]
*** done: (Job{default} | ExcelImportJob | [15])

在 Resque 仪表板中,作业未记录为失败。它们被执行,我可以在统计页面上看到“已处理”作业的增加。但正如我所说,数据库保持不变。这是怎么回事?如何更清楚地调试作业?有没有办法用 Pry 进入它?

4

1 回答 1

2

看来我的问题出在Resque.enqueue(ExcelImportJob, @upload.id).

我将代码更改为ExcelImportJob.perform_later(@upload.id),现在我的代码实际运行了!

我还添加了一个resque.rake任务,lib/tasks如下所述:http: //bica.co/2015/01/20/active-job-resque/

该链接还说明了如何rails runner在不运行完整的 Rails 服务器和触发作业的情况下调用作业,这对于调试很有用。

奇怪的是,我并没有按照@hoffm 的建议完成将任何内容打印到 STDOUT 的工作,但至少它让我找到了一个很好的查询途径。

我仍然不完全理解为什么调用 Resqueue.enqueue 仍然将我的作业添加到队列中并且确实似乎在运行它们之间的区别,但是代码没有执行,所以如果有人有更好的理解和解释,那将不胜感激。

TL; DR:打电话perform_later而不是Resque.enqueue解决问题,但我不知道为什么。

于 2017-10-19T11:11:00.753 回答