Skip to main content

Por qué esto es importante


Cada transacción en Midaz crea operaciones de saldo que Midaz debe persistir. En el modo síncrono predeterminado, Midaz las escribe directamente en la base de datos durante el ciclo de la solicitud. Esto es aceptable para volúmenes moderados. A escala — miles de transacciones por segundo — se convierte en el cuello de botella. El Bulk Recorder acumula 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 alto volumen son las que más ganan: pagos masivos, liquidaciones por lotes y procesamiento de pagos en tiempo real. Para estrategias más amplias para escalar Midaz, consulta Estrategias de escalabilidad.

Cómo funciona


El Bulk Recorder se sitúa entre el consumidor de RabbitMQ y la capa de base de datos. No inserta cada mensaje de inmediato. En su lugar, recopila los mensajes 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 — el tiempo de espera de descarga configurado expira, incluso si el búfer no está lleno.
La primera condición que ocurra desencadena la descarga. Esto da lotes grandes bajo carga y baja latencia durante los períodos de poca actividad.
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 cada mensaje de vuelta a RabbitMQ.

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

Aquí está el flujo completo, paso a paso:
  1. RabbitMQ entrega los mensajes al BulkCollector uno a la vez — Mensaje 1, Mensaje 2, hasta el Mensaje N.
  2. El BulkCollector los mantiene en memoria en lugar de una escritura por mensaje. Recopila hasta que el lote se llena o el tiempo de espera de descarga expira.
  3. El BulkCollector envía un INSERT en lote 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, por lo que los reintentos y las entregas duplicadas se mantienen 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 uno a la vez, no en un único ACK conjunto. Esto evita que un canal compartido confirme mensajes que otros workers aún procesan.

Habilitar el Bulk Recorder


El modo en lote requiere el modo asíncrono. La configuración explícita del Bulk Recorder es opcional porque está habilitada de forma predeterminada:
El modo asíncrono es obligatorio. Con RABBITMQ_TRANSACTION_ASYNC=true, el Bulk Recorder está habilitado de forma predeterminada; configura BULK_RECORDER_ENABLED=false para procesar los mensajes en cola individualmente. Sin modo asíncrono, Midaz persiste las transacciones directamente en lugar de consumir mensajes en cola.
BULK_RECORDER_ENABLED tiene el valor predeterminado true cuando no configuras la variable de entorno. Si ya estás ejecutando con RABBITMQ_TRANSACTION_ASYNC=true, es probable que el modo en lote esté activo. Para confirmarlo, revisa los logs de tu aplicación buscando Bulk mode is ACTIVE al inicio.

Configuración


Tamaño de lote calculado automáticamente

Cuando BULK_RECORDER_SIZE se establece en 0 (el valor predeterminado), Midaz deriva el tamaño del lote:
Esto alinea la capacidad del recopilador con el flujo real de mensajes desde RabbitMQ. Evita descargas parciales y presión de memoria.
Si configuras BULK_RECORDER_SIZE a mano, mantenlo alineado con tu configuración de prefetch. Un tamaño mucho mayor que workers × prefetch rara vez llena el recopilador. Entonces el recopilador descarga principalmente por el tiempo de espera.

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 tu prioridad: latencia o rendimiento.

Baja latencia (procesamiento en tiempo real)

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

Alto rendimiento (operaciones por lotes)

Lotes más grandes y 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.
Comienza con los valores predeterminados y ajusta a partir de lo que observes. Para confirmar tu configuración, observa el log Bulk mode configured for consumer al inicio.

Garantías de seguridad


El Bulk Recorder se mantiene seguro en toda condición:

Idempotencia

Cada inserción en lote usa ON CONFLICT (id) DO NOTHING. Si un mensaje llega dos veces — por un reintento, una re-entrega o un fallo de red — PostgreSQL descarta el duplicado. No obtienes corrupción de datos ni violaciones de restricciones.

Prevención de deadlocks

Antes de cada inserción en lote, Midaz ordena los IDs de los registros. Todos los escritores concurrentes adquieren entonces los bloqueos en el mismo orden. Esto elimina la fuente más común de deadlocks de PostgreSQL en 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 maneja esta fragmentación por ti. Cada INSERT incluye como máximo 1.000 filas. BULK_RECORDER_MAX_ROWS_PER_INSERT no se aplica actualmente al tamaño del fragmento de PostgreSQL.

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 usas procesamiento asíncrono de transacciones (RABBITMQ_TRANSACTION_ASYNC=true).
  • Quieres 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.
  • Depuras el procesamiento de transacciones y quieres un flujo más simple, mensaje por mensaje.