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

# Bulk Recorder

> Agrupa mensajes de transacciones desde RabbitMQ en inserciones masivas y obtén más de 10x de rendimiento en cargas de alto volumen.

## Por qué esto es importante

***

Cada transacción en Midaz genera operaciones de saldo que necesitan ser persistidas. En el modo predeterminado (sincrónico), cada mensaje desencadena una inserción individual en la base de datos. Eso está bien para volúmenes moderados, pero a escala — miles de transacciones por segundo — se convierte en el cuello de botella.

El Bulk Recorder cambia esto acumulando mensajes y escribiéndolos en lotes. Menos viajes de ida y vuelta a PostgreSQL, menos contención de bloqueos y un rendimiento significativamente mayor. Para operaciones de alto volumen como pagos masivos, liquidaciones por lotes o procesamiento de pagos en tiempo real, esta es la diferencia entre un sistema que mantiene el ritmo y uno que no.

Para estrategias más amplias sobre cómo escalar Midaz, consulta [Estrategias de escalabilidad](/es/midaz/scalability-strategies).

## Cómo funciona

***

El Bulk Recorder se sitúa entre el consumidor de RabbitMQ y la capa de base de datos. En lugar de insertar cada mensaje inmediatamente, los recopila en un búfer y los descarga bajo dos condiciones:

1. **Tamaño de lote alcanzado** — el búfer se llena hasta el tamaño configurado.
2. **Tiempo de espera agotado** — expira el tiempo de espera de descarga configurado, incluso si el búfer no está lleno.

Lo que ocurra primero desencadena la descarga. Esto garantiza tanto el rendimiento (lotes grandes bajo carga) como la latencia (ningún mensaje espera indefinidamente durante períodos de poca actividad).

<Frame caption="Figura 1. Flujo de extremo a extremo del Bulk Recorder entre RabbitMQ y PostgreSQL.">
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/es/d2/bulk-recorder-flow.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=e7b7c911538f89bc5cd5346b309f3dd4" alt="Diagrama de secuencia que muestra a RabbitMQ entregando mensajes al BulkCollector, el cual los almacena en un búfer hasta alcanzar el tamaño de lote o expirar el tiempo de espera, y luego envía un INSERT en lote fragmentado a PostgreSQL y confirma todos los mensajes de vuelta a RabbitMQ." className="mx-auto" style={{ width:"80%" }} width="807" height="994" data-path="images/es/d2/bulk-recorder-flow.svg" />
</Frame>

Aquí está el flujo completo, paso a paso:

1. **RabbitMQ entrega los mensajes** al BulkCollector uno a la vez — Mensaje 1, Mensaje 2, y así hasta el Mensaje N.
2. **El BulkCollector los mantiene en memoria** en lugar de escribir cada uno inmediatamente. Sigue recopilando hasta que se alcanza el tamaño del lote o expira el tiempo de espera de descarga.
3. **El BulkCollector envía un INSERT en lote fragmentado a PostgreSQL.** Los lotes grandes se dividen automáticamente en fragmentos que respetan los límites de parámetros de PostgreSQL. Cada fragmento utiliza `ON CONFLICT (id) DO NOTHING`, por lo que los reintentos y las entregas duplicadas se manejan de forma segura.
4. **PostgreSQL confirma la escritura,** asegurando que los datos fueron persistidos.
5. **El BulkCollector confirma todos los mensajes de vuelta a RabbitMQ** en un único ACK, liberándolos de la cola en conjunto.

## Habilitar el Bulk Recorder

***

El modo en lote requiere que dos condiciones estén activas simultáneamente:

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

Si alguna de las condiciones no se cumple, Midaz procesa los mensajes individualmente — el mismo comportamiento de antes. Sin cambios de código, sin migración necesaria.

<Tip>
  `BULK_RECORDER_ENABLED` tiene como valor predeterminado `true` cuando la variable de entorno no está configurada. Por lo tanto, si ya estás ejecutando con `RABBITMQ_TRANSACTION_ASYNC=true`, es probable que el modo en lote esté activo. Revisa los logs de tu aplicación buscando `Bulk mode is ACTIVE` al inicio para confirmarlo.
</Tip>

## Configuración

***

| Variable                            | Descripción                                                                        | Predeterminado   |
| :---------------------------------- | :--------------------------------------------------------------------------------- | :--------------- |
| `BULK_RECORDER_ENABLED`             | Habilita o deshabilita el modo en lote.                                            | `true`           |
| `BULK_RECORDER_SIZE`                | Número de mensajes a acumular antes de descargar. `0` = calculado automáticamente. | `0` (automático) |
| `BULK_RECORDER_FLUSH_TIMEOUT_MS`    | Tiempo máximo (ms) de espera antes de descargar un lote incompleto.                | `100`            |
| `BULK_RECORDER_MAX_ROWS_PER_INSERT` | Máximo de filas por sentencia `INSERT` enviada a PostgreSQL.                       | `1000`           |

