Pular para conteúdo

Algorithms vs. System Design

Dois tipos de problema, duas habilidades distintas — mas a mesma lógica de raciocínio estruturado.

Algorithm Questions

Problema fechado

Geralmente têm solução determinística. O foco está na correção do algoritmo, na complexidade O(n) e na implementação limpa. Exemplos: "encontre o caminho mais curto", "verifique se uma árvore é BST".

System Design Questions

Problema aberto

Não existe resposta única. O foco está nos tradeoffs, na escalabilidade, na comunicação e no raciocínio de engenharia. Exemplos: "projete o Twitter", "projete um sistema de encurtamento de URLs para 100M de usuários".

"Tackle the problem by doing it just like you would at work."
Faça perguntas. Engaje o entrevistador. Discuta os tradeoffs. Não existe resposta perfeita — existe raciocínio bem articulado e soluções justificadas.

Mindset essencial

Em system design, o entrevistador quer ver como você pensa, não apenas o que você memorizou. Um candidato que faz boas perguntas, explicita suas assunções e admite incertezas com raciocínio sólido frequentemente supera um candidato que tenta recitar uma arquitetura pronta.

O que o entrevistador avalia

Comunicação clara Raciocínio estruturado Conhecimento técnico Gestão de tradeoffs Estimativas realistas Identificar gargalos
Aspecto Algorithm Interview System Design Interview
Resposta corretaDeterminística, geralmente únicaMúltiplas soluções válidas
FocoCorreção e complexidadeTradeoffs e escalabilidade
Duração típica20–45 min45–60 min
ComunicaçãoImportanteCrítica — é a principal avaliação
Lousa/diagramaÚtilObrigatório

Handling the Questions

8 princípios para conduzir a entrevista de system design com confiança e clareza.

1
Communicate Pense em voz alta. O entrevistador quer acompanhar seu raciocínio, não apenas ver a solução final. Silêncio prolongado é o maior inimigo numa entrevista de system design — mesmo que você ainda não saiba a resposta, verbalize o que está pensando: "estou considerando usar um cache aqui porque..."
2
Go broad first Antes de mergulhar em detalhes, esboce o sistema inteiro em alto nível. Estabeleça os componentes principais antes de aprofundar em qualquer um deles. Detalhar prematuramente um componente que depois precisa ser redesenhado é um erro comum.
3
Use the whiteboard Desenhe! Diagramas ajudam tanto o entrevistador quanto você a manter o raciocínio organizado. Um diagrama de blocos claro comunica mais do que minutos de explicação verbal. Comece simples e refine.
4
Acknowledge interviewer concerns Se o entrevistador fizer uma objeção ou apontar um problema, não ignore nem defenda a posição a todo custo. Diga: "boa observação, deixa eu reconsiderar essa parte". Isso demonstra maturidade técnica e abertura para feedback.
5
Be careful about assumptions Assunções implícitas são armadilhas. Se você assumir que o sistema tem 1.000 usuários quando na verdade são 1 bilhão, toda a sua solução pode estar errada. Pergunte antes de assumir.
6
State your assumptions explicitly Quando precisar assumir algo (o que é inevitável), diga em voz alta: "vou assumir que o sistema precisa suportar 10M de usuários ativos por dia, com pico de 100k req/s". O entrevistador pode corrigir, confirmar ou dar mais contexto.
7
Estimate when necessary Back-of-envelope calculations são esperadas e valorizadas. Saiba fazer estimativas rápidas de throughput, storage e bandwidth. Ex.: "1M de usuários × 100KB/usuário = ~100GB de storage". Não precisa ser exato — a ordem de grandeza é o que importa.
8
Drive Você lidera a sessão. Não espere o entrevistador dizer o que fazer a seguir. Proponha o próximo passo: "vou focar agora no database layer, tudo bem?" ou "identifico dois gargalos principais — deixa eu abordar o mais crítico primeiro". Mostre protagonismo.

Dica prática: Use transições explícitas entre etapas. Ao passar de "componentes principais" para "identificar gargalos", diga: "Agora que temos os componentes principais, vou identificar onde estão os pontos críticos desse sistema." Isso mantém o entrevistador orientado na sua estrutura.


Design: Step-By-Step

Estrutura em 5 passos para projetar sistemas complexos de forma organizada durante a entrevista.

