MÓDULO 06 · CONCEITO 02 DE 12

A pilha de latência

A tabela de Jeff Dean — números que todo engenheiro deveria saber. L1, L2, RAM, SSD, datacenter, região, continente. Cada ordem de magnitude define um conjunto distinto de decisões; ignorar qualquer uma delas custa caro de formas previsíveis.

Tempo de leitura ~22 min Pré-requisito Conceito 01 (performance como disciplina) Próximo Latência vs throughput

Em 2009, Jeff Dean — então Senior Fellow do Google e um dos arquitetos por trás de MapReduce, BigTable, Spanner e Borg — apresentou em talks técnicos uma tabela curta e devastadora chamada "Latency Numbers Every Programmer Should Know". A tabela listava operações comuns (leitura de L1, RAM, SSD, rede) com seus tempos aproximados. O choque pedagógico era a diferença de magnitudes: leitura de L1 cache é cem mil vezes mais rápida que leitura de disco rotativo. Round-trip dentro do datacenter é mil vezes mais lento que acesso a RAM. Round-trip cross-continent é cem mil vezes mais lento que RAM. Engenheiros que conhecem esses números tomam decisões qualitativamente diferentes daqueles que não conhecem.

A tabela de Dean ganhou vida própria. Foi traduzida em analogias humanas (Carlos Bueno, 2014: se 1 ns vira 1 segundo, leitura de disco rotativo vira meio mês), em sites interativos (colin-scott.github.io/personal_website/research/interactive_latency.html), em camisetas. Mas a função canônica permanece: dar ao engenheiro uma régua mental para que decisões de design não ignorem ordens de magnitude. Esse conceito atualiza a tabela para 2026 (NVMe, 100Gbps Ethernet, mais cores, mais cache), explica o que cada salto significa em prática, e conecta cada nível com classes específicas de decisões arquiteturais.

A premissa: você não precisa decorar os números exatos. Você precisa saber as ordens de magnitude com precisão suficiente para reconhecer quando uma decisão está cruzando uma fronteira. Trocar uma operação L1 por uma operação RAM é fator 100. Trocar RAM por SSD é fator 1000. Trocar SSD por rede local é fator 5–10. Trocar rede local por cross-region é fator 50–100. Cada vez que o design transita de um para outro, o impacto é visível em benchmark — e em produção.

O conceito é deliberadamente "denso de números" para depois servir de referência. Os onze conceitos seguintes ficam mais leves; este é o que cabe na mesa do engenheiro como cartaz mental de orientação.

A tabela canônica — atualizada para 2026

A tabela abaixo combina os números originais de Dean com atualizações para hardware típico de servidor em 2026 (referências em CPU Xeon ou EPYC com NVMe Gen4, rede 25–100 Gbps, e DDR5). Os valores são aproximados — varia por processador específico — mas as ordens de magnitude são robustas.

OPERAÇÃO                                         TEMPO       NORMALIZADO
                                                             (1 ns = 1 segundo)
─────────────────────────────────────────────────────────────────────────────
Acesso a registrador da CPU                      < 1 ns      < 1 segundo
L1 cache hit                                     ~1 ns       1 segundo
Branch mispredict                                ~3 ns       3 segundos
L2 cache hit                                     ~3 ns       3 segundos
L3 cache hit                                     ~10 ns      10 segundos
Mutex lock/unlock (sem contenção)                ~15 ns      15 segundos
RAM access (DRAM hit)                            ~80 ns      1 minuto e 20 s
Memcpy 1KB                                       ~100 ns     1 min 40 s
Compactar 1KB com Snappy                         ~2 µs       33 minutos
Round-trip TCP loopback (same machine)           ~10 µs      2 horas 45 min
SSD NVMe random read 4KB                         ~25 µs      7 horas
Send 1KB sobre 25Gbps Ethernet                   ~50 µs      14 horas
Read 1MB sequencial de RAM                       ~100 µs     1 dia 4 horas
Read 1MB sequencial de SSD NVMe                  ~1 ms       11 dias 14 h
Round-trip dentro do datacenter (rack adj.)      ~250 µs     2 dias 21 h
Round-trip dentro do datacenter (cross-rack)     ~500 µs     5 dias 19 h
HDD (rotacional) seek                            ~5 ms       2 meses
TLS handshake (1 RTT cross-region)               ~50 ms      1 ano e 7 meses
Round-trip CA → Holanda                          ~150 ms     4 anos e 9 meses
Round-trip São Paulo → Tóquio                    ~250 ms     7 anos e 11 meses

