Как я могу обойти ограничение Heroku HTTP 30 секунд? - PullRequest
0 голосов
/ 21 мая 2019

Я унаследовал приложение rails, которое развернуто с помощью Heroku (я думаю). Я редактирую его в AWS Cloud9 IDE и пока просто делаю все в режиме разработки. Цель приложения - обработать большие объемы данных опроса и выложить их в отчет в формате PDF. Это работает для небольших отчетов с примерно 10 строками данных, но когда я загружаю отчет, который запрашивает загрузку данных более 5000 строк, чтобы создать HTML-страницу, которая преобразуется в PDF, это занимает около 105 секунд, что намного дольше, чем у Heroku. 30 секунд отведено на HTTP-запросы.

Heroku говорит об этом на своем сайте, что дало мне надежду:

"Heroku поддерживает функции HTTP 1.1, такие как длинные опросы и потоковые ответы. Приложение имеет начальное 30-секундное окно для ответа одним байтом обратно клиенту. Однако каждый байт передается после этого (либо получаемый от клиента, либо отправлено вашим приложением) сбрасывает 55-секундное окно. Если данные не отправляются в течение 55-секундного окна, соединение будет разорвано. " (Источник: https://devcenter.heroku.com/articles/request-timeout#long-polling-and-streaming-responses)

Это звучит отлично для меня - я могу отправлять запрос клиенту каждую секунду или около того в цикле, пока мы не закончим создание большого отчета в формате PDF. Однако я не знаю, как отправить или получить байт или около того, чтобы «сбросить скользящее 55-секундное окно», о котором они говорят.

Вот часть моего контроллера, которая отправляет запрос.

            return render pdf: pdf_name + " " + pdf_year.to_s,
                disposition: 'attachment',
                page_height: 1300,
                encoding: 'utf8',
                page_size:   'A4',
                footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
                margin:  {   top:    10,                     # default 10 (mm)
                            bottom: 20,
                            left:   10,
                            right:  10 },
                template: "recent_grad/report.html.erb",
                locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}

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

У меня такой вопрос: как я могу «отправить или получить байт клиенту», чтобы сказать Heroku «Я все еще пытаюсь создать этот массивный PDF, поэтому, пожалуйста, сбросьте таймер и дайте мне мои 55 секунд!» Это в форме запроса? Потому что, если это так, я снова и снова запрашиваю базу данных MySql в моем файле report.html.erb.

Кроме того, раньше он работал без проблем и работал с небольшими отчетами, но теперь я получаю сообщение об ошибке «504 Gateway Timeout» до того, как запрос будет завершен на текущей странице, но моя консоль puma продолжает запрашивать базу данных, как сумасшедший Я предполагаю, что это проблема Heroku, потому что ошибка 504 возникает ровно каждые 35 секунд (5 секунд для обработки других частей и 30 секунд для завершения цикла в шаблоне, чтобы он мог правильно отображаться).

Если вам нужна дополнительная информация или код, пожалуйста, спрашивайте! Заранее спасибо

EDIT: Оба комментария ниже предлагают возможные дубликаты, но ни у одного из них нет реального ответа с реальным кодом, они просто ссылаются на документы, которые я цитирую здесь. Я ищу пример кода (или, по крайней мере, способ получить мою ногу в дверь), а не просто ссылку на документы. Спасибо!

РЕДАКТИРОВАТЬ 2:

Я попробовал то, что сказал @Sergio, и установил SideKiq. Я думаю, что я действительно близко, но все еще есть некоторые проблемы с рабочим. Рабочий не имеет доступа к ActionView :: Base, который требуется для метода рендеринга в rails, поэтому он не работает. Я могу получить доступ к рабочему методу, который означает, что мои серверы sidekiq и redis работают правильно, но он попадает в строку ActionView с этой ошибкой:

WARN: NameError: неинициализированная константа HardWorker :: ActionView

Вот рабочий код:

require 'sidekiq'

Sidekiq.configure_client do |config|
  # config.redis = { db: 1 }
  config.redis = { url: 'redis://172.31.6.51:6379/0' }
end

Sidekiq.configure_server do |config|
  # config.redis = { db: 1 }
  config.redis = { url: 'redis://172.31.6.51:6379/0' }
end  

class HardWorker
  include Sidekiq::Worker
  def perform(pdf_name, pdf_year)
    av = ActionView::Base.new()
    av.view_paths = ActionController::Base.view_paths
    av.class_eval do
      include Rails.application.routes.url_helpers
      include ApplicationHelper
    end
    puts "inside hardworker"
    puts pdf_name, pdf_year

    av.render pdf: pdf_name + " " + pdf_year.to_s,
                disposition: 'attachment',
                page_height: 1300,
                encoding: 'utf8',
                page_size:   'A4',
                footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
                margin:  {   top:    10,                     # default 10 (mm)
                            bottom: 20,
                            left:   10,
                            right:  10 },
                template: "recent_grad/report.html.erb",
                locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
  end
end


Есть предложения?

РЕДАКТИРОВАТЬ 3: Я сделал то, что сказал @Sergio, и попытался сделать PDF из файла html.erb напрямую и сохранить его в файл. Вот мой код:

# /app/controllers/recentgrad_controller.rb

pdf = WickedPdf.new.pdf_from_html_file('home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb')
            save_path = Rails.root.join('pdfs', pdf_name + pdf_year.to_s + '.pdf')
            File.open(save_path, 'wb') do |file|
              file << pdf
            end

И вывод ошибки:

RuntimeError (Failed to execute:
["/usr/local/rvm/gems/ruby-2.4.1@gradSurvey/bin/wkhtmltopdf", "file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb", "/tmp/wicked_pdf_generated_file20190523-15416-hvb3zg.pdf"]
Error: PDF could not be generated!
 Command Error: Loading pages (1/6)
Error: Failed loading page file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb (sometimes it will work just to ignore this error with --load-error-handling ignore)
Exit with code 1 due to network error: ContentNotFoundError
):

Я понятия не имею, что это значит, когда он говорит "иногда это будет работать просто, чтобы игнорировать эту ошибку с помощью --load-error-processing ignore". Файл определенно существует, и я пробовал 5 вариантов пути к файлу.

1 Ответ

2 голосов
/ 22 мая 2019

Мне приходилось делать что-то подобное несколько раз. Во всех случаях я закончил тем, что написал фоновую работу, которая делает все тяжелое поколение поднятие И поскольку это не веб-запрос, на него не влияет 30-секундный таймаут. Это выглядит примерно так:

  1. клиент (ваш код JavaScript) запрашивает новый отчет.
  2. сервер генерирует описание задания и ставит его в очередь для вашего работника.
  3. работник выбирает задание из очереди и начинает работать (запрашивает базу данных и т. Д.)
  4. тем временем клиент периодически спрашивает сервер «мой отчет уже готов?». Сервер отвечает «еще нет, попробуйте позже»
  5. работник завершил создание отчета. Он загружает файл в некоторое хранилище (например, S3), устанавливает статус задания «выполнено», а результат задания - ссылкой для загрузки загруженного файла отчета.
  6. сервер, видя, что задание выполнено, теперь может отвечать на запросы обновления статуса клиента: «Да, это сделано. Вот URL. Хорошего дня.»
  7. Все счастливы. И никто не должен был делать потоковую передачу или играть с непрерывными тайм-аутами ответа герою.

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

...