Jobs en segundo plano en Rails 2026: Sidekiq vs Good Job y preguntas de entrevista

Comparación completa entre Sidekiq, Good Job y Solid Queue para tareas asíncronas en Rails, con las preguntas técnicas más frecuentes en entrevistas.

Comparación de soluciones de jobs en segundo plano para Rails: Sidekiq, Good Job y Solid Queue

Los jobs en segundo plano en Rails han evolucionado significativamente con Rails 8.x y la introducción de Solid Queue como backend predeterminado de Active Job. La elección entre Sidekiq, Good Job y Solid Queue depende de los requisitos de rendimiento, las restricciones de infraestructura y las funcionalidades que cada proyecto necesita.

Guía de decisión rápida

Para nuevos proyectos Rails 8, se recomienda comenzar con Solid Queue. Sidekiq es la opción adecuada para cargas de trabajo de alto rendimiento que superan los 100,000 jobs por día o cuando la latencia a nivel de Redis es crítica. Good Job es ideal para jobs respaldados por PostgreSQL con soporte nativo de batches y restricciones de unicidad.

Cómo Active Job unifica el procesamiento en segundo plano

Active Job proporciona una interfaz estandarizada para declarar, encolar y ejecutar trabajo en segundo plano en Rails. El framework abstrae las diferencias entre los backends de cola, permitiendo que el código sea portable entre Sidekiq, Good Job, Solid Queue o cualquier otro 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

Los callbacks retry_on y discard_on manejan fallos transitorios y errores permanentes sin código específico del backend. La directiva queue_as asigna prioridad, que cada backend interpreta según su propia estrategia de procesamiento de colas.

Sidekiq 8.x: rendimiento respaldado por Redis

Sidekiq sigue siendo la referencia de rendimiento para jobs en segundo plano en Rails. La serie 8.x requiere Ruby 3.2+, Rails 7.0+ y Redis 7.2+ (Valkey y Dragonfly funcionan como reemplazos directos desde que Redis cambió su licencia).

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

Sidekiq 8 introdujo tres cambios principales: una interfaz Web reescrita desde cero que reduce el CSS de 160KB a 16KB y el tiempo promedio de renderizado de 55ms a 3ms, perfilado de jobs con Vernier accesible desde una nueva pestaña Profiles, y retención de métricas hasta por 72 horas.

Cuándo elegir Sidekiq

Sidekiq es apropiado para cargas de trabajo donde el rendimiento sostenido, la latencia de recuperación de jobs inferior al milisegundo, o las características empresariales como limitación de tasa y jobs únicos justifican ejecutar Redis. Las Capsules, introducidas en la versión 7.0 y ahora estándar, permiten que un proceso Sidekiq ejecute múltiples pools de hilos aislados con su propia concurrencia y colas.

ruby
# config/sidekiq.yml
:concurrency: 10
:queues:
  - [critical, 3]
  - [default, 2]
  - [low, 1]

# Capsules para procesamiento aislado
:capsules:
  imports:
    :concurrency: 2
    :queues:
      - imports

La API de embedding también permite ejecutar Sidekiq dentro del proceso Puma para aplicaciones pequeñas, similar a cómo funciona el plugin Puma de Solid Queue.

¿Listo para aprobar tus entrevistas de Ruby on Rails?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Good Job 4.x: batches y unicidad nativas con PostgreSQL

Good Job utiliza LISTEN/NOTIFY de PostgreSQL para la recuperación de jobs y bloqueos advisory para garantizar ejecución única. La versión 4.19.x (actual en septiembre 2026) soporta Rails 6.1+ y 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

Good Job incluye batches y jobs únicos en la versión open-source, funcionalidades que Sidekiq reserva para licencias de pago. La llamada perform_later inserta una fila en la tabla good_jobs dentro de la misma transacción de base de datos que el código llamante, por lo que si la transacción se revierte, el job nunca se crea.

Batches sin licencia

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

# Encolar el batch
GoodJob::Batch.enqueue do |batch|
  BatchImportJob.perform_later(batch, file_ids: [1, 2, 3])
end

Jobs únicos para idempotencia

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

La restricción perform_limit: 1 asegura que solo se ejecute una sincronización por almacén en cualquier momento, previniendo trabajo duplicado cuando el mismo job se encola múltiples veces.

Solid Queue: el predeterminado de Rails 8

Solid Queue almacena jobs en la base de datos de la aplicación usando FOR UPDATE SKIP LOCKED, una característica de PostgreSQL y MySQL que convierte una tabla regular en una cola de jobs. Las nuevas aplicaciones Rails 8 vienen con Solid Queue configurado por defecto.

ruby
# config/solid_queue.yml
production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 5
      polling_interval: 0.1

