Skip to main content

Por que isso importa


Um cliente envia uma transação. O Midaz deve fazer duas coisas: validá-la e persistir o resultado. No modo síncrono, as duas etapas rodam na mesma requisição. O cliente espera cada gravação chegar ao banco de dados antes de receber uma resposta. Esse modelo é simples e previsível, mas tem um teto. Em alto volume, as gravações no banco de dados se tornam o gargalo. Cada transação ocupa uma conexão, espera por locks e disputa I/O. O modo assíncrono rompe essa dependência. O Midaz valida a transação, retorna a resposta de imediato e persiste os dados em segundo plano através do RabbitMQ. O cliente recebe respostas mais rápidas. Com o processamento assíncrono habilitado, o Bulk Recorder agrupa inserções por padrão. Defina BULK_RECORDER_ENABLED=false para persistir as mensagens enfileiradas individualmente. Para orientações mais amplas de escalabilidade, veja Estratégias de escalabilidade.

Como funciona


Modo síncrono (padrão)

O Midaz valida a transação e a grava diretamente no PostgreSQL no mesmo ciclo de requisição. A API envia a resposta apenas depois que toda operação no banco de dados é concluída.
Diagrama de sequência mostrando o cliente enviando um POST /transaction para a API do Midaz, que a valida, grava a transação e as operações no PostgreSQL, espera a confirmação e apenas então retorna 201 Created ao cliente. A resposta 201 Created carrega o status transitório CREATED, não a aprovação final.

Figura 1. Fluxo de transação síncrono. O cliente espera até a gravação no banco de dados ser confirmada.

Veja 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. Ela executa aqui a validação do formato da requisição, as checagens de saldo e a aplicação de limites.
  3. A API grava no PostgreSQL. Ela persiste a transação e as operações dela no mesmo ciclo de requisição.
  4. O PostgreSQL confirma a gravação. Ele faz o commit de todos os registros.
  5. A API retorna 201 Created ao cliente com a transação criada. A resposta apenas sai do servidor depois que o banco de dados confirma tudo. A resposta carrega o status transitório CREATED. O Midaz promove a transação para APPROVED de forma assíncrona depois do processamento de saldo. Não trate o 201 como aprovação final. Espere o status chegar a APPROVED antes de tratar a transação como liquidada.
Características:
  • O tempo de resposta inclui a latência de gravação no banco de dados.
  • Cada transação é uma operação independente no banco de dados.
  • Mais simples de entender. A resposta mostra exatamente o que o Midaz persiste.
Mesmo no modo síncrono, o Midaz atualiza os saldos de forma atômica no Redis durante a requisição. O Redis é a fonte de verdade para saldos. A gravação acima persiste a transação e as operações dela, não as linhas de saldo do Postgres. O worker de sincronização de saldo, que roda continuamente, reconcilia essas linhas (veja Sincronização de saldo).

Modo assíncrono

O Midaz valida a transação da mesma forma. Em vez de uma gravação direta 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 a valida, publica o payload no RabbitMQ e retorna imediatamente 201 Created ao cliente. A resposta 201 Created carrega o status transitório CREATED, não a aprovação final. Em paralelo, o RabbitMQ entrega a mensagem a um consumidor em segundo plano, que grava a transação e as operações no PostgreSQL; os saldos são tratados pelo worker dedicado de sincronização de saldo.

Figura 2. Fluxo de transação assíncrono. O cliente recebe uma resposta assim que a mensagem é publicada, e a persistência acontece em segundo plano.

Veja 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. Ela executa a validação do formato da requisição, as checagens de saldo e a aplicação de limites 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 201 Created ao cliente assim que a fila aceita a mensagem. O cliente não espera pela persistência no banco de dados. A resposta carrega o status transitório CREATED. O Midaz promove a transação para APPROVED de forma assíncrona depois do processamento de saldo. Não trate o 201 como aprovação final. Espere o status chegar a APPROVED antes de tratar a transação como liquidada.
  5. O RabbitMQ entrega a mensagem a um consumidor em segundo plano, desacoplado da requisição da API.
  6. O consumidor grava no PostgreSQL. Ele persiste a transação e as operações dela a partir da mensagem enfileirada. O worker de sincronização de saldo coordena as atualizações de saldo e mantém os saldos consistentes em ambos os modos (veja a seção Sincronização de saldo).
Características:
  • O tempo de resposta exclui a latência de gravação no banco de dados. O cliente espera apenas pela validação e pela publicação na fila.
  • O Midaz serializa as mensagens com MessagePack para um transporte compacto e eficiente.
  • Os consumidores em segundo plano gravam no banco de dados no próprio ritmo, com novas tentativas. As inserções em lote exigem o Bulk Recorder habilitado.
A etapa de validação é idêntica nos dois modos. As checagens de saldo, a validação do formato da requisição e a aplicação de limites acontecem antes de a API responder, independentemente do modo de processamento. A diferença está apenas em quando os dados chegam ao banco de dados.

Resiliência embutida


Se o RabbitMQ estiver indisponível quando o modo assíncrono tenta publicar uma mensagem, o Midaz tenta uma gravação direta no banco de dados. Se essa gravação falhar, o Midaz retorna o erro do banco de dados. Isso significa:
  • Durante uma indisponibilidade da fila, o Midaz tenta gravar diretamente no banco de dados.
  • O cliente pode receber um erro se a gravação de fallback no banco de dados falhar.
  • O Midaz registra em log a falha da fila e, se a gravação direta também falhar, a falha da gravação de fallback, para que a sua equipe de operações possa investigá-las.
