Pular para o conteúdo principal

Quando as gravações síncronas se tornam o gargalo


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. 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. Para orientações mais amplas de escalabilidade, veja Estratégias de escalabilidade.

Como funciona


Modo síncrono (padrão)

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 e retorna 201 Created ao cliente.

Figura 1. Fluxo de transação síncrono.

O fluxo completo tem estas etapas:
  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. A API retorna 201 Created ao cliente com a transação criada. A resposta carrega o status CREATED, e o Midaz armazena a transação como APPROVED. Um 201 significa que o Midaz aplicou a transação aos saldos.
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.
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. 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.

O fluxo completo tem estas etapas:
  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 CREATED, e o Midaz armazena a transação como APPROVED. Um 201 significa que o Midaz aplicou a transação aos saldos.
  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 copia os saldos para o PostgreSQL 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.
  • As consultas de lista mostram uma transação nova depois que o consumidor a grava.
  • 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 a publicação no RabbitMQ falhar no modo assíncrono, o Midaz grava a transação no banco de dados durante a requisição. Se essa gravação também falhar, a transação continua aplicada aos saldos. O Midaz mantém o registro da transação e tenta a gravação de novo em segundo plano. O modo síncrono usa a mesma recuperação quando a gravação dele no banco de dados falha. Isso significa:
  • Durante uma indisponibilidade da fila, o Midaz grava diretamente no banco de dados.
  • Uma gravação com falha no banco de dados não rejeita uma transação que o Midaz já aplicou aos saldos.
  • O Midaz registra em log a falha da fila e a gravação adiada, 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 ledger ainda se conecta ao RabbitMQ e roda o consumidor dele nesse modo. 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. O consumidor republica as mensagens que ele tenta de novo, então ele também precisa de acesso de escrita ao exchange de transações.

Sincronização de saldo


Um worker dedicado de sincronização de saldo copia os saldos do Redis para o PostgreSQL. Esse worker roda tanto no modo síncrono quanto no assíncrono. 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


Um circuit breaker embutido protege as publicações no RabbitMQ de mensagens de auditoria e eventos de overdraft. 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.

Configuração do circuit breaker

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.

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ê 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.