1
Scope the Problem Faça perguntas para delimitar o problema. Quantos usuários? Quais features são essenciais vs. nice-to-have? O sistema é leitura-intensiva ou escrita-intensiva? É necessária consistência forte ou eventual é suficiente? Defina o escopo antes de qualquer arquitetura. Perguntas típicas: "Quais são as principais features que devo projetar?", "Qual a escala esperada em 1 ano?", "Há restrições de latência?"
2
Make Reasonable Assumptions Com o escopo definido, declare assunções explícitas: "vou assumir 10M DAU, pico de 100k req/s, disponibilidade de 99.9% (downtime máximo de ~8.7h/ano), e que o sistema é muito mais read-heavy do que write-heavy (100:1)". Isso ancora toda a conversa seguinte e demonstra que você pensa em métricas concretas.
3
Draw the Major Components Esboce os blocos principais: clients → load balancers → web/app servers → caches → databases → message queues → CDN. Não entre em detalhes ainda — visão macro primeiro. Cada bloco deve ter um propósito claro. Rotule as setas com o tipo de dado que flui entre os componentes.
4
Identify the Key Issues Onde estão os gargalos? Quais são os single points of failure? Quais componentes vão "quebrar" primeiro com o crescimento? O banco de dados é o gargalo de leitura? O servidor de aplicação tem estado que impede escalar horizontalmente? Identifique os pontos críticos antes de propor soluções.
5
Redesign for the Key Issues Agora você aprofunda: adiciona sharding no banco, caching em hot paths, escala horizontal nos app servers, CDN para assets estáticos, filas para processamento assíncrono. Redesenhe iterativamente com foco nos problemas identificados. Justifique cada decisão com os tradeoffs envolvidos.
exemplo — arquitetura de URL shortener escalável
CDN static Client browser/app Load Balancer App Server 1 App Server 2 App Server 3 Cache Redis miss DB Primary MySQL / PG DB Replica read-only repl. Queue Kafka/SQS Analytics workers Analytics DB step 1 escopo steps 2–3 componentes step 4–5 gargalos / redesign async processing desacoplamento
Pergunta-chave de escopo

Sempre pergunte: "Este sistema é read-heavy ou write-heavy?" — isso muda tudo.
Read-heavy (ex: URL shortener, Twitter timeline) → caching agressivo, DB replicas para leitura.
Write-heavy (ex: log ingestion, chat em tempo real) → filas + processamento assíncrono, eventual consistency.


Algorithms that Scale: Step-By-Step

Quando o problema é algorítmico mas precisa operar em escala massiva — dados que não cabem em uma máquina.

Diferente do system design puro, aqui você começa com um algoritmo correto e vai iterando para que ele funcione com bilhões de registros. A chave é reconhecer onde a solução "quebra" ao escalar e abordar especificamente esses pontos.

1
Ask Questions Quantos dados? Qual a distribuição? Quais as constraints de tempo e memória? O dado cabe em RAM? Há duplicatas? Está ordenado? Pode haver aproximações (approximate algorithms) ou é necessária precisão exata? As perguntas certas revelam o verdadeiro problema e as técnicas aplicáveis.
2
Make Believe Assuma que o dado cabe em uma única máquina e resolva o problema lá. Encontre o algoritmo correto ignorando escala por enquanto. Isso dá a solução base que você vai adaptar. Ex.: para top-K palavras em 1TB de logs, começaria com um HashMap + MinHeap de tamanho K.
3
Get Real Agora enfrente a realidade: o dado não cabe em RAM, ou não cabe em disco de uma máquina, ou o processamento em série seria lento demais. Como você divide o problema? Particionamento por hash? Sampling? Streaming? Quais partes são paralelizáveis?
4
Solve Problems Identifique quais partes da solução do passo 2 quebram em escala e corrija especificamente esses pontos. Sharding, MapReduce, approximate counting (HyperLogLog para distinct counts, Count-Min Sketch para frequências), reservoir sampling para dados em stream.
Exemplo completo

Top-K palavras em 1TB de logs

Make believe: HashMap<String, Integer> para contar + MinHeap de tamanho K para os top-K. O(N log K).

Get real: 1TB não cabe em RAM (assume ~10GB de heap).

Solve: Particione o arquivo em N chunks. Cada worker processa um chunk → emite (palavra, count). Redutor central agrega contagens por palavra e mantém MinHeap global de tamanho K.

Alternativa: MapReduce — Map emite (palavra, 1), Reduce soma e cada reducer mantém top-K local, merge final.

Técnicas de escala

Quando usar cada abordagem

MapReduce: dados em batch, paralelizáveis, fits word count, PageRank, join de tabelas grandes.

HyperLogLog: contagem de elementos distintos com ~1% de erro usando O(log log N) memória. Ideal para "quantos usuários únicos visitaram?"

Count-Min Sketch: frequência aproximada de elementos. Ideal para top-K com alta cardinalidade.

