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

> Agrupe mensagens de transação do RabbitMQ em inserções em lote e ganhe mais de 10x de throughput em cargas de alto volume.

## Por que isso importa

***

Cada transação no Midaz gera operações de saldo que precisam ser persistidas. No modo padrão (síncrono), cada mensagem dispara uma inserção individual no banco de dados. Isso é aceitável para volumes moderados, mas em escala — milhares de transações por segundo — torna-se o gargalo.

O Bulk Recorder muda isso acumulando mensagens e gravando-as em lotes. Menos idas e vindas ao PostgreSQL, menos contenção de lock e throughput significativamente maior. Para operações de alto volume como pagamentos em massa, liquidações em lote ou processamento de pagamentos em tempo real, essa é a diferença entre um sistema que acompanha o ritmo e um que não acompanha.

Para estratégias mais amplas sobre como escalar o Midaz, veja [Estratégias de escalabilidade](/pt/midaz/scalability-strategies).

## Como funciona

***

O Bulk Recorder fica entre o consumidor do RabbitMQ e a camada de banco de dados. Em vez de inserir cada mensagem imediatamente, ele as coleta em um buffer e 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.

O que acontecer primeiro dispara o flush. Isso garante tanto throughput (lotes grandes sob carga) quanto latência (nenhuma mensagem espera para sempre durante períodos de baixa atividade).

<Frame caption="Figura 1. Fluxo ponta a ponta do Bulk Recorder entre o RabbitMQ e o PostgreSQL.">
  <img src="https://mintcdn.com/lerian-49cb71fc/ZrZBZTM4DWnrahSd/images/pt/d2/bulk-recorder-flow.svg?fit=max&auto=format&n=ZrZBZTM4DWnrahSd&q=85&s=05f08440484d695e3aeca963b1e82672" alt="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 todas as mensagens de volta ao RabbitMQ." className="mx-auto" style={{ width:"80%" }} width="804" height="994" data-path="images/pt/d2/bulk-recorder-flow.svg" />
</Frame>

Aqui está o fluxo completo, passo a passo:

1. **O RabbitMQ entrega as mensagens** ao BulkCollector uma de cada vez — Mensagem 1, Mensagem 2, e assim por diante até a Mensagem N.
2. **O BulkCollector as mantém em memória** em vez de gravar cada uma imediatamente. Ele continua coletando até que o tamanho do lote seja atingido ou o timeout de flush expire.
3. **O BulkCollector envia um INSERT em lote em chunks ao PostgreSQL.** Lotes grandes são automaticamente divididos 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 são tratados com segurança.
4. **O PostgreSQL confirma a gravação,** garantindo que os dados foram persistidos.
5. **O BulkCollector confirma todas as mensagens de volta ao RabbitMQ** em um único ACK, liberando-as da fila em conjunto.

## Habilitando o Bulk Recorder

***

O modo bulk requer que duas condições estejam ativas simultaneamente:

<CodeGroup>
  ```bash Variáveis de ambiente theme={null}
  RABBITMQ_TRANSACTION_ASYNC=true
  BULK_RECORDER_ENABLED=true
  ```
</CodeGroup>

Se qualquer uma das condições não for atendida, o Midaz processa mensagens individualmente — o mesmo comportamento de antes. Sem mudanças de código, sem migração necessária.

<Tip>
  `BULK_RECORDER_ENABLED` tem valor padrão `true` quando a variável de ambiente não está definida. Então, se você já está rodando com `RABBITMQ_TRANSACTION_ASYNC=true`, o modo bulk provavelmente está ativo. Verifique os logs da sua aplicação por `Bulk mode is ACTIVE` na inicialização para confirmar.
</Tip>

## Configuração

***

