MÓDULO 06 · 1 SEMANA · CACHE & PERFORMANCE

Cache & Performance

Latência não é número — é distribuição. Performance não é arte — é disciplina. Da tabela de Jeff Dean ao P99 que decide se o sistema escala, do flame graph ao soak test que desnuda o vazamento.

Duração ~1 semana
Conceitos 12 fundamentais
Projeto API com cache estratificado
Pré-requisito Módulos 03, 04 e 05
Performance Custo Escalabilidade

Doze conceitos para tratar performance como disciplina, não como improviso. Da regra de Knuth e da tabela de Dean (vocabulário e números canônicos) ao profiling sério e ao teste de carga SLO-driven, passando pela camada física (cache de CPU, alocação de memória, pressão de GC) e pela camada distribuída (HTTP cache, CDN, performance de banco). O objetivo do módulo é ensinar o senior a medir antes de mexer, articular onde a latência mora, e otimizar com critério em vez de chute.

01

Performance como disciplina — medir, perfilar, nunca adivinhar

Knuth, em 1974, e a frase mais mal citada da computação. Por que intuição erra em otimização e por que profiling-driven é o único workflow que sobrevive a sistemas reais.

estudar →
02

A pilha de latência — números que todo sênior precisa saber

L1/L2, RAM, SSD, datacenter, região. A tabela canônica de Jeff Dean atualizada para 2026, e o que cada ordem de magnitude significa em decisão de design.

estudar →
03

Latência vs throughput — duas métricas distintas

Little's Law, distribuições (P50/P95/P99), e por que "latência média" é a métrica mais enganosa do dicionário operacional.

estudar →
04

Tail latency — a cauda longa decide tudo

Dean & Barroso, The Tail at Scale (CACM, 2013). Hedged requests, speculative execution, e por que P99 mata sistemas distribuídos quando ninguém está olhando.

estudar →
05

Profiling — flame graphs, pprof, dotnet-trace, py-spy

Brendan Gregg e o flame graph (2011). Sampling vs instrumentation. Continuous profiling. As ferramentas que separam quem otimiza no escuro de quem otimiza com luz.

estudar →
06

Benchmarking — micro e macro

BenchmarkDotNet, go test -bench, pytest-benchmark. Warmup, JIT, ruído ambiental, statistical significance — o que separa benchmark sério de número que enganou três times sucessivos.

estudar →
07

CPU cache, memory locality, false sharing

Cache lines de 64B, struct layout, data-oriented design (Mike Acton, 2014). Por que dois contadores em variáveis vizinhas podem rodar 10× mais devagar que separados.

estudar →
08

Alocação de memória e pressão de GC

Stack vs heap, escape analysis, generational GC. ArrayPool<T>, sync.Pool, object pooling — quando alocar é o gargalo invisível.

estudar →
09

HTTP cache — RFC 9111

Cache-Control, ETag, Vary, conditional requests. A camada de cache mais ignorada e mais consequente — gratuita quando bem usada, vazadora quando mal.

estudar →
10

CDN e edge caching

Akamai (1998), Cloudflare, Fastly. Origin shielding, surrogate keys, edge compute. Por que mover lógica para a borda mudou a forma como APIs respondem em escala global.

estudar →
11

Performance de banco — N+1, batching, prepared statements

Complemento do módulo 03 (que tratou correção e índices). Aqui: a otimização vista da aplicação. Eager loading, dataloader, materialized views, denormalização para leitura.

estudar →
12

Performance testing — load, stress, soak

k6, Locust, Gatling, Vegeta. SLO-driven testing, capacity planning, soak test que desnuda vazamento. Por que "rodei ab e tava ok" não conta como teste de performance.

estudar →
princípio orientador

Performance é decidida em três camadas que se reforçam, e ignorar qualquer uma sai caro. Na camada física — CPU cache, RAM, disco, rede — a velocidade da luz e a hierarquia de memória impõem limites que algoritmo nenhum contorna. Na camada de processo — alocação, GC, threads, locks — o runtime cobra o preço de cada decisão de design. Na camada distribuída — banco, cache, CDN, fila, rede entre serviços — a soma de latências individuais vira o P99 que o usuário sente. O senior aprende a enxergar as três simultaneamente: nenhuma otimização local supera entender em qual camada o sistema está gargalado, e nenhuma medição local substitui o flame graph que mostra onde o tempo realmente vai.