Bloom Filter: verificação probabilística de pertencimento. "Este URL já foi visitado?" com zero falsos negativos.


Back-of-the-Envelope

Números de referência para estimativas rápidas — a ordem de grandeza é o que importa, não a precisão.

1 dia
~10⁵ s
86.400 s. Arredonde para 100k e faça as contas de cabeça.
1M req/dia
~12/s
Isso é a média. Pico típico: 2–5× a média — dimensione para o pico.
Read : Write
100:1
Proporção típica de sistemas read-heavy (feeds, URL shortener, catálogos).

Latências que todo engenheiro deveria conhecer

OperaçãoLatência aprox.Implicação
Referência ao cache L1~1 nsCPU é praticamente "grátis" frente a I/O
Acesso à memória principal (RAM)~100 nsPor isso caches em memória (Redis) respondem em sub-ms
Ler 1 MB sequencial da RAM~5–10 μsVarrer dados em memória é barato
Leitura aleatória em SSD (4 KB)~20–100 μs~1.000× mais lenta que RAM
Round trip no mesmo datacenter~0,5 msCada chamada entre microservices custa isso no mínimo
Ler 1 MB sequencial do SSD~0,2–1 msLeitura sequencial é muito mais barata que aleatória
Seek em disco rotacional (HDD)~5–10 msEvite acesso aleatório em HDD no caminho crítico
Round trip São Paulo ↔ Virgínia (us-east-1)~110–130 msEvite chamadas síncronas entre regiões; use CDN/edge

Disponibilidade — os "noves"

SLODowntime / anoDowntime / mês
99%~3,65 dias~7,3 h
99,9%~8,77 h~43,8 min
99,95%~4,38 h~21,9 min
99,99%~52,6 min~4,38 min
99,999%~5,26 min~26,3 s
Potências de 2

Unidades de armazenamento

2¹⁰ ≈ 10³ → KB (mil)
2²⁰ ≈ 10⁶ → MB (milhão)
2³⁰ ≈ 10⁹ → GB (bilhão)
2⁴⁰ ≈ 10¹² → TB (trilhão)
2⁵⁰ ≈ 10¹⁵ → PB

Tamanhos típicos

Para estimar storage

ID numérico / timestamp: 8 B
UUID: 16 B (36 B como texto)
Registro "pequeno" (URL, post curto): ~0,5–1 KB
Foto comprimida: ~200 KB – 2 MB
1 min de vídeo HD: ~50–100 MB

Como apresentar uma estimativa

Arredonde agressivamente, faça a conta em voz alta e, principalmente, diga o que o número muda na arquitetura. "São ~3 TB em 5 anos — cabe em um banco com réplicas, então sharding é uma otimização futura, não um requisito do dia 1." Estimativa que não leva a uma decisão é só aritmética.


Key Concepts

Os conceitos que aparecem em toda entrevista de system design — clique em cada item para expandir a explicação.

Vertical scaling (scale up): adiciona mais CPU, RAM ou disco na mesma máquina. É simples — não requer mudança de código. Mas tem limite físico e hardware (não existe uma máquina com RAM infinita), cria single point of failure e o custo cresce exponencialmente com a capacidade.

Horizontal scaling (scale out): adiciona mais máquinas. Teoricamente ilimitado e mais econômico em escala. Exige design stateless nos servidores de aplicação, load balancing, e coordenação entre nós. A maioria dos sistemas web de grande escala usa horizontal.

Estratégia prática: comece com vertical (mais simples, zero refactoring), mas projete para horizontal desde o início. Quando vertical atingir custo ou limite físico, migre para horizontal. O erro é não planejar isso desde o início.

AspectoVerticalHorizontal
ImplementaçãoSimplesComplexa
LimiteHardware máximoPraticamente ilimitado
CustoCresce exponencialmenteCresce linearmente
Single point of failureSimNão (com redundância)
Downtime na expansãoGeralmente simNão

Distribui requisições entre múltiplos servidores backend, impedindo que qualquer servidor fique sobrecarregado enquanto outros ficam ociosos. Também executa health checks e remove automaticamente servidores com falha do pool.

Algoritmos de distribuição:

  • Round-robin: cada servidor recebe requisições em rotação. Simples, mas ignora carga atual.
  • Weighted round-robin: servidores mais potentes recebem proporção maior de requisições.
  • Least connections: encaminha para o servidor com menos conexões ativas. Melhor para requests de duração variável.
  • IP hash: shard = hash(client_IP) % N. Garante sticky sessions (mesmo cliente → mesmo servidor). Útil quando há estado no servidor.

