2017-10-18 48 views
1

我有一個問題,即將用戶提供的excel文件中的大量記錄導入到數據庫中。這個邏輯工作正常,我使用ActiveRecord-import來減少數據庫調用次數。但是,如果文件太大,處理時間可能會過長,Heroku會返回超時。解決方案:Resque並將處理移至後臺作業。Rails + resque後臺作業導入不會向數據庫添加任何內容

到目前爲止,這麼好。我需要添加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行左右的小文件,作業也不會向數據庫添加任何內容。

就像我說過的,我的代碼在我沒有使用後臺作業時工作的很好,如果我在控制檯中運行它,它仍然有效。但由於某種原因,這項工作什麼都不做。

這是我第一次使用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儀表板中,作業不會記錄爲失敗。他們得到執行,我可以看到stats頁面上'已處理'作業的增量。但正如我所說的數據庫保持不變。這是怎麼回事?我怎樣才能更清楚地調試工作?有沒有辦法與Pry一起進入?

+0

你可以在'Sale.import sales'每次調用之前記錄'sales.count',以確保你正在用數據進行'import'調用嗎? – hoffm

+0

這是一個好主意。儘管我對Resque非常陌生。有什麼方法可以打印到Resque日誌?那是什麼語法?我會只使用'logger.info「收集#{sales.count}銷售來導入」'或類似的東西? –

+0

我想你會看到輸出,如果它只是被髮送到標準輸出,所以'放置'收集#{sales.count}銷售導入「'應該做的伎倆。我假設你用'rake resque:work'開始Resque工作? – hoffm

回答

1

它看起來像我的問題是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的建議,把任何東西打印到標準輸出中,但至少這讓我失望了一個很好的調查渠道。

我仍然不完全理解爲什麼調用Resqueue.enqueue仍然將我的作業添加到隊列中,並確實似乎運行它們,但代碼沒有執行,所以如果有人有更好的把握和解釋,這將不勝感激。

TL; DR:調用perform_later而不是Resque.enqueue修復了這個問題,但我不知道爲什麼。