2010-10-15 2 views
8

Als ich mit der Entwicklung dieses Projekts begann, war es nicht erforderlich, große Dateien zu erstellen, es ist jetzt jedoch ein Ergebnis.Wie generiere ich große Dateien (PDF und CSV) mit AppEngine und Datastore?

Lange Rede, kurzer Sinn, GAE spielt einfach nicht gut mit großen Datenmanipulationen oder der Generierung von Inhalten. Abgesehen von der Tatsache, dass keine Dateispeicher vorhanden sind, scheint selbst etwas so Einfaches wie das Erzeugen einer PDF mit ReportLab mit 1500 Datensätzen einen DeadlineExceededError zu treffen. Dies ist nur ein einfaches PDF, das aus einer Tabelle besteht.

Ich verwende den folgenden Code ein:

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) 

Nichts besonders schick, aber es Drosseln. Gibt es einen besseren Weg, dies zu tun? Wenn ich in irgendeine Art von Dateisystem schreiben könnte und die Datei in Bits erzeugen könnte, und dann wieder beitreten, könnte das funktionieren, aber ich denke, das System schließt dies aus.

Ich muss das gleiche für eine CSV-Datei tun, aber die Grenze ist offensichtlich ein bisschen höher, da es nur Rohausgabe ist.

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') 

Alle Vorschläge würden sehr geschätzt werden.

Edit: possible solutions

Above ich drei mögliche Lösungen auf meiner Forschung basiert dokumentiert hatte, sowie Vorschläge etc

Sie sind nicht unbedingt gegenseitig aus, und konnte eine leichte Variation oder Kombination von irgendwelchen sein die drei, aber der Kern der Lösungen ist da. Lass mich wissen, welches deiner Meinung nach am meisten Sinn macht und vielleicht das Beste macht.

Lösung A: Verwenden Sie Mapreduce (oder Aufgaben), serialisieren Sie jeden Datensatz und erstellen Sie einen Memcache-Eintrag für jeden einzelnen Datensatz, der mit dem Schlüsselnamen codiert ist. Verarbeiten Sie diese Elemente dann einzeln in die pdf/xls-Datei. (Verwenden Sie get_multi und set_multi)

Lösung B: Verwenden Sie Aufgaben, serialisieren Gruppen von Datensätzen und laden Sie sie als Blob in die Datenbank. Dann lösen Sie eine Aufgabe aus, sobald alle Datensätze verarbeitet sind, die jeden Blob laden, deserialisieren und dann die Daten in die endgültige Datei laden.

Lösung C: Verwenden Sie mapreduce, rufen Sie die Schlüsselnamen ab und speichern Sie sie als Liste oder serialisierten Blob. Dann laden Sie die Datensätze per Schlüssel, was schneller wäre als die aktuelle Ladeart. Wenn ich dies tun würde, was wäre besser, sie als Liste zu speichern (und was wären die Einschränkungen ... ich nehme an, eine Liste von 100.000 würde die Fähigkeiten des Datenspeichers übersteigen) oder als serialisiertes Blob (oder klein Stücke, die ich dann verketten oder verarbeiten)

Vielen Dank im Voraus für eine Beratung.

+0

Wahrscheinlich eine geringe Ineffizienz, aber data.append ([...]) wird viel effizienter sein als Daten + = [[...]]. –

+0

Ich habe den Code bearbeitet, um dies widerzuspiegeln. Danke für den Tipp! – etc

Antwort

3

Hier ist ein kurzer Gedanke, angenommen, es ist aus dem Datastore crapping herausholen. Sie könnten tasks und cursors verwenden, um die Daten in kleineren Blöcken zu holen, und dann die Generierung am Ende durchführen.

Starten Sie eine Aufgabe, die die anfängliche Abfrage durchführt und 300 Datensätze (beliebige Anzahl) abruft, und reihen Sie dann eine benannte (! Wichtige) Aufgabe in die Warteschlange ein, an die Sie den Cursor übergeben. Dieser wiederum fragt [Ihre beliebige Nummer] Datensätze ab und übergibt den Cursor dann an eine neue benannte Aufgabe außerdem. Fahren Sie fort, bis Sie genug Datensätze haben.

Innerhalb jeder Aufgabe die Entitäten verarbeiten und dann das serialisierte Ergebnis in einer Text- oder Blob-Eigenschaft in einem 'Verarbeitungsmodell' speichern. Ich würde das key_name des Modells dasselbe wie die Aufgabe machen, die es erzeugte. Beachten Sie, dass die serialisierten Daten unter der Größenbeschränkung für API-Aufrufe liegen müssen.

Um Ihren Tisch ziemlich schnell serialisieren Sie nutzen könnten:

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