Repare nas três colunas. A primeira é a operação. A segunda é o tempo medido em unidades reais. A terceira é a analogia humana de Carlos Bueno: se 1 nanossegundo virasse 1 segundo, quanto tempo a operação levaria. Round-trip cross-continent vira quase oito anos em escala humana. É essa magnitude que torna decisões "vamos fazer essa chamada cross-region em loop" tão devastadoras.

Velocidade da luz — a constante que ninguém negocia

Antes de qualquer otimização de software, há um piso físico imposto pela velocidade da luz no vácuo: 299.792.458 m/s. Em fibra óptica, a velocidade efetiva é ~2/3 disso (~200.000 km/s) por causa do índice de refração do material. As consequências são duras.

Distância e latência mínima. Round-trip entre dois pontos a 1.000 km tem piso de ~10 ms (ida + volta), independente de hardware ou protocolo. Round-trip São Paulo → Frankfurt (cerca de 9.700 km) tem piso de ~95 ms. São Paulo → Tóquio (~18.500 km) tem piso de ~190 ms. Esses números são a velocidade da luz; nenhum CDN, otimização TCP, ou protocolo binário contorna.

Implicação 1: arquitetura geográfica. Sistemas com SLO de latência baixa para usuários globais precisam de réplica geográfica — não é luxo, é exigência física. Uma chamada que leva 95 ms só de fibra mais 5 ms de processamento local jamais vai responder em <100 ms se o datacenter está em outro continente. O design tem que colocar lógica perto do usuário.

Implicação 2: chatty vs chunky. Um protocolo "chatty" — que faz N round-trips para completar uma operação — paga N × latência mínima. "Chunky" — que faz uma chamada com payload maior — paga a latência mínima uma vez. HTTP/1.1 sem keep-alive era chatty (handshake TCP + TLS por request); HTTP/2 multiplex em uma conexão; HTTP/3 sobre QUIC reduz handshakes. Mover de chatty para chunky em chamada cross-region pode multiplicar performance por 10x.

Os saltos — o que cada ordem de magnitude muda

A pilha de latência tem fronteiras qualitativas — pontos onde a ordem de magnitude muda e o tipo de design viável muda junto. Nomear essas fronteiras é um dos serviços mais úteis que o conceito presta.

Sub-microssegundo (ns–µs): CPU, cache, RAM

Tudo que cabe nessa faixa é operação local da CPU. Hot loops, structures imutáveis em memória, contadores atomic, mutex sem contenção, hash lookups com bom layout de cache. Decisões aqui — escolha de struct vs class, array of structs vs struct of arrays, padding por cache line — fazem diferença mensurável apenas em código que executa milhões de vezes por segundo. O conceito 07 detalha.

Microssegundo (µs–dezenas de µs): SSD, syscall, IPC

Aqui mora o SSD NVMe (~25 µs random read), o syscall simples Linux (~1 µs), o IPC entre processos no mesmo host (~5–20 µs). Decisões nessa faixa: paginação do banco, log writes, comunicação inter-processo. Trocar entre essas operações é factor de 5–10 em performance — sensível, mas raramente decisivo sozinho.

Centenas de microssegundos a poucos milissegundos: rede local, query simples

Round-trip dentro do datacenter (~250–500 µs), query indexada em banco bem ajustado (~1–5 ms incluindo rede), leitura de 1 MB sequencial de SSD (~1 ms). Essa é a faixa onde aplicações de latência baixa operam — APIs internas de microsserviços que respondem em <10 ms vivem aqui. Decisões: connection pooling, query plan cache, cache em camada local.