Tipos de implementação: Hardware (F5, Cisco), software (Nginx, HAProxy, Envoy), cloud-managed (AWS ALB/NLB, GCP Cloud Load Balancing).

Cuidado: o load balancer em si pode se tornar single point of failure. Solução: pares ativo/passivo (primary + standby com failover automático) ou DNS-based load balancing (múltiplos IPs no DNS com TTL curto).

Denormalization: desnormalizar um banco relacional significa duplicar dados estrategicamente para evitar JOINs caros. Você troca consistência (updates precisam ser feitos em múltiplos lugares) por performance de leitura. Funciona bem quando leituras superam escritas em grande proporção e JOINs são um gargalo comprovado.

NoSQL — quando e qual usar:

  • Key-Value (Redis, DynamoDB): acesso O(1) por chave. Ideal para sessions, caching, leaderboards, rate limiting.
  • Document (MongoDB, Couchbase): dados semi-estruturados, schema flexível. Ideal para catálogos de produtos, perfis de usuário.
  • Column-family (Cassandra, HBase): escrita massiva distribuída, queries por partition key. Ideal para séries temporais, IoT, logs.
  • Graph (Neo4j, Amazon Neptune): relacionamentos complexos entre entidades. Ideal para social graphs, sistemas de recomendação, detecção de fraude.

CAP Theorem: em um sistema distribuído, você pode garantir no máximo 2 das 3 propriedades: Consistency (todos os nós veem os mesmos dados), Availability (toda request recebe resposta), Partition tolerance (o sistema funciona mesmo com falhas de rede). Na prática, P é inevitável — você escolhe entre CP (consistência) ou AP (disponibilidade).

Divide o banco de dados em múltiplos nós (shards), onde cada nó armazena um subconjunto dos dados. Permite escalar horizontalmente o armazenamento e a throughput de queries além dos limites de uma única máquina.

Estratégias de sharding:

  • Vertical partitioning: diferentes tabelas em diferentes servidores. Simples, mas limita o crescimento de tabelas individuais.
  • Horizontal (range-based): usuários com id 1–1M no shard A, 1M–2M no B. Bom para range queries, mas pode criar hot spots se a distribuição for desigual.
  • Hash-based: shard = hash(user_id) % N. Distribuição uniforme, evita hot spots. Dificulta range queries. Consistent hashing minimiza o remapeamento de dados ao adicionar ou remover nós.
  • Directory-based: uma lookup table mapeia chave → shard. Flexível (permite mover dados sem rehashing), mas a tabela vira um gargalo e um ponto único de falha.

Problema clássico: queries que cruzam shards (cross-shard joins) são extremamente caras. Projete o sharding key levando em conta os access patterns mais frequentes. Se 90% das queries são por user_id, use user_id como sharding key.

Armazena resultados computados ou dados frequentemente acessados em memória (muito mais rápida que disco) para evitar recomputation ou I/O ao banco. É uma das otimizações de maior impacto em sistemas read-heavy.

Políticas de eviction (quando a memória está cheia):

  • LRU (Least Recently Used): remove o item acessado há mais tempo. É o padrão — funciona bem na maioria dos workloads.
  • LFU (Least Frequently Used): remove o item acessado com menos frequência. Melhor quando a popularidade dos dados é estável ao longo do tempo.
  • TTL (Time-To-Live): cada item tem um prazo de expiração. Essencial para dados com validade temporal (ex: preços, sessões).

Write strategies:

  • Write-through: escreve no cache e no banco simultaneamente. Consistência garantida, mas latência de escrita maior.
  • Write-back (write-behind): escreve no cache imediatamente, propaga ao banco de forma assíncrona. Maior throughput, mas risco de perda de dados se o cache falhar.
  • Write-around: escreve direto no banco, bypassa o cache. O cache só é populado na leitura seguinte. Útil quando os dados escritos raramente são lidos logo após.

Problemas clássicos: Cache stampede (expiração simultânea de muitas chaves causa flood de queries ao banco — use probabilistic expiration ou mutex lock), cold start (cache vazio após deploy — use warm-up scripts), cache poisoning (dados inválidos no cache).

Em vez de processar tudo de forma síncrona dentro da request do usuário, você enfileira o trabalho pesado para execução assíncrona. O usuário recebe uma resposta rápida (confirmação de recebimento), e workers processam em background.

Casos de uso clássicos:

  • Envio de email/SMS após cadastro
  • Geração de thumbnail de imagem após upload
  • Processamento de pagamento com retry automático
  • Indexação de conteúdo para mecanismo de busca
  • Notificações push para dispositivos móveis

