> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Procesamiento asíncrono de transacciones

> El procesamiento asíncrono de transacciones valida la llamada rápido y luego persiste la escritura a través de RabbitMQ, para menor latencia de la API y mayor rendimiento.

## Por qué esto es importante

***

Un cliente envía una transacción. Midaz debe hacer dos cosas: validarla y persistir el resultado. En el modo sincrónico, ambos pasos se ejecutan en la misma solicitud. El cliente espera a que cada escritura llegue a la base de datos antes de obtener una respuesta.

Este modelo es simple y predecible, pero tiene un techo. A alto volumen, las escrituras en la base de datos se convierten en el cuello de botella. Cada transacción retiene una conexión, espera por 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 a través de RabbitMQ. El cliente obtiene respuestas más rápidas. Con el procesamiento asíncrono habilitado, el Bulk Recorder agrupa inserciones de forma predeterminada; configura `BULK_RECORDER_ENABLED=false` para persistir los mensajes en cola individualmente.

Para orientación más amplia sobre escalamiento, consulta [Estrategias de escalabilidad](/es/midaz/scalability-strategies).

## Cómo funciona

***

### Modo sincrónico (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 cada operación de base de datos se completa.

<Frame caption="Figura 1. Flujo de transacción sincrónico — el cliente espera hasta que la escritura en la base de datos sea confirmada.">
  <img src="https://mintcdn.com/lerian-49cb71fc/1c9ccgtH4HzuLWJe/images/es/d2/sync-transaction-flow.svg?fit=max&auto=format&n=1c9ccgtH4HzuLWJe&q=85&s=3aaa75a322cccc30823fa5a56cd33131" alt="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." className="mx-auto" style={{ width:"80%" }} width="866" height="830" data-path="images/es/d2/sync-transaction-flow.svg" />
</Frame>

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 la estructura de la solicitud, las verificaciones de saldo y la aplicación de límites aquí.

3. **La API escribe en PostgreSQL** — persiste la transacción y sus operaciones dentro del mismo ciclo de solicitud.

4. **PostgreSQL confirma la escritura** — hace commit de 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 considerar la transacción liquidada.

**Características:**

* El tiempo de respuesta incluye la latencia de escritura en la base de datos.
* Cada transacción es una operación independiente de base de datos.
* Más simple de razonar. La respuesta muestra exactamente lo que Midaz persiste.