Dezenas a centenas de milissegundos: cross-region, query pesada, internet

Round-trip cross-region cross-continent (50–250 ms), query analítica em GB de dados (10–500 ms), TLS handshake completo (50–200 ms cross-region). É a faixa do usuário humano consciente — abaixo de 100 ms o usuário percebe como instantâneo, acima de 200 ms como "carregando", acima de 1 s como "lento", acima de 10 s como "quebrou". Decisões nessa faixa: CDN, edge compute, cache HTTP, réplica geográfica, denormalização para leitura.

Segundos: jobs batch, processos longos

Carga de página complexa, compilação, jobs pequenos, operações que envolvem dezenas de chamadas. Domínio diferente — usuário tipicamente tolera spinner se houver indicação de progresso. Decisões: paginação, lazy loading, streaming de resposta, breakdown em sub-tarefas paralelas.

Como aplicar a pilha em decisão

A utilidade prática da tabela é dar à pergunta "isso vai ser rápido?" uma resposta calibrada antes de codificar. Considere três decisões reais que aparecem em projeto.

"Vou consultar o banco N vezes em loop." Cada query custa 1–5 ms. Para N=100, é 100–500 ms — fora do orçamento de qualquer SLO de API moderna. Já se sabe antes de medir: o padrão precisa virar batch (uma query com IN (...) ou um JOIN), ou dataloader, ou denormalização. A escolha entre as três é design, mas que o loop está errado é certeza pela tabela.

"Vou chamar serviço externo cross-region em streaming." Cross-region é 50–150 ms. Em streaming, você paga isso no primeiro byte; o restante flui. Para chamada única síncrona, é 100–300 ms de overhead. Decisão informada: se a operação chama cross-region uma vez, OK. Se chama 5 vezes em sequência, é 0.5–1.5 s só de rede — está fora do orçamento.

"Vou cachear o resultado por 5 minutos em Redis." Redis local: ~1 ms. Cache hit melhora a latência de "consultar o banco" (1–5 ms) em fator modesto. Mas se o cálculo cacheado custa 200 ms (várias queries + lógica), o ganho é ordem de magnitude. Decisão: cache vale para cálculos caros, não para evitar query única que já está abaixo de 5 ms — o overhead da própria consulta de cache é da mesma ordem.

HTTP no rodapé da pilha — o que cada hop adiciona

Para sistemas web, a soma de latências tem composição bem conhecida. O que o usuário sente é a somatória dessas etapas:

USUÁRIO → DNS → TCP → TLS → ENVIO → SERVIDOR → RESPOSTA → DECODE → RENDER

DNS lookup (cached)                     ~1 ms (cache local)
DNS lookup (uncached)                   ~30–100 ms
TCP handshake (3-way)                   1 RTT
TLS 1.3 handshake (1-RTT)               1 RTT (0-RTT em sessões repetidas)
HTTP request (envio)                    payload / bandwidth + 1 RTT
Servidor processa                       (depende da operação)
HTTP response (recebimento)             payload / bandwidth + 1 RTT
Browser decode + render                 ~50–500 ms (depende do conteúdo)

Em uma página complexa, a latência percebida é dominada por três fatores frequentes: número de RTTs (cada uma paga a latência cross-region), tamanho dos payloads (cobra bandwidth), e renderização do browser. Otimizações como HTTP/2 (multiplex), HTTP/3 (sem handshake de TCP), e TLS 1.3 com 0-RTT atacam o "RTTs", que é onde a velocidade da luz paga seu preço. CDN ataca a distância (move a origem para perto do usuário). Compressão (gzip, brotli) ataca o "tamanho do payload".

O ranking de decisões por impacto

Saber a tabela permite ranquear o impacto de cada decisão pela ordem de magnitude do salto que ela elimina. Pelo retorno típico:

1. Eliminar viagem de rede (cache, CDN, batching) — ganho típico: 50–200 ms por viagem eliminada cross-region; 1–5 ms por viagem dentro do datacenter. Maior retorno por linha de código mexida.

