---
id: system-design-tradeoffs
domain: architecture
agents: [architect]
when: "ao desenhar a arquitetura de um sistema e precisar pesar trade-offs de escala, latência e consistência"
---

# System Design Trade-offs — pesando escala, latência e consistência

## O problema

Arquitetura medíocre escolhe componentes por reflexo: "vamos usar cache", "vamos shardar o banco",
"coloca um load balancer". Sem nomear o trade-off, sem estimar a ordem de grandeza, sem dizer **por
que esse e não o default**. O resultado é um diagrama bonito que desmorona na primeira pergunta de
"e quando dobrar a carga?".

O tell do desenho genérico:

- Decisão de consistência implícita ("o banco resolve") — quando *forte vs eventual* muda toda a UX.
- Cache jogado em cima sem definir a **estratégia de escrita** nem a **invalidação** (a parte difícil).
- "Vamos escalar horizontalmente" sem back-of-the-envelope: nenhum número de RPS, storage, banda.
- Replicação master-slave assumida sem citar o custo (lag de replicação, promoção manual de slave).
- L4 vs L7 no load balancer tratados como detalhe, quando definem o que você consegue rotear.

A régua deste pack: **toda escolha de componente carrega um trade-off nomeado e uma estimativa de
ordem de grandeza.** Se você não consegue dizer o que perdeu ao ganhar, não escolheu — chutou.
Fonte canônica das tabelas abaixo: *System Design Primer* (donnemartin).

## Os princípios / o conhecimento

### 1. Números de latência que ancoram qualquer estimativa

Decoreba útil: as ordens de grandeza relativas. Memória é ~100 ns, datacenter round-trip é ~500 us
(5000x), disco HDD seek é ~10 ms (20x o datacenter), e ir da Califórnia à Holanda e voltar é ~150 ms.

| Operação | Latência | Relativo |
|---|---|---|
| L1 cache reference | 0.5 ns | — |
| Branch mispredict | 5 ns | 10x L1 |
| L2 cache reference | 7 ns | 14x L1 |
| Mutex lock/unlock | 25 ns | — |
| Main memory reference (RAM) | 100 ns | 20x L2, 200x L1 |
| Compress 1 KB com Zippy/Snappy | 10.000 ns (10 us) | — |
| Send 1 KB sobre rede 1 Gbps | 10.000 ns (10 us) | — |
| Read 4 KB random de SSD | 150.000 ns (150 us) | ~1 GB/s |
| Read 1 MB sequencial da memória | 250.000 ns (250 us) | — |
| Round trip dentro do mesmo datacenter | 500.000 ns (500 us) | — |
| Read 1 MB sequencial de SSD | 1.000.000 ns (1 ms) | ~1 GB/s, 4x memória |
| HDD seek | 10.000.000 ns (10 ms) | 20x datacenter |
| Read 1 MB sequencial sobre rede 1 Gbps | 10.000.000 ns (10 ms) | 40x memória |
| Read 1 MB sequencial de HDD | 30.000.000 ns (30 ms) | 120x memória, 3x SSD |
| Round trip CA → Holanda → CA | 150.000.000 ns (150 ms) | — |

**Métricas de mão (throughput sequencial):**

| Mídia | Leitura sequencial |
|---|---|
| HDD | ~30 MB/s |
| Ethernet 1 Gbps | ~100 MB/s |
| SSD | ~1 GB/s |
| Memória principal | ~4 GB/s |

Conclusões operacionais: SSD é ~4x a banda do disco e ~6x a banda da rede 1 Gbps; round trips
mundiais cabem ~6–7 por segundo, contra ~2.000 round trips/s dentro do datacenter. **Evite o disco
quando puder; mantenha dados quentes na memória; minimize round trips cross-region.**

### 2. Estimativa back-of-the-envelope (potências de 2 e RPS)

| Potência | Valor exato | Aprox. | Bytes |
|---|---|---|---|
| 2^10 | 1.024 | mil | 1 KB |
| 2^20 | 1.048.576 | milhão | 1 MB |
| 2^30 | 1.073.741.824 | bilhão | 1 GB |
| 2^40 | 1.099.511.627.776 | trilhão | 1 TB |

