Skip to main content
Voltar para Insights
Ledger9 min de leitura

Liquidação determinística: por que bancos de dados de propósito geral falham em escala

Contenção de locks, pausas de GC e overhead de deserialização. O que um motor de liquidação especializado faz de diferente.

Settlement Determinístico: Por Que a Latência Sub-Milissegundo Importa em Finanças


Uma transferência em voo é capital bloqueado. O saldo do remetente está debitado. O saldo do destinatário ainda não está creditado. Essa diferença, a lacuna entre compromisso e disponibilidade, é dinheiro que nenhuma parte pode usar.

Em baixo volume, o custo é invisível. A 1.000 transferências por segundo com tempo médio de settlement de 200 milissegundos, aproximadamente 200 transferências estão em voo a qualquer momento. A EUR 500 de valor médio, são EUR 100.000 perpetuamente bloqueados em trânsito. Aumente a latência de settlement para 2 segundos e o capital bloqueado sobe para EUR 1.000.000. O custo de infraestrutura dos servidores é um erro de arredondamento comparado ao custo de capital da latência.

Os bancos mantêm buffers de capital exatamente por essa razão. O buffer cobre a janela de incerteza: o período entre "nos comprometemos a enviar" e "confirmamos que chegou." Reduza a janela, reduza o buffer. A relação é direta.

Por Que Bancos de Dados de Propósito Geral Atingem um Teto

A arquitetura padrão para um ledger financeiro: PostgreSQL (ou MySQL, ou qualquer banco de dados relacional), uma tabela transactions, e lógica a nível de aplicação para atualizar saldos. Para as primeiras 10.000 contas e 100 TPS, isso funciona sem incidentes.

O teto aparece quando as contas se tornam "quentes." Uma conta de cobrança de comissões tocada por cada transação. Uma conta de settlement que agrega todos os pagamentos enviados. Uma conta de receita de plataforma que recebe cada split de comissão. Essas contas são acessadas por uma proporção desproporcional de transferências, e cada acesso compete pelo mesmo bloqueio de linha.

PostgreSQL serializa escritas na mesma linha. Sob isolamento SERIALIZABLE (o único nível que previne todas as anomalias em workloads financeiros), a detecção de conflitos adiciona overhead extra. Quando duas transações concorrentes tocam a mesma conta, uma ganha e a outra retenta. Sob carga, as retentativas se acumulam em cascata. O throughput se degrada não linearmente.

A Lei de Amdahl quantifica o teto. Se 5% do seu workload é serializado, porque 5% das transferências tocam uma conta quente, a aceleração teórica máxima da paralelização é 20x. Adicione mais cores, mais conexões, mais réplicas: o teto não se move. É uma propriedade do workload, não do hardware.

Os workarounds são familiares:

  • Sharding, dividir o espaço de contas entre bancos de dados. Mas transferências cross-shard (remetente no shard A, destinatário no shard B) requerem transações distribuídas. Two-phase commit é lento e complexo. Trocou contenção de bloqueios por overhead de coordenação.
  • Consistência eventual, relaxar o nível de isolamento, aceitar que os saldos podem ser temporariamente inexatos, reconciliar depois. Em finanças, "temporariamente inexato" significa "o cliente vê um saldo que está errado." Inaceitável para qualquer sistema que exibe saldos em tempo real.
  • Bloqueio a nível de aplicação, usar Redis ou advisory locks para serializar acesso fora do banco de dados. Agora tem dois sistemas para manter consistentes, dois modos de falha para gerenciar, e um gerenciador de bloqueios que se torna seu próprio ponto único de contenção.

Cada workaround resolve o problema imediato e introduz um novo. O problema fundamental permanece: um banco de dados de propósito geral foi projetado para lidar com consultas arbitrárias contra esquemas arbitrários. O settlement financeiro não é um workload arbitrário. É uma operação específica, restrita e de alta frequência que se beneficia de um modelo de execução projetado para esse propósito.

O Que uma Engine Projetada Faz de Diferente

Uma engine de settlement projetada para workloads financeiros toma três decisões arquitetônicas que um banco de dados de propósito geral não pode:

Records de tamanho fixo. Cada conta é exatamente 128 bytes. Cada transferência é exatamente 128 bytes. Alinhados com linha de cache. Sem campos de comprimento variável, sem tabelas TOAST, sem páginas de overflow. A CPU pode prever padrões de acesso à memória, e a engine de armazenamento pode calcular a posição de qualquer record por aritmética. Sem lookup de índice necessário.

Processamento em lote. Em vez de adquirir e liberar um bloqueio por transferência, a engine coleta transferências em lotes, até 8.190 operações por lote, e as processa em um único passo. A contenção de bloqueios é irrelevante porque não há bloqueios. O lote é a unidade de trabalho. Todas as transferências em um lote são aplicadas atomicamente.

