Quando começamos a desenvolver uma aplicação, muitas vezes a arquitetura parece simples:

Usuário → Aplicação → Banco de Dados

E, para muitos sistemas, isso é suficiente.

Mas imagine que essa aplicação cresce.

Primeiro chegam 100 usuários.

Depois 10 mil.

Depois 1 milhão.

O banco começa a receber milhares de consultas por segundo. Algumas operações ficam lentas. Um servidor já não consegue processar todo o tráfego. Um serviço externo fica indisponível e começa a derrubar requisições.

Agora surgem novas perguntas:

  • Precisamos de mais servidores?
  • Como distribuir as requisições entre eles?
  • Devemos adicionar cache?
  • Precisamos processar algumas operações de forma assíncrona?
  • O que acontece se o banco ficar indisponível?
  • Podemos perder uma mensagem?
  • Quanto tempo uma resposta pode levar?
  • Precisamos armazenar todos esses dados?
  • O sistema precisa continuar funcionando durante uma falha?

Responder perguntas como essas faz parte de System Design.

O que é System Design?

System Design é o processo de definir como os diferentes componentes de um sistema de software trabalham juntos para atender determinados requisitos.

Isso inclui decisões sobre:

  • Aplicações e serviços;
  • APIs;
  • Bancos de dados;
  • Cache;
  • Filas e eventos;
  • Armazenamento;
  • Balanceamento de carga (Load Balancer);
  • Comunicação entre serviços;
  • Escalabilidade;
  • Disponibilidade;
  • Consistência;
  • Segurança;
  • Observabilidade.

Mas System Design não significa simplesmente adicionar todas essas tecnologias a uma arquitetura.

Na verdade, uma das habilidades mais importantes em System Design é saber quando não adicionar complexidade.

Não comece pela tecnologia

Um erro comum ao aprender System Design é pensar primeiro nas ferramentas:

"Vou usar Kubernetes, Kafka, Redis, PostgreSQL e Elasticsearch."

Mas ainda não sabemos qual problema estamos tentando resolver.

Imagine que precisamos construir um sistema que recebe pedidos de uma pequena loja online.

Talvez isso seja suficiente:

            ┌─────────────┐
Usuário ──→ │     API     │
            └──────┬──────┘
                   │
                   ▼
            ┌─────────────┐
            │ PostgreSQL  │
            └─────────────┘

Não existe motivo para adicionar Kafka, Redis ou dezenas de microservices apenas porque essas tecnologias aparecem em arquiteturas de grandes empresas.

Agora imagine que os requisitos mudam.

A plataforma passa a processar milhões de pedidos e, depois que um pedido é criado, diferentes operações precisam acontecer:

Criar pedido
     │
     ├── Reservar estoque
     ├── Processar pagamento
     ├── Enviar e-mail
     └── Atualizar analytics

Talvez algumas dessas operações não precisem acontecer durante a requisição do usuário.

Nesse cenário, processamento assíncrono pode começar a fazer sentido:

                    ┌──────────────┐
Usuário ──→ API ──→ │   Pedidos    │
                    └──────┬───────┘
                           │
                           ▼
                     ┌──────────┐
                     │  Eventos │
                     └────┬─────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
          Estoque     Pagamento   Notificação

Perceba a diferença.

Não começamos dizendo:

"Precisamos de Kafka."

Começamos dizendo:

"Temos operações que podem acontecer de forma assíncrona, queremos desacoplar os consumidores e precisamos absorver picos de processamento."

Agora existe um problema que pode justificar o uso de uma fila ou plataforma de eventos.

Esse raciocínio é a essência de System Design.

Requisitos funcionais e não funcionais

Antes de desenhar uma arquitetura, precisamos entender o que estamos construindo.

Normalmente podemos dividir os requisitos em dois grupos.

Requisitos funcionais

Descrevem o que o sistema deve fazer.

Em um encurtador de URLs, por exemplo:

  • Criar uma URL curta;
  • Redirecionar uma URL curta para a URL original;
  • Permitir que um link expire.

