Избегание дублирующихся заданий при использовании Sidekiq `unique_for` и` Sidekiq :: Limiter.concurrent` в одном и том же работнике - PullRequest
1 голос
/ 25 марта 2020

Сводка

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

Решение 1: используйте unique_until: :start с Sidekiq::Limiter.concurrent

В настоящее время работник использует unique_until: :start и Sidekiq::Limiter.concurrent lock.

Недостатком этого решения является то, что взаимодействие между этими двумя функциями Sidekiq вызывает много дублирующихся заданий в очереди. Вот последовательность событий, которые вызывают повторяющиеся задания: 1. Worker.perform_async(1) ставит в очередь задание A1 2. Задание A1 запускается, снимает свою уникальную блокировку и получает параллельную блокировку 3. Worker.perform_async(2) ставит в очередь задание B1 4. Задание B1 запускается, освобождает его уникальная блокировка, не может получить одновременную блокировку, и перепланирует себя. Теперь нет блокировки для Worker с аргументом 2. 5. Worker.perform_async(2) ставит в очередь задание B2. Я хотел бы, чтобы это было неоперативным, но оно ставит другую задачу в очередь, потому что мы сняли уникальную блокировку на шаге 4. 6. Worker.perform_async(2) ставит в очередь задание B3 ... и т. Д.

Решение 2: используйте unique_until: :success с Sidekiq::Limiter.concurrent

Я могу исправить проблему с дублирующимися заданиями, если переключусь на unique_until: :success (поведение по умолчанию, если unique_until не указано).

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

Решение 3: замените ограничитель выделенным процессом Sidekiq

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

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

Пример кода для решения 1

Здесь показано, как я использую функции уникальности и ограничения:

class ExpensiveWorker
  include Sidekiq::Worker
  sidekiq_options unique_for: 30.minutes, unique_until: :start

  EXPENSIVE_JOB_LIMITER = Sidekiq::Limiter.concurrent('expensive_job',
                                                      1,
                                                      wait_timeout: 5.seconds,
                                                      lock_timeout: 15.minutes)

  def perform(id)
    EXPENSIVE_JOB_LIMITER.within_limit do
      Rails.logger.info "Processing #{id}..."
      sleep 10
    end
  end
end
* 10 41 * Подробная информация о том, что я пытаюсь решить

Ради простоты я опишу данные, с которыми мы работаем, в качестве авторских моделей, каждая из которых имеет много книг. У нас есть RebuildAuthorImagesWorker и ClassifyAuthorGenreWorker, которые оба принимают идентификатор автора в качестве единственного аргумента.

Оба эти сотрудника выполняют вычисления с интенсивным использованием ЦП и ОЗУ для автора и его книг. Мы используем Sidekiq::Limiter.concurrent, чтобы гарантировать, что только один из этих работников имеет активную работу в любой момент времени. Мы делаем это, чтобы не влиять на наши слабые серверы. (У нас также есть много других работников, которые не должны быть ограничены таким образом.)

Обычно многие обновления происходят на одном и том же авторе или на книгах этого автора в течение короткого периода времени, потому что нескольких активных пользователей или потому что один пользователь обновил несколько книг одного и того же автора. Мы используем unique_for: :start, чтобы не допустить многократного помещения в очередь RebuildAuthorImagesWorker для одного и того же автора. То же самое для ClassifyAuthorGenreWorker. Мы хотим избежать дублирующих заданий из-за системных накладных расходов, связанных с их выполнением. Задания являются идемпотентными, поэтому дублированные задания не вызывают проблем с данными. (Это нормально и нормально для одного задания каждого работника ставиться в очередь для одного и того же автора.)

Если RebuildAuthorImagesWorker активно выполняется для автора A, а затем пользователь X делает обновление для автора A до того, как RebuildAuthorImagesWorker задание заканчивается, затем мы делаем хотим поставить в очередь второе RebuildAuthorImagesWorker задание для автора A, поэтому мы не пропускаем включение данных из обновления пользователя X в изображение. Вот почему мы используем unique_until: :start.

1 Ответ

1 голос
/ 26 марта 2020

Одна идея:

Когда пользователь хочет сменить автора A, я поставлю в очередь запланированный, уникальный UpdateAuthorJob для автора A, который обновляет свою информацию через 10 минут . Таким образом, пользователь может внести множество изменений в автора, и система будет ждать этого 10-минутного периода восстановления перед выполнением фактической работы по обновлению, гарантируя, что вы получите все обновления как одну группу.

...