Pular para o conteúdo principal

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.
Diagrama de sequência mostrando o cliente enviando um POST /transaction para a API do Midaz, que valida, grava a transação, operações e saldos no PostgreSQL, espera a confirmação e só então retorna 200 OK ao cliente.
Aqui está o fluxo completo, passo a passo:
  1. O cliente envia um POST /transaction para a API do Midaz.
  2. A API valida a requisição — parsing do DSL, verificações de saldo e aplicação de limites acontecem aqui.
  3. A API grava no PostgreSQL — transação, operações e atualizações de saldo são persistidas dentro do mesmo ciclo de requisição.
  4. O PostgreSQL confirma a gravação, sinalizando que todos os registros foram commitados.
  5. A API retorna 200 OK ao cliente com a transação criada. A resposta só sai do servidor depois que o banco de dados confirmou tudo.
Características:
  • 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.
Diagrama de sequência mostrando o cliente enviando um POST /transaction para a API do Midaz, que valida, publica o payload no RabbitMQ e retorna imediatamente 200 OK ao cliente. Em paralelo, o RabbitMQ entrega a mensagem a um consumidor em segundo plano, que grava a transação, operações e saldos no PostgreSQL.
Aqui está o fluxo completo, passo a passo:
  1. O cliente envia um POST /transaction para a API do Midaz.
  2. 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.
  3. A API publica o payload da transação no RabbitMQ em vez de gravar diretamente no banco de dados.
  4. A API retorna 200 OK ao cliente imediatamente depois que a mensagem é aceita pela fila — o cliente não espera pela persistência no banco de dados.
  5. O RabbitMQ entrega a mensagem a um consumidor em segundo plano, desacoplado da requisição da API.
  6. O consumidor grava no PostgreSQL — transação, operações e atualizações de saldo são persistidas separadamente da requisição original.
Características:
  • 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.
O passo de validação é idêntico em ambos os modos. Verificações de saldo, parsing do DSL, aplicação de limites — tudo isso acontece antes da API responder, independentemente do modo de processamento. A diferença está apenas em quando os dados chegam ao banco de dados.

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.
O fallback automático garante a segurança dos dados, mas também significa que a latência vai disparar durante uma indisponibilidade da fila (já que as escritas vão direto ao banco de dados). Monitore a saúde do seu RabbitMQ para manter o modo assíncrono operando como o esperado.

Habilitando o modo assíncrono


Configure uma variável de ambiente na aplicação do ledger:
RABBITMQ_TRANSACTION_ASYNC=true
Quando definida como 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ávelDescriçãoPadrão
RABBITMQ_TRANSACTION_ASYNCHabilita o processamento assíncrono.false
RABBITMQ_HOSTHostname do servidor RabbitMQ.midaz-rabbitmq
RABBITMQ_PORT_HOSTPorta da API de gerenciamento.3003
RABBITMQ_PORT_AMQPPorta do protocolo AMQP.3004
RABBITMQ_DEFAULT_USERCredenciais do produtor (usuário).transaction
RABBITMQ_DEFAULT_PASSCredenciais do produtor (senha).
RABBITMQ_CONSUMER_USERCredenciais do consumidor (usuário).consumer
RABBITMQ_CONSUMER_PASSCredenciais do consumidor (senha).
RABBITMQ_NUMBERS_OF_WORKERSNúmero de goroutines worker do consumidor.5
RABBITMQ_NUMBERS_OF_PREFETCHMensagens prefetched por worker.10
RABBITMQ_TRANSACTION_BALANCE_OPERATION_EXCHANGENome do exchange para mensagens de transação.transaction.transaction_balance_operation.exchange
RABBITMQ_TRANSACTION_BALANCE_OPERATION_KEYRouting key.transaction.transaction_balance_operation.key
RABBITMQ_TRANSACTION_BALANCE_OPERATION_QUEUENome da fila.transaction.transaction_balance_operation.queue
O consumidor usa credenciais separadas (RABBITMQ_CONSUMER_USER / RABBITMQ_CONSUMER_PASS) do produtor. Isso segue o princípio do menor privilégio — o consumidor só precisa de acesso de leitura à fila.

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ávelDescriçãoPadrão
BALANCE_SYNC_BATCH_SIZENúmero de atualizações de saldo a agrupar antes do flush.50
BALANCE_SYNC_FLUSH_TIMEOUT_MSTempo máximo de espera (ms) antes de descarregar um lote incompleto.500
BALANCE_SYNC_POLL_INTERVAL_MSFrequência (ms) com que o worker verifica atualizações pendentes.50
O worker de sincronização de saldos roda automaticamente quando o modo assíncrono está habilitado. Nenhuma configuração adicional é necessária além de ter o Redis disponível.

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 circuito abre quando qualquer uma destas condições é atendida:
  • 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ávelDescriçãoPadrão
RABBITMQ_CIRCUIT_BREAKER_CONSECUTIVE_FAILURESFalhas consecutivas antes de o circuito abrir.15
RABBITMQ_CIRCUIT_BREAKER_FAILURE_RATIOPercentual de falhas (0–100) que dispara o estado aberto.50
RABBITMQ_CIRCUIT_BREAKER_MIN_REQUESTSRequisições mínimas antes de avaliar a proporção de falhas.10
RABBITMQ_CIRCUIT_BREAKER_INTERVALJanela de tempo (segundos) para contar falhas. Os contadores resetam após cada intervalo.120
RABBITMQ_CIRCUIT_BREAKER_TIMEOUTPor quanto tempo (segundos) o circuito permanece aberto antes de transicionar para semi-aberto.30
RABBITMQ_CIRCUIT_BREAKER_MAX_REQUESTSRequisições permitidas no estado semi-aberto para sondar a recuperação.3
RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_INTERVALFrequência (segundos) com que o health checker em segundo plano faz ping no RabbitMQ.30
RABBITMQ_CIRCUIT_BREAKER_HEALTH_CHECK_TIMEOUTTimeout (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.
Para a maioria dos deployments em produção, os valores padrão funcionam bem. Ajuste CONSECUTIVE_FAILURES e TIMEOUT se seu cluster do RabbitMQ tiver padrões de recuperação conhecidos — por exemplo, diminua o timeout se seu broker normalmente se recupera em segundos, ou aumente as falhas consecutivas se você enfrenta flutuações transitórias de rede.

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:
  1. 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.
  2. Bulk Recorder otimiza como o consumidor escreve essas mensagens no banco de dados — agrupando múltiplas mensagens em inserções em lote únicas.
O Bulk Recorder é ativado apenas quando o modo assíncrono está habilitado (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çãoComportamento de processamento
Async falseEscrita direta no banco de dados por transação (síncrono)
Async true, Bulk Recorder falseBaseado em fila, uma mensagem processada por vez
Async true, Bulk Recorder trueBaseado 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.
Mantenha o modo síncrono quando:
  • 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.
Você pode alternar entre os modos a qualquer momento alterando RABBITMQ_TRANSACTION_ASYNC e reiniciando a aplicação do ledger. Nenhuma migração de dados é necessária — o formato da transação é o mesmo em ambos os caminhos.