2010-10-15 90 views
8

當我第一次開始開發這個項目時,並沒有要求生成大文件,但它現在是可交付成果。長篇故事,GAE對任何大規模數據操作或內容生成都不太好。拋開文件存儲的缺失,甚至像使用帶有1500條記錄的ReportLab生成pdf一樣簡單,似乎會遇到DeadlineExceededError。這只是一個由表格組成的簡單pdf。如何使用AppEngine和Datastore生成大文件(PDF和CSV)?

我使用下面的代碼:

self.response.headers['Content-Type'] = 'application/pdf' 
    self.response.headers['Content-Disposition'] = 'attachment; filename=output.pdf' 
    doc = SimpleDocTemplate(self.response.out, pagesize=landscape(letter)) 

    elements = [] 

    dataset = Voter.all().order('addr_str') 

    data = [['#', 'STREET', 'UNIT', 'PROFILE', 'PHONE', 'NAME', 'REPLY', 'YS', 'VOL', 'NOTES', 'MAIN ISSUE']] 

    i = 0 
    r = 1 
    s = 100 

    while (i < 1500): 
     voters = dataset.fetch(s, offset=i) 
     for voter in voters: 
      data.append([voter.addr_num, voter.addr_str, voter.addr_unit_num, '', voter.phone, voter.firstname+' '+voter.middlename+' '+voter.lastname ]) 
      r = r + 1 
     i = i + s 

    t=Table(data, '', r*[0.4*inch], repeatRows=1) 
    t.setStyle(TableStyle([('ALIGN',(0,0),(-1,-1),'CENTER'), 
          ('INNERGRID', (0,0), (-1,-1), 0.15, colors.black), 
          ('BOX', (0,0), (-1,-1), .15, colors.black), 
          ('FONTSIZE', (0,0), (-1,-1), 8) 
          ])) 

    elements.append(t) 

    doc.build(elements) 

沒有什麼特別花哨,但它扼流圈。有一個更好的方法嗎?如果我可以寫入某種文件系統並逐個生成文件,然後重新加入它們,但我認爲系統排除了這種情況。

我需要爲CSV文件做同樣的事情,但是限制顯然有點高,因爲它只是原始輸出。

self.response.headers['Content-Type'] = 'application/csv' 
    self.response.headers['Content-Disposition'] = 'attachment; filename=output.csv' 

    dataset = Voter.all().order('addr_str') 

    writer = csv.writer(self.response.out,dialect='excel') 
    writer.writerow(['#', 'STREET', 'UNIT', 'PROFILE', 'PHONE', 'NAME', 'REPLY', 'YS', 'VOL', 'NOTES', 'MAIN ISSUE']) 

    i = 0 
    s = 100 
    while (i < 2000): 
     last_cursor = memcache.get('db_cursor') 
     if last_cursor: 
      dataset.with_cursor(last_cursor) 
     voters = dataset.fetch(s) 
     for voter in voters: 
      writer.writerow([voter.addr_num, voter.addr_str, voter.addr_unit_num, '', voter.phone, voter.firstname+' '+voter.middlename+' '+voter.lastname]) 
     memcache.set('db_cursor', dataset.cursor()) 
     i = i + s 
    memcache.delete('db_cursor') 

任何建議將非常感激。

編輯: possible solutions

上面我已經記錄在案根據我的研究,三種可能的解決方案,以及建議等

他們不一定是相互排斥的,可能是任何一個微小的變化或組合但三種解決方案的主旨都在那裏。讓我知道你認爲哪一個最有意義,並且可能表現最好。

解決方案A:使用mapreduce(或任務),序列化每條記錄,併爲每個使用鍵名鍵入的單個記錄創建一個memcache條目。然後將這些項目單獨處理到pdf/xls文件中。 (使用get_multi和set_multi)

解決方案B:使用任務,序列化記錄組,並將它們作爲一個blob加載到數據庫中。然後在處理所有記錄時觸發一個任務,這些記錄將加載每個blob,反序列化它們,然後將數據加載到最終文件中。

解決方案C:使用mapreduce,檢索鍵名並將它們存儲爲列表或序列化的blob。然後按鍵加載記錄,這會比當前的加載方法更快。如果我要這樣做,那會更好,將它們存儲爲列表(以及限制是什麼......我假定列表中的100,000個將超出數據存儲的功能)或者作爲序列化的blob(或小的大塊,然後我連接或處理)

在此先感謝您的任何建議。

+0

