# Jobs em segundo plano no Rails em 2026: Sidekiq vs Good Job e perguntas de entrevista > Comparação completa entre Sidekiq, Good Job e Solid Queue para tarefas assíncronas no Rails, com as perguntas técnicas mais frequentes em entrevistas. - Published: 2026-09-12 - Updated: 2026-09-12 - Author: Anthony Fillion-Maillet - Tags: Rails, Sidekiq, Good Job, Background Jobs, Active Job - Reading time: 12 min --- Os jobs em segundo plano no Rails evoluíram significativamente com o Rails 8.x e a introdução do Solid Queue como backend padrão do Active Job. A escolha entre Sidekiq, Good Job e Solid Queue depende dos requisitos de throughput, das restrições de infraestrutura e das funcionalidades que cada projeto necessita. > **Guia de decisão rápida** > > Para novos projetos Rails 8, o recomendado é começar com Solid Queue. Sidekiq é a escolha adequada para cargas de trabalho de alto throughput que ultrapassam 100.000 jobs por dia ou quando a latência no nível do Redis é crítica. Good Job é ideal para jobs armazenados no PostgreSQL com suporte nativo a batches e restrições de unicidade. ## Como o Active Job unifica o processamento em segundo plano O Active Job fornece uma interface padronizada para declarar, enfileirar e executar trabalho em segundo plano no Rails. O framework abstrai as diferenças entre os backends de fila, permitindo que o código permaneça portável entre Sidekiq, Good Job, Solid Queue ou qualquer outro adaptador. ```ruby # app/jobs/payment_processor_job.rb class PaymentProcessorJob < ApplicationJob queue_as :critical retry_on Stripe::RateLimitError, wait: :polynomially_longer, attempts: 5 discard_on Stripe::InvalidRequestError def perform(order_id) order = Order.find(order_id) PaymentService.process(order) OrderMailer.confirmation(order).deliver_later end end ``` Os callbacks `retry_on` e `discard_on` tratam falhas transitórias e erros permanentes sem código específico do backend. A diretiva `queue_as` atribui prioridade, que cada backend interpreta de acordo com sua própria estratégia de processamento de filas. ## Sidekiq 8.x: throughput otimizado com Redis [Sidekiq](https://github.com/sidekiq/sidekiq) continua sendo a referência de desempenho para jobs em segundo plano no Rails. A série 8.x requer Ruby 3.2+, Rails 7.0+ e Redis 7.2+ (Valkey e Dragonfly funcionam como substitutos diretos desde que o Redis mudou sua licença). ```ruby # config/initializers/sidekiq.rb Sidekiq.configure_server do |config| config.redis = { url: ENV.fetch('REDIS_URL') } config.concurrency = 10 end Sidekiq.configure_client do |config| config.redis = { url: ENV.fetch('REDIS_URL') } end ``` O Sidekiq 8 trouxe três mudanças importantes: uma interface Web reescrita do zero reduzindo o CSS de 160KB para 16KB e o tempo médio de renderização de 55ms para 3ms, profiling de jobs com [Vernier](https://github.com/jhawthorn/vernier) acessível através de uma nova aba Profiles, e retenção de métricas por até 72 horas. ### Quando escolher Sidekiq Sidekiq é apropriado para cargas de trabalho onde throughput sustentado, latência de recuperação de jobs inferior a milissegundo, ou funcionalidades enterprise como rate limiting e jobs únicos justificam a utilização do Redis. As Capsules, introduzidas na versão 7.0 e agora padrão, permitem que um processo Sidekiq execute múltiplos pools de threads isolados com sua própria concorrência e filas. ```ruby # config/sidekiq.yml :concurrency: 10 :queues: - [critical, 3] - [default, 2] - [low, 1] # Capsules para processamento isolado :capsules: imports: :concurrency: 2 :queues: - imports ``` A API de embedding também permite executar o Sidekiq dentro do processo Puma para aplicações pequenas, similar ao funcionamento do plugin Puma do Solid Queue. ## Good Job 4.x: batches e unicidade nativas com PostgreSQL [Good Job](https://github.com/bensheldon/good_job) utiliza LISTEN/NOTIFY do PostgreSQL para recuperação de jobs e advisory locks para garantir execução única. A versão 4.19.x (atual em setembro de 2026) suporta Rails 6.1+ e Ruby 3.0+. ```ruby # config/application.rb config.active_job.queue_adapter = :good_job # config/initializers/good_job.rb Rails.application.configure do config.good_job.execution_mode = :async config.good_job.max_threads = 5 config.good_job.poll_interval = 5 config.good_job.shutdown_timeout = 25 end ``` O Good Job inclui batches e jobs únicos na versão open-source, funcionalidades que o Sidekiq reserva para licenças pagas. A chamada `perform_later` insere uma linha na tabela `good_jobs` dentro da mesma transação de banco de dados que o código chamador, então se a transação for revertida, o job nunca é criado. ### Batches sem licença ```ruby # app/jobs/batch_import_job.rb class BatchImportJob < ApplicationJob def perform(batch, params) batch.on(:finish, ImportCompletionJob, params) params[:file_ids].each do |file_id| batch.add do ProcessFileJob.perform_later(file_id) end end end end # Enfileirar o batch GoodJob::Batch.enqueue do |batch| BatchImportJob.perform_later(batch, file_ids: [1, 2, 3]) end ``` ### Jobs únicos para idempotência ```ruby # app/jobs/sync_inventory_job.rb class SyncInventoryJob < ApplicationJob include GoodJob::ActiveJobExtensions::UniqueJob good_job_control_concurrency_with( perform_limit: 1, key: -> { "sync_inventory_#{arguments.first}" }, duration: 1.hour ) def perform(warehouse_id) InventorySync.run(warehouse_id) end end ``` A restrição `perform_limit: 1` garante que apenas uma sincronização seja executada por armazém em qualquer momento, prevenindo trabalho duplicado quando o mesmo job é enfileirado múltiplas vezes. ## Solid Queue: o padrão do Rails 8 [Solid Queue](https://github.com/rails/solid_queue) armazena jobs no banco de dados da aplicação usando `FOR UPDATE SKIP LOCKED`, uma funcionalidade do PostgreSQL e MySQL que transforma uma tabela regular em uma fila de jobs. Novas aplicações Rails 8 vêm com o Solid Queue configurado por padrão. ```ruby # config/solid_queue.yml production: dispatchers: - polling_interval: 1 batch_size: 500 workers: - queues: "*" threads: 5 polling_interval: 0.1 ``` O Solid Queue pode ser executado dentro do processo do servidor web Puma com `SOLID_QUEUE_IN_PUMA=true`, o que simplifica o deploy para aplicações que não precisam de processos worker dedicados. Os jobs persistem nas tabelas do banco de dados, sobrevivendo a reinícios de processos e deploys sem dependências externas. ## Tabela comparativa de funcionalidades | Funcionalidade | Sidekiq 8.x | Good Job 4.x | Solid Queue 1.x | |----------------|-------------|--------------|------------------| | Backend | Redis 7.2+ | PostgreSQL | PostgreSQL/MySQL/SQLite | | Batches | Pro/Enterprise | Incluído | Planejado | | Jobs únicos | Enterprise | Incluído | Manual via locks | | Agendamento cron | Enterprise | Incluído | Via solid_queue_cron | | Interface Web | Incluída | Incluída | Mínima (Mission Control) | | Teto de throughput | 100k+ jobs/min | 10k jobs/min | 10k jobs/min | | Embed no Puma | Sim (8.x) | Não | Sim | | Licença | LGPL + Comercial | MIT | MIT | ## Perguntas de entrevista sobre jobs em segundo plano no Rails Entrevistas técnicas frequentemente avaliam a compreensão de confiabilidade de jobs, estratégias de retry e decisões arquiteturais. A seguir estão perguntas que distinguem candidatos experientes. ### P1: Qual a diferença entre `retry_on` e a lógica de retry específica do backend? `retry_on` é um callback do Active Job que funciona com todos os backends. Ele captura exceções específicas, aplica uma estratégia de espera (`:polynomially_longer` realiza backoff exponencial) e reenfileira o job. A lógica de retry específica do backend, como `sidekiq_retry_in` do Sidekiq, só funciona com aquele backend e pode sobrescrever o comportamento do Active Job. ```ruby # Retry do Active Job (portável) class ApiSyncJob < ApplicationJob retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 8 def perform(record_id) ExternalApi.sync(record_id) end end ``` ### P2: O que acontece com um job se a transação de banco de dados que o enfileirou for revertida? Com filas baseadas em banco de dados (Good Job, Solid Queue), a linha do job faz parte da transação, então ela é revertida junto com todo o resto. Com filas baseadas em Redis (Sidekiq), o job já está no Redis antes da transação ser completada, criando jobs órfãos. A solução é usar callbacks `after_commit`: ```ruby # Padrão correto para Sidekiq class Order < ApplicationRecord after_commit :enqueue_confirmation, on: :create private def enqueue_confirmation OrderConfirmationJob.perform_later(id) end end ``` ### P3: Quando o Good Job superaria o Sidekiq? O Good Job supera o Sidekiq quando o custo operacional do Redis excede seus benefícios de throughput. Para aplicações que processam menos de 50.000 jobs por dia, filas baseadas em PostgreSQL evitam a complexidade de infraestrutura, monitoramento e failover de um cluster Redis separado. O Good Job também fornece batches e jobs únicos sem custos de licença, o que importa para equipes que precisam dessas funcionalidades com orçamento limitado. ### P4: Explicar idempotência de jobs e por que ela é importante para retries Idempotência significa que executar o mesmo job múltiplas vezes produz o mesmo resultado que executá-lo uma vez. Jobs devem ser idempotentes porque falhas de rede, crashes de processos ou tratamento de timeouts podem causar execuções duplicadas. As técnicas incluem restrições de unicidade no banco de dados, verificar resultados existentes antes de processar e usar chaves de idempotência externas para APIs de pagamento. ```ruby # Processamento de pagamento idempotente def perform(order_id, idempotency_key) return if Payment.exists?(idempotency_key: idempotency_key) Payment.create!( order_id: order_id, idempotency_key: idempotency_key, amount: Order.find(order_id).total ) end ``` ### P5: Como lidar com jobs que devem executar exatamente uma vez em workers distribuídos? Locks distribuídos previnem execução concorrente. O Good Job usa advisory locks do PostgreSQL. O Sidekiq Enterprise oferece jobs únicos. Para implementação manual, usar Redis `SETNX` ou PostgreSQL `pg_try_advisory_lock`: ```ruby class ExclusiveJob < ApplicationJob def perform(resource_id) lock_key = "exclusive_job:#{resource_id}" ActiveRecord::Base.connection.execute( "SELECT pg_try_advisory_lock(hashtext('#{lock_key}'))" ).first['pg_try_advisory_lock'] or return begin process_resource(resource_id) ensure ActiveRecord::Base.connection.execute( "SELECT pg_advisory_unlock(hashtext('#{lock_key}'))" ) end end end ``` ## Escolhendo o backend certo para jobs em segundo plano no Rails - Começar com Solid Queue para novos projetos Rails 8 que ainda não têm Redis no stack - Migrar para Sidekiq quando o volume de jobs exceder 50.000 por dia ou quando latência de recuperação inferior a 100ms for importante - Escolher Good Job para deploys somente PostgreSQL que precisem de batches, jobs únicos ou agendamento cron sem custos de licença - Usar callbacks `after_commit` ao enfileirar jobs com Sidekiq para evitar jobs órfãos em caso de rollback de transação - Tornar cada job idempotente porque retries e processamento distribuído podem causar execuções duplicadas - Para entrevistas, demonstrar compreensão de enfileiramento transacional de jobs, estratégias de retry e padrões de locking distribuído --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/ruby-on-rails/rails-background-jobs-sidekiq-good-job-comparison