Ferramentas e tradeoffs:

  • RabbitMQ: AMQP, garantia de entrega (at-least-once), roteamento complexo por exchange/binding. Ideal para workloads que precisam de garantias de entrega.
  • Apache Kafka: log distribuído, retenção persistente de mensagens, possibilidade de replay, throughput muito alto (milhões de msg/s). Ideal para event streaming, auditoria, múltiplos consumidores independentes.
  • Amazon SQS: managed, zero ops, SQS Standard (at-least-once, sem ordem) ou FIFO (exactly-once, ordenado). Ideal para quem quer simplicidade na nuvem AWS.

O padrão produtor-consumidor desacopla serviços, absorve picos de tráfego (o produtor pode ser muito mais rápido que o consumidor por curtos períodos) e permite retry automático em caso de falha do worker.

Três métricas fundamentais para entender e dimensionar a capacidade de rede de um sistema distribuído:

Bandwidth
cap.
Capacidade máxima teórica do canal em bits/segundo. É o teto — quanto o link pode transportar no máximo.
real
Throughput
Taxa real de transferência bem-sucedida. Sempre ≤ bandwidth. Reduzida por congestionamento, retransmissões e overhead.
RTT
Latency
Tempo para um pacote ir de A a B (one-way) ou A→B→A (Round-Trip Time). Composta por propagation + transmission + processing + queuing delay.

Componentes da latência:

  • Propagation delay: velocidade da luz × distância. São Paulo → Nova York ≈ 100ms mínimo.
  • Transmission delay: tamanho do pacote ÷ bandwidth. Um arquivo de 1GB em link de 100Mbps leva ~80 segundos.
  • Processing delay: tempo que roteadores levam para processar o header e decidir o próximo salto.
  • Queuing delay: tempo esperando em buffers de roteadores congestionados. Variável e imprevisível.

Regra prática: otimizar throughput (batching, compressão) pode aumentar latência. Otimizar latência (requests menores, conexões persistentes) pode reduzir throughput. Entenda qual é mais crítico para o seu sistema antes de otimizar.

Paradigma de programação para processar grandes datasets em paralelo num cluster. O programador define apenas as funções Map e Reduce; o framework cuida de distribuição, paralelismo, fault tolerance e shuffle.

Fluxo das duas fases:

  • Map: cada worker lê um chunk dos dados de entrada e emite pares (key, value). Ex.: para word count, cada linha → emite (palavra, 1) para cada palavra encontrada.
  • Shuffle & Sort: o framework agrupa automaticamente todos os valores emitidos pela mesma key e os envia para o mesmo reducer.
  • Reduce: o reducer recebe (key, [v1, v2, v3, ...]) e produz o resultado final. Ex.: (palavra, [1,1,1]) → (palavra, 3).

O framework cuida de: distribuição de dados entre workers, execução paralela, re-execução automática de tasks que falharam (fault tolerance), shuffle/sort entre Map e Reduce, e monitoramento.

Implementações: Hadoop MapReduce (batch, I/O em disco — robusto mas lento), Apache Spark (in-memory — 10-100x mais rápido para iterative algorithms como ML, streaming via Spark Streaming).

Quando NÃO usar: queries em tempo real com requisito de baixa latência, transações ACID que precisam de consistência forte, sistemas de cache ou lookup simples. MapReduce é para processamento batch de grandes volumes.

# Pseudocódigo — Word Count em MapReduce
def map(document_id, text):
    for word in text.split():
        emit(word, 1)           # emite (key, value)

def reduce(word, counts):       # recebe (key, [v1, v2, ...])
    emit(word, sum(counts))     # agrega e emite resultado final

O problema: com shard = hash(key) % N, mudar N (adicionar um nó ou perder um) remapeia quase todas as chaves. Num cache distribuído isso equivale a esvaziar o cache inteiro de uma vez — e todo o tráfego cai no banco.

A solução: nós e chaves são posicionados num anel de hash (ex.: 0 … 2³²−1). Cada chave pertence ao primeiro nó encontrado no sentido horário. Ao adicionar ou remover um nó, só as chaves do intervalo vizinho mudam de dono — em média K/N chaves.

  • Virtual nodes: cada nó físico ocupa várias posições no anel, o que uniformiza a distribuição e permite dar mais peso a máquinas maiores.
  • Replicação: a chave também é guardada nos próximos R−1 nós do anel (modelo do Dynamo e do Cassandra).
  • Onde aparece: Cassandra, DynamoDB, Riak, clientes de Memcached (ketama), Nginx (hash $request_uri consistent;).

Replicar dados aumenta disponibilidade e capacidade de leitura, mas obriga você a escolher o que o cliente pode ver enquanto as réplicas convergem.