O modelo de throughput se inverte: em vez de se degradar sob concorrência, melhora. Mais clientes concorrentes significa lotes mais cheios. Lotes mais cheios significa melhor amortização do overhead por lote. O sistema se torna mais rápido à medida que a carga aumenta, até os limites físicos de largura de banda de memória e I/O de disco.

Zero allocation, zero GC. Toda a memória é alocada estaticamente na inicialização. Nenhum coletor de lixo executa durante a operação. Sem ciclos malloc/free. Sem fragmentação ao longo do tempo. A latência é determinística: o tempo para processar um lote é função do tamanho do lote, não do estado do heap.

O resultado: latência sub-milissegundo por transferência com throughput previsível sob carga. Sem curva de degradação. Sem spikes de tail latency por pausas de GC. Sem comportamento surpresa durante horários de pico.

PropriedadeBD de Propósito GeralEngine Projetada
Acesso a recordsLookup de índice (traversal B-tree)Aritmética (cálculo de offset)
Modelo de concorrênciaBloqueios de linha, detecção de conflitosProcessamento em lote, sem bloqueios
Throughput sob contençãoSe degrada não linearmenteMelhora com lotes mais cheios
Gestão de memóriaAlocação dinâmica + GC ou free manualAlocação estática, zero GC
Tail latency (p99)Imprevisível (GC, waits de bloqueio, vacuum)Determinística
Imposição de invariantesCódigo de aplicação ou triggersNível de engine (protocolo rejeita transferências inválidas)

O Que o Settlement Determinístico Habilita

O settlement sub-milissegundo não é uma métrica de vaidade. Habilita quatro capacidades operacionais:

Visibilidade de saldos em tempo real. Transferências internas são liquidadas instantaneamente. Sem estado "pendente" para movimentos on-platform. O saldo que o cliente vê é o saldo que tem. Isso elimina toda uma categoria de tickets de suporte ("Por que meu saldo está errado?") e elimina a necessidade da distinção "saldo disponível" vs. "saldo de ledger" para fluxos internos.

Buffers de capital menores. Menos incerteza significa menos reserva necessária. Se o settlement se completa em menos de um milissegundo, o capital em voo em qualquer momento dado é desprezível. O capital que de outra forma estaria bloqueado em buffers pode ser empregado produtivamente.

Reconciliação mais rápida. Quando o settlement e o registro ocorrem na mesma operação atômica, a reconciliação se torna um passo de verificação em vez de uma investigação. O ledger e a engine de settlement concordam por construção. Discrepâncias só podem surgir na fronteira, quando sistemas externos (bancos, redes de clearing) estão envolvidos.

Testes de simulação determinística. Se a engine é determinística, mesmas entradas sempre produzem mesmas saídas, na mesma ordem, você pode reproduzir workloads de produção em um ambiente de teste e obter resultados idênticos. É assim que se testa um sistema financeiro sob carga: não com mocks que aproximam comportamento, mas com replay determinístico que o reproduz exatamente. Injete falhas (corrupção de disco, partições de rede, crashes de processo) e verifique que a engine se recupera corretamente. Toda vez. Reproduzivelmente.

O Problema das Afirmações de Desempenho

A maioria dos provedores de core banking publica números de throughput. "50.000 TPS." "100.000 contas por segundo." Esses números raramente são significativos sem contexto.

Perguntas que importam:

  • Que tipo de transação? Uma consulta de saldo não é um settlement. Uma leitura não é uma escrita. "TPS" sem especificar a operação não tem sentido.
  • Que distribuição de contas? 1.000 TPS contra 1 milhão de contas uniformemente distribuídas é trivial. 1.000 TPS contra 100 contas com distribuição Zipfiana (contas quentes) é um workload completamente diferente.
  • Que modelo de consistência? "50.000 TPS" sob READ COMMITTED é um número diferente de sob SERIALIZABLE. Workloads financeiros requerem o isolamento mais forte. Cite o número nesse nível.
  • Que percentil? A latência p50 indica o caso típico. p99 indica o pior caso 1-de-100. p100 indica o pior caso real. Para settlement financeiro, p99 e p100 são o que importa. Um sistema que é rápido 99% do tempo e para por 2 segundos na requisição 100 não é determinístico.

A abordagem honesta: publique sua metodologia de testes junto com seus números. Descreva o workload. Nomeie o nível de isolamento. Mostre a distribuição de percentis. Deixe os engenheiros avaliarem a afirmação contra seu próprio perfil de workload. Números de desempenho não qualificados são marketing. Dados de desempenho qualificados são engenharia.


Leia mais: O Ledger | A Arquitetura de um Sistema Operacional Financeiro


Fontes:

  • Amdahl, Gene M. "Validity of the single processor approach to achieving large scale computing capabilities." AFIPS '67, 1967.
  • Jim Gray, "A Measure of Transaction Processing Power" (1985), benchmark TPC débito/crédito
  • Thought Machine, "Vault Core Performance", metodologia de benchmark certificada com verificação independente
  • "Gray Failure: The Achilles' Heel of Cloud-Scale Systems" (Microsoft Research, 2017), degradação silenciosa de hardware