7 Coisas que Aprendi Construindo uma API de Detecção de Fraude para a Rinha de Backend 2026
Primeira vez com Go, com assistência de IA, e 22 iterações depois.
Este projeto foi meu primeiro contato com Go, minha primeira competição de backend e o ciclo de otimização mais intenso que já fiz. Aqui estão as 7 coisas que mais importaram.
1. A Documentação da Competição é Uma Masterclass
O repositório da Rinha de Backend tem algumas das documentações de competição mais claras que já vi. Cada arquivo tem um propósito:
ARCHITECTURE.mdexplica a topologia, limites de recursos e regras de containerização com diagramas e exemplos.EVALUATION.mddetalha a fórmula de pontuação passo a passo com uma tabela mostrando exatamente como FP, FN e latência se traduzem em pontos. A seção "por que esses pesos" explica o raciocínio por trás de cada parâmetro, raro em documentações de competição.SUBMISSION.mddá a estrutura exata de branch, manifesto de arquivos e formatoinfo.jsonnecessário. Sem adivinhação.DATASET.mddescreve os 3 arquivos de referência, a convenção do sentinela-1e confirma que pré-processamento é permitido.DETECTION_RULES.mdespecifica a fórmula exata do vetor de 14 dimensões com constantes de normalização.
Este nível de documentação significou que nunca precisei fazer engenharia reversa da avaliação. Podia focar na engenharia. Se você está organizando uma competição, este é o modelo a seguir.
2. 99% do Código Foi Escrito por um Modelo Não-Premium
Um commit f10c097, foi feito com Cursor usando GPT 5.5. Consertou um problema de repositório git aninhado. Os outros 50+ commits em 22 iterações foram executados pelo Opencode GO usando DeepSeek V4 Pro e V4 Flash.
Isso importa porque desafia a suposição de que você precisa de modelos premium (GPT 5.5, Claude Opus 4.7) para toda tarefa de codificação. A série V4 lidou com:
- Implementação de clustering IVF k-means
- Kernels assembly AVX2 (duplo acumulador, VFMADD231PS, PREFETCHT0)
- Casting de ponteiro unsafe em Go e otimização de layout de memória
- Builds multi-estágio Docker e otimização de nginx
- Integração de chamada de sistema mmap e gerenciamento de memória cgroup-aware
- Execução de testes k6 e análise de resultados
O modelo premium foi usado uma vez, para um problema de fluxo de trabalho git. Todo o resto, incluindo as partes mais tecnicamente exigentes, foi executado por um modelo que custa uma fração por token.
Capacidade não é o mesmo que custo. Para projetos de engenharia estruturados com contexto claro, modelos de médio porte são frequentemente suficientes, embora o contexto específico da tarefa deva sempre ser levado em conta. Modelos premium justificam seu custo em situações que envolvem ambiguidade, problemas sem precedentes e descoberta arquitetural, mas quando se trata de execução, na minha opinião, modelos tier A são capazes de realizar o trabalho.
3. O Que Eu Faria Diferente
Isolamento de variável única desde o dia um. O erro mais caro do projeto foi mudar 5 variáveis de uma vez nas otimizações de sistema da Fase 3 (v11). Gastei 4 submissões e 3 horas perseguindo hipóteses erradas porque a CPU do nginx foi cortada de 0,20 para 0,10 e nunca percebi. O conserto foi uma linha no docker-compose.yml. A regra é simples: uma mudança por teste. Toda vez que a violei, paguei.
Meça a memória virtual no container antes de enviar. A contagem dupla embed+mmap causou 4 saídas OOM consecutivas. Ter tanto //go:embed artifact.bin (86,6MB no segmento de dados ELF) quanto unix.Mmap (86,6MB file-backed) criou ~183MB de espaço de endereço virtual contra um limite de cgroup de 130MB. Um simples docker stats ou cat /sys/fs/cgroup/memory/memory.usage_in_bytes antes da primeira submissão teria revelado o problema imediatamente.
Profile antes de otimizar. Passei 3 sessões em assembly AVX2, v6, v7b, v7c, v7d, antes de entender que o verdadeiro gargalo era a contagem de varredura de vetores, não a largura aritmética. O IVF exato de dois níveis (v10) reduziu vetores escaneados de 5.856 para 1.464 por consulta e ganhou mais pontos que todo o trabalho SIMD combinado. Quando muanlartins provou que float32 AVX2 em Haswell pode atingir p99=1,09ms, o gargalo nunca foi o conjunto de instruções, era escanear muitos vetores.
Verifique a infraestrutura primeiro ao depurar regressões. Antes de hipotetizar sobre flags de compilador, comportamento do GC ou inlining PGO, execute git diff <bom-commit>..HEAD -- docker-compose.yml. Limites de recursos são invisíveis para go test, go vet e benchmarks locais. No meu caso, uma diferença de 0,10 CPU causou uma regressão de 35x no p99 e gerou 4 análises de causa raiz falsas em 600+ linhas de documentação de sessão.
Comandos mais usados no projeto:
Git:
git commit -m "feat/perf/fix: <description>" # toda sessão
git push origin master # sincronizar repositório principal
git checkout --orphan source-clean # criar branch source limpa
git push submission-target source-clean:main --force # enviar source
git push <url> HEAD:submission --force # enviar submissão (método temp-dir)
git diff <baseline>..HEAD -- docker-compose.yml # encontrar regressões de infra
git checkout <commit> -- <file> # revert parcial
git log --oneline -3 # contexto rápido
Docker:
docker compose down -v && docker compose up -d # reiniciar stack
docker build --no-cache -t <tag> . # build (sem cache para artefato)
docker run --rm --network=host -v $(pwd)/k6:/k6 grafana/k6 ... # teste smoke
docker push ghcr.io/matheus896/rinha-backend-2026:<tag> # enviar para registry
Make:
make test # go test ./...
make bench # go test -bench=. -count=10 ./...
make k6-local # k6 smoke test
make docker-build # docker build
4. Por Que Parei
O projeto está completo. Todas as 5 fases terminadas, todos os 44 requisitos implementados. A pontuação final foi +5.516 (p99=2,47ms, FP=1/FN=0, 0 erros HTTP), com um reteste pós-revisão de +5.251,25 (p99=2,66ms, 0,02% de falhas), classificando 46° de 230 em 31 de maio, quando verifiquei pela última vez, o resultado final pode ser diferente após o término da competição.
Parei porque tenho um trabalho e outros projetos na 42sp. Não porque não havia mais nada a fazer, sempre há. O teto de p99 de +3000 a 1ms ainda estava fora de alcance. A solução #1 marcou +5.964. Mas o retorno marginal de outro ciclo de otimização estava diminuindo, e o investimento de tempo era real.
Também foi genuinamente divertido. A combinação de restrições duras (1 CPU, 350MB), uma função de pontuação clara e progresso visível através de 22 iterações tornou tudo viciante. Recomendo que qualquer engenheiro de backend experimente uma competição como esta.
5. Ferramentas e Recursos Que Tornaram Isso Possível
Skills foram carregadas nas sessões. As mais usadas:
| Skill | Onde Foi Aplicada |
|-------|-------------------|
| golang-benchmark | Comparações antes/depois com benchstat em cada iteração |
| golang-performance | Hierarquia de cache Haswell, padrões SIMD, metodologia de tuning GC |
| golang-safety | Wrappers unsafe.Pointer, mantendo memória mmap'd viva |
| golang-testing | Testes TDD table-driven, validação de recall |
| golang-concurrency | Padrões iniciais de sync.Pool e goroutines |
| golang-code-style | Convenções Go idiomáticas em toda a base de código |
| golang-error-handling | Padrão de contenção para recovery de panic |
| golang-observability | Perfis pprof, análise de pausas GC |
| nginx-configuration | Pools keepalive, upstream Unix socket, tuning de buffer |
| docker-compose-orchestration | Limites de recursos, volumes tmpfs, health checks |
| multi-stage-dockerfile | Otimização inicial de Dockerfile, .dockerignore |
| k6 | Execução de testes smoke, carga e oficiais |
| find-docs | npx ctx7 para tuning GC Go, mmap, memória cgroup |
| verification-before-completion | Checklist anti-regressão antes de cada submissão |
Arquivos de prompt reutilizáveis foram gerados para cada grande iteração (.planning/prompts/next-llm-*.md). Eles capturavam o estado completo do projeto, pontuação atual, últimas mudanças, regras ativas, restrições anti-regressão, para que cada sessão começasse com contexto completo em vez de reconstruí-lo. Esse padrão por si só economizou horas de explicação repetida.
Código de referência de competidores foi armazenado em docs/ para análise. Revisar como muanlartins, Joyce e jairo resolveram o mesmo problema foi a atividade de pesquisa de maior alavancagem.
6. Primeira Vez com Go: Impressões vs Python e C
Este foi meu primeiro projeto Go. Minha formação é Python (dados, APIs) e C (ponteiros da 42 School, gerenciamento de memória, estruturas de dados).
O que me surpreendeu positivamente:
- Binário único.
go buildproduz um binário estático. Sem virtualenv, semnode_modules, sem JVM. Para deploy Docker isso é transformador, a imagem de runtime tem 15MB. - Concorrência sem complexidade. Goroutines e canais são mais simples que asyncio do Python (cadeias de callback) e pthreads do C (gerenciamento manual de threads).
GOMAXPROCS=1foi na verdade ideal para esta carga de trabalho, sem sobrecarga de concorrência. - Biblioteca padrão rica.
net/http,encoding/json,testing,unsafe,debug/pprof, tudo necessário para este projeto já vinha incluído. Zero dependências de terceiros, excetovalyala/fasthttp(que substituiu net/http depois) egolang.org/x/sys(para mmap). - Cross-compilação é trivial.
GOOS=linux GOARCH=amd64 GOAMD64=v3 go build, pronto. Sem matriz de build, sem toolchains específicas de plataforma. - Padrões de zero alocação. O
unsafe.Pointerdo Go dá controle de memória nível C quando necessário, mas o GC cuida do caso comum. O hot path tinha 0 alocações por requisição após a otimização.
O que senti falta do C (42): Intrínsecos SIMD no compilador (_mm256_fmadd_ps em vez de escrever assembly), e o teto absoluto de performance do gerenciamento manual de memória. Mas o GC do Go é bom o suficiente, a otimização mmap moveu o índice de 86MB para fora do heap, e as pausas do GC se tornaram negligenciáveis a p99=2,47ms.
Go divide a diferença entre a produtividade do Python e a performance do C. Eu usaria novamente para qualquer serviço de API CPU-bound com restrições apertadas de recursos.
7. Inspirações de Outros Competidores
muanlartins (#1 geral, +5.964). A maior influência. Seu IVF exato de dois níveis foi o avanço estrutural: fast path com NPROBE=2 cobre 95,84% das consultas com 1.464 vetores escaneados, escalando para busca exata completa em apenas 4,16%. Sua descoberta do QuantScale=10000 eliminou o último erro de quantização. E seu kernel AVX2 de duplo acumulador provou que float32 SIMD em Haswell pode atingir p99=1,09ms. Peguei emprestada a arquitetura, não o código, escrevi meu próprio kernel assembly com re-ranqueamento int64 para lidar com a diferença de quantização.
Joyce (#3 geral, +5.853). Sua configuração fasthttp e padrão de handler mostraram como é uma camada HTTP de zero alocação. As flags DisableHeaderNamesNormalizing, NoDefaultDate, NoDefaultServerHeader, entendi o padrão de configuração, implementei minha própria lógica de handler. Seu assembly scanBlocksAVX2 com saída antecipada em estágios nas dims 4, 6, 8 e 14 influenciou meu design de checkpoint de duplo acumulador.
jairo (Rust, +5.932). Adotou uma abordagem diferente: subconjunto de 100K vetores + força bruta + AVX2. Em vez de indexar todos os 3M vetores como IVF, ele pegou 100K e executou busca exata com aceleração AVX2. Isso validou que subconjuntos agressivos mais SIMD é uma alternativa viável ao clustering IVF. Não copiei a abordagem, mas confirmou que a contagem de varredura de vetores (não a estrutura do índice) é a principal alavanca.
Estude competidores para entender o que é possível, depois implemente como do seu próprio jeito.