> ## 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

> Procesa transacciones de forma asíncrona mediante RabbitMQ para reducir la latencia de la API y sostener mayor rendimiento a escala.

## Por qué esto es importante

***

Cuando un cliente envía una transacción, tienen que ocurrir dos cosas: validarla y persistir los resultados. En el modo sincrónico, ambas suceden en la misma solicitud — el cliente espera hasta que todo esté escrito en la base de datos antes de obtener una respuesta.

Eso es simple y predecible, pero tiene un techo. A altos volúmenes, 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. La transacción se valida, la respuesta se devuelve inmediatamente y la persistencia ocurre en segundo plano a través de RabbitMQ. El cliente obtiene respuestas más rápidas. La base de datos recibe escrituras en lotes controlados y optimizados. El sistema maneja más con menos.

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

## Cómo funciona

***

### Modo sincrónico (predeterminado)

La transacción se valida y se escribe directamente en PostgreSQL dentro del mismo ciclo de solicitud. La respuesta de la API solo se envía después de que todas las operaciones de base de datos se completen.

<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/lIYlSqCbra7OC-Hs/images/es/d2/sync-transaction-flow.svg?fit=max&auto=format&n=lIYlSqCbra7OC-Hs&q=85&s=98c2ae408287cca3a76c20203a4d0bd0" 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 200 OK al cliente." className="mx-auto" style={{ width:"80%" }} width="746" 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** — el parsing del DSL, las verificaciones de saldo y la aplicación de límites ocurren aquí.
3. **La API escribe en PostgreSQL** — la transacción y sus operaciones se persisten dentro del mismo ciclo de solicitud.
4. **PostgreSQL confirma la escritura,** reconociendo que todos los registros fueron commiteados.
5. **La API devuelve `200 OK`** al cliente con la transacción creada. La respuesta solo sale del servidor después de que la base de datos haya confirmado todo.

**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 — lo que ves en la respuesta es lo que está persistido.

