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.