Por que isso importa
Quando um cliente envia uma transação, duas coisas precisam acontecer: validá-la e persistir os resultados. No modo síncrono, ambas ocorrem na mesma requisição — o cliente espera até que tudo seja escrito no banco de dados antes de receber uma resposta. Isso é simples e previsível, mas tem um teto. Em altos volumes, as escritas no banco de dados se tornam o gargalo. Cada transação segura uma conexão, espera por locks e concorre por I/O. O modo assíncrono quebra essa dependência. A transação é validada, a resposta é retornada imediatamente e a persistência acontece em segundo plano através do RabbitMQ. O cliente recebe respostas mais rápidas. O banco de dados recebe escritas em lotes controlados e otimizados. O sistema lida com mais usando menos. Para orientação mais ampla sobre escalabilidade, veja Estratégias de escalabilidade.
Como funciona
Modo síncrono (padrão)
A transação é validada e escrita diretamente no PostgreSQL dentro do mesmo ciclo de requisição. A resposta da API só é enviada depois que todas as operações de banco de dados forem concluídas.
- O cliente envia um
POST /transactionpara a API do Midaz. - A API valida a requisição — parsing do DSL, verificações de saldo e aplicação de limites acontecem aqui.
- A API grava no PostgreSQL — transação, operações e atualizações de saldo são persistidas dentro do mesmo ciclo de requisição.
- O PostgreSQL confirma a gravação, sinalizando que todos os registros foram commitados.
- A API retorna
200 OKao cliente com a transação criada. A resposta só sai do servidor depois que o banco de dados confirmou tudo.
- O tempo de resposta inclui a latência de escrita no banco de dados.
- Cada transação é uma operação independente de banco de dados.
- Mais simples de raciocinar — o que você vê na resposta é o que está persistido.
Modo assíncrono
A transação é validada da mesma forma, mas em vez de escrever no banco de dados, o Midaz publica uma mensagem no RabbitMQ. Um consumidor em segundo plano pega a mensagem e trata a persistência separadamente.
- O cliente envia um
POST /transactionpara a API do Midaz. - A API valida a requisição — parsing do DSL, verificações de saldo e aplicação de limites acontecem exatamente como no modo síncrono.
- A API publica o payload da transação no RabbitMQ em vez de gravar diretamente no banco de dados.
- A API retorna
200 OKao cliente imediatamente depois que a mensagem é aceita pela fila — o cliente não espera pela persistência no banco de dados. - O RabbitMQ entrega a mensagem a um consumidor em segundo plano, desacoplado da requisição da API.
- O consumidor grava no PostgreSQL — transação, operações e atualizações de saldo são persistidas separadamente da requisição original.
- O tempo de resposta exclui a latência de escrita no banco de dados — o cliente só espera pela validação e pela publicação na fila.
- As mensagens são serializadas com MessagePack para um transporte compacto e eficiente.
- Os consumidores em segundo plano escrevem no banco de dados no próprio ritmo, com capacidades de batching e retries.
Resiliência integrada
Se o RabbitMQ estiver indisponível quando o modo assíncrono tentar publicar uma mensagem, o Midaz não falha a transação. Em vez disso, ele recorre automaticamente a uma escrita direta no banco de dados — o mesmo caminho do modo síncrono. Isso significa:
- Nenhuma transação é perdida por causa de uma indisponibilidade da fila.
- O cliente ainda recebe uma resposta de sucesso.
- O fallback é registrado em log para que seu time de operações possa investigar o problema da fila.
Habilitando o modo assíncrono
Configure uma variável de ambiente na aplicação do ledger:
false (o padrão), todas as transações usam processamento síncrono. Nenhum consumidor do RabbitMQ é necessário.
Quando definida como true, o ledger publica os payloads da transação no exchange configurado do RabbitMQ e um consumidor em segundo plano trata a persistência.
Configuração do RabbitMQ
O modo assíncrono utiliza as seguintes configurações do RabbitMQ (todas no
.env do ledger):
| Variável | Descrição | Padrão |
|---|---|---|
RABBITMQ_TRANSACTION_ASYNC | Habilita o processamento assíncrono. | false |
RABBITMQ_HOST | Hostname do servidor RabbitMQ. | midaz-rabbitmq |
RABBITMQ_PORT_HOST | Porta da API de gerenciamento. | 3003 |
RABBITMQ_PORT_AMQP | Porta do protocolo AMQP. | 3004 |
RABBITMQ_DEFAULT_USER | Credenciais do produtor (usuário). | transaction |
RABBITMQ_DEFAULT_PASS | Credenciais do produtor (senha). | — |
RABBITMQ_CONSUMER_USER | Credenciais do consumidor (usuário). | consumer |
RABBITMQ_CONSUMER_PASS | Credenciais do consumidor (senha). | — |
RABBITMQ_NUMBERS_OF_WORKERS | Número de goroutines worker do consumidor. | 5 |
RABBITMQ_NUMBERS_OF_PREFETCH | Mensagens prefetched por worker. | 10 |
RABBITMQ_TRANSACTION_BALANCE_OPERATION_EXCHANGE | Nome do exchange para mensagens de transação. | transaction.transaction_balance_operation.exchange |
RABBITMQ_TRANSACTION_BALANCE_OPERATION_KEY | Routing key. | transaction.transaction_balance_operation.key |
RABBITMQ_TRANSACTION_BALANCE_OPERATION_QUEUE | Nome da fila. | transaction.transaction_balance_operation.queue |
Sincronização de saldos
Ao rodar em modo assíncrono, as atualizações de saldo são coordenadas através de um worker dedicado de sincronização que utiliza o Redis como camada de coordenação. Isso garante que os saldos permaneçam consistentes mesmo quando múltiplos consumidores processam mensagens concorrentemente.
| Variável | Descrição | Padrão |
|---|---|---|
BALANCE_SYNC_BATCH_SIZE | Número de atualizações de saldo a agrupar antes do flush. | 50 |
BALANCE_SYNC_FLUSH_TIMEOUT_MS | Tempo máximo de espera (ms) antes de descarregar um lote incompleto. | 500 |
BALANCE_SYNC_POLL_INTERVAL_MS | Frequência (ms) com que o worker verifica atualizações pendentes. | 50 |
Circuit breaker do RabbitMQ
Quando o modo assíncrono está habilitado, o Midaz depende do RabbitMQ para a persistência das transações. Para proteger contra indisponibilidades do broker, o Midaz inclui um circuit breaker integrado que monitora a saúde da conexão com o RabbitMQ e falha rápido quando o broker está indisponível — evitando acúmulo de requisições e falhas em cascata. O circuit breaker está sempre ativo quando o RabbitMQ está em uso. Ele segue o modelo padrão de três estados:
- Fechado (normal): as requisições fluem normalmente para o RabbitMQ. As falhas são contabilizadas.
- Aberto (disparado): as requisições são rejeitadas imediatamente sem contatar o RabbitMQ. Um health checker em segundo plano monitora o broker e tenta a recuperação.
- Semi-aberto (sondagem): um número limitado de requisições é permitido para testar se o RabbitMQ se recuperou. Se tiverem sucesso, o circuito se fecha. Se falharem, ele reabre.
- O número de falhas consecutivas atinge o limite, OU
- A proporção de falhas excede o percentual configurado dentro da janela de contagem
Configuração do circuit breaker
| Variável | Descrição | Padrão |
|---|---|---|
RABBITMQ_CIRCUIT_BREAKER_CONSECUTIVE_FAILURES | Falhas consecutivas antes de o circuito abrir. | 15 |
RABBITMQ_CIRCUIT_BREAKER_FAILURE_RATIO | Percentual de falhas (0–100) que dispara o estado aberto. | 50 |
RABBITMQ_CIRCUIT_BREAKER_MIN_REQUESTS | Requisições mínimas antes de avaliar a proporção de falhas. | 10 |
RABBITMQ_CIRCUIT_BREAKER_INTERVAL | Janela de tempo (segundos) para contar falhas. Os contadores resetam após cada intervalo. | 120 |
RABBITMQ_CIRCUIT_BREAKER_TIMEOUT | Por quanto tempo (segundos) o circuito permanece aberto antes de transicionar para semi-aberto. | 30 |
RABBITMQ_CIRCUIT_BREAKER_MAX_REQUESTS | Requisições permitidas no estado semi-aberto para sondar a recuperação. | 3 |
RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_INTERVAL | Frequência (segundos) com que o health checker em segundo plano faz ping no RabbitMQ. | 30 |
RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_TIMEOUT | Timeout (segundos) para cada ping de health check. | 10 |
Quando o circuito está aberto, as transações assíncronas fazem fallback para escritas síncronas diretas no banco de dados — a transação não é perdida. Esse fallback garante a integridade dos dados mesmo durante indisponibilidades do broker.
Como o modo assíncrono se conecta ao Bulk Recorder
O modo assíncrono e o Bulk Recorder são funcionalidades complementares que trabalham juntas:
- Modo assíncrono desacopla a resposta da API da persistência — as transações vão para o RabbitMQ em vez de diretamente para o PostgreSQL.
- Bulk Recorder otimiza como o consumidor escreve essas mensagens no banco de dados — agrupando múltiplas mensagens em inserções em lote únicas.
RABBITMQ_TRANSACTION_ASYNC=true E BULK_RECORDER_ENABLED=true). Sem o modo assíncrono, não há fila de mensagens — e sem fila, não há nada para agrupar.
| Configuração | Comportamento de processamento |
|---|---|
Async false | Escrita direta no banco de dados por transação (síncrono) |
Async true, Bulk Recorder false | Baseado em fila, uma mensagem processada por vez |
Async true, Bulk Recorder true | Baseado em fila, mensagens agrupadas para inserções em lote (10×+ throughput) |
Quando usar o modo assíncrono
Use o modo assíncrono quando:
- Você precisa de tempos de resposta de API mais baixos para a criação de transações.
- Sua carga de trabalho envolve altos volumes de transações (centenas+ por segundo).
- Você está rodando operações em lote como pagamentos em massa ou liquidações.
- Você quer desacoplar sua camada de API do desempenho do banco de dados.
- Você precisa da configuração mais simples possível (sem dependência do RabbitMQ).
- O volume de transações é baixo a moderado.
- Você quer a garantia de que uma resposta de sucesso da API significa que os dados já estão persistidos.
- Você está em um ambiente de desenvolvimento ou teste onde a simplicidade importa mais do que o throughput.