Topologias:

  • Leader-follower (primary-replica): escritas no líder, leituras escaladas nas réplicas. Com replicação assíncrona existe replication lag — uma leitura na réplica pode retornar dado antigo.
  • Multi-leader: escritas aceitas em várias regiões. Exige resolução de conflitos (last-write-wins, CRDTs, merge na aplicação).
  • Leaderless (quorum): N réplicas; a escrita é confirmada por W e a leitura consulta R. Se R + W > N, toda leitura encontra ao menos uma réplica com a última escrita confirmada. Ex.: N=3, W=2, R=2.

Modelos de consistência (do mais forte ao mais fraco): linearizability (strong) → read-your-writes → monotonic reads → eventual consistency.

Truque prático: para garantir read-your-writes sem consistência forte global, leia do líder por alguns segundos depois que o próprio usuário escreveu (ex.: após editar o perfil).

Uma CDN (Content Delivery Network) é uma rede de servidores de borda que guardam cópias do conteúdo perto do usuário. Reduz a latência (o RTT até a borda é muito menor que até a origem) e tira carga da origem.

  • Pull CDN: a borda busca na origem no primeiro acesso (cache miss) e guarda conforme Cache-Control/TTL. Simples, padrão na maioria dos casos.
  • Push CDN: você publica o conteúdo na CDN explicitamente. Útil para arquivos grandes e pouco alterados.
  • Invalidação: prefira versionar o nome do arquivo (app.3f9a1c.js) a invalidar cache — invalidação é lenta e cara.
  • Além de estáticos: TLS termination, proteção DDoS/WAF, edge compute e cache de respostas dinâmicas com TTL curto.

Exemplos: Amazon CloudFront, Cloudflare, Akamai, Fastly.

Limita quantas requisições um cliente (usuário, API key, IP) pode fazer numa janela de tempo. Protege contra abuso e picos acidentais, garante fair use e controla custo. A resposta padrão é HTTP 429 Too Many Requests com header Retry-After.

Algoritmos:

  • Token bucket: tokens são repostos a uma taxa fixa; cada request consome um. Permite bursts até o tamanho do balde. O mais usado em APIs.
  • Leaky bucket: requests entram numa fila que é drenada a taxa constante. Suaviza o tráfego (é o modelo do limit_req do Nginx).
  • Fixed window counter: contador por janela (ex.: por minuto). Simples, mas permite até 2× o limite na virada da janela.
  • Sliding window (log ou counter): mais preciso que a janela fixa, com custo maior de memória/cálculo.

Distribuído: com várias réplicas do gateway, o contador precisa ser compartilhado — tipicamente Redis (INCR + EXPIRE, ou um script Lua para atomicidade). Decida também o comportamento se o Redis cair: fail-open (deixa passar) ou fail-closed (bloqueia).

API Gateway: ponto único de entrada do sistema. Concentra preocupações transversais — roteamento, autenticação (validação de JWT), rate limiting, TLS, CORS, agregação de respostas — para que os serviços internos não precisem repeti-las.

  • Comunicação: síncrona (REST/gRPC) quando o cliente precisa da resposta agora; assíncrona (eventos em fila) quando o trabalho pode acontecer depois.
  • Database per service: cada serviço é dono dos seus dados. Consistência entre serviços via Saga (orquestrada ou coreografada) e Outbox pattern para publicar eventos de forma confiável.
  • Service discovery: DNS do orquestrador (Kubernetes Services) ou registry (Eureka, Consul).

Tradeoff que impressiona em entrevista: saber quando não usar microservices. Time pequeno e domínio ainda instável → comece com um monolito modular e extraia serviços quando houver motivo concreto (escala independente, deploy independente, times distintos).

Em sistemas distribuídos, falhas parciais são a regra. Estes padrões evitam que a falha de uma dependência derrube o sistema inteiro (falha em cascata).

  • Timeout: toda chamada remota precisa de um. Sem timeout, uma dependência lenta esgota as threads de quem a chama.
  • Retry com exponential backoff + jitter: apenas para erros transitórios e operações idempotentes. O jitter evita que todos os clientes tentem de novo ao mesmo tempo (thundering herd).
  • Circuit breaker: closed → open (falha rápido, sem chamar a dependência) → half-open (testa se ela voltou).
  • Bulkhead: pools de recursos isolados por dependência, para que uma não consuma todas as conexões.
  • Idempotency key: o cliente envia um identificador único; o servidor deduplica repetições da mesma operação.
  • Fallback / graceful degradation: resposta degradada (cache, valor padrão) em vez de erro.
  • Load shedding: rejeitar parte do tráfego cedo quando saturado, em vez de degradar todas as requisições.

