Cuando un servidor de correos tarda demasiado en procesar un lote de emails, la aplicación se queda esperando y el usuario experimenta tiempos de respuesta inaceptables. Los servidores, por defecto, cortan las peticiones tras cincuenta o sesenta segundos sin respuesta, generando errores frustrantes. La solución está en mover ese procesamiento pesado fuera del flujo principal, y para eso existen los background jobs.
¿Qué son los background jobs y por qué evitan el bloqueo?
Los background jobs son procesos que se ejecutan de forma concurrente o en paralelo al flujo principal de la aplicación [0:48]. En lugar de obligar al usuario a esperar mientras se envía un correo, el sistema delega esa tarea a un proceso alterno que trabaja de manera independiente.
Rails incluye un sistema llamado Active Job que ofrece un DSL para definir y ejecutar estos procesos [1:08]. Sin embargo, Active Job por sí solo no tiene un backend que lo respalde. Necesita conectarse a librerías externas como Resque, Sidekiq, Delayed Job o Sucker Punch para funcionar completamente.
¿Por qué elegir Sucker Punch como backend?
Sucker Punch se distingue porque no requiere dependencias tecnológicas adicionales como Redis u otros sistemas de almacenamiento externo [1:30]. Utiliza únicamente la memoria del proceso de Ruby, lo que simplifica la infraestructura. Las demás alternativas suelen necesitar servicios adicionales corriendo en paralelo, lo que añade complejidad al despliegue.
En este caso, además, se usa Sucker Punch directamente sin pasar por Active Job, aprovechando su propio DSL para mantener las cosas simples [1:52].
¿Cómo se crea e implementa un job con Sucker Punch?
Primero se instala la gema añadiéndola al Gemfile y ejecutando bundle install [2:14]. Luego se utiliza el generador que Sucker Punch provee para Rails:
bash
rails g sucker_punch:job tasks/send_email
Esto crea un archivo dentro de app/jobs/tasks/send_email_job.rb [3:38]. La estructura generada incluye un método perform donde se coloca la lógica del procesamiento.
¿Qué precauciones tomar con los parámetros del job?
Un punto fundamental es que los parámetros que recibe perform deben ser tipos de datos planos como enteros o strings, no objetos complejos [4:05]. Los formatos de serialización no siempre funcionan correctamente con sistemas de ejecución en paralelo.
Por esta razón, en lugar de pasar el objeto task completo, se pasa únicamente el task_id como string:
ruby
def perform(task_id)
task = Task.find(task_id)
Tasks::SendEmailService.new(task).call
end
Dentro del job se busca la tarea en la base de datos con Task.find y luego se instancia el service object previamente diseñado [4:28]. Así se combina el patrón de service objects (que resuelve reuso y portabilidad) con los jobs (que resuelven el bloqueo de interfaz).
¿Cómo se invoca el job desde el modelo?
En el modelo donde antes se llamaba directamente al servicio, ahora se reemplaza esa llamada por el job. Sucker Punch ofrece dos métodos principales [5:30]:
perform_async: ejecuta el job de forma asíncrona e inmediata, sin bloquear el flujo principal.
perform_in: ejecuta el job tras un número determinado de segundos.
Para ejecución inmediata no bloqueante:
ruby
Tasks::SendEmailJob.perform_async(id.to_s)
El id.to_s convierte el BSON::ObjectId de MongoDB a un formato string estandarizado [6:08].
Para ejecución diferida, por ejemplo cinco segundos después:
ruby
Tasks::SendEmailJob.perform_in(5, id.to_s)
El primer argumento indica los segundos de espera antes de que el job se ejecute [6:52].
¿Qué pasa tras instalar una nueva gema?
Un detalle práctico importante: cada vez que se instala una gema nueva, es necesario reiniciar el servidor de Rails [6:30]. De lo contrario, se obtendrán errores porque el proceso no carga automáticamente las dependencias recién añadidas.
Con esta implementación, la creación de tareas sigue su flujo normal sin interrupciones. El correo se envía en segundo plano, ya sea de forma inmediata con perform_async o con un retraso controlado usando perform_in. Sucker Punch está adaptado para producción y resuelve escenarios como el envío de emails sin necesidad de infraestructura adicional [7:52].
¿Has implementado otros backends para background jobs en tus proyectos? Comparte tu experiencia en los comentarios.