En el ecosistema de microservicios hay una pieza que suele aparecer muy pronto: las colas de trabajo. En cuanto una aplicación necesita enviar emails en segundo plano, procesar ficheros, generar PDFs, recalcular informes o ejecutar tareas programadas sin bloquear peticiones HTTP, llega la misma decisión: montar Redis, RabbitMQ o Kafka… o intentar resolverlo con lo que ya se tiene.
En esa segunda categoría entra Kool Queue, una utilidad open source para Micronaut que plantea una idea simple: usar PostgreSQL como backend de colas y evitar dependencias externas. El proyecto se apoya en un patrón conocido en bases de datos: guardar trabajos en una tabla y repartirlos entre varios workers con bloqueos no bloqueantes, gracias a FOR UPDATE SKIP LOCKED.
Qué es Kool Queue y por qué llama la atención
Kool Queue se define como un backend de colas “DB-based” para Micronaut, diseñado con simplicidad y rendimiento, y con una decisión técnica muy concreta: PostgreSQL + FOR UPDATE SKIP LOCKED para que varios workers puedan “pescar” trabajos pendientes sin quedarse esperando bloqueos.
En la práctica, la propuesta encaja con un escenario realista y muy común: ya existe PostgreSQL en producción, se quiere añadir ejecución asíncrona y se prefiere no introducir otra pieza de infraestructura (Redis/cola externa) solo para esto.
Ojo: no es “la cola definitiva” para todo. Es una solución pragmática para una clase concreta de problemas, igual que ocurre con enfoques similares (por ejemplo, Solid Queue en Rails).
La clave técnica: FOR UPDATE SKIP LOCKED
La cláusula SKIP LOCKED permite que, si un worker ya ha bloqueado una fila (un job), otro worker la ignore y pase a la siguiente. Esto evita esperas y reduce la contención cuando hay concurrencia. PostgreSQL incorporó este comportamiento para SELECT ... FOR UPDATE (y variantes) en la versión 9.5.
En el contexto de una cola en BD, el patrón suele ser:
- Seleccionar un job pendiente.
- Bloquearlo (para que nadie más lo ejecute).
- Marcarlo “en progreso”.
- Ejecutar la tarea.
- Marcar “DONE” o “ERROR”.
Kool Queue describe precisamente este flujo, con estados que evolucionan de PENDING → IN_PROGRESS → DONE/ERROR y un scheduler que va consultando trabajos pendientes.
Instalación y dependencias (lo mínimo para arrancar)
El repositorio propone integración vía JitPack y dependencia del proyecto, y en paralelo se observan versiones publicadas en repositorios Maven (por ejemplo, 0.2.0 y 0.2.1 para el módulo core).
A nivel de stack, Kool Queue pide lo típico en Micronaut cuando trabajas con JPA/Hibernate y PostgreSQL: Micronaut Data Hibernate JPA, Hikari y el driver de PostgreSQL.
Ejemplo (Kotlin/Gradle) orientativo:
dependencies {
implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
runtimeOnly("org.postgresql:postgresql")
// Kool Queue (ejemplo; revisa la versión disponible en tu repo elegido)
implementation("com.github.joaquindiez:micronaut-kool-queue:0.2.1")
}
Lenguaje del código: JavaScript (javascript)
Y un application.yml típico (adaptado) con datasource y scheduler:
datasources:
default:
url: jdbc:postgresql://localhost:5432/tu_bd
username: tu_usuario
password: tu_password
driver-class-name: org.postgresql.Driver
jpa:
default:
properties:
hibernate:
hbm2ddl:
auto: update
micronaut:
scheduler:
kool-queue:
enabled: true
max-concurrent-tasks: 3
default-interval: 30s
default-initial-delay: 10s
shutdown-timeout-seconds: 30
Uso: definir un job y lanzarlo “en diferido”
El repositorio muestra el enfoque con una clase de job que procesa un payload y un método para encolar (processLater).
Un ejemplo práctico (Kotlin) podría ser:
import jakarta.inject.Singleton
data class EmailData(val recipient: String, val subject: String, val body: String)
@Singleton
class EmailNotificationJob : ApplicationJob<EmailData>() {
override fun process(data: EmailData): Result<Boolean> {
return try {
// Lógica real: enviar email, llamar a API, etc.
println("Enviando email a ${data.recipient}: ${data.subject}")
Result.success(true)
} catch (e: Exception) {
Result.failure(e)
}
}
}
Lenguaje del código: HTML, XML (xml)
Y desde un controlador:
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.*
@Controller("/notifications")
class NotificationController(private val emailJob: EmailNotificationJob) {
@Post("/send-email")
fun sendEmail(@Body emailData: EmailData): HttpResponse<String> {
val ref = emailJob.processLater(emailData)
return HttpResponse.ok("Email encolado. Job ID: ${ref.jobId}")
}
}
Lenguaje del código: HTML, XML (xml)
La librería indica que los trabajos se procesan automáticamente con el scheduler: sondeo periódico, concurrencia limitada por configuración, y transición de estados.
Dónde encaja (y dónde no)
Una cola en base de datos puede ser “suficiente” —incluso ideal— si el objetivo es reducir complejidad operativa. Pero conviene tener claras las fronteras.
| Enfoque | Infra extra | Punto fuerte | Riesgo típico | Cuándo elegirlo |
|---|---|---|---|---|
| Kool Queue (PostgreSQL) | No | Menos piezas; rápido de adoptar | Contención/crecimiento de tabla; tuning de BD | Jobs internos, throughput moderado, stack minimalista |
| Redis + workers | Sí (Redis) | Alta velocidad; patrones maduros | Operar Redis y persistencia | Mucha cola, latencias bajas, escalado fácil |
| RabbitMQ | Sí | Enrutado, ACK/NACK, mensajería clásica | Operación y diseño de exchanges | Mensajería enterprise, workflows con routing |
| Kafka | Sí | Streaming, retención, replay | Complejidad y coste operativo | Eventos, auditoría, pipelines de datos |
| Solid Queue (Rails) | No | Patrones consolidados en el mundo Rails | Límites similares a DB-queue | Apps Rails que quieren evitar Redis |
Recomendaciones “de admin” para que no se convierta en un problema
Si se usa PostgreSQL como cola, el éxito depende más de operaciones que de magia:
- Índices: si la tabla de jobs crece, necesitas índices por estado/cola/fecha de ejecución.
- Vacuum y bloat: los cambios de estado generan actualizaciones; vigila autovacuum y el tamaño real de la tabla.
- Tiempo de transacción: si un worker mantiene bloqueos demasiado tiempo, penaliza al resto (aunque
SKIP LOCKEDreduzca esperas). - Observabilidad: métricas de backlog (PENDING), tasa de errores (ERROR), tiempo medio en IN_PROGRESS.
- Backoff y reintentos: define estrategias para fallos (reintentar con delay, límite de reintentos, dead-letter lógico si aplica).
Kool Queue no pretende ocultar estas realidades: propone un camino directo para “tener colas ya”, pero sigue siendo responsabilidad del equipo tratar la cola como un subsistema con métricas y mantenimiento.
Preguntas frecuentes
¿Cómo crear una cola de trabajos en Micronaut sin Redis?
Con enfoques DB-based como Kool Queue, usando PostgreSQL y patrones tipo FOR UPDATE SKIP LOCKED para repartir jobs entre workers.
¿Qué ventajas tiene usar PostgreSQL como sistema de colas?
Menos infraestructura, despliegue más simple y menos puntos de fallo, especialmente si PostgreSQL ya es parte del stack.
¿FOR UPDATE SKIP LOCKED sirve para ejecutar varios workers en paralelo?
Sí: permite que cada worker ignore filas ya bloqueadas por otros, reduciendo esperas y contención en escenarios concurrentes.
¿Cuándo conviene pasar de una cola en base de datos a RabbitMQ/Kafka/Redis?
Cuando crecen el volumen, la necesidad de latencia muy baja, el enrutado avanzado, la retención/replay de eventos o la separación estricta entre cargas.