Por que isso importa
Cada transação no Midaz cria operações de saldo que o Midaz precisa persistir. No modo síncrono padrão, o Midaz as grava diretamente no banco de dados durante o ciclo da requisição. Isso é aceitável para volumes moderados. Em escala — milhares de transações por segundo — torna-se o gargalo. O Bulk Recorder acumula mensagens e as grava em lotes. Ele faz menos idas e vindas ao PostgreSQL, reduz a contenção de locks e aumenta o throughput. As cargas de alto volume são as que mais ganham: pagamentos em massa, liquidações em lote e processamento de pagamentos em tempo real. Para estratégias mais amplas para escalar o Midaz, veja Estratégias de escalabilidade.
Como funciona
O Bulk Recorder fica entre o consumidor do RabbitMQ e a camada de banco de dados. Ele não insere cada mensagem de imediato. Em vez disso, coleta as mensagens em um buffer e as descarrega sob duas condições:
- Tamanho do lote atingido — o buffer enche até o tamanho configurado.
- Timeout expirado — o timeout de flush configurado expira, mesmo se o buffer não estiver cheio.
Figura 1. Fluxo ponta a ponta do Bulk Recorder entre o RabbitMQ e o PostgreSQL.
- O RabbitMQ entrega as mensagens ao BulkCollector uma de cada vez — Mensagem 1, Mensagem 2, até a Mensagem N.
- O BulkCollector as mantém em memória em vez de uma gravação por mensagem. Ele coleta até o lote encher ou o timeout de flush expirar.
-
O BulkCollector envia um INSERT em lote fragmentado em chunks ao PostgreSQL. O Midaz divide os lotes grandes em chunks que respeitam os limites de parâmetros do PostgreSQL. Cada chunk usa
ON CONFLICT (id) DO NOTHING, então retries e entregas duplicadas permanecem seguros. - O PostgreSQL confirma a gravação e armazena os dados.
- O BulkCollector confirma cada mensagem de volta ao RabbitMQ após a gravação. Ele as confirma uma de cada vez, não em um único ACK conjunto. Isso evita que um canal compartilhado confirme mensagens que outros workers ainda processam.
Habilitando o Bulk Recorder
O modo bulk exige o modo assíncrono. A configuração explícita do Bulk Recorder é opcional porque ela fica habilitada por padrão:
RABBITMQ_TRANSACTION_ASYNC=true, o Bulk Recorder fica habilitado por padrão; defina BULK_RECORDER_ENABLED=false para processar mensagens enfileiradas individualmente. Sem modo assíncrono, o Midaz persiste transações diretamente em vez de consumir mensagens da fila.
Configuração
Tamanho de lote calculado automaticamente
QuandoBULK_RECORDER_SIZE é definido como 0 (o padrão), o Midaz deriva o tamanho do lote:
Ajustando para sua carga de trabalho
As duas principais alavancas são o tamanho do lote e o timeout de flush. O equilíbrio certo depende da sua prioridade: latência ou throughput.
Baixa latência (processamento em tempo real)
Mantenha os lotes pequenos e os timeouts curtos. O Midaz persiste as mensagens rapidamente, mesmo quando os lotes não estão cheios.Alto throughput (operações em lote)
Lotes maiores e timeouts mais longos maximizam a eficiência do banco de dados. Use isso para pagamentos em massa, liquidações de fim de dia ou cargas de trabalho de migração.Garantias de segurança
O Bulk Recorder permanece seguro em qualquer condição:
Idempotência
Cada inserção em lote usaON CONFLICT (id) DO NOTHING. Se uma mensagem chega duas vezes — por um retry, um redelivery ou uma falha de rede — o PostgreSQL descarta a duplicata. Você não tem corrupção de dados nem violações de constraint.
Prevenção de deadlock
Antes de cada inserção em lote, o Midaz ordena os IDs dos registros. Todos os escritores concorrentes adquirem então os locks na mesma ordem. Isso elimina a fonte mais comum de deadlocks do PostgreSQL em alta concorrência.Chunking interno
O Midaz divide os lotes grandes em chunks que cabem dentro do limite de 65.535 parâmetros por query do PostgreSQL:
O Midaz cuida desse chunking para você. Cada
INSERT inclui no máximo 1.000 linhas. BULK_RECORDER_MAX_ROWS_PER_INSERT não é aplicado atualmente ao tamanho do chunk PostgreSQL.
Quando usar
Use o Bulk Recorder quando:
- Você processa altos volumes de transações (centenas ou milhares por segundo).
- Sua carga de trabalho inclui operações em lote como pagamentos em massa, liquidações ou migrações de dados.
- Você já usa processamento assíncrono de transações (
RABBITMQ_TRANSACTION_ASYNC=true). - Você quer reduzir a carga do banco de dados e a pressão de conexões.
- Seu volume é baixo o suficiente para que inserções individuais não sejam um gargalo.
- Você precisa de garantias estritas de ordenação por mensagem que o processamento em lote quebraria.
- Você depura o processamento de transações e quer um fluxo mais simples, mensagem por mensagem.