**Regra de RPS:** divida as requisições por dia por **86.400 segundos**. Ex.: 2,5 milhões de
requisições/dia ÷ 86.400 ≈ **~29 req/s**. Sempre traduza "X por dia" em "Y por segundo" antes de
dimensionar load balancer, fila ou banco.

### 3. Modelos de consistência (a escolha que muda a UX)

| Modelo | Garantia após write | Replicação | Use quando | Exemplos reais |
|---|---|---|---|---|
| **Fraca (weak)** | reads podem ou não ver o write; best-effort | — | latência > exatidão; perda tolerável | memcached; VoIP, vídeo-chat, jogos multiplayer em tempo real |
| **Eventual** | reads *eventualmente* veem (tipicamente ms); async | assíncrona | alta disponibilidade vale mais que ler-na-hora | DNS, e-mail |
| **Forte (strong)** | reads sempre veem o write | síncrona | transações, dinheiro, integridade | sistemas de arquivo, RDBMS |

Trade-off central: **consistência forte custa latência e disponibilidade** (write síncrono, espera
confirmação). Eventual ganha disponibilidade e throughput, mas exige UX que tolere janela de
desatualização. *Read-after-write* (ler o próprio write) é a expectativa que mais quebra com
eventual — trate explicitamente quando a tela mostra o que o usuário acabou de gravar.

### 4. Disponibilidade em números (os "noves") e como compõem

| Disponibilidade | Downtime/ano | Downtime/dia |
|---|---|---|
| 99,9% (três 9s) | 8h 45min 57s | 1min 26,4s |
| 99,99% (quatro 9s) | 52min 35,7s | 8,6s |

**Composição** — dois componentes mudam o número total conforme a topologia:

| Topologia | Fórmula | Efeito |
|---|---|---|
| Em **sequência** | `A_total = A_foo * A_bar` | **derruba** (90% * 90% = 81%) |
| Em **paralelo** | `A_total = 1 - (1 - A_foo) * (1 - A_bar)` | **eleva** (90% ∥ 90% = 99%) |

Lição: caminho crítico com muitos componentes em série degrada a disponibilidade; redundância em
paralelo a recupera. Conte os componentes em série antes de prometer SLA.

### 5. Padrões de disponibilidade: fail-over e replicação

| Padrão | Como funciona | Custo / risco |
|---|---|---|
| **Active-passive** | heartbeat entre ativo e standby; se o heartbeat cai, o passivo assume o IP | capacidade ociosa; janela até o standby assumir |
| **Active-active** | ambos servem tráfego, dividindo carga | exige lidar com estado/sessão em ambos |
| **Master-slave** | master serve read+write, replica para slaves (read) | promover slave a master exige lógica extra; risco de perda se o master cai antes de replicar |
| **Master-master** | ambos servem read+write | precisa de LB ou lógica na app; quase sempre *loosely consistent* (viola ACID) ou aumenta latência de write; conflitos de escrita |

Custo geral de replicação: **writes são replayados nas réplicas de leitura**. Muitos writes
afogam as réplicas; quanto mais read slaves, maior o **replication lag**.

### 6. Escalar o banco: federation, sharding, denormalização, SQL tuning

| Técnica | O que faz | Ganho | Custo |
|---|---|---|---|
| **Federation** | divide o banco por **função** (ex.: users / products / forums) | menos replication lag, menos tráfego por banco, mais cache locality | inútil se o schema exige funções/tabelas enormes que cruzam domínios; joins cross-DB na app |
| **Sharding** | distribui dados entre bancos por chave (ex.: por usuário) | menos read/write por shard, menos replicação, mais cache hits | distribuição pode ficar desbalanceada (hot shard); rebalancear adiciona complexidade; joins cross-shard difíceis |
| **Denormalização** | duplica dados para evitar joins caros | melhora **leitura** | piora a **escrita**; sob carga pesada de write pode ficar pior que normalizado; manter cópias em sincronia |
| **SQL tuning** | índices nas colunas consultadas; `CHAR` p/ tamanho fixo, `VARCHAR` p/ variável; particionar tabelas | leitura rápida sem trocar de paradigma | índice custa escrita e storage; é o primeiro passo, não o último |

