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

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

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

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

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

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

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

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

Любые идеи или мысли оценены!

Ответ 1

Об этом говорит поддержка Heroku:

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

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

Ответ 2

Запланируйте задачу очистки

Сводка: поставьте в очередь задачу для выполнения с самым низким приоритетом. Как только все другие задачи будут выполнены, будет запущена задача очистки.

подробности

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

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

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

require 'platform-api'

# Simple class to interact with Heroku 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

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

Ответ 3

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

Ответ 4

Теперь можно отключить конкретный dyno с помощью команды 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 немедленно запустит новый динозавр, чтобы заменить тот, который был закрыт. Но, возможно, это все еще полезно для вас (или других людей, читающих этот вопрос).