Haben die letzte Aufgabe Kick der PDF- oder CSV-Generation (wenn Sie genügend Datensätze erhalten). Wenn Sie key_names für Ihre Modelle verwenden, sollten Sie in der Lage sein, alle Entitäten mit codierten Daten per Schlüssel zu erfassen. Fetchings per Schlüssel sind ziemlich schnell, Sie werden die Schlüssel des Modells kennen, da Sie den letzten Tasknamen kennen. Auch hier sollten Sie die Größe Ihrer Abrufe im Datenspeicher berücksichtigen. Um

deserialisieren:

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

Jetzt führen Sie Ihre PDF/CSV-Generation auf den Daten. Wenn das Aufteilen der Datenspeicherabrufe alleine nicht hilft, müssen Sie mehr Verarbeitung in jeder Aufgabe durchführen.

Vergessen Sie nicht, in der 'build' Aufgabe eine Ausnahme auszulösen, wenn eines der vorläufigen Modelle noch nicht vorhanden ist. Ihre letzte Aufgabe wird automatisch wiederholt.

+0

Wäre das Durchlaufen der Ergebnisse mit einem Limit im Wesentlichen nicht das Gleiche? Ich glaube nicht, dass es der Datenzugriff ist, der die Zeitüberschreitung erzeugt - obwohl Sie richtig liegen könnten. Wenn ich mich richtig erinnere, gibt es eine 30-Sekunden-Timeout für den Prozess, aber eine 10-Sekunden-Timeout für Anfragen. 30 Sekunden sollten mehr als genug Zeit sein, um 1500-2000 Datensätze zu verarbeiten und sie in einem PDF auszugeben. Das ist viel schneller als das. – etc

+0

Mein Vorschlag hier sollte den Datenabruf teilweise von der Verarbeitung trennen. In App Engine, das 1.500 Entitäten abruft, könnte * leicht * mehrere * Sekunden * Verarbeitungszeit beanspruchen. Also ja, wenn du es noch nicht ausprobiert hast, würde ich es definitiv mit einem Limit von 10 versuchen. Wenn du es noch nicht getan hast, solltest du Appstats verwenden (http://code.google.com/appengine/docs/python/ tools/appstats.html) und versuche genau festzustellen, was so viel Zeit in Anspruch nimmt. –

+0

Tolle Idee - die gerade mit der neuen Version veröffentlicht wurde, oder? Hab es noch nicht ausprobiert, aber ich mache es jetzt. – etc

1

Vor einiger Zeit hatte ich das gleiche Problem mit GAE. Nach vielen Versuchen bin ich einfach zu einem anderen Webhosting umgezogen, da ich es geschafft habe. Bevor ich umzog, hatte ich noch zwei Ideen, wie ich es lösen könnte. Ich habe sie nicht implementiert, aber Sie können es versuchen.

Erste Idee ist es, SOA/RESTfulService auf einem anderen Server, wenn es möglich ist. Sie können sogar eine andere Anwendung auf GAE in Java erstellen, die ganze Arbeit dort erledigen (ich denke, mit Javas PDFBox wird es viel weniger Zeit dauern, PDF zu erzeugen) und das Ergebnis an Python zurückgeben. Aber diese Option erfordert, dass Sie Java kennen und auch Ihre App in mehrere Teile mit schrecklicher Modularität teilen.

Also, es gibt einen anderen Ansatz: Sie können ein "Ping-Pong" Spiel mit dem Browser eines Benutzers erstellen. Die Idee ist, dass, wenn Sie nicht alles in einer einzigen Anfrage machen können, zwingen Sie den Browser, Ihnen mehrere zu senden. Machen Sie während der ersten Anfrage nur einen Teil der Arbeit, der 30 Sekunden Grenze entspricht, speichern Sie dann den Status und generieren Sie "Ticket" - eindeutige Kennung eines "Jobs". Schließlich senden Sie die Benutzerantwort, die einfache Seite mit Redirect zurück zu Ihrer App ist, durch ein Jobticket parametrisiert. Wenn du es bekommst. Stellen Sie einfach den Status wieder her und fahren Sie mit dem nächsten Teil des Jobs fort.

+0

Großartige Vorschläge - ich weiß einfach nicht, ob ich Zeit habe, sie umzusetzen. Ich arbeite daran, ein paar verschiedene Optionen herauszufinden. Ich halte dich auf dem Laufenden! – etc

+0

BTW, das einzige Problem mit Ihrem letzten Ansatz ist die endgültige Generierung der PDF-Datei, die in einem Prozess erfolgen muss. Es scheint Zeit zu sein, wenn es mehr als 1500 Datensätze gibt. :/ – etc

+0

Da ich mit ReportLab nicht vertraut bin, kann es sein, dass Sie mehrere PDF-Teile separat generieren und dann verketten können. Auch wenn Sie mehrere Tabellen nicht zusammenführen können, können Sie dennoch eine Tabelle von Tabellen erstellen, die gleich aussehen. – ffriend

Verwandte Themen