Ordem de ataque recomendada: **otimize o que tem (SQL tuning) → federation → sharding → denormalização**.
Não pule direto pro sharding: ele é o mais caro de operar.

### 7. Cache: onde cachear e a estratégia de escrita

**Camadas de cache** (do cliente ao banco): client caching (browser/SO) · CDN · web server (reverse
proxy) · database caching · application caching (Memcached/Redis em memória).

A parte difícil não é cachear — é **escrever e invalidar**. As quatro estratégias:

| Estratégia | Como funciona | Vantagem | Custo / risco |
|---|---|---|---|
| **Cache-aside** (lazy loading) | app lê cache; no miss, lê o banco e popula o cache | só cacheia o que é pedido; resiliente a falha do cache | cada miss = **3 trips** (cache → banco → cache), latência no miss; dados podem ficar stale após write no banco |
| **Write-through** | app escreve no cache, que escreve **síncrono** no banco | leitura sempre consistente com o write | latência de write maior; nó novo não tem entradas até serem atualizadas (cache frio) |
| **Write-behind** (write-back) | app escreve no cache; cache escreve no banco **async** | write rápido, absorve picos | **perda de dados** se o cache cai antes de persistir; complexidade |
| **Refresh-ahead** | cache recarrega proativamente entradas quentes antes de expirar | esconde latência em dados previsíveis | previsão errada = recarga desperdiçada, pior que não fazer |

Regra: **cache-aside** é o default seguro para a maioria; **write-through** quando read-after-write
importa; **write-behind** quando o write é o gargalo e você tolera perda; **refresh-ahead** só com
padrão de acesso previsível.

### 8. CDN: push vs pull

| Tipo | Como funciona | Bom para | Custo |
|---|---|---|---|
| **Pull CDN** | busca o conteúdo da sua origem no **primeiro request** do usuário, depois cacheia | sites de **tráfego alto** (cache se mantém quente) | primeiro request é mais lento; conteúdo pode ficar stale antes do TTL |
| **Push CDN** | **você** sobe o conteúdo direto pro CDN e gerencia o que existe lá | sites de **tráfego baixo** ou conteúdo que muda raramente | você assume a responsabilidade de subir/expirar; storage de conteúdo pouco acessado |

### 9. Load balancer e reverse proxy

**L4 vs L7:**

| Camada | Inspeciona | Custo | Quando |
|---|---|---|---|
| **Layer 4** (transporte) | IP origem/destino e portas | menos tempo e recursos | roteamento simples, throughput máximo, sem precisar olhar o conteúdo |
| **Layer 7** (aplicação) | headers, mensagem, cookies | mais caro que L4 | roteamento por conteúdo (ex.: `/video` → cluster de vídeo, `/api` → cluster de API) |

**Benefícios do load balancer:** SSL termination (descarrega cripto dos backends) · session
persistence via cookie (mesma instância) · evita mandar request para servidor não-saudável · remove
ponto único de falha (em par active-active/active-passive) · evita sobrecarga de recursos.

**Reverse proxy vs load balancer:** o LB roteia para um **conjunto de servidores com a mesma função**;
o reverse proxy **centraliza serviços internos atrás de uma interface pública unificada** e faz sentido
**mesmo com um único web server**. Ambos oferecem SSL termination, compressão, cache e serviço de
estático. Use LB quando o motivo é distribuir carga; reverse proxy quando é unificar/proteger.

### 10. Assincronismo: tirar trabalho do caminho crítico

| Componente | Faz | Quando |
|---|---|---|
| **Message queue** | recebe, segura e entrega mensagens; app publica job, worker processa async | desacoplar produtor de consumidor; absorver pico |
| **Task queue** | recebe tarefas, executa e entrega resultado; suporta agendamento | trabalho pesado/agendado fora do request |
| **Back pressure** | limita o tamanho da fila; retorna **HTTP 503** quando cheia | proteger memória e dar feedback de degradação ao cliente |

