ESTUDO DE CASO · 2026
Quando cada microssegundo importa.
Um detector de fraude em Rust projetado para responder com precisão e latência mínima sob limites rígidos de CPU e memória.

01 / O QUE É A RINHA
Uma competição em que arquitetura deixa de ser teoria.
A Rinha de Backend é uma competição amistosa criada para colocar implementações diferentes diante do mesmo problema e das mesmas restrições. Cada edição define um domínio, um contrato HTTP, um ambiente limitado e uma carga de teste. Não vence simplesmente quem escolhe a linguagem mais rápida: vence quem entende melhor o problema, mede o sistema inteiro e faz boas escolhas dentro do orçamento disponível.
Isso torna a competição especialmente interessante porque decisões que costumam parecer pequenas — formato dos dados, número de processos, alocações, comunicação entre serviços e comportamento do sistema operacional — aparecem diretamente no resultado.
Processar cada transação com baixa latência sem sacrificar a precisão.
02 / O DESAFIO DE 2026
Detectar fraude comparando cada transação com 3 milhões de referências.
A edição de 2026 propôs uma API de detecção de fraude em transações de cartão. Para cada requisição, os dados da compra precisavam ser transformados em um vetor normalizado de 14 dimensões. Em seguida, a solução deveria encontrar, entre 3 milhões de vetores rotulados, as cinco transações mais próximas.
O número de vizinhos marcados como fraude define o fraud_score, ou índice de fraude: zero vizinhos fraudulentos produz valor 0; cinco produzem valor 1. A transação é aprovada quando esse índice fica abaixo de 0,6. Portanto, desempenho não poderia vir de qualquer resposta rápida: a classificação precisava continuar correta.
Tudo isso deveria rodar em contêineres com a soma máxima de 1 CPU e 350 MB de memória. A avaliação combinava correção e latência p99. O p99 mostra um limite de tempo atendido por 99% das requisições; apenas o 1% mais lento fica acima dele. Erros derrubavam a pontuação, mas uma solução correta e lenta também perdia competitividade.
03 / A ESTRATÉGIA
Evitar o trabalho caro quando a resposta segura já era evidente.
A primeira decisão foi dividir a classificação em dois caminhos. O primeiro foi chamado de fast path, ou caminho rápido: uma rota curta que evita executar a busca mais cara quando já há evidências fortes de que a compra é normal. Ele aprova somente transações que atendem simultaneamente a condições conservadoras, como valor baixo em relação à média do cliente, poucas parcelas, distância limitada, loja conhecida e categoria comercial de baixo risco.
Esse caminho não tenta adivinhar fraudes. Ele existe apenas para reconhecer casos inequivocamente seguros e retornar fraud_score = 0. Se uma única condição não for satisfeita, a transação segue para o k-NN exato: o algoritmo que procura, sem aproximação, as cinco referências mais parecidas com a compra recebida.
Gasto seguro
Regras conservadoras passam → aprovação imediata.
Caso não seguro
Qualquer regra falha → busca exata dos cinco vizinhos.
A vantagem é estrutural: a solução reduz o custo médio sem transformar velocidade em perda de precisão. A implementação não procura respostas prontas a partir das requisições conhecidas do teste nem usa uma regra aproximada para negar transações. Nos casos não seguros, a decisão continua vindo do índice vetorial.
04 / A ARQUITETURA
Menos etapas entre a requisição e a resposta.
A submissão foi organizada em três processos: um load balancer, responsável por distribuir as conexões, e duas instâncias da API, responsáveis pela classificação. O distribuidor escuta a porta 9999 e aceita as conexões TCP — o protocolo usado para transportar os dados entre cliente e servidor.
Em vez de receber a requisição e criar uma segunda conexão para repassá-la, o distribuidor entrega à API a própria conexão já aberta. Tecnicamente, ele transfere o descritor do socket — a representação da conexão no sistema operacional — por um canal local entre processos, usando SCM_RIGHTS. A API passa a ler e responder diretamente na conexão original. Isso evita repetir interpretação, cópia e encaminhamento dos dados.
As APIs foram isoladas da rede Docker e receberam afinidade de CPU definida — cada uma foi direcionada a um núcleo específico do processador. Além de tornar o trabalho mais previsível, essa decisão ajudou a aproveitar melhor o cache de cada núcleo.
O cache é uma memória muito pequena e rápida localizada dentro ou próxima do processador. Ele mantém por perto dados usados recentemente, evitando buscas mais demoradas na memória principal. Quando um processo é movido de um núcleo para outro, parte desse conteúdo precisa ser carregada novamente. Mantendo cada API no mesmo núcleo, os vetores, partes do índice e instruções mais acessados tinham maior chance de continuar disponíveis no cache.
Organizar os dados para aproveitar o cache do processador reduziu mais a latência do que otimizar apenas a regra de negócio.
Essa combinação de afinidade de CPU e organização dos dados reduziu interferências entre aceitação de conexões, cálculo e envio da resposta. Na prática, o melhor aproveitamento do cache foi o grande salto de desempenho do projeto.
05 / O ÍNDICE VETORIAL
Pré-processar tudo que não precisava acontecer durante a requisição.
Percorrer 3 milhões de referências e calcular 14 distâncias para cada requisição seria correto, mas caro demais. Como a base de dados permanecia estável durante o teste, a solução gerava o índice antes de iniciar a API. Assim, o servidor recebia uma estrutura já preparada para consulta, em vez de reconstruí-la durante o teste.
Os números dos vetores foram convertidos para um formato compacto chamado i16, que usa 16 bits por valor. Isso reduz o consumo de memória e permite manter mais dados perto do processador. O índice também foi dividido e organizado para descartar rapidamente regiões que não poderiam conter os melhores resultados.
A busca continua exata: a estrutura elimina candidatos impossíveis e acelera o cálculo, mas preserva os cinco vizinhos corretos. Isso foi decisivo para combinar zero falhas de classificação com p99 abaixo de um milissegundo.
06 / ONDE O TEMPO ERA GASTO
A resposta não depende apenas da velocidade do algoritmo.
Em sistemas de alta performance, chama-se hot path, ou caminho crítico, a sequência de operações executada em praticamente toda requisição. Neste projeto, ela começa quando a conexão é aceita e termina quando a resposta é enviada. Cada etapa consome uma parte do tempo total disponível.
Para que cada requisição exigisse menos trabalho, a aplicação evitava criar dados que seriam descartados logo depois. Como o resultado só podia assumir seis valores — 0, 0,2, 0,4, 0,6, 0,8 ou 1 —, as respostas já ficavam prontas na memória. O índice de transações também era organizado antes da API iniciar, e o processador comparava vários valores ao mesmo tempo. Isso reduziu bastante o tempo de classificação.
Mesmo com a regra de negócio otimizada, uma requisição ainda precisava atravessar a rede, esperar o sistema operacional liberar tempo de processador, encontrar os dados na memória e concluir o envio pelo socket. Quando o cálculo passa a durar microssegundos, pequenas esperas nessas etapas deixam de ser ruído e passam a representar uma parcela relevante da latência.
Por isso, o trabalho maior passou a ser reduzir o custo do caminho crítico e controlar o jitter. Jitter é a instabilidade no tempo de resposta: mesmo quando a maioria das requisições termina rapidamente, algumas podem sofrer atrasos inesperados. Isso acontece, por exemplo, quando uma requisição espera mais pela rede, por tempo de CPU, por dados na memória ou pelo envio da resposta. Por esse motivo, acelerar uma função isolada nem sempre melhora o p99, que mede justamente as requisições mais lentas.
07 / COMO AS DECISÕES FORAM MEDIDAS
Medir cada etapa para descobrir o gargalo real.
Para evitar otimizações baseadas apenas em intuição, a aplicação passou a medir separadamente o tempo de leitura da requisição, interpretação do JSON, caminho rápido, transformação em vetor, busca dos vizinhos, preparação da resposta e conclusão do envio. Isso permitiu identificar em qual etapa o tempo estava sendo gasto.
A média, sozinha, poderia esconder problemas. Imagine 100 requisições: se 99 terminam rapidamente e uma demora muito mais, a média ainda pode parecer boa. O p99 evidencia essa região mais lenta e ajuda a medir a estabilidade da solução, não apenas sua velocidade típica.
Uma mudança podia acelerar milhares de respostas e, ao mesmo tempo, tornar algumas poucas mais lentas. Por isso, cada decisão precisava ser testada no sistema completo, sob carga e dentro dos mesmos limites de CPU e memória da competição.
Latência não é apenas o tempo do algoritmo. É o tempo de todo o percurso da requisição.
08 / RESULTADO E APRENDIZADO
10º lugar entre soluções levadas ao limite.
A solução terminou a competição com score máximo de 6.000, nenhuma falha e p99 de 0,5102 ms, alcançando o 10º lugar geral. Foi uma experiência muito boa para medir o impacto real de decisões de arquitetura e implementação em um ambiente com restrições severas de CPU e memória.
Mais importante que a posição, o projeto mostrou na prática que desempenho é resultado do conjunto. Foi necessário eliminar trabalho desnecessário, organizar os dados para o processador, observar as requisições mais lentas e considerar rede, distribuição de tempo de CPU, memória e entrada e saída como partes da mesma arquitetura.