Уменьшить конкретные рабочие характеристики Heroku?

Я создаю веб-приложение, которое предоставляет пользователям возможность загружать большие изображения и обрабатывать их. Обработка занимает около 3 минут, и я подумал, что Heroku станет идеальной платформой для выполнения этих заданий обработки по требованию и с высокой степенью масштабируемости. Сама задача обработки довольно затратна в вычислительном отношении и требует запуска высокопроизводительного динамометра PX. Я хочу максимизировать распараллеливание и минимизировать (эффективно исключить) время, затрачиваемое заданием на ожидание в очереди. Другими словами, я хочу иметь N PX dynos для N рабочих мест.

К счастью, я могу сделать это довольно легко с помощью API Heroku (или, при желании, такого сервиса, как Hirefire). Всякий раз, когда поступает новый запрос на обработку, я могу просто увеличить количество рабочих, и новый работник извлечет задание из очереди и немедленно начнет обработку.

Тем не менее, в то время как масштабирование является безболезненным, масштабирование - это то, с чего начинаются проблемы. API Heroku крайне ограничен. Я могу только установить количество работающих работников, а не убивать неработающих. Это означает, что если у меня есть 20 рабочих, каждый из которых обрабатывает изображение, и один из них выполняет свою задачу, я не могу безопасно масштабировать число рабочих до 19, потому что Heroku убьет произвольного рабочего динамо, независимо от того, находится ли он на самом деле посреди работы! Оставлять всех рабочих на работах до завершения всех работ просто невозможно, потому что стоимость будет астрономической. Представьте, что 100 рабочих, созданных во время всплеска, продолжают бездействовать бесконечно, так как несколько новых рабочих мест появляются в течение дня!

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

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

Фактически, я приблизился к этому идеальному миру, переключившись с рабочих динамов на одноразовые (которые заканчиваются после завершения процесса, то есть вы перестаете платить за динамо после выхода его "корневой программы"). Тем не менее, Heroku устанавливает жесткий лимит в 5 одноразовых динамов, которые можно запускать одновременно. Это я могу понять, поскольку я определенно злоупотреблял одноразовыми динамо... но, тем не менее, это очень расстраивает.

Есть ли способ, как я могу уменьшить свои работники? Я бы предпочел не делать радикальной реорганизации моего алгоритма обработки... разделив его на несколько кусков, которые выполняются за 30-40 секунд вместо одного 3-минутного отрезка (таким образом, случайное убийство работающего работника не будет катастрофический). Такой подход значительно усложнит мой код обработки и привнесет несколько новых точек отказа. Однако, если это мой единственный вариант, мне придется это сделать.

Любые идеи или мысли приветствуются!

4 ответа

Вот что ответила поддержка Heroku по этому поводу:

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

Я нашел этот комментарий интересным в этом контексте, хотя он на самом деле не решил эту проблему.

[ПРИМЕЧАНИЕ: как только я напишу этот ответ, я пойму, что он не учитывает вашу потребность в том, чтобы раскрутить конкретного рабочего динамо. Но вы должны быть в состоянии использовать ключевую технику, показанную здесь: поставить задачу DJ с низким приоритетом для очистки, когда все остальное было обработано.]

Мне повезло, используя Heroku [platform-api][1] драгоценный камень, чтобы раскрутить Delayed Job работников по требованию и раскрутить их, когда они закончат. Для упрощения я создал файл heroku_control.rb следующим образом.

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

require 'platform-api'

# Simple class to interact with Heroku's platform API, allowing
# you to start and stop worker dynos under program control.
class HerokuControl

  API_TOKEN = "<redacted>"
  APP_NAME = "<redacted>"

  def self.heroku
    @heroku ||= PlatformAPI.connect_oauth(API_TOKEN)
  end

  # Spin up one worker dyno
  def self.worker_up(act = Rails.env.production?)
    self.worker_set_quantity(1) if act
  end

  # Spin down all worker dynos
  def self.worker_down(act = Rails.env.production?)
    self.worker_set_quantity(0) if act
  end

  def self.worker_set_quantity(quantity)
    heroku.formation.update(APP_NAME, 'worker', {"quantity" => quantity.to_s})
  end

end

И в моем приложении я делаю что-то вроде этого:

LOWEST_PRIORITY = 100

def start_long_process
  queue_lengthy_process
  queue_cleanup_task        # clean up when everything else is processed
  HerokuControl::worker_up  # assure there is a worker dyno running
end

def queue_lengthy_process
  # do long job here...
end
handle_asynchronously :queue_lengthy_process, :priority => 1

# This gets processed when Delayed::Job has nothing else
# left in its queue.
def queue_cleanup_task
  HerokuControl::worker_down # shut down all worker dynos
end
handle_asynchronously :queue_cleanup_task, :priority => LOWEST_PRIORITY

Надеюсь это поможет.

Теперь можно отключить определенный динамо, используя heroku ps:stop команда.

например, если ваш heroku ps вывод содержит:

web.1: up 2017/09/01 13:03:50 -0700 (~ 11m ago)
web.2: up 2017/09/01 13:03:48 -0700 (~ 11m ago)
web.3: up 2017/09/01 13:04:15 -0700 (~ 11m ago)

Вы можете запустить heroku ps:stop web.2 убить второго динамо в списке.

Это не будет делать именно то, что вы хотите, потому что Heroku немедленно запустит новый dyno, чтобы заменить тот, который был выключен. Но, возможно, это все еще полезно для вас (или других людей, читающих этот вопрос).

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

Другие вопросы по тегам