Saltar al contenido principal

Cuando las escrituras síncronas se convierten en el cuello de botella


Un cliente envía una transacción. Midaz debe hacer dos cosas: validarla y persistir el resultado. En el modo síncrono, ambos pasos se ejecutan en la misma solicitud. Este modelo es simple y predecible, pero tiene un límite. Con un volumen alto, las escrituras a la base de datos se convierten en el cuello de botella. Cada transacción retiene una conexión, espera bloqueos y compite por I/O. El modo asíncrono rompe esa dependencia. Midaz valida la transacción, devuelve la respuesta de inmediato y persiste los datos en segundo plano mediante RabbitMQ. El cliente obtiene respuestas más rápidas. Para una guía más amplia de escalabilidad, consulta Estrategias de escalabilidad.

Cómo funciona


Modo síncrono (predeterminado)

Diagrama de secuencia que muestra al cliente enviando un POST /transaction a la API de Midaz, que lo valida, escribe la transacción y las operaciones en PostgreSQL y devuelve 201 Created al cliente.

Figura 1. Flujo de transacción síncrono.

El flujo completo tiene estos pasos:
  1. El cliente envía un POST /transaction a la API de Midaz.
  2. La API valida la solicitud. Aquí ejecuta la validación de forma de la solicitud, las verificaciones de saldo y la aplicación de límites.
  3. La API escribe en PostgreSQL. Persiste la transacción y sus operaciones dentro del mismo ciclo de solicitud.
  4. La API devuelve 201 Created al cliente con la transacción creada. La respuesta lleva el estado CREATED, y Midaz almacena la transacción como APPROVED. Un 201 significa que Midaz aplicó la transacción a los saldos.
Características:
  • El tiempo de respuesta incluye la latencia de la escritura en la base de datos.
  • Cada transacción es una operación de base de datos independiente.
  • Más simple de razonar.
Incluso en modo síncrono, Midaz actualiza los saldos de forma atómica en Redis durante la solicitud. Redis es la fuente autoritativa de los saldos. La escritura anterior persiste la transacción y sus operaciones, no las filas de saldo en Postgres. El worker de sincronización de saldos, siempre activo, reconcilia esas filas (consulta Sincronización de saldos).

Modo asíncrono

Midaz valida la transacción de la misma forma. En lugar de una escritura directa a la base de datos, Midaz publica un mensaje en RabbitMQ. Un consumidor en segundo plano recoge el mensaje y maneja la persistencia por separado.
Diagrama de secuencia que muestra al cliente enviando un POST /transaction a la API de Midaz, que lo valida, publica el payload en RabbitMQ y devuelve de inmediato 201 Created al cliente. En paralelo, RabbitMQ entrega el mensaje a un consumidor en segundo plano, que escribe la transacción y las operaciones en PostgreSQL; los saldos son manejados por el worker dedicado de sincronización de saldos.

Figura 2. Flujo de transacción asíncrono. El cliente recibe una respuesta en cuanto se publica el mensaje, y la persistencia ocurre en segundo plano.

El flujo completo tiene estos pasos:
  1. El cliente envía un POST /transaction a la API de Midaz.
  2. La API valida la solicitud. Ejecuta la validación de forma de la solicitud, las verificaciones de saldo y la aplicación de límites exactamente igual que en el modo síncrono.
  3. La API publica el payload de la transacción en RabbitMQ en lugar de una escritura directa a la base de datos.
  4. La API devuelve 201 Created al cliente en cuanto la cola acepta el mensaje. El cliente no espera la persistencia en la base de datos. La respuesta lleva el estado CREATED, y Midaz almacena la transacción como APPROVED. Un 201 significa que Midaz aplicó la transacción a los saldos.
  5. RabbitMQ entrega el mensaje a un consumidor en segundo plano, desacoplado de la solicitud de la API.
  6. El consumidor escribe en PostgreSQL. Persiste la transacción y sus operaciones a partir del mensaje en cola. El worker de sincronización de saldos copia los saldos a PostgreSQL en ambos modos (consulta la sección Sincronización de saldos).
Características:
  • El tiempo de respuesta excluye la latencia de la escritura en la base de datos.
  • Las consultas de lista muestran una transacción nueva después de que el consumidor la escribe.
  • Los consumidores en segundo plano escriben en la base de datos a su propio ritmo, con reintentos. Las inserciones por lote requieren que el Bulk Recorder esté habilitado.