Em um aplicativo de mensagens:

  • Enviar mensagens;
  • Receber mensagens;
  • Mostrar conversas;
  • Indicar mensagens lidas.

Requisitos não funcionais

Descrevem como o sistema deve se comportar.

Por exemplo:

  • Suportar 100 mil requisições por segundo;
  • Responder em menos de 200 ms;
  • Permanecer disponível mesmo se um servidor falhar;
  • Não perder mensagens;
  • Permitir consistência eventual em determinadas operações.

Esses requisitos frequentemente têm impacto muito maior na arquitetura do que parece.

Considere dois sistemas que fazem exatamente a mesma coisa:

Sistema A
10.000 usuários
100 req/s

Sistema B
100 milhões de usuários
500.000 req/s

Os requisitos funcionais podem ser idênticos.

A arquitetura provavelmente não será.

Escala muda a arquitetura

Vamos voltar ao nosso sistema simples:

Infográfico em três etapas: uma aplicação conectada ao banco de dados; várias APIs com balanceador de carga e cache; e a inclusão de uma fila de eventos para estoque, pagamento e notificação.
A arquitetura evolui quando escala e novas necessidades justificam componentes adicionais.Ampliar imagem
Usuário → API → Banco

Ele pode funcionar perfeitamente durante muito tempo.

Até que a aplicação cresça.

Problema 1: um servidor não suporta mais o tráfego

Podemos executar várias instâncias da aplicação e distribuir as requisições entre elas:

                     ┌──→ API 1
Usuário → Load Balancer ─→ API 2
                     └──→ API 3

Agora conseguimos aumentar a capacidade horizontalmente.

Mas surge outra pergunta:

O banco consegue acompanhar esse crescimento?

Problema 2: muitas leituras no banco

Imagine que milhares de usuários consultem repetidamente os mesmos dados.

Talvez possamos armazenar temporariamente os dados mais acessados:

Usuário
   │
   ▼
  API
   │
   ├────→ Cache
   │        │
   │        └── cache hit → resposta
   │
   └────→ Banco
          cache miss

Isso pode diminuir a latência e reduzir a carga no banco.

Mas acabamos de criar outro problema:

Como manter o Cache atualizado?

Esse é um exemplo clássico de trade-off.

Toda decisão tem um custo

Não existe arquitetura perfeita.

Existem arquiteturas adequadas para determinados requisitos.

Adicionar cache pode reduzir latência e carga no banco, mas introduz problemas de invalidação e dados desatualizados.

Adicionar réplicas pode aumentar disponibilidade e capacidade de leitura, mas pode introduzir atraso de replicação.

Processamento assíncrono pode absorver picos e desacoplar serviços, mas introduz questões como retries, mensagens duplicadas e consistência eventual.

Particionar dados pode permitir que o sistema cresça além da capacidade de uma única máquina, mas aumenta significativamente a complexidade de consultas e operações.

Por isso, uma pergunta muito importante em System Design é:

O que estamos ganhando e o que estamos sacrificando com essa decisão?

Esse é o conceito de trade-off.

Alguns trade-offs aparecem o tempo todo

Durante o estudo de System Design você encontrará várias decisões recorrentes:

Consistência       ↔ Disponibilidade

Latência           ↔ Consistência

Performance        ↔ Complexidade

Custo              ↔ Redundância

Síncrono           ↔ Assíncrono

SQL                ↔ NoSQL

Escala vertical    ↔ Escala horizontal

Nenhum lado é automaticamente melhor.

A escolha depende dos requisitos.

Um sistema financeiro pode exigir consistência forte para determinadas operações.

Um feed de rede social talvez aceite que uma publicação demore alguns segundos para aparecer para todos os usuários.

São problemas diferentes.

Logo, podem exigir decisões diferentes.

Os principais blocos de um sistema

Apesar de existirem milhares de tecnologias, grande parte das arquiteturas modernas é construída combinando alguns blocos fundamentais:

