如何使用AppEngine和Datastore生成大文件(PDF和CSV)?

时间:2010-10-15 01:34:57

标签: google-app-engine google-cloud-datastore

当我第一次开始开发这个项目时,没有要求生成大文件,但它现在是可交付的。

长话短说,GAE对任何大规模数据操作或内容生成都不好。除了缺少文件存储之外,甚至像使用带有1500条记录的ReportLab生成pdf这样简单的事情似乎遇到了DeadlineExceededError。这只是一个由表格组成的简单pdf。

我使用以下代码:

    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(或者小的)然后我连接或处理的块

提前感谢任何建议。

2 个答案:

答案 0 :(得分:3)

这是一个快速思考,假设它正在从数据存储区中取出。您可以使用taskscursors以较小的块来获取数据,然后在最后执行生成。

启动执行初始查询并获取300(任意数量)记录的任务,然后将您传递光标的命名(!important)任务排入队列。那个查询依次查询 [您的任意数字] 记录,然后将光标传递给新的命名任务。继续,直到你有足够的记录。

在每个任务流程中,实体将序列化结果存储在“处理”模型上的text或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生成。如果单独拆分数据存储区提取没有帮助,您将不得不考虑在每个任务中执行更多处理。

不要忘记在“构建”任务中,如果任何临时模型尚未出现,您将要引发异常。您的最终任务将自动重试。

答案 1 :(得分:1)

前段时间我遇到了与GAE相同的问题。经过多次尝试后,我才转移到另一个虚拟主机,因为我可以做到。然而,在移动之前,我有两个想法如何解决它。我没有实现它们,但你可以尝试。

如果可能的话,首先想法是在另一台服务器上使用SOA / RESTful 服务。您甚至可以在Java中使用GAE创建另一个应用程序,在那里完成所有工作(我想使用Java PDFBox生成PDF需要花费更少的时间),并将结果返回给Python。但是这个选项需要你了解Java,并将你的应用程序分成几个部分,模块化程度很差。

所以,还有另一种方法:你可以用用户的浏览器创建一个“乒乓球”游戏。我们的想法是,如果您无法在一个请求中创建所有内容,请强制浏览器向您发送几个请求。在第一次请求期间,仅使一部分工作符合30秒的限制,然后保存状态并生成“票证” - “作业”的唯一标识符。最后,将用户响应(简单页面)重定向发送回您的应用程序,并通过作业单进行参数化。当你得到它。只需恢复状态并继续下一部分工作。