El paso de validación es idéntico en ambos modos. Las verificaciones de saldo, la validación de forma de la solicitud y la aplicación de límites ocurren antes de que la API responda, sin importar el modo de procesamiento. La diferencia está solo en cuándo los datos llegan a la base de datos.

Resiliencia integrada


Si la publicación en RabbitMQ falla en el modo asíncrono, Midaz escribe la transacción en la base de datos durante la solicitud. Si esa escritura también falla, la transacción sigue aplicada a los saldos. Midaz conserva el registro de la transacción y reintenta la escritura en segundo plano. El modo síncrono usa la misma recuperación cuando falla su escritura en la base de datos. Esto significa:
  • Durante una interrupción de la cola, Midaz escribe directamente en la base de datos.
  • Una escritura fallida en la base de datos no rechaza una transacción que Midaz ya aplicó a los saldos.
  • Midaz registra la falla de la cola y la escritura diferida, para que tu equipo de operaciones pueda investigarlas.
Durante una interrupción de la cola, la latencia puede aumentar porque las escrituras van directo a la base de datos. Monitorea el estado de tu RabbitMQ para mantener activo el modo asíncrono.

Habilitar el modo asíncrono


Define una variable de entorno en la aplicación del ledger:
Con false (el valor predeterminado), todas las transacciones usan procesamiento síncrono y persisten directamente en PostgreSQL. El ledger de todos modos se conecta a RabbitMQ y ejecuta su consumidor en este modo. Con true, el ledger publica los payloads de transacción en el exchange de RabbitMQ configurado. Un consumidor en segundo plano maneja entonces la persistencia.

Configuración de RabbitMQ


El modo asíncrono usa la siguiente configuración de RabbitMQ (todo en el .env del ledger):
El consumidor usa credenciales separadas (RABBITMQ_CONSUMER_USER / RABBITMQ_CONSUMER_PASS) de las del productor. El consumidor vuelve a publicar los mensajes que reintenta, así que también necesita acceso de escritura al exchange de transacciones.

Sincronización de saldos


Un worker dedicado de sincronización de saldos copia los saldos de Redis a PostgreSQL. Este worker se ejecuta tanto en modo síncrono como en modo asíncrono. El worker de sincronización de saldos se ejecuta automáticamente en ambos modos. No necesitas configuración adicional más allá de una instancia de Redis disponible.

Circuit breaker de RabbitMQ


Un circuit breaker integrado protege las publicaciones en RabbitMQ de mensajes de auditoría y eventos de sobregiro. Monitorea el estado de la conexión con RabbitMQ y falla rápido cuando el broker cae. Esto evita la acumulación de solicitudes y las fallas en cascada.

Configuración del circuit breaker

Cómo se conecta el modo asíncrono con Bulk Recorder


El modo asíncrono y el Bulk Recorder trabajan juntos:
  1. El modo asíncrono desacopla la respuesta de la API de la persistencia. Las transacciones van a RabbitMQ en lugar de ir directamente a PostgreSQL.
  2. Bulk Recorder optimiza cómo el consumidor escribe esos mensajes en la base de datos. Agrupa varios mensajes en inserciones masivas únicas.

Cuándo usar el modo asíncrono


Usa el modo asíncrono cuando:
  • Necesitas tiempos de respuesta de API más bajos para la creación de transacciones.
  • Tu carga de trabajo implica volúmenes altos de transacciones (cientos o más por segundo).
  • Ejecutas operaciones por lote como pagos masivos o liquidaciones.
  • Quieres desacoplar tu capa de API del rendimiento de la base de datos.
Mantén el modo síncrono cuando:
  • Necesitas persistencia de transacciones directa, ligada a la solicitud.
  • El volumen de transacciones es bajo o moderado.
  • Trabajas en un entorno de desarrollo o pruebas donde la simplicidad importa más que el throughput.
Puedes cambiar entre modos en cualquier momento. Cambia RABBITMQ_TRANSACTION_ASYNC y reinicia la aplicación del ledger. No necesitas migración de datos, porque el formato de la transacción es el mismo en ambas rutas.