2010-10-15 14 views
8

Cuando comencé a desarrollar este proyecto, no había ningún requisito para generar archivos de gran tamaño, sin embargo, ahora es un entregable.Cómo generar archivos de gran tamaño (PDF y CSV) utilizando AppEngine y Datastore?

En pocas palabras, GAE simplemente no funciona bien con cualquier manipulación de datos a gran escala o generación de contenido. La falta de almacenamiento de archivos a un lado, incluso algo tan simple como generar un pdf con ReportLab con 1500 registros parece llegar a DeadlineExceededError. Esto es solo un pdf simple compuesto por una tabla.

estoy usando el siguiente código:

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) 

Nada particularmente elegante, pero ahoga. ¿Hay una mejor manera de hacer esto? Si pudiera escribir en algún tipo de sistema de archivos y generar el archivo en bits, y luego volver a unirme a ellos podría funcionar, pero creo que el sistema lo impide.

Necesito hacer lo mismo para un archivo CSV, sin embargo, el límite obviamente es un poco más alto ya que es solo resultado bruto.

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

Cualquier sugerencia sería muy apreciada.

Editar: possible solutions

Por encima de que había documentado tres posibles soluciones sobre la base de mi investigación, además de sugerencias, etc.

No son necesariamente excluyentes entre sí, y podría haber una ligera variación o combinación de cualquiera de los tres, sin embargo, la esencia de las soluciones está ahí. Déjame saber cuál crees que tiene más sentido y podría funcionar mejor.

Solución A: utilizando mapreduce (o tareas), serialice cada registro y cree una entrada de Memcache para cada registro individual con la clave keyname. Luego procese estos elementos individualmente en el archivo pdf/xls. (use get_multi y set_multi)

Solución B: utilizando tareas, serialice grupos de registros y cárguelos en la base de datos como un blob. A continuación, active una tarea una vez que se hayan procesado todos los registros que cargarán cada blob, los deserializarán y luego cargarán los datos en el archivo final.

Solución C: utilizando mapreduce, recupere los nombres de los clave y guárdelos como una lista o blob serializado. A continuación, cargue los registros por clave, lo que sería más rápido que el método de carga actual. Si tuviera que hacer esto, que sería mejor, almacenarlos como una lista (y cuáles serían las limitaciones ... supongo que una lista de 100.000 estaría más allá de las capacidades del almacén de datos) o como un blob serializado (o pequeño). trozos que luego concatenar o procesar)

Gracias de antemano por cualquier consejo.

+0

Probablemente una ineficiencia menor, pero data.append ([...]) será mucho más eficiente que data + = [[...]]. –

+0

He editado el código para reflejar esto. ¡Gracias por el consejo! – etc

Respuesta

3

He aquí una idea rápida, suponiendo que se está recuperando del datastore. Puede usar tasks y cursors para buscar los datos en trozos más pequeños, luego haga la generación al final.

Comience una tarea que hace la consulta inicial y obtiene 300 registros (número arbitrario), luego encola una tarea nombrada (! Important) a la que le pasa el cursor. Ese a su vez consulta [su número arbitrario] registros, y luego pasa el cursor a una nueva tarea nombrada también. Continúa hasta que tengas suficientes registros.

Dentro de cada tarea, procese las entidades, luego almacene el resultado serializado en una propiedad de texto o blob en un modelo de 'procesamiento'. Yo haría que el nombre_clave del modelo sea el mismo que la tarea que lo creó. Tenga en cuenta que los datos serializados deberán estar debajo del límite de tamaño de la llamada API.

Para serializar su mesa bastante rápido puede usar:

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

tienen la última tarea (cuando se obtiene suficientes registros) Tiro de la generación de PDF o CSV. Si usa key_names para sus modelos, debería ser capaz de tomar todas las entidades con datos codificados por clave. Las capturas por clave son bastante rápidas, conocerás las claves del modelo ya que conoces el último nombre de la tarea. Una vez más, querrás tener en cuenta el tamaño de tus capturas en el almacén de datos.

deserializar:

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

Ahora ejecutar su generación PDF/CSV de los datos. Si dividir las búsquedas del almacén de datos por sí solo no ayuda, tendrá que buscar hacer más del procesamiento en cada tarea.

No olvide que en la tarea 'compilar' querrá hacer una excepción si alguno de los modelos provisionales aún no está presente. Tu última tarea se volverá a intentar automáticamente.

+0

¿No pasaría cíclicamente por los resultados con un límite esencialmente sería lo mismo? No creo que sea la extracción de datos lo que genere el tiempo de espera, aunque podría estar en lo cierto. Si recuerdo correctamente, hay un tiempo de espera de 30 segundos en el proceso, pero un tiempo de espera de 10 segundos en las solicitudes. 30 segundos deberían ser más que suficiente tiempo para procesar realmente 1500-2000 registros y darlos como PDF. Esto se está agotando mucho más rápido que eso. – etc

+0

Mi sugerencia aquí tenía la intención de separar parcialmente la recuperación de datos del procesamiento. En App Engine recuperar 1.500 entidades podría * fácilmente * consumir varios * segundos * de tiempo de procesamiento. Así que sí, si aún no lo has intentado, definitivamente intentaré ejecutar esto con un límite de 10. Además, si aún no lo has hecho, debes usar Appstats (http://code.google.com/appengine/docs/python/ tools/appstats.html) y trata de determinar exactamente qué es lo que toma tanto tiempo. –

+0

Gran idea - que acaba de ser lanzada con la nueva versión ¿no es así? Todavía no lo he probado, pero lo voy a hacer ahora. – etc

1

Hace algún tiempo tuve el mismo problema con GAE. Después de muchos intentos, me mudé a otro alojamiento web porque podía hacerlo. Sin embargo, antes de moverme tuve 2 ideas de cómo resolverlo. No los he implementado, pero puedes intentarlo.

La primera idea es usar el servicio SOA/RESTfulen otro servidor, si es posible. Incluso puede crear otra aplicación en GAE en Java, hacer todo el trabajo allí (supongo que con Java PDFBox tardará mucho menos tiempo para generar PDF) y devolver el resultado a Python. Pero esta opción necesita que conozcas Java y también dividas tu aplicación en varias partes con una modularidad terrible.

Por lo tanto, hay otro enfoque: puede crear un juego "ping-pong" con el navegador de un usuario. La idea es que si no puedes hacer todo en una sola solicitud, forza al navegador a enviarte varios. Durante la primera solicitud, haga solo una parte del trabajo que se ajuste a un límite de 30 segundos, luego guarde el estado y genere el 'ticket', el identificador único de un 'trabajo'. Finalmente, envíe la respuesta del usuario, que es una página simple, con redireccionamiento de regreso a su aplicación, parametrizado por un ticket de trabajo. Cuando lo consigas. solo restaure el estado y proceda con la siguiente parte del trabajo.

+0

Grandes sugerencias: simplemente no sé si tengo tiempo para implementarlas. Estoy trabajando en descubrir algunas opciones diferentes. ¡Te mantendré informado! – etc

+0

Por cierto, el único problema con su último enfoque es la generación final del archivo pdf, que tiene que suceder en un solo proceso. Parece que se está agotando el tiempo cuando hay más de 1500 registros. :/ – etc

+0

Si no estoy familiarizado con ReportLab, supongo que puede generar varias partes de PDF por separado y luego concatenarlas. Incluso en el caso de que no pueda combinar varias tablas, aún puede hacer una tabla de tablas, que parecerá lo mismo. – ffriend

Cuestiones relacionadas