ComponenteResponsabilidade
ClientAplicação web, mobile ou outro serviço
APIInterface de comunicação com o sistema
Load BalancerDistribuir tráfego entre servidores
Application ServerExecutar regras de negócio
DatabasePersistir dados
CacheAcelerar acessos frequentes
Message Queue / Event StreamProcessar trabalho de forma assíncrona
Object StorageArmazenar arquivos e grandes objetos
CDNDistribuir conteúdo próximo dos usuários
ObservabilityLogs, métricas e traces

Aprender System Design envolve entender cada um desses componentes.

Mas existe uma pergunta ainda mais importante:

qual problema cada componente resolve?

Por exemplo:

"Redis deixa o sistema escalável."

não explica muita coisa.

Uma justificativa melhor seria:

Temos um endpoint com grande volume de leitura
e dados que mudam poucas vezes.

Adicionar cache pode reduzir a quantidade de consultas
ao banco e diminuir a latência desse endpoint.

Agora existe uma relação clara:

Requisito → Problema → Decisão → Trade-off

Esse modelo mental é extremamente útil para estudar System Design.

Como abordar um problema de System Design

Não existe um único processo obrigatório, mas uma sequência simples ajuda bastante.

1. Entenda o problema

Antes de desenhar qualquer arquitetura, faça perguntas.

Por exemplo, se pedirem:

"Projete um encurtador de URLs."

Ainda existem muitas coisas que precisamos saber.

  • Quantos usuários teremos?

  • Quantas URLs serão criadas por dia?

  • Quantos redirecionamentos acontecerão?

  • Links podem expirar?

  • Precisamos de analytics?

  • Qual latência esperamos no redirecionamento?

2. Faça estimativas

Não precisamos calcular tudo com precisão absoluta.

Queremos entender a ordem de grandeza.

Por exemplo:

100 milhões de redirecionamentos por dia

Dividindo aproximadamente pelo número de segundos de um dia:

100.000.000 / 86.400 ≈ 1.157 req/s

Mas tráfego raramente é uniforme.

Se considerarmos um pico de 5x:

≈ 5.800 req/s

Isso já nos ajuda a pensar sobre capacidade, cache, banco de dados e escalabilidade.

O objetivo não é acertar exatamente quantas requisições acontecerão às 14:37 de uma terça-feira.

É entender a dimensão do problema.

3. Desenhe primeiro a solução mais simples

Comece com:

Client
  │
  ▼
 API
  │
  ▼
Database

Depois pergunte:

Onde essa arquitetura quebra considerando nossos requisitos?

Talvez o banco receba leituras demais.

Adicionamos cache.

Talvez uma instância da aplicação não seja suficiente.

Adicionamos múltiplas instâncias e um load balancer.

Talvez determinado processamento seja lento e não precise bloquear a requisição.

Adicionamos processamento assíncrono.

A arquitetura cresce porque os requisitos exigem — não porque queremos deixar o diagrama mais sofisticado.

4. Identifique gargalos e falhas

Depois de desenhar o fluxo principal, comece a quebrar mentalmente o sistema.

Pergunte:

  • E se uma instância cair?
  • E se o banco ficar indisponível?
  • E se o cache estiver vazio?
  • E se recebermos 10x mais tráfego?
  • E se uma mensagem for processada duas vezes?
  • E se um serviço externo levar 30 segundos para responder?
  • E se um consumidor parar de processar eventos?

Sistemas reais falham.

Um bom design considera como o sistema se comporta quando isso acontece.

5. Discuta os trade-offs

Finalmente, explique suas decisões.

Em vez de:

"Vou usar PostgreSQL."

Explique:

"Os dados possuem relacionamentos claros e algumas operações exigem transações. Por isso, começaria com um banco relacional como PostgreSQL. Se o volume crescer além da capacidade de uma única instância, podemos avaliar replicação, particionamento ou outras estratégias."

A tecnologia aparece depois do raciocínio.

System Design não é apenas para sistemas gigantes

Existe uma ideia equivocada de que System Design só importa quando estamos construindo algo do tamanho de Netflix, Uber ou WhatsApp.

Não é verdade.

Mesmo sistemas relativamente pequenos precisam responder perguntas arquiteturais.

