Skip to main content

Por qué esto importa


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. El cliente espera hasta que cada escritura llega a la base de datos antes de recibir una respuesta. 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. Con el procesamiento asíncrono habilitado, el Bulk Recorder agrupa las inserciones de forma predeterminada. Define BULK_RECORDER_ENABLED=false para persistir los mensajes en cola de forma individual. Para una guía más amplia de escalabilidad, consulta Estrategias de escalabilidad.

Cómo funciona


Modo síncrono (predeterminado)

Midaz valida la transacción y la escribe directamente en PostgreSQL dentro del mismo ciclo de solicitud. La API envía su respuesta solo después de que se completa cada operación de base de datos.
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, espera la confirmación y solo entonces devuelve 201 Created al cliente. La respuesta 201 Created lleva el estado transitorio CREATED, no la aprobación final.

Figura 1. Flujo de transacción síncrono. El cliente espera hasta que se confirma la escritura en la base de datos.

Aquí está el flujo completo, paso a paso:
  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. PostgreSQL confirma la escritura. Confirma todos los registros.
  5. La API devuelve 201 Created al cliente con la transacción creada. La respuesta sale del servidor solo después de que la base de datos confirma todo. La respuesta lleva el estado transitorio CREATED. Midaz promueve la transacción a APPROVED de forma asíncrona después del procesamiento de saldos. No trates el 201 como aprobación final. Espera a que el estado llegue a APPROVED antes de tratar la transacción como liquidada.
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. La respuesta muestra exactamente lo que Midaz persiste.
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. La respuesta 201 Created lleva el estado transitorio CREATED, no la aprobación final. 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.

Aquí está el flujo completo, paso a paso:
  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 transitorio CREATED. Midaz promueve la transacción a APPROVED de forma asíncrona después del procesamiento de saldos. No trates el 201 como aprobación final. Espera a que el estado llegue a APPROVED antes de tratar la transacción como liquidada.
  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 coordina las actualizaciones de saldo y mantiene los saldos consistentes 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. El cliente espera solo la validación y la publicación en la cola.
  • Midaz serializa los mensajes con MessagePack para un transporte compacto y eficiente.
  • 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 RabbitMQ no está disponible cuando el modo asíncrono intenta publicar un mensaje, Midaz intenta una escritura directa a la base de datos. Si esa escritura falla, Midaz devuelve el error de la base de datos. Esto significa:
  • Durante una interrupción de la cola, Midaz intenta escribir directamente en la base de datos.
  • El cliente puede recibir un error si la escritura de reserva a la base de datos también falla.
  • Midaz registra la falla de la cola y, si la escritura directa también falla, la falla de la escritura de reserva, 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 bootstrap actual del Ledger de todos modos inicializa RabbitMQ y conecta su consumidor. 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. Esto sigue el principio de mínimo privilegio. El consumidor solo necesita acceso de lectura a la cola.

Sincronización de saldos


Un worker dedicado de sincronización de saldos coordina las actualizaciones de saldo. Usa Redis como capa de coordinación. Este worker se ejecuta tanto en modo síncrono como en modo asíncrono. Mantiene los saldos consistentes incluso cuando varios consumidores procesan mensajes al mismo tiempo. El worker de sincronización de saldos se ejecuta automáticamente en ambos modos. No necesitas configuración adicional más allá de contar con una instancia de Redis disponible.

Circuit breaker de RabbitMQ


Cuando habilitas el modo asíncrono, Midaz depende de RabbitMQ para la persistencia de transacciones. Un circuit breaker integrado protege contra interrupciones del broker. 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. El circuit breaker está activo en la ruta de RabbitMQ de tenant único. El RabbitMQ multi-tenant usa en su lugar la gestión de conexiones por tenant. El circuit breaker sigue el modelo estándar de tres estados:
  • Closed (normal): las solicitudes fluyen hacia RabbitMQ. El breaker cuenta las fallas.
  • Open (activado): el breaker no contacta a RabbitMQ. Midaz evita el broker e intenta una escritura directa a la base de datos para cada transacción asíncrona. Si esa escritura falla, Midaz devuelve el error de la base de datos. Un verificador de estado en segundo plano monitorea el broker e intenta la recuperación.
  • Half-open (en prueba): el breaker permite el paso de un número limitado de solicitudes para probar la recuperación de RabbitMQ. Si tienen éxito, el circuito se cierra. Si fallan, se vuelve a abrir.
El circuito se abre cuando se cumple cualquiera de estas condiciones:
  • El número de fallas consecutivas alcanza el umbral, O
  • El ratio de fallas supera el porcentaje configurado dentro de la ventana de conteo

Configuración del circuit breaker

Cuando el circuito está abierto, Midaz intenta escrituras directas a la base de datos para las transacciones asíncronas. Si una escritura directa falla, Midaz devuelve el error de la base de datos. La reserva no garantiza la entrega de la transacción durante interrupciones del broker.
Para la mayoría de los despliegues de producción, los valores predeterminados funcionan bien. Ajusta CONSECUTIVE_FAILURES y TIMEOUT si tu clúster de RabbitMQ tiene patrones de recuperación conocidos. Por ejemplo, reduce el timeout si tu broker se recupera en segundos. Aumenta las fallas consecutivas si observas interrupciones de red transitorias.

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.
Bulk Recorder está activo con el modo asíncrono a menos que definas explícitamente BULK_RECORDER_ENABLED=false. Sin el modo asíncrono, la persistencia de transacciones usa escrituras directas a la base de datos y no hay cola de transacciones para agrupar.

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.
  • Quieres que una respuesta exitosa de la API signifique que Midaz ya persistió los datos.
  • 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.