Performance está cheia de regras populares ("cache resolve tudo", "ORM é lento", "GC é o problema") que mais atrapalham do que ajudam. Cada decisão abaixo é confronto direto com uma dessas regras — e a resposta saudável quase sempre começa com "depende da medição".

Vale otimizar antes de medir?

Quase nunca. A frase de Donald Knuth — "premature optimization is the root of all evil" — é mal citada, mas o argumento original (de 1974) sobrevive: a intuição humana é notoriamente ruim para identificar onde o tempo de um programa está sendo gasto. Profilers existem por isso. As exceções são duas e estreitas: (1) decisões arquiteturais que são caras de reverter depois (escolha de modelo de execução, layout de dados crítico, protocolo de rede), e (2) padrões reconhecidos como armadilhas estruturais (N+1 query, alocação em loop quente, retry storm). Fora dessas, a regra é medir, encontrar o gargalo, mexer, medir de novo.

Latência média ou percentis?

Percentis. Em sistemas reais, distribuições de latência são fortemente assimétricas — a média é dominada pela cauda, e duas distribuições com a mesma média podem ter experiências de usuário radicalmente diferentes. P50 (mediana) conta como a maioria das requests se comporta; P95 e P99 contam o que o usuário experiencia em momentos ruins; P99.9 conta o que sustentadores noturnos vêem. Em sistemas distribuídos com fan-out alto (uma request atinge dezenas de serviços), o P99 individual de cada serviço vira a latência mediana do usuário — fenômeno documentado por Dean & Barroso em 2013. Métrica de equipe sênior é histograma, não média.

Cache em qual camada?

Em todas, idealmente, mas com responsabilidades distintas. Browser cache (via Cache-Control) elimina viagem de rede para conteúdo estável. CDN elimina viagem ao origin para conteúdo público dinâmico (com cuidado de Vary em endpoints personalizados). Cache distribuído (Redis) na camada de aplicação serve dado mutável compartilhado entre réplicas. Cache local in-memory cobre lookup tables quase imutáveis. Cada camada tem invalidação distinta, e a soma — quando bem orquestrada — entrega latência ordens de magnitude melhor que qualquer camada isolada. Quando mal orquestrada, vira fonte de bugs de "às vezes vejo dado antigo" que ninguém consegue reproduzir.

Profiling em produção ou em ambiente de teste?

Em produção. Hoje, com continuous profiling (Pyroscope, Datadog Continuous Profiler, Parca), o overhead é < 2% e os dados refletem o tráfego real — tipos de query, distribuições de payload, padrões de uso. Profiling em ambiente de teste mente: load sintético raramente reproduz o caso real, e otimizações baseadas nele frequentemente atacam o gargalo errado. A regra de senior é continuous profiling sempre ligado em produção, com retenção curta (poucos dias) e alertas em mudança brusca de hot path.

Otimizar para latência ou para throughput?

São métricas distintas, frequentemente em tensão. APIs voltadas a humano (web, mobile, voz) priorizam latência — o usuário sente cada milissegundo da P99. Sistemas voltados a processamento batch (ETL, treinamento, geração de relatório) priorizam throughput — quanto custa por registro processado, quantos por hora. A confusão clássica é otimizar throughput de um sistema interativo, fazendo batches grandes que matam a P99. Ou o oposto: otimizar latência de um pipeline batch, processando registros um a um quando o ganho seria batch. Articular qual é a métrica relevante antes de otimizar evita esforço perdido.

O projeto força a sentir cada camada da pilha de cache em operação simultânea, e a validar SLOs com teste de carga sério — não com ab rodando por dois minutos. O objetivo final é uma API onde se consegue articular, em diagrama e em métricas, em que camada cada porcentagem de latência é resolvida.

PROJETO PRÁTICO

API com cache estratificado e SLO validado

Uma API REST de catálogo (continuação possível do projeto do módulo 05) com quatro camadas de cache em operação: HTTP cache via Cache-Control+ETag, CDN (Cloudflare ou simulado via Varnish local), cache distribuído (Redis), e cache local in-memory. Profiling contínuo configurado (Pyroscope ou equivalente). Suíte de teste de carga com k6 que valida SLO articulado (P99 < 200ms a 1000 RPS, por exemplo). Documento ADR explicando em qual camada cada percentual de latência é resolvido.

