Как генерировать большие файлы (PDF и CSV), используя AppEngine и Datastore? - PullRequest
8 голосов
/ 15 октября 2010

Когда я впервые начал разработку этого проекта, не было необходимости создавать большие файлы, однако теперь он является готовым.

Короче говоря, GAE просто не очень хорошо справляется с крупномасштабными манипуляциями с данными или генерацией контента. Помимо отсутствия файлового хранилища, даже такого простого, как создание PDF-файла с ReportLab с 1500 записями, похоже, возникла ошибка 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')

Буду очень признателен за любые предложения.

Edit: possible solutions

Выше я задокументировал три возможных решения, основанных на моих исследованиях, плюс предложения и т. Д.

Они не обязательно являются взаимоисключающими и могут быть небольшим изменением или комбинацией любого из трех, однако суть решений есть. Дайте мне знать, какой из них, по вашему мнению, имеет наибольшее значение и может работать лучше.

Решение A: Используя mapreduce (или задачи), сериализуйте каждую запись и создайте запись memcache для каждой отдельной записи, для которой введено имя ключа. Затем обработайте эти элементы по отдельности в файл pdf / xls. (используйте get_multi и set_multi)

Решение B: Используя задачи, сериализуйте группы записей и загружайте их в БД в виде большого двоичного объекта. Затем запустите задачу после обработки всех записей, которые будут загружать каждый BLOB-объект, десериализовать их и затем загрузить данные в окончательный файл.

Решение C: Используя mapreduce, получите имена ключей и сохраните их в виде списка или сериализованного большого двоичного объекта. Затем загрузите записи по ключу, который будет быстрее, чем текущий метод загрузки. Если бы я сделал это, что было бы лучше, хранить их в виде списка (и каковы будут ограничения ... Я предполагаю, что список из 100 000 будет выше возможностей хранилища данных) или как сериализованный большой двоичный объект (или маленький куски, которые я потом соединяю или обрабатываю)

Заранее спасибо за любой совет.

Ответы [ 2 ]

3 голосов
/ 15 октября 2010

Вот одна быстрая мысль, если предположить, что она извлекает выгоду из хранилища данных.Вы можете использовать tasks и курсоры для извлечения данных небольшими порциями, а затем выполнить генерацию в конце.

Запустить задачу, которая выполняет начальный запрос и выбирает300 (произвольное число) записей, затем ставит в очередь именованную (! Важную) задачу, на которую вы передаете курсор.Тот, в свою очередь, запрашивает [ваше произвольное число] записей, а затем передает курсор и на новую именованную задачу.Продолжайте до тех пор, пока у вас не будет достаточно записей.

Внутри каждой задачи обрабатывают сущности, затем сохраните сериализованный результат в свойстве text или blob в модели «обработка».Я бы сделал имя ключа модели таким же, как задача, которая его создала.Имейте в виду, что для сериализованных данных должен быть установлен предел размера вызовов 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 для данных.Если разделение выборок из хранилища данных само по себе не помогает, вам придется уделять больше внимания обработке каждой задачи.

Не забудьте в задаче 'build', вы захотите вызвать исключениеесли какой-либо из промежуточных моделей еще нет.Ваша последняя задача будет автоматически повторена.

1 голос
/ 15 октября 2010

Некоторое время назад я столкнулся с той же проблемой с GAE.После многих попыток я просто перешел на другой веб-хостинг, так как мог это сделать.Тем не менее, до переезда у меня было 2 идеи, как это решить.Я их не реализовал, но вы можете попробовать.

Первая идея - использовать сервис SOA / RESTful на другом сервере , если это возможно.Вы даже можете создать другое приложение на GAE в Java, выполнить всю работу там (я думаю, с Java PDFBox это займет гораздо меньше времени для генерации PDF) и вернуть результат в Python.Но эта опция требует от вас знания Java, а также для разделения вашего приложения на несколько частей с ужасной модульностью.

Итак, есть другой подход: вы можете создать игру " ping-pong " с помощью браузера пользователя.Идея заключается в том, что если вы не можете сделать все в одном запросе, заставить браузер отправлять вам несколько.Во время первого запроса выполните только часть работы, которая соответствует ограничению 30 секунд, затем сохраните состояние и сгенерируйте «тикет» - уникальный идентификатор «задания».Наконец, отправьте ответ пользователя, который представляет собой простую страницу с перенаправлением, обратно в ваше приложение, параметризованное заявкой на работу.Когда вы получите это.просто восстановите состояние и переходите к следующей части задания.

...