<Note>
  Incluso en modo sincrónico, los saldos se actualizan atómicamente en Redis —que es la fuente autoritativa— durante la solicitud; la escritura anterior persiste la transacción y sus operaciones, no las filas de saldo en Postgres. Esas filas se reconcilian mediante el worker de sincronización de saldos, siempre activo (consulta [Sincronización de saldos](#sincronización-de-saldos)).
</Note>

### Modo asíncrono

La transacción se valida de la misma manera, pero en lugar de escribir 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/SEOef3JqTInYAAau/images/es/d2/async-transaction-flow.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=5e251b1361036ec4c60499807f7c59a6" 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 200 OK 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 los maneja el worker dedicado de sincronización de saldos." className="mx-auto" style={{ width:"80%" }} width="1302" 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** — el parsing del DSL, las verificaciones de saldo y la aplicación de límites ocurren exactamente como en el modo sincrónico.
3. **La API publica el payload de la transacción en RabbitMQ** en lugar de escribir directamente en la base de datos.
4. **La API devuelve `200 OK`** al cliente inmediatamente después de que el mensaje es aceptado por la cola — el cliente no espera por la persistencia en la base de datos.
5. **RabbitMQ entrega el mensaje** a un consumidor en segundo plano, desacoplado de la solicitud de la API.
6. **El consumidor escribe en PostgreSQL** — la transacción y sus operaciones se persisten a partir del mensaje encolado. Las actualizaciones de saldo se coordinan a través del worker dedicado de sincronización de saldos (consulta la sección **Sincronización de saldos**), que mantiene los saldos consistentes en ambos modos.

**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.
* Los mensajes se serializan con MessagePack para un transporte compacto y eficiente.
* Los consumidores en segundo plano escriben en la base de datos a su propio ritmo, con capacidades de batching y reintentos.

<Tip>
  El paso de validación es idéntico en ambos modos. Verificaciones de saldo, parsing del DSL, 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 no falla la transacción. En su lugar, recurre automáticamente a una escritura directa en la base de datos — la misma ruta que el modo sincrónico.

Esto significa:

* Ninguna transacción se pierde debido a una interrupción de la cola.
* El cliente aún recibe una respuesta exitosa.
* El fallback se registra para que tu equipo de operaciones pueda investigar el problema de la cola.

<Warning>
  El fallback automático garantiza la seguridad de los datos, pero también significa que la latencia se disparará durante una interrupción de la cola (ya que las escrituras van directamente a la base de datos). Monitorea la salud de tu RabbitMQ para mantener el modo asíncrono operando como se espera.
</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>

Cuando se establece en `false` (el valor predeterminado), todas las transacciones usan procesamiento sincrónico. No se necesita ningún consumidor de RabbitMQ.

Cuando se establece en `true`, el ledger publica los payloads de transacción en el exchange configurado de RabbitMQ y 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 de la API de gestión.                      | `3003`                                               |
| `RABBITMQ_PORT_AMQP`                              | Puerto del protocolo AMQP.                        | `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

***

Las actualizaciones de saldos se coordinan a través de un worker dedicado de sincronización que utiliza Redis como capa de coordinación. Este worker se ejecuta tanto en modo síncrono como asíncrono y mantiene los saldos consistentes incluso cuando múltiples consumidores procesan mensajes simultáneamente.

| 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 tanto en modo síncrono como asíncrono. No se requiere configuración adicional más allá de tener Redis disponible.

## Circuit breaker de RabbitMQ

***

Cuando el modo asíncrono está habilitado, Midaz depende de RabbitMQ para la persistencia de las transacciones. Para proteger contra interrupciones del broker, Midaz incluye un circuit breaker integrado que monitorea la salud de la conexión con RabbitMQ y falla rápidamente cuando el broker no está disponible — previniendo la acumulación de solicitudes y las fallas en cascada.

El circuit breaker está siempre activo cuando RabbitMQ está en uso. Sigue el modelo estándar de tres estados:

* **Cerrado** (normal): las solicitudes fluyen hacia RabbitMQ. Las fallas se contabilizan.
* **Abierto** (disparado): las solicitudes se rechazan inmediatamente sin contactar a RabbitMQ. Un verificador de salud en segundo plano monitorea el broker e intenta la recuperación.
* **Semi-abierto** (sondeo): se permite un número limitado de solicitudes para probar si RabbitMQ se ha recuperado. 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
* 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, las transacciones asíncronas recurren a escrituras sincrónicas directas en la base de datos — la transacción no se pierde. Este fallback garantiza la integridad de los datos incluso 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 típicamente se recupera en segundos, o aumenta las fallas consecutivas si experimentas 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. **Modo asíncrono** desacopla la respuesta de la API de la persistencia — las transacciones van a RabbitMQ en lugar de directamente a PostgreSQL.
2. **Bulk Recorder** optimiza cómo el consumidor escribe esos mensajes en la base de datos — agrupando múltiples mensajes en inserciones en lote únicas.

El Bulk Recorder se activa solo cuando el modo asíncrono está habilitado (`RABBITMQ_TRANSACTION_ASYNC=true` Y `BULK_RECORDER_ENABLED=true`). Sin el modo asíncrono, no hay cola de mensajes — y sin una cola, no hay nada 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 `true`  | Basado en cola, mensajes agrupados para inserciones en lote (10×+ de rendimiento) |

## 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).
* Estás ejecutando 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 la configuración más simple posible (sin dependencia de RabbitMQ).
* El volumen de transacciones es bajo a moderado.
* Deseas la garantía de que una respuesta exitosa de la API significa que los datos ya están persistidos.
* Estás en un entorno de desarrollo o pruebas donde la simplicidad importa más que el rendimiento.

<Tip>
  Puedes cambiar entre modos en cualquier momento cambiando `RABBITMQ_TRANSACTION_ASYNC` y reiniciando la aplicación del ledger. No se necesita migración de datos — el formato de la transacción es el mismo en ambas rutas.
</Tip>
