Skip to main content

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:
  1. Tamanho do lote atingido — o buffer enche até o tamanho configurado.
  2. Timeout expirado — o timeout de flush configurado expira, mesmo se o buffer não estiver cheio.
A primeira condição que ocorrer dispara o flush. Isso dá lotes grandes sob carga e baixa latência durante os períodos de baixa atividade.
Diagrama de sequência mostrando o RabbitMQ entregando mensagens ao BulkCollector, que as mantém em buffer até o tamanho do lote ou o timeout serem atingidos, e então envia um INSERT em lote fragmentado em chunks ao PostgreSQL e confirma cada mensagem de volta ao RabbitMQ.

Figura 1. Fluxo ponta a ponta do Bulk Recorder entre o RabbitMQ e o PostgreSQL.

Aqui está o fluxo completo, passo a passo:
  1. O RabbitMQ entrega as mensagens ao BulkCollector uma de cada vez — Mensagem 1, Mensagem 2, até a Mensagem N.
  2. 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.
  3. 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.
  4. O PostgreSQL confirma a gravação e armazena os dados.
  5. 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:
O modo assíncrono é obrigatório. Com 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.
BULK_RECORDER_ENABLED tem valor padrão true quando você não define a variável de ambiente. Se você já roda com RABBITMQ_TRANSACTION_ASYNC=true, o modo bulk provavelmente está ativo. Para confirmar, verifique os logs da sua aplicação por Bulk mode is ACTIVE na inicialização.

Configuração


Tamanho de lote calculado automaticamente

Quando BULK_RECORDER_SIZE é definido como 0 (o padrão), o Midaz deriva o tamanho do lote:
Isso alinha a capacidade do coletor com o fluxo real de mensagens do RabbitMQ. Evita flushes parciais e pressão de memória.
Se você definir BULK_RECORDER_SIZE manualmente, mantenha-o alinhado com suas configurações de prefetch. Um tamanho muito maior que workers × prefetch raramente enche o coletor. O coletor então descarrega principalmente pelo timeout.

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.
Comece com os valores padrão e ajuste a partir do que você observar. Para confirmar suas configurações, observe o log Bulk mode configured for consumer na inicialização.

Garantias de segurança


O Bulk Recorder permanece seguro em qualquer condição:

Idempotência

Cada inserção em lote usa ON 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.
Mantenha desabilitado quando:
  • 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.