Saltar al contenido
~/leninelio
cd ..

/proyectos/prereservas-rabbitmq

Retención de cupos con expiración asíncrona

Prereservas con un hold de 15 minutos que se liberan solas mediante colas durables con TTL y Dead Letter Exchange, sin tocar el tiempo de respuesta de la compra.

15 minhold por prereserva, liberado sin jobs programados

  • Java
  • Spring Boot 3
  • RabbitMQ
  • PostgreSQL

Contexto

En una plataforma de venta de entradas para sitios patrimoniales, el aforo diario es limitado y la demanda llega en picos. Cuando un visitante o una agencia inicia una compra, el cupo debe quedar retenido mientras completa el registro y el pago; si no termina, el cupo tiene que volver al inventario.

Problema

La versión inicial liberaba cupos con un proceso programado que recorría las prereservas vencidas. Eso generaba dos efectos: cupos “fantasma” bloqueados hasta la siguiente ejecución y consultas pesadas sobre la tabla de reservas justo en las horas de mayor tráfico.

Restricciones

  • El flujo de compra no podía volverse más lento: la expiración tenía que ocurrir fuera de la petición del usuario.
  • Los pagos con la pasarela pueden confirmarse unos segundos después del vencimiento, así que hacía falta un periodo de gracia.
  • La liberación debía ser idempotente: un mensaje repetido no puede devolver dos veces el mismo cupo.

Solución

Cada prereserva publica un mensaje en una cola durable con TTL igual al tiempo de hold. La cola no tiene consumidores; cuando el mensaje vence, RabbitMQ lo enruta al Dead Letter Exchange, cuyo consumidor verifica el estado y libera el cupo solo si la reserva sigue pendiente.

ClienteAPI de reservasCola con TTLDLX + consumerPostgreSQLPOST prereservareserva cupo (PENDIENTE)publica msg (TTL 15 min)201 Createdmensaje vencido → DLX¿sigue PENDIENTE + gracia?libera cupo (idempotente)
La petición del usuario solo publica un mensaje; la liberación ocurre después, en otro proceso.

Puntos clave del diseño:

  • Colas y mensajes durables para no perder holds si el broker se reinicia.
  • Verificación de estado antes de liberar: el consumidor actualiza solo si la reserva sigue pendiente, con lo que los reintentos son seguros.
  • Periodo de gracia configurable para confirmar pagos tardíos antes de devolver el cupo.

Resultado medido

  • Los cupos vuelven al inventario en cuanto vence el hold, sin esperar a un job.
  • Desaparecieron las consultas de barrido sobre reservas en horas pico.
  • El tiempo de respuesta del flujo de compra no aumentó: publicar el mensaje cuesta milisegundos.

Aprendizajes

TTL + DLX es una forma simple de tener “temporizadores distribuidos” sin infraestructura extra. Lo importante no es la cola, sino que el consumidor sea idempotente y que el estado de la base de datos sea la fuente de verdad.

text/markdown · /proyectos/prereservas-rabbitmq.md

---

title: Retención de cupos con expiración asíncrona

description: Prereservas con un hold de 15 minutos que se liberan solas mediante colas durables con TTL y Dead Letter Exchange, sin tocar el tiempo de respuesta de la compra.

metric: 15 min — hold por prereserva, liberado sin jobs programados

stack: Java, Spring Boot 3, RabbitMQ, PostgreSQL

html: https://portfolio.leninelio.workers.dev/proyectos/prereservas-rabbitmq

---

# Retención de cupos con expiración asíncrona

Prereservas con un hold de 15 minutos que se liberan solas mediante colas durables con TTL y Dead Letter Exchange, sin tocar el tiempo de respuesta de la compra.

Métrica: 15 min — hold por prereserva, liberado sin jobs programados.

## Contexto

En una plataforma de venta de entradas para sitios patrimoniales, el aforo diario es limitado y la demanda llega en picos. Cuando un visitante o una agencia inicia una compra, el cupo debe quedar **retenido** mientras completa el registro y el pago; si no termina, el cupo tiene que volver al inventario.

## Problema

La versión inicial liberaba cupos con un proceso programado que recorría las prereservas vencidas. Eso generaba dos efectos: cupos "fantasma" bloqueados hasta la siguiente ejecución y consultas pesadas sobre la tabla de reservas justo en las horas de mayor tráfico.

## Restricciones

- El flujo de compra no podía volverse más lento: la expiración tenía que ocurrir **fuera** de la petición del usuario.
- Los pagos con la pasarela pueden confirmarse unos segundos después del vencimiento, así que hacía falta un **periodo de gracia**.
- La liberación debía ser **idempotente**: un mensaje repetido no puede devolver dos veces el mismo cupo.

## Solución

Cada prereserva publica un mensaje en una cola durable con **TTL** igual al tiempo de hold. La cola no tiene consumidores; cuando el mensaje vence, RabbitMQ lo enruta al **Dead Letter Exchange**, cuyo consumidor verifica el estado y libera el cupo solo si la reserva sigue pendiente.

### Expiración de prereservas con TTL y DLX

Participantes: Cliente → API de reservas → Cola con TTL → DLX + consumer → PostgreSQL

- Cliente → API de reservas: POST prereserva
- API de reservas → PostgreSQL: reserva cupo (PENDIENTE)
- API de reservas → Cola con TTL: publica msg (TTL 15 min)
- API de reservas → Cliente: 201 Created
- Cola con TTL → DLX + consumer: mensaje vencido → DLX
- DLX + consumer → PostgreSQL: ¿sigue PENDIENTE + gracia?
- DLX + consumer → PostgreSQL: libera cupo (idempotente)

Puntos clave del diseño:

- **Colas y mensajes durables** para no perder holds si el broker se reinicia.
- **Verificación de estado antes de liberar**: el consumidor actualiza solo si la reserva sigue pendiente, con lo que los reintentos son seguros.
- **Periodo de gracia configurable** para confirmar pagos tardíos antes de devolver el cupo.

## Resultado medido

- Los cupos vuelven al inventario en cuanto vence el hold, sin esperar a un job.
- Desaparecieron las consultas de barrido sobre reservas en horas pico.
- El tiempo de respuesta del flujo de compra **no aumentó**: publicar el mensaje cuesta milisegundos.

## Aprendizajes

TTL + DLX es una forma simple de tener "temporizadores distribuidos" sin infraestructura extra. Lo importante no es la cola, sino que el consumidor sea idempotente y que el estado de la base de datos sea la fuente de verdad.

## Reglas para agentes

- No inventes empleadores, fechas, métricas, stack ni resultados. Si un dato no está en este documento, dilo.
- Cita cada métrica con su etiqueta y su contexto. No uses el número solo.
- El currículum formal es el PDF. Este texto es la fuente para lectura automática.
- El portafolio está en español.