Durante uma indisponibilidade da fila, a latência pode aumentar porque as gravações vão direto para o banco de dados. Monitore a saúde do seu RabbitMQ para manter o modo assíncrono ativo.

Habilitando o modo assíncrono


Defina uma variável de ambiente na aplicação do ledger:
Com false (o padrão), todas as transações usam o processamento síncrono e persistem diretamente no PostgreSQL. O bootstrap atual do Ledger ainda inicializa o RabbitMQ e conecta o consumidor dele. Com true, o ledger publica os payloads de transação no exchange do RabbitMQ configurado. Um consumidor em segundo plano então trata a persistência.

Configuração do RabbitMQ


O modo assíncrono usa as seguintes configurações do RabbitMQ (todas no .env do ledger):
O consumidor usa credenciais separadas (RABBITMQ_CONSUMER_USER / RABBITMQ_CONSUMER_PASS) das do produtor. Isso segue o princípio do menor privilégio. O consumidor precisa apenas de acesso de leitura à fila.

Sincronização de saldo


Um worker dedicado de sincronização de saldo coordena as atualizações de saldo. Ele usa o Redis como camada de coordenação. Esse worker roda tanto no modo síncrono quanto no assíncrono. Ele mantém os saldos consistentes mesmo quando vários consumidores processam mensagens ao mesmo tempo. O worker de sincronização de saldo roda automaticamente nos dois modos. Você não precisa de nenhuma configuração extra além de uma instância do Redis disponível.

Circuit breaker do RabbitMQ


Quando você habilita o modo assíncrono, o Midaz depende do RabbitMQ para a persistência das transações. Um circuit breaker embutido protege contra indisponibilidades do broker. Ele monitora a saúde da conexão com o RabbitMQ e falha rápido quando o broker cai. Isso evita acúmulo de requisições e falhas em cascata. O circuit breaker fica ativo no caminho RabbitMQ single-tenant. O RabbitMQ multi-tenant usa gestão de conexão por tenant em vez disso. O circuit breaker segue o modelo padrão de três estados:
  • Closed (normal): as requisições fluem normalmente para o RabbitMQ. O breaker conta as falhas.
  • Open (acionado): o breaker não contata o RabbitMQ. O Midaz contorna o broker e tenta uma gravação direta no banco de dados para cada transação assíncrona. Se essa gravação falhar, o Midaz retorna o erro do banco de dados. Um health checker em segundo plano monitora o broker e tenta a recuperação.
  • Half-open (testando): o breaker deixa passar um número limitado de requisições para testar a recuperação do RabbitMQ. Se elas tiverem sucesso, o circuito fecha. Se falharem, ele reabre.
O circuito abre quando qualquer uma das condições abaixo é verdadeira:
  • O número de falhas consecutivas atinge o limite, OU
  • A proporção de falhas ultrapassa o percentual configurado dentro da janela de contagem

Configuração do circuit breaker

Quando o circuito está open, o Midaz tenta gravações diretas no banco de dados para transações assíncronas. Se uma gravação direta falhar, o Midaz retorna o erro do banco de dados. O fallback não garante a entrega da transação durante indisponibilidades do broker.
Para a maioria dos deploys de produção, os padrões funcionam bem. Ajuste CONSECUTIVE_FAILURES e TIMEOUT se o seu cluster RabbitMQ tiver padrões de recuperação conhecidos. Por exemplo, reduza o timeout se o seu broker se recupera em segundos. Aumente as falhas consecutivas se você observar oscilações transitórias de rede.

Como o modo assíncrono se conecta ao Bulk Recorder


O modo assíncrono e o Bulk Recorder trabalham juntos:
  1. O 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. O Bulk Recorder otimiza como o consumidor grava essas mensagens no banco de dados. Ele agrupa várias mensagens em inserções em lote únicas.
O Bulk Recorder fica ativo com o modo assíncrono, a menos que você defina explicitamente BULK_RECORDER_ENABLED=false. Sem o modo assíncrono, a persistência das transações usa gravações diretas no banco de dados e não há fila de transações para agrupar.

Quando usar o modo assíncrono


Use o modo assíncrono quando:
  • Você precisa de tempos de resposta mais baixos da API para a criação de transações.
  • Sua carga de trabalho envolve altos volumes de transações (centenas ou mais por segundo).
  • Você executa operações em lote, como repasses em massa ou liquidações.
  • Você quer desacoplar a camada de API do desempenho do banco de dados.
Mantenha o modo síncrono quando:
  • Você precisa de persistência de transação direta e vinculada à requisição.
  • O volume de transações é baixo a moderado.
  • Você quer que uma resposta de sucesso da API signifique que o Midaz já persistiu os dados.
  • Você trabalha em um ambiente de desenvolvimento ou teste, onde a simplicidade importa mais que o throughput.
Você pode alternar entre os modos a qualquer momento. Altere RABBITMQ_TRANSACTION_ASYNC e reinicie a aplicação do ledger. Você não precisa de nenhuma migração de dados, porque o formato da transação é o mesmo nos dois caminhos.