2. Eliminar query pesada (índice adequado, materialized view, denormalização) — ganho: 10–500 ms por query. Custos: complexidade de invalidação ou de manutenção do índice.

3. Eliminar alocação em hot path (object pool, struct em vez de class, Span<T>) — ganho: alguns µs por iteração; dominante em loops com milhões de iterações. Custos: complexidade de código.

4. Reorganizar dados para cache de CPU (struct of arrays, padding, cache-line alignment) — ganho: 2–10x em código numérico hot. Custos: indireção, legibilidade.

5. Trocar algoritmo (hash em vez de lista, log n em vez de n²) — ganho: depende da escala. Em N=10, irrelevante; em N=10⁶, transformador.

Note como as três primeiras dominam impacto em sistemas típicos de aplicação web. Em sistemas de hot path computacional (game engine, ML inference), as últimas duas sobem em prioridade. Saber em qual classe de sistema você está define qual otimização traz retorno.

Os três experimentos que calibram intuição

Apenas conhecer a tabela em texto não calibra. Calibra ver os números acontecerem em código que você escreve. Três experimentos pequenos consolidam a régua mental.

C# — medindo cache vs RAM
using System.Diagnostics;

const int N = 1024 * 1024;          // 1M ints = 4 MB
var arr = new int[N];
for (int i = 0; i < N; i++) arr[i] = i;

// acesso sequencial — bom para prefetcher de cache
var sw = Stopwatch.StartNew();
long sum = 0;
for (int i = 0; i < N; i++) sum += arr[i];
sw.Stop();
Console.WriteLine($"sequencial: {sw.Elapsed.TotalMilliseconds:F2} ms, sum={sum}");

// acesso "random" — força cache miss
var rnd = new Random(42);
var indices = Enumerable.Range(0, N).OrderBy(_ => rnd.Next()).ToArray();
sw.Restart();
sum = 0;
for (int i = 0; i < N; i++) sum += arr[indices[i]];
sw.Stop();
Console.WriteLine($"random:     {sw.Elapsed.TotalMilliseconds:F2} ms, sum={sum}");

Em CPU típica, sequencial roda em ~1 ms (cache hit), random em 8–15 ms (cache miss). Fator 10x. A diferença é a hierarquia de cache em ação — vale ver o número no seu próprio hardware.

Python — medindo round-trip local vs cross-region
import asyncio
import time
import httpx