Projetar o sistema não basta: você precisa saber quando e onde ele está falhando. Mencionar observabilidade no fim da entrevista mostra maturidade de produção.

  • Logs: o que aconteceu, evento a evento. Estruturados (JSON) e com trace id.
  • Métricas: quanto e com que frequência. Método RED para serviços (Rate, Errors, Duration) e USE para recursos (Utilization, Saturation, Errors).
  • Traces: onde o tempo foi gasto numa requisição que atravessa vários serviços.

SLI / SLO / SLA: SLI é a medida (ex.: % de requests com latência < 300 ms); SLO é a meta interna (ex.: 99,9% em 28 dias); SLA é o contrato com o cliente, com penalidade. O error budget (1 − SLO) define quanto risco você pode gastar com deploys e experimentos.

Use percentis, não médias: p50, p95 e p99. A média esconde a cauda — e é na cauda que estão os usuários insatisfeitos.


Exemplo Guiado: URL Shortener

Os 5 passos aplicados ao sistema do diagrama acima — o roteiro de uma resposta de ~45 minutos.

1
Scope the Problem Funcionais: encurtar uma URL longa, redirecionar o código curto para a URL original, alias customizado e expiração (opcionais), contagem de cliques. Fora do escopo: contas de usuário e edição de links. Não-funcionais: redirect com baixa latência (p99 < 100 ms), alta disponibilidade e códigos não triviais de adivinhar.
2
Make Reasonable Assumptions 100M novas URLs/mês → ~40 escritas/s. Read:write de 100:1 → ~4.000 redirects/s em média (pico ~10k/s). Storage em 5 anos: 6B registros × ~500 B ≈ 3 TB. Cache: ~350M redirects/dia; guardar os 20% mais acessados ≈ 35 GB em RAM — cabe em um pequeno cluster Redis.
3
Draw the Major Components API: POST /api/v1/urls {longUrl, alias?, expiresAt?} → 201 {shortUrl} e GET /{code} → 301/302 Location: longUrl. Modelo: urls(code PK, long_url, created_at, expires_at). Componentes: load balancer → app servers stateless → banco.
4
Identify the Key Issues (a) Gerar códigos únicos sem colisão e sem coordenação cara entre servidores. (b) O banco recebe todo o tráfego de leitura. (c) Contar cliques de forma síncrona deixaria o redirect mais lento.
5
Redesign for the Key Issues Cache-aside em Redis para os códigos quentes; réplicas de leitura no banco; sharding por hash do code só se o volume exigir. Cada clique vira um evento numa fila (Kafka/SQS) consumido por workers que gravam no banco de analytics — o redirect não espera por isso.

Como gerar o código curto?

Opção A

Hash + truncar

Hash (MD5/SHA-256) da URL, primeiros 7 caracteres em base62. Determinístico: a mesma URL gera o mesmo código. Contra: colisões precisam ser detectadas e resolvidas (re-hash com salt).

Opção B

Contador + base62

ID único (sequence, Snowflake ou faixas pré-alocadas por servidor) convertido para base62. Sem colisões. Contra: códigos sequenciais são previsíveis e permitem enumerar links.

Opção C

Key Generation Service

Um serviço pré-gera chaves aleatórias e as entrega em lotes aos app servers. Sem colisão no caminho da requisição e imprevisível. Contra: mais um componente para operar e replicar.

# base62: 62^7 ≈ 3,5 trilhões de códigos — sobra para 6B URLs
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def base62(n: int) -> str:
    s = ""
    while n > 0:
        n, r = divmod(n, 62)
        s = ALPHABET[r] + s
    return s or "0"
Tradeoff clássico: 301 ou 302?

301 (Moved Permanently) é cacheado pelo navegador: menos carga no servidor, mas os cliques seguintes não passam por você — a contagem fica incompleta.
302 (Found) passa sempre pelo servidor: analytics preciso, mas mais tráfego. Não existe resposta certa — escolha e justifique com os requisitos do passo 1.


Problemas para Praticar