REQUISITOS
  • Endpoints GET com Cache-Control e ETag apropriados, retornando 304 quando válido
  • CDN configurada (Cloudflare em produção, Varnish em dev local) com cache key + invalidação por tag
  • Cache Redis na camada de aplicação para dado mutável compartilhado, com TTL e single-flight
  • Cache local in-memory para lookup tables (categorias, configurações)
  • Continuous profiling exportando flame graphs (Pyroscope/Parca)
  • Métricas exportadas via OTel: latência por endpoint, hit rate por camada de cache, alocações por request
  • SLO articulado em SLI documento: P99 < X ms a Y RPS, error rate < 0.1%
  • Suíte k6 com cenários: load steady (1h), stress até quebrar, soak test (12h)
  • ADR documentando trade-offs de cada camada de cache (TTL, invalidação, consistência)
  • Comparativo cold (sem caches) vs warm (todas as camadas operando) com gráfico de latência
Performance Custo Escalabilidade
STACK SUGERIDA POR LINGUAGEM
STACK
.NET 10 + ASP.NET Core OutputCache + HybridCache + StackExchange.Redis + BenchmarkDotNet + dotnet-trace + Pyroscope.NET + k6 + Cloudflare/Varnish
ESTRUTURA / NOTAS
  • Catalog.Api/ com endpoints minimal API e OutputCache tag-based
  • Catalog.Caching/ com HybridCache (L1+L2) e wrapper por agregado
  • Catalog.Benchmarks/ com BenchmarkDotNet para hot paths identificados
  • perf/k6/ com scripts de load, stress e soak
  • Pyroscope agent rodando em sidecar de produção
  • SLO definido em docs/slo.md; alertas em Grafana
STACK
Python 3.13 + FastAPI + aiocache (Redis) + cachetools (in-memory) + pytest-benchmark + py-spy + Pyroscope-Python + k6 + Varnish
ESTRUTURA / NOTAS
  • catalog/api/ com headers de cache HTTP idiomáticos via FastAPI middleware
  • catalog/caching/ com decorators @cached e wrapper de repositório
  • benchmarks/ com pytest-benchmark e perfis py-spy capturados
  • perf/k6/ idêntico ao C#
  • Pyroscope agent injetado via decorator em rotas críticas
  • asgi-cache para HTTP cache + ETag automático
STACK
Go 1.23 + chi + go-redis + ristretto (in-memory) + go test -bench + pprof + grafana-pyroscope-go + k6 + Cloudflare/Varnish
ESTRUTURA / NOTAS
  • internal/api/ com middleware de Cache-Control e geração de ETag
  • internal/cache/ com camadas L1 (ristretto) e L2 (Redis) explícitas
  • internal/bench/ com benchmarks em hot paths
  • net/http/pprof exposto em endpoint admin, scrape periódico
  • perf/k6/ idêntico
  • singleflight em torno de fetch ao banco
entregável

Repositório com README documentando: o diagrama do pipeline de cache (com qual camada resolve qual % das requests, segundo medição), o flame graph antes e depois da otimização principal, gráficos de P50/P95/P99 sob carga steady de 1h, gráfico de soak test de 12h mostrando estabilidade (ou descobrindo vazamento de memória), o ADR justificando TTLs e estratégias de invalidação, e um README curto explicando como o sistema se comporta quando uma camada é desligada de propósito. Bonus: comparativo de latência geográfica (com e sem CDN, mensurada via k6 distribuído).

Performance aparece em duas formas em entrevista de sênior: como diagnóstico de gargalo ("o sistema está lento, o que você faria?") e como design ("como você organizaria cache numa arquitetura nova?"). As perguntas abaixo cobrem a faixa que diferencia quem acha que cache resolve tudo de quem entende a pilha.

Q.01

Um endpoint que costumava responder em 50ms agora responde em 500ms. Você não tem dashboards. Como conduziria o diagnóstico em ordem?

Q.02

Explique a diferença entre P50, P95, P99 e por que monitorar só a média é uma armadilha. Em que cenário você se preocuparia mais com P99.9 do que com P99?

Q.03

Você tem uma API com 1000 RPS e SLO de P99 < 100ms. Como você desenharia a estratégia de cache desde o browser até o banco? Articule trade-offs em cada camada.

Q.04

Em um loop quente que processa 1M de objetos pequenos, você descobre que 70% do tempo é GC. Quais são suas três primeiras hipóteses de causa, e como verificaria cada uma?

Q.05

Diferença entre load test, stress test e soak test. Para um produto novo lançando para 10× o tráfego atual, qual sequência você executaria, e o que cada teste descobriria?