async def medir(url: str, n: int = 50) -> float:
    async with httpx.AsyncClient() as c:
        # warmup
        await c.get(url)
        ts = []
        for _ in range(n):
            t0 = time.perf_counter()
            r = await c.get(url)
            ts.append((time.perf_counter() - t0) * 1000)
        ts.sort()
        return ts[n // 2]      # mediana

async def main():
    # local: localhost (round-trip loopback)
    local = await medir("http://localhost:8080/health")
    # public US: cloudflare
    us = await medir("https://1.1.1.1/")
    # cross-continent: provavelmente Tóquio se você está no Brasil
    asia = await medir("https://www.google.co.jp/")
    print(f"local:        {local:6.1f} ms")
    print(f"US:           {us:6.1f} ms")
    print(f"asia:         {asia:6.1f} ms")

asyncio.run(main())

Resultado típico do Brasil: local <1 ms, US 100–150 ms, Ásia 250–350 ms. A diferença é o pedágio da velocidade da luz mais hops de rede.

Go — medindo SSD vs RAM
package main

import (
    "fmt"
    "io"
    "os"
    "time"
)

func medir(label string, op func()) {
    op() // warmup
    start := time.Now()
    op()
    fmt.Printf("%s: %v\n", label, time.Since(start))
}

func main() {
    // 100 MB de dados
    data := make([]byte, 100*1024*1024)
    for i := range data { data[i] = byte(i % 256) }

    // RAM: copy in-memory
    medir("RAM copy 100MB", func() {
        dst := make([]byte, len(data))
        copy(dst, data)
        _ = dst
    })

    // SSD: write then read
    medir("SSD write 100MB", func() {
        f, _ := os.Create("/tmp/bench.dat")
        f.Write(data)
        f.Sync()                // força flush ao disco
        f.Close()
    })

    medir("SSD read 100MB", func() {
        f, _ := os.Open("/tmp/bench.dat")
        _, _ = io.Copy(io.Discard, f)
        f.Close()
    })
}

Em hardware típico: RAM copy ~30 ms (3 GB/s), SSD write 200–500 ms (200–500 MB/s), SSD read 100–300 ms (300 MB/s a 1 GB/s). Fator de 10x entre RAM e SSD, como a tabela canônica prevê.

armadilha em produção

Decisão de arquitetura tomada sem checar a pilha. Caso clássico: equipe coloca o cache compartilhado (Redis) em outra região porque "tem só uma instância para todos os ambientes". Cada cache lookup em produção agora paga ~80 ms cross-region, transformando "cache" em desaceleração — sai mais rápido bater no banco local. Sintoma: cache não melhora latência, equipe culpa "cache lento". Causa real: violação de ordem de magnitude (Redis local seria ~1 ms; cross-region é ~80 ms; banco local é ~5 ms; cache cross-region é 16× pior que o banco local). A tabela teria advertido antes de codar.

O que a tabela não cobre — variáveis ocultas

A tabela é régua mental, não previsão exata. Há três variáveis ocultas que distorcem o número real e merecem conhecimento.

Contention. Mutex sem contenção custa ~15 ns; com 100 threads brigando, vira centenas de microssegundos por aquisição. Connection pool sem contenção entrega conexão imediato; saturado, força espera que pode chegar a segundos. Sob carga, recursos compartilhados podem virar 1000× mais lentos do que sob teste.

Variabilidade (jitter). Os números são medianos. Em produção, P99 pode ser 5–10× pior que P50, especialmente com GC pause, swap, throttling de CPU, contention de network. O conceito 04 (tail latency) cobre essa variabilidade.

Camadas escondidas. "Acesso a SSD" no papel é 25 µs; em prática, o filesystem, o page cache do SO, e o controller do dispositivo adicionam latência própria. RAM "acessível" pode ser swap. Network "loopback" pode ser virtualizada via container/VM com overhead. Sempre vale medir no contexto real, não no papel.

heurística do sênior

Antes de qualquer decisão de design que envolve "fazer N viagens", responda: "qual é a magnitude da viagem, e qual é o N?". Magnitude × N pequeno = OK. Magnitude baixa × N grande = OK (sequencial em RAM). Magnitude alta × N grande = problema garantido. A regra simples: cross-region em loop é problema; banco em loop é problema; cache em loop apertado pode ser problema. Substitua por batch, paralelismo, ou eliminação. Esses são os erros que profile expõe depois — você pode preveni-los com a tabela na cabeça.

Por que importa para a sua carreira

A tabela de Dean é vocabulário de sênior. Quem articula decisões em ordens de magnitude — "essa chamada cross-region adiciona 80–150ms; vale só se acontecer uma vez por request" — convence em revisão e em design review onde quem fala em "rápido" e "lento" não convence. Em entrevista, "quanto tempo leva uma chamada cross-region?" ou "qual o ratio entre L1 e RAM?" são perguntas reais para vagas backend pleno-sênior — a resposta forte cita magnitudes e a fonte (Dean's table). Em design review, a frase "essa decisão coloca operação X numa magnitude que ela não deveria estar" é um dos atos mais altos de senior por unidade de esforço — a defesa estrutural é o número físico, não opinião. Em pos-mortem de incidente de latência, articular "passamos do orçamento de magnitude M na camada X" guia a investigação para o lugar certo.

Como praticar

  1. Reproduzir a tabela em sua máquina. Implemente os três experimentos do lang-compare (cache vs RAM, RTT local vs cross-region, RAM vs SSD) na sua linguagem principal. Anote os números em um documento seu — eles vão ser referência por anos. Compare com a tabela canônica e identifique onde sua máquina diverge (servidor mais novo? rede mais lenta? SSD mais rápido?). Esse exercício torna a tabela viva, não só decorada.
  2. Mapeamento de magnitudes em projeto. Pegue um endpoint de algum projeto seu. Liste todas as operações que ele executa: queries de banco, chamadas externas, leituras de cache, decodificação de payload, serialização. Para cada uma, anote a magnitude esperada (use a tabela). Some. Compare com a latência medida real do endpoint. A diferença entre soma teórica e medida é onde mora overhead invisível — vale investigar.
  3. Latência geográfica. Use traceroute e mtr (Linux/Mac) ou tracert (Windows) para mapear hops e latências para 5 destinos: serviço local, serviço em outro datacenter da mesma região, serviço em outro continente, serviço em outro hemisfério, satélite (se possível). Plote os RTTs versus distância geográfica estimada. Você verá a velocidade da luz como linha reta de fundo, com jitter de roteamento por cima. Visualizar isso uma vez fixa o conhecimento.

Referências para aprofundar

  1. artigo Numbers Everyone Should Know — Jeff Dean (Stanford talk, 2009). A apresentação onde a tabela canônica apareceu. Dean é coautor de papers fundadores do Google (MapReduce, BigTable, Spanner). Slides ainda circulam — vale procurar.
  2. artigo Latency Numbers Every Programmer Should Know — Colin Scott (interactive page, 2017+). colin-scott.github.io/personal_website/research/interactive_latency.html — Site interativo com slider para diferentes anos. A versão mais utilizada da tabela atualizada.
  3. artigo Computer Latency at a Human Scale — Carlos Bueno (Velocity, 2014). A analogia "1 ns = 1 segundo" que escala latências para tempo humano. Vídeo no YouTube. A visualização que faz o conhecimento grudar.
  4. livro Designing Data-Intensive Applications — Martin Kleppmann (O'Reilly, 2017). Cap. 8 (Trouble with Distributed Systems) cobre latência cross-network com profundidade rara. Conexões com clock skew e particionamento.
  5. livro Computer Systems: A Programmer's Perspective (3ª ed.) — Bryant & O'Hallaron (Pearson, 2015). Cap. 6 (The Memory Hierarchy) é o tratamento universitário canônico das ordens de magnitude entre cache, RAM e disco. Indispensável.
  6. livro What Every Programmer Should Know About Memory — Ulrich Drepper (Red Hat, 2007). PDF gratuito disponível em akkadia.org/drepper/cpumemory.pdf. 114 páginas sobre cache, NUMA, prefetching. Datado em alguns detalhes mas conceitualmente atual.
  7. livro High Performance Browser Networking — Ilya Grigorik (O'Reilly, 2013). Disponível gratuitamente em hpbn.co. Cobre HTTP/1.1, HTTP/2, TLS, conexões persistentes — toda a camada que define a latência percebida no browser.
  8. artigo Latency Lags Bandwidth — David Patterson (CACM, 2004). Patterson (coautor de "Computer Organization and Design") observa que bandwidth tem crescido mais que latência por décadas. Argumento estrutural sobre por que latência é restrição persistente.
  9. artigo Speed of Light in Networks — Various (NANOG presentations, 2010+). Apresentações de operadores de rede no NANOG (North American Network Operators Group). Quantificam o limite físico em casos reais.
  10. docs Linux Performance — Brendan Gregg. brendangregg.com/linuxperf.html — Catálogo de ferramentas de medição em Linux com cheat sheets visuais. Referência diária.
  11. docs Cloudflare Latency Map. speed.cloudflare.com — Ferramenta interativa que mostra round-trips reais entre você e datacenters globais. Calibração ao vivo.
  12. vídeo The Hardware Trends That Drive Software Design — Cliff Click (várias palestras, 2014–2020). YouTube. Click foi engenheiro do JIT da Sun/Oracle. Articula como arquitetura de hardware impõe restrições que software precisa respeitar.