Точный порядок выполнения задания для Rails delayed_job

#ruby-on-rails #delayed-job

#ruby-on-rails #отложенное задание

Вопрос:

Я использую версию delayed_job от collectiveidea:https://github.com/collectiveidea/delayed_job

Кто-нибудь может указать мне на фактические критерии, используемые, когда работник переходит к выбору следующего задания для работы? Я предполагаю, что это что-то вроде «ВЫБЕРИТЕ идентификатор ИЗ delayed_jobs, ГДЕ run_at > NOW () ПОРЯДОК ПО приоритету ASC, run_at ПРЕДЕЛ ASC 1» (сначала выбирается по приоритету, во время run_at второй), но я не смог найти точно, что учитывается. Я немного покопался в коде на GitHub, но не нашел фактического запроса для следующего задания. Любопытно по паре вещей, в том числе, учитывают ли ‘created_at’, ‘attempts’ или ‘failed_at’ при определении приоритетов вообще. (и я понимаю, что вряд ли найду реальный SQL, просто простой способ представить то, что, как я предполагаю, выполняет запрос).

Во-вторых, есть ли хорошие источники актуальной документации для этого драгоценного камня? Для чего-то, что так часто используется в Rails, документация, которую я видел, чертовски скудна.

Ответ №1:

Небольшое изучение исходного кода показало это в серверной части / active_record.rb:

     scope :ready_to_run, lambda {|worker_name, max_run_time|
      where(['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name])
    }
    scope :by_priority, order('priority ASC, run_at ASC')

    # Find a few candidate jobs to run (in case some immediately get locked by others).
    def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
      scope = self.ready_to_run(worker_name, max_run_time)
      scope = scope.scoped(:conditions => ['priority >= ?', Worker.min_priority]) if Worker.min_priority
      scope = scope.scoped(:conditions => ['priority <= ?', Worker.max_priority]) if Worker.max_priority

      ::ActiveRecord::Base.silence do
        scope.by_priority.all(:limit => limit)
      end
    end
  

Также представляет интерес этот бит в backend / base.rb:

     def reserve(worker, max_run_time = Worker.max_run_time)
      # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
      # this leads to a more even distribution of jobs across the worker processes
      find_available(worker.name, 5, max_run_time).detect do |job|
        job.lock_exclusively!(max_run_time, worker.name)
      end
    end
  

reserve вызывается работником для выбора следующего задания.

Комментарии:

1. Спасибо, очень полезно. Интересно, что он использует столбец ‘failed_at’, чтобы определить, не повторять попытку, а не попытки < max_attempts. Это означает, что увеличение числа попыток в конфигурации не приведет к повторной попытке запуска «неудачных» заданий.

2. Конечно, это имеет тот полезный эффект, что вы можете принудительно запустить «неудачное» задание снова, просто очистив поле failed_at вместо увеличения конфигурации max attempts или очистки / уменьшения зарегистрированных попыток для этого задания.

3. Возможно, это еще не все. По предположению, failed_at может не быть установлено до тех пор, пока задание не будет выполнено максимальное количество раз. предполагается, что delayed_job повторяет задания, если они настроены таким образом.

4. Да, в этом ты прав, Алекс. failed_at остается нулевым, пока не будет достигнуто максимальное количество попыток. У меня было несколько заданий, которые пытались выполнить 25 раз (максимум по умолчанию) из-за проблемы с конфигурацией. Простое задание failed_at = NULL привело к тому, что они были предприняты в 26-й раз без необходимости увеличивать настроенное максимальное количество попыток.

5. fwiw, поддержка ActiveRecord исключена из Delayed::Job и теперь ее можно найти в github.com/collectiveidea/delayed_job_active_record — это объясняет, почему я не смог найти определение для find_available :).

Ответ №2:

 delayed_job/lib/delayed/backend/active_record.rb
  

self.find_available вызовы ready_to_run
затем определяет область его действия на основе минимального / максимального приоритета текущих работников
затем упорядочивает его по priority ASC, run_at ASC

scope :ready_to_run вызывает

 where(['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name])
  

таким образом, он генерирует что-то вроде

 SELECT * FROM jobs where run at <= ? AND locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL AND priority >= ? AND priority <= ? ORDER BY priority ASC, run_at ASC