可能是一個小小的低效率,但data.append([...])將比data + = [[...]]更有效率。 – 2010-10-15 09:21:01

+0

我編輯了代碼來反映這一點。謝謝你的提示! – etc 2010-10-16 06:05:19

回答

3

下面是一個簡單的想法,假設它正在從數據存儲中取出數據。您可以使用taskscursors以較小的塊讀取數據,然後在最後執行生成。

啓動一個執行初始查詢並獲取300(任意數量)記錄的任務,然後將您傳遞光標的命名(!重要)任務排入隊列。那個人又查詢[你的任意數字]記錄,然後將光標傳遞給一個新的命名任務。繼續,直到你有足夠的記錄。

在每個任務中處理實體,然後將序列化結果存儲在「處理」模型的文本或blob屬性中。我會讓模型的key_name與創建它的任務相同。請記住序列化的數據將需要在API調用大小限制之下。

要非常快,你可以使用你的序列表:

serialized_data = "\x1e".join("\x1f".join(voter) for voter in data) 

有PDF或CSV一代的最後一個任務(如果你得到足夠的記錄)踢。如果您使用key_names爲您建模,則應該能夠通過密鑰獲取具有編碼數據的所有實體。按鍵獲取速度非常快,您知道模型的關鍵點,因爲您知道最後一個任務名稱。再一次,你會想要從數據存儲中獲得提取的大小!

反序列化:

list(voter.split('\x1f') for voter in serialized_data.split('\x1e')) 

現在對數據運行PDF/CSV一代。如果單獨拆分數據存儲區獲取並不能幫助您,則必須考慮在每個任務中執行更多的處理。

不要忘記在'構建'任務中,如果任何臨時模型尚未出現,您將想要引發異常。您的最終任務將自動重試。

+0

不會通過限制循環結果本質上是同一件事?我不相信這是產生超時的數據拉取 - 儘管你可能是正確的。如果我沒有記錯,過程中會有30秒的超時時間,但請求超時10秒。 30秒應該有足夠的時間來實際處理1500-2000條記錄並以PDF格式輸出。這比這更快地超時。 – etc 2010-10-15 05:05:17

+0

我的建議是爲了將數據檢索與處理部分分開。在獲取1,500個實體的App Engine上*可以輕鬆*消耗幾秒*的處理時間。所以是的,如果你還沒有嘗試過,我一定會嘗試運行這個限制爲10.另外,如果你還沒有,你應該使用Appstats(http://code.google.com/appengine/docs/python/ tools/appstats.html)並嘗試確定需要花費那麼多時間的確切內容。 – 2010-10-15 05:16:18

+0

好主意 - 剛剛發佈的新版本不是嗎?還沒有嘗試過,但我現在要做。 – etc 2010-10-15 06:06:24

1

前段時間我面對GAE的同樣的問題。經過多次嘗試後,我轉移到另一個虛擬主機,因爲我可以做到這一點。然而,在搬家之前,我有2個想法如何解決它。我沒有實施它們,但你可以嘗試。

如果可能,第一個想法是在另一臺服務器上使用SOA/RESTful服務。你甚至可以在Java中使用GAE創建另一個應用程序,完成所有工作(我想用Java的PDFBox生成PDF需要更少的時間),並將結果返回給Python。但是這個選項需要您瞭解Java,並且還需要將應用程序劃分爲幾個可怕的模塊化部分。

所以,還有另一種方法:你可以用用戶的瀏覽器創建一個「乒乓球」遊戲。這個想法是,如果你不能在一個請求中完成所有的事情,強迫瀏覽器向你發送幾個請求。在第一次請求期間,只做適合30秒限制的工作的一部分,然後保存狀態並生成'票證' - '工作'的唯一標識符。最後,將用戶響應以簡單頁面的形式重新發送回您的應用程序,並通過作業單進行參數化。當你得到它。只是恢復狀態並繼續下一部分工作。

+0

偉大的建議 - 只是不知道我是否有時間來實施它們。我正在研究一些不同的選項。我會及時通知你的! – etc 2010-10-15 08:19:30

+0

順便說一句,你最後一種方法的唯一問題是最終一代的pdf文件,它必須在一個進程中發生。當有超過1500條記錄時,似乎會超時。 :/ – etc 2010-10-15 08:20:03

+0

通過我不熟悉ReportLab,我想你可以分別生成PDF的幾個部分,然後連接它們。即使萬一你不能合併幾個表格,你仍然可以創建一張表格,這看起來是一樣的。 – ffriend 2010-10-15 09:45:09