<Note>
  Incluso en modo sincrónico, Midaz actualiza los saldos atómicamente en Redis durante la solicitud. Redis es la fuente autoritativa para 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](#sincronización-de-saldos)).
</Note>

### Modo asíncrono

Midaz valida la transacción de la misma manera. En lugar de una escritura directa en la base de datos, Midaz publica un mensaje en RabbitMQ. Un consumidor en segundo plano recoge el mensaje y maneja la persistencia por separado.

<Frame caption="Figura 2. Flujo de transacción asíncrono — el cliente recibe una respuesta tan pronto como el mensaje se publica, y la persistencia ocurre en segundo plano.">
  <img src="https://mintcdn.com/lerian-49cb71fc/eJbUTctk-eLsW0J5/images/es/d2/async-transaction-flow.svg?fit=max&auto=format&n=eJbUTctk-eLsW0J5&q=85&s=22ddd2974a8cf915917711bdfbbef9a1" alt="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 inmediatamente 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 los maneja el worker dedicado de sincronización de saldos." className="mx-auto" style={{ width:"80%" }} width="1335" height="912" data-path="images/es/d2/async-transaction-flow.svg" />
</Frame>

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 la estructura de la solicitud, las verificaciones de saldo y la aplicación de límites exactamente como en el modo sincrónico.

3. **La API publica el payload de la transacción en RabbitMQ** en lugar de una escritura directa en la base de datos.

4. **La API devuelve `201 Created`** al cliente en cuanto la cola acepta el mensaje — el cliente no espera por 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 considerar la transacción 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 encolado. 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 escritura en la base de datos — el cliente solo espera por 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 en lote requieren un Bulk Recorder habilitado.

<Tip>
  El paso de validación es idéntico en ambos modos. Verificaciones de saldo, validación de la estructura de la solicitud, aplicación de límites — todo eso ocurre antes de que la API responda, independientemente del modo de procesamiento. La diferencia está solo en *cuándo* los datos llegan a la base de datos.
</Tip>

## Resiliencia incorporada

***

Si RabbitMQ no está disponible cuando el modo asíncrono intenta publicar un mensaje, Midaz intenta realizar una escritura directa en 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 falla la escritura de fallback en la base de datos.
* Midaz registra la falla de la cola y, si también falla la escritura directa, la falla del fallback para que tu equipo de operaciones pueda investigarlas.

<Warning>
  Durante una interrupción de la cola, la latencia puede aumentar porque las escrituras van directo a la base de datos. Monitorea la salud de RabbitMQ para mantener activo el modo asíncrono.
</Warning>

## Habilitar el modo asíncrono

***

Configura una variable de entorno en la aplicación del ledger:

<CodeGroup>
  ```bash Variable de entorno theme={null}
  RABBITMQ_TRANSACTION_ASYNC=true
  ```
</CodeGroup>

Con `false` (el valor predeterminado), todas las transacciones usan procesamiento sincrónico y se persisten directamente en PostgreSQL. El bootstrap actual del Ledger todavía inicializa RabbitMQ y conecta su consumidor.

Con `true`, el ledger publica los payloads de transacción en el exchange configurado de RabbitMQ. Luego un consumidor en segundo plano maneja la persistencia.

## Configuración de RabbitMQ

***

El modo asíncrono utiliza las siguientes configuraciones de RabbitMQ (todas en el `.env` del ledger):

| Variable                                          | Descripción                                       | Predeterminado                                       |
| :------------------------------------------------ | :------------------------------------------------ | :--------------------------------------------------- |
| `RABBITMQ_TRANSACTION_ASYNC`                      | Habilita el procesamiento asíncrono.              | `false`                                              |
| `RABBITMQ_HOST`                                   | Hostname del servidor RabbitMQ.                   | `midaz-rabbitmq`                                     |
| `RABBITMQ_PORT_HOST`                              | Puerto del protocolo AMQP.                        | `3003`                                               |
| `RABBITMQ_PORT_AMQP`                              | Puerto de la API de gestión.                      | `3004`                                               |
| `RABBITMQ_DEFAULT_USER`                           | Credenciales del productor (usuario).             | `transaction`                                        |
| `RABBITMQ_DEFAULT_PASS`                           | Credenciales del productor (contraseña).          | —                                                    |
| `RABBITMQ_CONSUMER_USER`                          | Credenciales del consumidor (usuario).            | `consumer`                                           |
| `RABBITMQ_CONSUMER_PASS`                          | Credenciales del consumidor (contraseña).         | —                                                    |
| `RABBITMQ_NUMBERS_OF_WORKERS`                     | Número de goroutines worker del consumidor.       | `5`                                                  |
| `RABBITMQ_NUMBERS_OF_PREFETCH`                    | Mensajes prefetched por worker.                   | `10`                                                 |
| `RABBITMQ_TRANSACTION_BALANCE_OPERATION_EXCHANGE` | Nombre del exchange para mensajes de transacción. | `transaction.transaction_balance_operation.exchange` |
| `RABBITMQ_TRANSACTION_BALANCE_OPERATION_KEY`      | Routing key.                                      | `transaction.transaction_balance_operation.key`      |
| `RABBITMQ_TRANSACTION_BALANCE_OPERATION_QUEUE`    | Nombre de la cola.                                | `transaction.transaction_balance_operation.queue`    |

<Tip>
  El consumidor utiliza credenciales separadas (`RABBITMQ_CONSUMER_USER` / `RABBITMQ_CONSUMER_PASS`) del productor. Esto sigue el principio de menor privilegio — el consumidor solo necesita acceso de lectura a la cola.
</Tip>

## Sincronización de saldos

***

Un worker dedicado de sincronización de saldos coordina las actualizaciones de saldo. Utiliza Redis como capa de coordinación. Este worker se ejecuta tanto en modo sincrónico como asíncrono. Mantiene los saldos consistentes incluso cuando múltiples consumidores procesan mensajes al mismo tiempo.

| Variable                        | Descripción                                                               | Predeterminado |
| :------------------------------ | :------------------------------------------------------------------------ | :------------- |
| `BALANCE_SYNC_BATCH_SIZE`       | Número de actualizaciones de saldo a agrupar antes de vaciar.             | `50`           |
| `BALANCE_SYNC_FLUSH_TIMEOUT_MS` | Tiempo máximo de espera (ms) antes de vaciar un lote incompleto.          | `500`          |
| `BALANCE_SYNC_POLL_INTERVAL_MS` | Frecuencia (ms) con la que el worker verifica actualizaciones pendientes. | `50`           |

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

***

Cuando habilitas el modo asíncrono, Midaz depende de RabbitMQ para la persistencia de las transacciones. Un circuit breaker integrado protege contra interrupciones del broker. Monitorea la salud de la conexión con RabbitMQ y falla rápido cuando el broker se cae. Esto previene la acumulación de solicitudes y las fallas en cascada.

El circuit breaker está activo en la ruta de RabbitMQ de tenant único. RabbitMQ multi-tenant usa la gestión de conexiones por tenant. El circuit breaker sigue el modelo estándar de tres estados:

* **Cerrado** (normal): las solicitudes fluyen hacia RabbitMQ. El breaker contabiliza las fallas.
* **Abierto** (disparado): el breaker rechaza las solicitudes de inmediato y no contacta a RabbitMQ. Un verificador de salud en segundo plano monitorea el broker e intenta la recuperación.
* **Semi-abierto** (sondeo): el breaker deja pasar 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 cualquiera de estas condiciones es verdadera:

* El número de fallas consecutivas alcanza el umbral, O
* La proporción de fallas excede el porcentaje configurado dentro de la ventana de conteo

### Configuración del circuit breaker

| Variable                                         | Descripción                                                                                             | Predeterminado |
| :----------------------------------------------- | :------------------------------------------------------------------------------------------------------ | :------------- |
| `RABBITMQ_CIRCUIT_BREAKER_CONSECUTIVE_FAILURES`  | Fallas consecutivas antes de que el circuito se abra.                                                   | `15`           |
| `RABBITMQ_CIRCUIT_BREAKER_FAILURE_RATIO`         | Porcentaje de fallas (0–100) que activa el estado abierto.                                              | `50`           |
| `RABBITMQ_CIRCUIT_BREAKER_MIN_REQUESTS`          | Solicitudes mínimas antes de evaluar la proporción de fallas.                                           | `10`           |
| `RABBITMQ_CIRCUIT_BREAKER_INTERVAL`              | Ventana de tiempo (segundos) para contar fallas. Los contadores se reinician después de cada intervalo. | `120`          |
| `RABBITMQ_CIRCUIT_BREAKER_TIMEOUT`               | Cuánto tiempo (segundos) el circuito permanece abierto antes de transicionar a semi-abierto.            | `30`           |
| `RABBITMQ_CIRCUIT_BREAKER_MAX_REQUESTS`          | Solicitudes permitidas en estado semi-abierto para sondear la recuperación.                             | `3`            |
| `RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_INTERVAL` | Frecuencia (segundos) con la que el verificador de salud en segundo plano hace ping a RabbitMQ.         | `30`           |
| `RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_TIMEOUT`  | Tiempo de espera (segundos) para cada ping de verificación de salud.                                    | `10`           |

<Note>
  Cuando el circuito está abierto, Midaz intenta realizar escrituras directas en la base de datos para las transacciones asíncronas. Si una escritura directa falla, Midaz devuelve el error de la base de datos; el fallback no garantiza la entrega de la transacción durante interrupciones del broker.
</Note>

<Tip>
  Para la mayoría de los deployments en 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 ves fluctuaciones transitorias de red.
</Tip>

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

***

El modo asíncrono y el [Bulk Recorder](/es/midaz/bulk-recorder) son funcionalidades complementarias que funcionan juntas:

1. **El modo asíncrono** desacopla la respuesta de la API de la persistencia — las transacciones van a RabbitMQ en lugar de directamente a PostgreSQL.

2. **El Bulk Recorder** optimiza cómo el consumidor escribe esos mensajes en la base de datos — agrupa múltiples mensajes en inserciones en lote únicas.

El Bulk Recorder está activo con el modo asíncrono salvo que establezcas explícitamente `BULK_RECORDER_ENABLED=false`. Sin modo asíncrono, la persistencia de transacciones usa escrituras directas en la base de datos y no hay cola de transacciones que agrupar.

| Configuración                                           | Comportamiento de procesamiento                                    |
| :------------------------------------------------------ | :----------------------------------------------------------------- |
| Async `false`                                           | Escritura directa en la base de datos por transacción (sincrónico) |
| Async `true`, Bulk Recorder `false`                     | Basado en cola, un mensaje procesado a la vez                      |
| Async `true`, Bulk Recorder habilitado (predeterminado) | Basado en cola, mensajes agrupados para inserciones en lote        |

## 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 involucra altos volúmenes de transacciones (cientos+ por segundo).
* Ejecutas operaciones por lotes como pagos masivos o liquidaciones.
* Deseas desacoplar tu capa de API del rendimiento de la base de datos.

**Mantén el modo sincrónico cuando:**

* Necesitas persistencia de transacciones directa y ligada a la solicitud.
* El volumen de transacciones es bajo a 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 rendimiento.

<Tip>
  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.
</Tip>