Perguntas clássicas de entrevista. Tente resolver com os 5 passos antes de abrir os pontos-chave.

  • Escolha do algoritmo (token bucket vs. sliding window) e justificativa.
  • Estado compartilhado entre réplicas do gateway (Redis) e atomicidade (script Lua).
  • Regras por cliente/endpoint, resposta 429 com Retry-After.
  • Fail-open vs. fail-closed se o armazenamento de contadores cair.
  • Fan-out on write (push para timelines pré-computadas) vs. fan-out on read (monta a timeline na leitura).
  • Modelo híbrido: push para usuários comuns, pull para celebridades com milhões de seguidores.
  • Timeline em cache (lista de IDs no Redis), mídia em object storage + CDN.
  • Paginação por cursor, não por offset.
  • Conexões persistentes (WebSocket) e um serviço que sabe em qual servidor cada usuário está conectado.
  • Ordenação por conversa (sequence number por conversation_id) — ordem global não é necessária.
  • Entrega para usuários offline (store-and-forward) e confirmações de entrega/leitura.
  • Particionamento do armazenamento de mensagens por conversa (ex.: Cassandra).
  • Múltiplos canais (push, e-mail, SMS) com filas e workers separados por canal.
  • Retries com backoff e idempotência para não notificar duas vezes.
  • Preferências e opt-out do usuário, rate limit por usuário, templates.
  • Integração com provedores externos (APNs, FCM, SES, Twilio) e o que fazer quando eles falham.
  • URL frontier: fila com prioridade e particionada por domínio.
  • Politeness: respeitar robots.txt e limitar requisições por domínio.
  • Deduplicação: Bloom filter para URLs já vistas, hash de conteúdo para páginas duplicadas.
  • Cache de DNS, armazenamento distribuído das páginas, detecção de spider traps.
  • Atualizações frequentes de localização (a cada poucos segundos) — sistema write-heavy.
  • Índice geoespacial: geohash, quadtree ou H3 para "motoristas próximos".
  • Matching com garantia de que um motorista não seja atribuído a duas corridas (lock/consistência forte nesse ponto).
  • Preço dinâmico, ETA e particionamento por região/cidade.
  • Particionamento com consistent hashing e virtual nodes.
  • Replicação com quorum configurável (N, R, W) e o tradeoff consistência × latência.
  • Detecção de falhas (gossip), hinted handoff e anti-entropy (Merkle trees).
  • Resolução de conflitos: vector clocks ou last-write-wins.

Conexão com a Disciplina

Onde cada conceito de entrevista aparece nas aulas e nos hands-ons de 2026.2.

Os hands-ons constroem, peça por peça, a mesma arquitetura do diagrama: gateway → load balancer → serviços stateless → cache → banco → fila → observabilidade. Use o seu projeto como exemplo concreto na entrevista: "no meu projeto, coloquei Redis compartilhado atrás do gateway porque cada réplica tinha um cache local inconsistente..."


Quiz

Teste seus conhecimentos com questões no estilo de entrevista real.

Questão 1

Checklist de Entrevista

Use para avaliar sua preparação antes de entrar numa entrevista de System Design. Marque os itens conforme pratica — o progresso fica salvo neste navegador.


Referências

Recursos fundamentais para aprofundamento — organizados por tipo e relevância para entrevistas técnicas.

Livros

Recursos Online Gratuitos

GitHub — repositório essencial
System Design Primer
Donnemartin. O repositório mais completo e gratuito sobre system design. Inclui flashcards, exemplos completos, diagramas e guia de estudo estruturado. Mais de 280k stars no GitHub.
github.com/donnemartin/system-design-primer
Blog — casos reais
High Scalability
Blog dedicado a como grandes sistemas (Twitter, Netflix, Amazon, Instagram) foram construídos e escalados. Cada post é um estudo de caso real de arquitetura.
highscalability.com
YouTube — vídeos
ByteByteGo (Alex Xu)
Canal do YouTube com animações e explicações visuais de system design. Excelente para entender visualmente como os componentes se conectam. Complementa o livro System Design Interview.
youtube.com/@ByteByteGo
Curso interativo
Grokking Modern System Design Interview
Educative.io. Curso estruturado com problemas práticos e soluções passo a passo. Cobre design de sistemas como TinyURL, Instagram, Netflix e Uber.
educative.io
Tutorial
GeeksforGeeks — System Design Tutorial
Tutorial completo com artigos individuais para cada conceito: load balancing, caching, sharding, CAP theorem, consistent hashing, SQL vs NoSQL e mais.
geeksforgeeks.org/system-design-tutorial
Prática
LeetCode — System Design Discussions
Fórum do LeetCode com discussões de system design questions reais feitas em entrevistas nas principais empresas de tecnologia (FAANG, startups de alto crescimento).
leetcode.com/discuss/system-design
Livro online — gratuito
Site Reliability Engineering (Google)
Os livros de SRE do Google, disponíveis gratuitamente. Referência para SLOs, error budgets, monitoramento e resposta a incidentes — a parte "operar em produção" do system design.
sre.google/books

Artigos & Papers Técnicos

Ferramentas para Praticar


Coding Interview Prep · System Design & Scalability Material de apoio — uso educacional