| Variável                            | Descrição                                                                       | Padrão     |
| :---------------------------------- | :------------------------------------------------------------------------------ | :--------- |
| `BULK_RECORDER_ENABLED`             | Habilita ou desabilita o modo bulk.                                             | `true`     |
| `BULK_RECORDER_SIZE`                | Número de mensagens a acumular antes do flush. `0` = calculado automaticamente. | `0` (auto) |
| `BULK_RECORDER_FLUSH_TIMEOUT_MS`    | Tempo máximo (ms) de espera antes de descarregar um lote incompleto.            | `100`      |
| `BULK_RECORDER_MAX_ROWS_PER_INSERT` | Máximo de linhas por instrução `INSERT` enviada ao PostgreSQL.                  | `1000`     |

### Tamanho de lote calculado automaticamente

Quando `BULK_RECORDER_SIZE` é definido como `0` (o padrão), o tamanho do lote é derivado automaticamente:

```
tamanho do lote = RABBITMQ_NUMBERS_OF_WORKERS × RABBITMQ_NUMBERS_OF_PREFETCH
```

Isso alinha a capacidade do coletor com o fluxo real de mensagens do RabbitMQ, evitando flushes parciais ou pressão de memória.

<Warning>
  Se você definir `BULK_RECORDER_SIZE` manualmente, certifique-se de que esteja alinhado com suas configurações de prefetch. Um tamanho muito maior que `workers × prefetch` significa que o coletor raramente vai encher e dependerá principalmente de flushes baseados em timeout.
</Warning>

## 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 de priorizar latência ou throughput.

### Baixa latência (processamento em tempo real)

Mantenha lotes pequenos e timeouts curtos. Mensagens são persistidas rapidamente, mesmo que os lotes não estejam cheios.

<CodeGroup>
  ```bash Configuração de baixa latência theme={null}
  RABBITMQ_NUMBERS_OF_WORKERS=5
  RABBITMQ_NUMBERS_OF_PREFETCH=10
  BULK_RECORDER_SIZE=0          # auto: 5 × 10 = 50
  BULK_RECORDER_FLUSH_TIMEOUT_MS=50
  ```
</CodeGroup>

### Alto throughput (operações em lote)

Lotes maiores e timeouts mais longos maximizam a eficiência do banco de dados. Ideal para pagamentos em massa, liquidações de fim de dia ou cargas de trabalho de migração.

<CodeGroup>
  ```bash Configuração de alto throughput theme={null}
  RABBITMQ_NUMBERS_OF_WORKERS=10
  RABBITMQ_NUMBERS_OF_PREFETCH=20
  BULK_RECORDER_SIZE=0          # auto: 10 × 20 = 200
  BULK_RECORDER_FLUSH_TIMEOUT_MS=300
  ```
</CodeGroup>

<Tip>
  Comece com os valores padrão e ajuste com base no comportamento observado. Monitore o log `Bulk mode configured for consumer` na inicialização para confirmar que suas configurações foram aplicadas.
</Tip>

## Garantias de segurança

***

O Bulk Recorder foi projetado para ser seguro em todas as condições:

### Idempotência

Cada inserção em lote usa `ON CONFLICT (id) DO NOTHING`. Se uma mensagem for entregue duas vezes — devido a um retry, redelivery ou falha de rede — a duplicata é descartada silenciosamente. Sem corrupção de dados, sem violações de constraint.

### Prevenção de deadlock

Antes de cada inserção em lote, os IDs dos registros são ordenados. Isso garante que todos os escritores concorrentes adquiram locks na mesma ordem, eliminando a fonte mais comum de deadlocks do PostgreSQL em cenários de alta concorrência.

### Chunking interno

Lotes grandes são automaticamente divididos em chunks que cabem dentro do limite de 65.535 parâmetros por query do PostgreSQL:

| Tipo de registro | Colunas por linha | Linhas por chunk | Parâmetros por chunk |
| :--------------- | :---------------- | :--------------- | :------------------- |
| Transaction      | 15                | 1.000            | 15.000               |
| Operation        | 30                | 1.000            | 30.000               |

Esse chunking é tratado internamente. Você só precisa configurar `BULK_RECORDER_MAX_ROWS_PER_INSERT` se quiser ajustar o tamanho do chunk — o padrão de 1.000 linhas é ideal para a maioria dos deployments.

## 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á está usando 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ê está depurando o processamento de transações e quer um fluxo mais simples, mensagem por mensagem.