### Tamaño de lote calculado automáticamente

Cuando `BULK_RECORDER_SIZE` se establece en `0` (el valor predeterminado), el tamaño del lote se deriva automáticamente:

```
tamaño de lote = RABBITMQ_NUMBERS_OF_WORKERS × RABBITMQ_NUMBERS_OF_PREFETCH
```

Esto alinea la capacidad del recopilador con el flujo real de mensajes desde RabbitMQ, evitando descargas parciales o presión de memoria.

<Warning>
  Si configuras `BULK_RECORDER_SIZE` manualmente, asegúrate de que esté alineado con tu configuración de prefetch. Un tamaño mucho mayor que `workers × prefetch` significa que el recopilador rara vez se llenará y dependerá principalmente de descargas basadas en tiempo de espera.
</Warning>

## Ajuste para tu carga de trabajo

***

Las dos palancas principales son el **tamaño del lote** y el **tiempo de espera de descarga**. El equilibrio correcto depende de si priorizas la latencia o el rendimiento.

### Baja latencia (procesamiento en tiempo real)

Mantén los lotes pequeños y los tiempos de espera cortos. Los mensajes se persisten rápidamente, incluso si los lotes no están llenos.

<CodeGroup>
  ```bash Configuración de baja latencia theme={null}
  RABBITMQ_NUMBERS_OF_WORKERS=5
  RABBITMQ_NUMBERS_OF_PREFETCH=10
  BULK_RECORDER_SIZE=0          # automático: 5 × 10 = 50
  BULK_RECORDER_FLUSH_TIMEOUT_MS=50
  ```
</CodeGroup>

### Alto rendimiento (operaciones por lotes)

Lotes más grandes y tiempos de espera más largos maximizan la eficiencia de la base de datos. Ideal para pagos masivos, liquidaciones de fin de día o cargas de trabajo de migración.

<CodeGroup>
  ```bash Configuración de alto rendimiento theme={null}
  RABBITMQ_NUMBERS_OF_WORKERS=10
  RABBITMQ_NUMBERS_OF_PREFETCH=20
  BULK_RECORDER_SIZE=0          # automático: 10 × 20 = 200
  BULK_RECORDER_FLUSH_TIMEOUT_MS=300
  ```
</CodeGroup>

<Tip>
  Comienza con los valores predeterminados y ajusta según el comportamiento observado. Monitorea el log `Bulk mode configured for consumer` al inicio para confirmar que tu configuración se aplicó.
</Tip>

## Garantías de seguridad

***

El Bulk Recorder está diseñado para ser seguro en todas las condiciones:

### Idempotencia

Cada inserción en lote utiliza `ON CONFLICT (id) DO NOTHING`. Si un mensaje se entrega dos veces — debido a un reintento, una re-entrega o un problema de red — el duplicado se descarta silenciosamente. Sin corrupción de datos, sin violaciones de restricciones.

### Prevención de deadlocks

Antes de cada inserción en lote, los IDs de los registros se ordenan. Esto garantiza que todos los escritores concurrentes adquieran los bloqueos en el mismo orden, eliminando la fuente más común de deadlocks de PostgreSQL en escenarios de alta concurrencia.

### Fragmentación interna

Los lotes grandes se dividen automáticamente en fragmentos que se ajustan al límite de 65.535 parámetros por consulta de PostgreSQL:

| Tipo de registro | Columnas por fila | Filas por fragmento | Parámetros por fragmento |
| :--------------- | :---------------- | :------------------ | :----------------------- |
| Transaction      | 15                | 1.000               | 15.000                   |
| Operation        | 30                | 1.000               | 30.000                   |

Esta fragmentación se maneja internamente. Solo necesitas configurar `BULK_RECORDER_MAX_ROWS_PER_INSERT` si deseas ajustar el tamaño del fragmento — el valor predeterminado de 1.000 filas es óptimo para la mayoría de los deployments.

## Cuándo usar

***

**Usa el Bulk Recorder cuando:**

* Procesas altos volúmenes de transacciones (cientos o miles por segundo).
* Tu carga de trabajo incluye operaciones por lotes como pagos masivos, liquidaciones o migraciones de datos.
* Ya estás utilizando procesamiento asíncrono de transacciones (`RABBITMQ_TRANSACTION_ASYNC=true`).
* Deseas reducir la carga de la base de datos y la presión de conexión.

**Mantenlo deshabilitado cuando:**

* Tu volumen es lo suficientemente bajo como para que las inserciones individuales no sean un cuello de botella.
* Necesitas garantías estrictas de ordenamiento por mensaje que el procesamiento por lotes rompería.
* Estás depurando el procesamiento de transacciones y quieres un flujo más simple, mensaje por mensaje.