Por exemplo:

  • Precisamos realmente de microservices?

  • Vale adicionar Redis?

  • Essa operação deveria ser síncrona?

  • Como evitamos processar essa mensagem duas vezes?

  • O que acontece quando esse serviço externo falhar?

  • Precisamos de consistência forte aqui?

  • Como vamos descobrir por que uma requisição ficou lenta?

Tudo isso é System Design.

Na verdade, uma boa decisão arquitetural muitas vezes é não construir uma arquitetura distribuída quando ela ainda não é necessária.

Um monólito com PostgreSQL pode ser uma excelente arquitetura.

A pergunta não é:

"Essa arquitetura parece sofisticada?"

A pergunta é:

"Essa arquitetura atende aos requisitos com uma complexidade razoável?"

System Design em entrevistas

System Design também aparece com frequência em processos seletivos para engenheiros mais experientes.

Uma pergunta típica poderia ser:

"Desenhe um Encurtador de URLs"

ou:

"Desenhe o WhatsApp."

ou:

"Desenhe um Sistema de Notificação."

O objetivo normalmente não é descobrir se você memorizou a arquitetura usada por essas empresas.

O entrevistador quer observar como você pensa.

Você consegue:

  • Descobrir requisitos?
  • Fazer estimativas?
  • Identificar gargalos?
  • Decompor o problema?
  • Justificar suas escolhas?
  • Reconhecer riscos?
  • Discutir alternativas?
  • Explicar trade-offs?
  • Adaptar a arquitetura quando os requisitos mudam?

Por isso, duas pessoas podem propor arquiteturas diferentes e ambas apresentarem boas soluções.

O importante é conseguir justificar as decisões.

System Design na vida real

Existe uma diferença importante entre entrevistas e sistemas reais.

Em entrevistas, normalmente começamos com uma folha em branco.

Na vida real, quase nunca.

Você encontrará:

Código legado
      +
Banco existente
      +
Sistemas externos
      +
Restrições de orçamento
      +
Times diferentes
      +
Débito técnico
      +
Requisitos de segurança
      +
Prazos

Por isso, System Design não termina quando desenhamos um diagrama.

Sistemas evoluem.

Uma arquitetura pode funcionar perfeitamente para 10 mil usuários e precisar mudar quando chegar a 1 milhão.

O processo é iterativo:

Projetar
   ↓
Construir
   ↓
Medir
   ↓
Encontrar gargalos
   ↓
Evoluir
   ↓
Medir novamente

Esse ciclo faz parte da engenharia de software.

Então, o que precisamos aprender?

System Design pode parecer enorme quando encontramos termos como:

Load Balancer
Cache
Replicas(Replication)
Fragmentação (Sharding)
Particionamento (Partitioning)
CDN
Filas (Queues)
Streaming de eventos (Event Streaming)
Teorema CAP (CAP Theorem)
Hashing consistente (Consistent Hashing)
Rate Limiter
Circuit Breaker
Idempotência (Idempotency)
Consistência Eventual (Eventual Consistency)

Mas você não precisa aprender tudo de uma vez.

Grande parte de System Design consiste em aprender alguns blocos fundamentais e entender:

  1. Qual problema eles resolvem;
  2. Quando devemos utilizá-los;
  3. Quais problemas novos eles introduzem.

É exatamente essa abordagem que seguiremos nesta trilha.

O modelo mental mais importante

Se você lembrar de apenas uma coisa deste artigo, lembre-se desta sequência:

Requisitos
    ↓
Escala
    ↓
Problemas
    ↓
Decisões arquiteturais
    ↓
Trade-offs

Não comece perguntando:

"Qual tecnologia devo usar?"

Comece perguntando:

"Qual problema preciso resolver?"

Depois:

"Quais requisitos realmente importam?"

Depois:

"Onde a solução mais simples deixa de funcionar?"

E somente então:

"Qual componente ou tecnologia pode resolver esse problema?"

System Design, no fim das contas, é isso: transformar requisitos e restrições em decisões técnicas conscientes.

E aprender System Design significa desenvolver a capacidade de explicar não apenas como construir um sistema, mas principalmente por que ele deveria ser construído daquela maneira.