mais lidos
Life at Nu
Conheça a sede do Nubank em Pinheiros, São Paulo/Brasil jan 11
Design
A nova aparência do Nubank: conheça nossa nova logo maio 17
Culture & Values
Como os valores e a cultura da Nu moldam os produtos que criamos ago 7
Carreiras
Reunimos grandes mentes de diversas origens que permitem a discussão e o debate e melhoram a resolução de problemas.
Saiba mais sobre nossas carreiras



Autor: Jonatan Michael
No Nubank, latência não é apenas uma métrica de performance. Ela impacta diretamente a experiência do cliente e os resultados do negócio. Ao construir produtos financeiros em escala, respostas consistentes abaixo de um segundo influenciam taxas de conversão, estabilidade operacional e o nível de confiança com que nos integramos a parceiros externos.
Em um dos nossos sistemas centrais de pagamentos, uma integração específica exigia respostas dentro de 360 milissegundos no percentil 90 (P90). Nos primeiros benchmarks, a latência P90 da nossa Payment Conditions API estava em torno de 1200ms, com o P99 frequentemente ultrapassando 1600ms. Reduzir esse gap exigiu mais do que ajustes de infraestrutura ou otimizações pontuais — foi necessário entender como decisões arquiteturais se materializavam em comportamento em tempo de execução.
Nossa abordagem foi intencional e incremental. Começamos tratando ineficiências locais do desenho existente (Fase 1). Quando os limites dessas melhorias ficaram evidentes, avançamos para uma mudança estrutural que desacoplou o caminho de leitura de chamadas síncronas a serviços downstream (Fase 2). Cada fase partiu de um problema claramente definido, seguido de uma resposta de engenharia direcionada, equilibrando corretude, performance e segurança operacional.
Fase 1: Otimizando a arquitetura existente
Em sistemas distribuídos de grande escala, ineficiências tendem a se acumular ao longo do tempo, adicionando latência sem entregar valor proporcional ao negócio. Nesta fase, nosso objetivo foi eliminar esses custos e isolar quais componentes da latência eram inerentes à própria arquitetura.
Problema 1: Cadeias profundas de chamadas entre domínios
Uma única requisição para calcular condições de pagamento percorria, de forma síncrona, uma longa cadeia de serviços:
O problema da cadeia de chamadas síncronas
Imagem criada por IA (Gemini)
Cada salto adicionava sobrecarga de rede, custo de serialização e exposição à tail latency. Embora nenhum serviço individual fosse particularmente lento, o efeito acumulado da composição estritamente síncrona entre domínios dominava o tempo de resposta ponta a ponta.
Solução 1: Propagação de contexto
A instrumentação e o tracing mostraram que múltiplos serviços buscavam e validavam de forma independente os mesmos dados, como limites de crédito e metadados de conta. Em vez de permitir que cada serviço recomputasse essas informações, refatoramos a camada de orquestração para propagar contexto já validado ao longo do fluxo.
Essa mudança reduziu as chamadas redundantes e diminuiu a carga nos serviços downstream. Além disso, tornou a responsabilidade sobre os dados mais explícita, melhorando tanto a eficiência em runtime quanto a clareza do sistema.
Problema 2: Dependências sequenciais sem semântica de domínio
Alguns serviços no grafo de chamadas existiam majoritariamente como camadas de roteamento. Eles encaminhavam requisições sem agregar lógica relevante de domínio, mas ainda assim impunham custos de latência.
Solução 2: Colapso de camadas não semânticas
Onde os limites de domínio permitiam, reorganizamos o fluxo para que o orquestrador chamasse diretamente os serviços de destino. A remoção dessas camadas intermediárias reduziu a profundidade da cadeia de chamadas e simplificou o raciocínio sobre o caminho crítico.
Remover dependências intermediárias
Imagem criada por IA (Gemini)
Limites da Fase 1
Essas otimizações foram eficazes e de baixo risco, mas expuseram um limite estrutural. Mesmo após eliminar redundâncias e colapsar camadas, a requisição ainda dependia de chamadas síncronas a diversos serviços independentes.
Características operacionais amplificavam o problema. Muitos serviços rodam sobre a JVM e são implantados em instâncias Spot na AWS. Reciclagem de instâncias, deploys e efeitos de warm-up da JVM impactavam diretamente a tail latency. Enquanto o caminho de leitura dependesse desses serviços, a variabilidade de latência seria inevitável.
A partir desse ponto, melhorias adicionais exigiam uma mudança de estratégia arquitetural, e não apenas otimizações incrementais.
Conheça nossas oportunidades
Fase 2: Desacoplando a composição em tempo de leitura das dependências de back-end
Apesar dos ganhos da Fase 1, as condições de pagamento ainda eram compostas de forma síncrona no momento da leitura. Isso significava que a latência percebida pelo cliente continuava limitada pela cadeia downstream mais lenta do sistema.
Para cumprir SLOs rigorosos de forma consistente, reformulamos o problema. Em vez de montar visões complexas de domínio a cada requisição, passamos a avaliar se essas visões poderiam ser pré-computadas continuamente e disponibilizadas para leituras rápidas por meio de uma estratégia de cache.
Problema 3: Cache passivo não elimina tail latency
Adicionar um cache passivo à frente do fluxo existente não resolveria o problema de forma estrutural. Em sistemas distribuídos com alta latência, cache misses ainda acionam o caminho síncrono original. Sob carga, esses misses se tornam indistinguíveis de timeouts e amplificam a tail latency.
O que precisávamos era de um modelo em que cache hits fossem o comportamento padrão, e cache misses, a exceção.
Solução 3: Cache persistido ativo e orientado a eventos
Formalizamos um framework de decisão para escolher entre cache passivo e ativo. Os critérios incluíam complexidade dos dados, frequência de atualização, volume de leitura e requisitos de consistência.
As condições de pagamento envolvem lógica de negócio não trivial, são lidas em altíssimo volume e mudam com frequência a partir de eventos observáveis do domínio. Consistência eventual era aceitável dentro de limites bem definidos, mas latência imprevisível não. Essas características tornaram o cache ativo a escolha adequada.
Design arquitetural: Agregação no momento da escrita
Na nova arquitetura, serviços de domínio emitem eventos sempre que ocorrem mudanças relevantes de estado, como atualizações de limites de crédito ou saldos de conta. Um serviço dedicado de agregação consome esses eventos, busca dados canônicos quando necessário, aplica a lógica de agregação e persiste uma representação totalmente materializada em um datastore de baixa latência.
Essa representação persistida passa a ser a principal superfície de leitura da Payment Conditions API. A maioria das requisições é atendida por uma única consulta em escala de milissegundos, removendo completamente os serviços downstream do caminho crítico.
Ao deslocar a complexidade para o caminho de escrita, isolamos a latência percebida pelo cliente de efeitos como variabilidade de infraestrutura, warm-up da JVM e deploys não relacionados em outros domínios.
Segurança, corretude e fallbacks
Em uma plataforma financeira, ganhos de performance não podem comprometer a corretude. Por isso, o cache persistido não é tratado como única fonte de verdade. Os clientes implementam obrigatoriamente um caminho de fallback. Caso o cache esteja indisponível ou não atenda aos requisitos de tolerância de dados obsoletos, as requisições retornam de forma transparente ao fluxo síncrono original.
Embora mais lento, esse fallback preserva a corretude e garante que problemas temporários na camada de cache não bloqueiem ações do cliente.
Observabilidade em uma arquitetura assíncrona
Com a complexidade movida para o lado da escrita, o foco operacional acompanhou essa mudança. Monitoramos lag de consumo de eventos, taxas de hit e miss do cache, taxa de sucesso e latências percentílicas das leituras persistidas. Esses sinais nos permitem detectar degradações precocemente e manter confiança tanto na corretude quanto na performance.
Resultados e impacto
Após o deploy da nova arquitetura, a latência P90 caiu de aproximadamente 1200ms para 280ms, uma redução de 76%. A tail latency se estabilizou significativamente, e as taxas de sucesso atingiram cinco noves, atendendo com folga os SLAs dos parceiros.
Mais importante ainda, o sistema se tornou previsível. A latência deixou de estar acoplada à dependência downstream mais lenta ou à variabilidade da infraestrutura. Essa previsibilidade reduziu riscos operacionais e viabilizou novas integrações que antes eram inviáveis.
Esse esforço reforçou uma lição importante no Nubank. O caminho para melhorias de performance frequentemente começa com ajustes pontuais (como propagação de contexto e colapso de camadas não semânticas). O próximo passo exige mudanças estruturais (como uma arquitetura de cache persistente orientada a eventos). Ao mover a complexidade para o caminho de escrita, aceitamos o custo de agregação assíncrona, gerenciamento de estado e observabilidade em troca de leituras rápidas e confiáveis.
A abordagem em fases foi fundamental. Ao esgotar as otimizações táticas antes de introduzir mudanças estruturais, evitamos complexidade arquitetural desnecessária e ganhamos confiança de que cada passo atacava uma restrição real.
Engenharia em escala não é sobre maximizar otimizações. É sobre escolher as abstrações corretas para atender às necessidades do cliente de forma confiável, sustentável e com o nível adequado de complexidade.
Conheça nossas oportunidades