Princípio: se a operação não precisa do resultado **na resposta**, tire-a do caminho síncrono. Fila
custa complexidade operacional e consistência eventual — só vale quando o pico/latência justifica.

## Checklist — o método (requisitos → estimativa → desenho → deep dive → gargalos)

Antes de fechar um desenho, percorra as quatro etapas. Pular a 1 ou a 2 é o erro mais comum.

- [ ] **1. Requisitos e escopo.** Levantei os casos de uso e as restrições? Perguntei quem usa, qual
      volume, o que é leitura vs escrita, qual a tolerância a inconsistência?
- [ ] **2. Estimativa back-of-the-envelope.** Traduzi requisições/dia em **req/s** (÷ 86.400)? Estimei
      storage (potências de 2), banda e QPS de leitura vs escrita? Os números cabem nas ordens de
      grandeza de latência (memória vs disco vs rede)?
- [ ] **3. Desenho high-level.** Esbocei os componentes principais e as conexões, e **justifiquei**
      cada um? Defini o modelo de consistência de cada caminho (forte vs eventual)?
- [ ] **4. Deep dive nos componentes core.** Para cada parte crítica detalhei: estratégia de cache
      (aside/through/behind/refresh-ahead) **e** invalidação? Estratégia de banco (tuning → federation
      → sharding → denorm)? Replicação (master-slave vs master-master) com o custo nomeado?
- [ ] **5. Gargalos.** Identifiquei e tratei o gargalo dado as restrições? Preciso de LB (L4 ou L7),
      escala horizontal, cache, sharding? Calculei a disponibilidade composta (série derruba, paralelo
      eleva)?
- [ ] Cada componente carrega um **trade-off nomeado** — eu sei o que perdi ao ganhar?

## Tabela de decisão "use X quando Y"

| Use X | Quando Y |
|---|---|
| **Consistência forte** | dinheiro, transações, read-after-write obrigatório; tolera latência de write síncrono |
| **Consistência eventual** | alta disponibilidade e throughput valem mais que ler-na-hora; UX tolera janela de ms |
| **Consistência fraca** | tempo real (voz/vídeo/jogo); perder um update é melhor que esperar |
| **Master-slave** | leitura domina; escrita centralizada num nó é aceitável; tolera promoção manual no fail |
| **Master-master** | precisa de write em mais de um nó/região; aceita loose consistency ou conflito a resolver |
| **Federation** | o banco divide por função natural (users/products/...); funções não cruzam domínios |
| **Sharding** | uma tabela/dataset não cabe nem escala num nó; existe chave de partição balanceável |
| **Denormalização** | leitura é o gargalo e os joins são caros; carga de write é moderada |
| **SQL tuning primeiro** | sempre — antes de federar/shardar; é o passo mais barato |
| **Cache-aside** | default; tolera staleness pós-write e o custo do miss (3 trips) |
| **Write-through** | leitura precisa refletir o write na hora; aceita write mais lento |
| **Write-behind** | write é o gargalo, picos altos; tolera risco de perda se o cache cair |
| **Refresh-ahead** | padrão de acesso previsível em dados quentes; vale o custo de recarga proativa |
| **Pull CDN** | tráfego alto; conteúdo dinâmico/atualizado; quer simplicidade de operação |
| **Push CDN** | tráfego baixo ou conteúdo raramente atualizado; quer controle do que está no edge |
| **Load balancer L4** | roteamento simples, throughput máximo, sem decisão por conteúdo |
| **Load balancer L7** | roteamento por path/header/cookie; precisa inspecionar a aplicação |
| **Reverse proxy** | unificar/proteger serviços internos; faz sentido mesmo com 1 web server |
| **Active-passive** | redundância onde standby ocioso é aceitável; fail-over com janela tolerável |
| **Active-active** | precisa usar toda a capacidade; resolveu estado/sessão compartilhado |
| **Message/Task queue + back pressure** | trabalho fora do caminho síncrono; absorver pico; degradar com 503 em vez de cair |
