Ruby on RailsSeniorSystem design

Что такое Active Job и как он интегрируется с Sidekiq или Resque?

Active Job — абстрактный слой очередей в Rails с унифицированным API для любого бэкенда (Sidekiq, Resque, Delayed Job). Sidekiq подключается через адаптер и исполняет джобы в отдельных воркер-процессах.

Что такое Active Job

Active Job — это фреймворк-обёртка, стандартизирующий интерфейс фоновых задач в Rails. Вместо прямого вызова Sidekiq API вы пишете один класс Job, а бэкенд (очередь) подключается через адаптер. Это позволяет менять Sidekiq на Resque или наоборот без изменения бизнес-логики.

Базовый пример

# app/jobs/send_welcome_email_job.rb
class SendWelcomeEmailJob < ApplicationJob
  queue_as :default
  retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
  discard_on ActiveRecord::RecordNotFound

  def perform(user_id)
    user = User.find(user_id)
    UserMailer.welcome(user).deliver_now
  end
end

# Постановка в очередь
SendWelcomeEmailJob.perform_later(user.id)
SendWelcomeEmailJob.set(wait: 10.minutes).perform_later(user.id)
SendWelcomeEmailJob.set(queue: :critical).perform_later(user.id)

Интеграция с Sidekiq

# Gemfile
gem "sidekiq"

# config/application.rb
config.active_job.queue_adapter = :sidekiq
# config/sidekiq.yml
:concurrency: 10
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]
bundle exec sidekiq -C config/sidekiq.yml

Sidekiq Web UI

# config/routes.rb
require "sidekiq/web"

authenticate :user, ->(u) { u.admin? } do
  mount Sidekiq::Web => "/sidekiq"
end

Прямой вызов Sidekiq API (минуя Active Job)

Если нужны специфические функции Sidekiq (батчи, throttling из Sidekiq Pro), можно унаследоваться от Sidekiq::Job напрямую:

class HeavyReportWorker
  include Sidekiq::Job
  sidekiq_options queue: "reports", retry: 3

  def perform(report_id)
    Report.find(report_id).generate!
  end
end

HeavyReportWorker.perform_async(report.id)
HeavyReportWorker.perform_in(1.hour, report.id)

Интеграция с Resque

# config/application.rb
config.active_job.queue_adapter = :resque

# Gemfile
gem "resque"

Callbacks и instrumentation

class ApplicationJob < ActiveJob::Base
  around_perform do |job, block|
    Rails.logger.tagged(job.class.name) { block.call }
  end

  after_discard do |job, exception|
    ErrorTracker.notify(exception, job: job.class.name, args: job.arguments)
  end
end

Подводные камни

  • Active Job сериализует аргументы в JSON через GlobalID — передавайте user.id или сам объект (если он GlobalID::Identification), но не сложные Ruby-объекты без GlobalID.
  • При передаче AR-объекта напрямую он будет десериализован через find — если запись удалена к моменту выполнения, без discard_on ActiveRecord::RecordNotFound джоб будет ретраиться бесконечно.
  • Очередь :default в конфиге Sidekiq должна быть явно указана — иначе джобы молча уходят в неотслеживаемое место.
  • Параметр wait: в retry_on со значением :polynomially_longer может откладывать джоб на часы при большом числе попыток — следите за дедлайном.
  • Sidekiq по умолчанию использует :default как строку, а Active Job — как символ: в конфиге sidekiq.yml всегда пишите имена очередей строками.
  • В тестах ActiveJob::TestHelper требует queue_adapter = :test — в Rails 7+ это устанавливается автоматически, но в более старых версиях нужно явно.
  • Sidekiq не гарантирует ровно одно исполнение (at-least-once) — делайте джобы идемпотентными через уникальные индексы или Sidekiq::UniqueJobs.

Common mistakes

  • Сводить active job sidekiq resque к названию метода без lifecycle и failure path.
  • Игнорировать модель runtime: Rails 8.1 строит приложение вокруг Rack, routing, controllers, Active Record, views и conventions over configuration.
  • Не отделять validation, authorization, transaction boundary и business logic.
  • Не обсуждать idempotency, retries, shutdown и observability.

What the interviewer is testing

  • Объясняет active job sidekiq resque через конкретную точку lifecycle в Ruby on Rails.
  • Приводит корректный минимальный пример без вымышленных методов или callbacks.
  • Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.
  • Связывает решение с метриками, backpressure, retry policy и graceful shutdown.

Sources

Related topics