Solid Queue puede ejecutarse dentro del proceso del servidor web Puma con SOLID_QUEUE_IN_PUMA=true, lo que simplifica el despliegue para aplicaciones que no necesitan procesos worker dedicados. Los jobs persisten en las tablas de la base de datos, sobreviviendo a reinicios de procesos y despliegues sin dependencias externas.

Tabla comparativa de funcionalidades

FuncionalidadSidekiq 8.xGood Job 4.xSolid Queue 1.x
BackendRedis 7.2+PostgreSQLPostgreSQL/MySQL/SQLite
BatchesPro/EnterpriseIncluidoPlanificado
Jobs únicosEnterpriseIncluidoManual via bloqueos
Programación cronEnterpriseIncluidoVia solid_queue_cron
Interfaz WebIncluidaIncluidaMínima (Mission Control)
Techo de rendimiento100k+ jobs/min10k jobs/min10k jobs/min
Embeber en PumaSí (8.x)No
LicenciaLGPL + ComercialMITMIT

Preguntas de entrevista sobre jobs en segundo plano en Rails

Las entrevistas técnicas frecuentemente evalúan la comprensión de la confiabilidad de jobs, estrategias de retry y decisiones arquitectónicas. A continuación se presentan preguntas que distinguen a los candidatos experimentados.

P1: ¿Cuál es la diferencia entre retry_on y la lógica de retry específica del backend?

retry_on es un callback de Active Job que funciona con todos los backends. Captura excepciones específicas, aplica una estrategia de espera (:polynomially_longer realiza backoff exponencial) y vuelve a encolar el job. La lógica de retry específica del backend, como sidekiq_retry_in de Sidekiq, solo funciona con ese backend y puede sobrescribir el comportamiento de Active Job.

ruby
# Retry de Active Job (portable)
class ApiSyncJob < ApplicationJob
  retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 8
  
  def perform(record_id)
    ExternalApi.sync(record_id)
  end
end

P2: ¿Qué sucede con un job si la transacción de base de datos que lo encoló se revierte?

Con colas respaldadas por base de datos (Good Job, Solid Queue), la fila del job es parte de la transacción, por lo que se revierte junto con todo lo demás. Con colas respaldadas por Redis (Sidekiq), el job ya está en Redis antes de que la transacción se complete, creando jobs huérfanos. La solución es usar callbacks after_commit:

ruby
# Patrón correcto para Sidekiq
class Order < ApplicationRecord
  after_commit :enqueue_confirmation, on: :create

  private

  def enqueue_confirmation
    OrderConfirmationJob.perform_later(id)
  end
end

P3: ¿Cuándo Good Job superaría a Sidekiq?

Good Job supera a Sidekiq cuando el costo operacional de Redis excede sus beneficios de rendimiento. Para aplicaciones que procesan menos de 50,000 jobs por día, las colas respaldadas por PostgreSQL evitan la complejidad de infraestructura, monitoreo y failover de un cluster Redis separado. Good Job también proporciona batches y jobs únicos sin costos de licencia, lo cual importa para equipos que necesitan estas funcionalidades con presupuesto limitado.

P4: Explicar la idempotencia de jobs y por qué es importante para los retries

Idempotencia significa que ejecutar el mismo job múltiples veces produce el mismo resultado que ejecutarlo una vez. Los jobs deben ser idempotentes porque las fallas de red, crashes de procesos o manejo de timeouts pueden causar ejecuciones duplicadas. Las técnicas incluyen restricciones de unicidad en la base de datos, verificar resultados existentes antes de procesar y usar claves de idempotencia externas para APIs de pago.

ruby
# Procesamiento de pago 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: ¿Cómo manejar jobs que deben ejecutarse exactamente una vez en workers distribuidos?

Los bloqueos distribuidos previenen la ejecución concurrente. Good Job usa bloqueos advisory de PostgreSQL. Sidekiq Enterprise ofrece jobs únicos. Para implementación manual, usar Redis SETNX o 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

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Elegir el backend correcto para jobs en segundo plano en Rails

  • Comenzar con Solid Queue para nuevos proyectos Rails 8 que aún no tienen Redis en el stack
  • Cambiar a Sidekiq cuando el volumen de jobs exceda 50,000 por día o cuando la latencia de recuperación inferior a 100ms sea importante
  • Elegir Good Job para despliegues solo PostgreSQL que necesiten batches, jobs únicos o programación cron sin costos de licencia
  • Usar callbacks after_commit al encolar jobs con Sidekiq para evitar jobs huérfanos en caso de rollback de transacción
  • Hacer cada job idempotente porque los retries y el procesamiento distribuido pueden causar ejecuciones duplicadas
  • Para entrevistas, demostrar comprensión del encolado transaccional de jobs, estrategias de retry y patrones de bloqueo distribuido
Reto diario

¿Sabrías detectar el bug en Ruby on Rails?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 12 de septiembre de 2026

Etiquetas

#Rails
#Sidekiq
#Good Job
#Background Jobs
#Active Job

Compartir

Artículos relacionados