Skip to main content

Por qué esto importa


Cada transacción en Midaz crea operaciones de saldo que Midaz debe conservar. En el modo síncrono predeterminado, Midaz las escribe directamente en la base de datos dentro del ciclo de la solicitud. Esto funciona bien para volúmenes moderados. A gran escala (miles de transacciones por segundo), se convierte en el cuello de botella. Bulk Recorder acumula los mensajes y los escribe en lotes. Hace menos viajes de ida y vuelta a PostgreSQL, reduce la contención de bloqueos y aumenta el rendimiento. Las cargas de trabajo de alto volumen son las que más se benefician: pagos masivos, liquidaciones por lotes y procesamiento de pagos en tiempo real. Para conocer estrategias más amplias para escalar Midaz, consulta Estrategias de escalabilidad.

Cómo funciona


Bulk Recorder se ubica entre el consumidor de RabbitMQ y la capa de base de datos. No inserta cada mensaje de inmediato. En cambio, recopila los mensajes en un búfer y los vacía bajo dos condiciones:
  1. Se alcanza el tamaño del lote: el búfer se llena hasta el tamaño configurado.
  2. Se agota el tiempo de espera: el tiempo de espera de vaciado configurado expira, incluso si el búfer no está lleno.
La condición que ocurra primero activa el vaciado. Esto genera lotes grandes bajo carga y baja latencia durante los períodos tranquilos.
Diagrama de secuencia que muestra a RabbitMQ entregando mensajes al BulkCollector, que los almacena en búfer hasta que se alcanza el tamaño del lote o el tiempo de espera, y luego envía un INSERT masivo fragmentado a PostgreSQL y confirma cada mensaje de vuelta a RabbitMQ.

Figura 1. Flujo de extremo a extremo de Bulk Recorder entre RabbitMQ y PostgreSQL.

Este es el flujo completo, paso a paso:
  1. RabbitMQ entrega los mensajes al BulkCollector de a uno a la vez: mensaje 1, mensaje 2, hasta el mensaje N.
  2. El BulkCollector los mantiene en memoria en lugar de hacer una escritura por mensaje. Recopila hasta que el lote se llena o el tiempo de espera de vaciado expira.
  3. El BulkCollector envía un INSERT masivo fragmentado a PostgreSQL. Midaz divide los lotes grandes en fragmentos que respetan los límites de parámetros de PostgreSQL. Cada fragmento usa ON CONFLICT (id) DO NOTHING, de modo que los reintentos y las entregas duplicadas permanecen seguros.
  4. PostgreSQL confirma la escritura y almacena los datos.
  5. El BulkCollector confirma cada mensaje de vuelta a RabbitMQ después de la escritura. Los confirma de a uno a la vez, no en una única confirmación masiva. Esto evita que un canal compartido confirme mensajes que otros workers todavía procesan.

Habilitar Bulk Recorder


El modo bulk requiere el modo asíncrono. El ajuste explícito de Bulk Recorder es opcional porque está habilitado de forma predeterminada:
El modo asíncrono es obligatorio. Con RABBITMQ_TRANSACTION_ASYNC=true, Bulk Recorder está habilitado de forma predeterminada. Establece BULK_RECORDER_ENABLED=false para procesar los mensajes en cola de forma individual. Sin el modo asíncrono, Midaz conserva las transacciones directamente en lugar de consumir mensajes en cola.
BULK_RECORDER_ENABLED usa true de forma predeterminada cuando no estableces la variable de entorno. Si ya ejecutas con RABBITMQ_TRANSACTION_ASYNC=true, el modo bulk probablemente está activo. Para confirmarlo, revisa los registros de tu aplicación en busca de Bulk mode is ACTIVE en el arranque.

Configuración


Tamaño de lote calculado automáticamente

Cuando BULK_RECORDER_SIZE está en 0 (el valor predeterminado), Midaz deriva el tamaño del lote:
Esto alinea la capacidad del colector con el flujo real de mensajes de RabbitMQ. Evita vaciados parciales y presión de memoria.
Si estableces BULK_RECORDER_SIZE manualmente, mantenlo alineado con tus ajustes de prefetch. Un tamaño mucho mayor que workers × prefetch rara vez llena el colector. En ese caso, el colector vacía casi siempre por el tiempo de espera.

Ajustar para tu carga de trabajo


Las dos palancas principales son el tamaño del lote y el tiempo de espera de vaciado. El equilibrio adecuado depende de tu prioridad: latencia o rendimiento.

Baja latencia (procesamiento en tiempo real)

Mantén los lotes pequeños y los tiempos de espera cortos. Midaz conserva los mensajes rápidamente, incluso cuando los lotes no están llenos.

Alto rendimiento (operaciones por lotes)

Los lotes más grandes y los tiempos de espera más largos maximizan la eficiencia de la base de datos. Usa esto para pagos masivos, liquidaciones de fin de día o cargas de trabajo de migración.
Empieza con los valores predeterminados y ajusta según lo que observes. Para confirmar tus ajustes, busca el registro Bulk mode configured for consumer en el arranque.

Garantías de seguridad


Estos mecanismos mantienen seguro a Bulk Recorder:

Idempotencia

Cada inserción masiva usa ON CONFLICT (id) DO NOTHING. Si un mensaje llega dos veces (por un reintento, una reentrega o una falla de red), PostgreSQL descarta el duplicado. No obtienes corrupción de datos ni violaciones de restricciones.

Prevención de interbloqueos

Antes de cada inserción masiva, Midaz ordena los ID de los registros. Luego, todos los escritores concurrentes adquieren los bloqueos en el mismo orden. Esto elimina la fuente más común de interbloqueos de PostgreSQL bajo alta concurrencia.

Fragmentación interna

Midaz divide los lotes grandes en fragmentos que caben dentro del límite de 65,535 parámetros por consulta de PostgreSQL: Midaz gestiona esta fragmentación por ti. Cada INSERT lleva como máximo 1,000 filas. BULK_RECORDER_MAX_ROWS_PER_INSERT actualmente no se aplica al tamaño del fragmento de PostgreSQL.

Cuándo usarlo


Usa Bulk Recorder cuando:
  • Procesas grandes 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 usas el procesamiento asíncrono de transacciones (RABBITMQ_TRANSACTION_ASYNC=true).
  • Quieres reducir la carga de la base de datos y la presión de conexiones.
Mantenlo deshabilitado cuando:
  • Tu volumen es lo bastante bajo como para que las inserciones individuales no sean un cuello de botella.
  • Necesitas un orden estricto por mensaje que el procesamiento por lotes rompería.
  • Depuras el procesamiento de transacciones y quieres un flujo más simple, mensaje por mensaje.