ENGENHARIA DE SOFTWARE · LEITURA DE ~18 MIN
System Design:
as decisões que fazem um sistema crescer
Todo mundo aprende a programar. Pouca gente aprende a fazer um sistema continuar funcionando quando ele dá certo. Este artigo explica, do zero e sem pressa, as decisões que separam um projeto que roda na sua máquina de um sistema pronto para produção — e você vai poder mexer em cada conceito ao longo do texto.
CAPÍTULO 01
Quando o app cresce
Imagine que você criou um app de delivery. O código está limpo, os testes passam, e as primeiras dez pessoas usam sem nenhum problema. Cem usuários? Ainda funciona. Aí o app aparece num vídeo viral — e de repente dez mil pedidos chegam ao mesmo tempo.
O problema pode não estar na regra de negócio. Muitas vezes ele aparece quando limites de recursos e defeitos invisíveis em baixa carga — condições de corrida, vazamentos de memória, consultas N+1, locks, pools mal configurados ou algoritmos caros — começam a se manifestar. Consultas passam a esperar por conexões, locks e capacidade de processamento; novas tentativas podem piorar tudo.
Na demo, CPU e conexões de banco saturam primeiro. Na prática, são gargalos comuns — não universais. Rede, disco, memória, garbage collector, locks, thread pools, APIs externas ou limites do provedor podem ser o primeiro teto. Medir vem antes de escolher a solução.
CAPÍTULO 02
O que é System Design
Programar é resolver problemas dentro de um componente. System Design é decidir como os componentes formam um sistema: onde os dados moram, como os serviços conversam, o que acontece quando algo falha e como você descobre que falhou.
Na prática, toda decisão de design responde a uma destas sete perguntas:
- DADOSOnde cada informação fica guardada — e quem é a fonte da verdade?
- COMUNICAÇÃOOs serviços conversam de forma síncrona (esperando resposta) ou assíncrona (via filas)?
- ESCALAQuando a demanda dobrar, o sistema cresce trocando a máquina ou adicionando máquinas?
- FALHASO que acontece quando — não “se” — uma parte cair?
- SEGURANÇAQuem pode acessar o quê, e como o sistema se protege de abuso?
- OBSERVABILIDADEComo você enxerga o que está acontecendo lá dentro, em produção?
- OPERAÇÃO & EVOLUÇÃOComo implantar, migrar, testar, reverter e mudar o sistema sem interromper usuários?
Nenhuma dessas perguntas tem uma resposta “certa” universal. System Design é a disciplina dos trade-offs: cada escolha compra um benefício pagando um custo, e o bom projetista sabe qual custo aceita pagar naquele momento do produto.
CAPÍTULO 03
O começo simples
Toda arquitetura famosa começou igual: um cliente, um servidor, um banco de dados. O usuário faz uma requisição, o servidor processa, consulta o banco, e a resposta volta pelo mesmo caminho.
Vale entender esse caminho com calma, porque tudo que vem depois — cache, filas, réplicas — é uma variação dele. Aperte o botão e acompanhe a requisição:
Essa arquitetura tem um nome meio injusto: “simples demais”. Para muitos produtos em estágio inicial e com carga moderada, ela é frequentemente a decisão certa. O número de usuários sozinho não decide: importam frequência de uso, custo de cada operação, volume de dados e disponibilidade exigida.
CAPÍTULO 04
Escalabilidade: uma máquina maior ou várias máquinas?
Quando um componente satura, existem dois caminhos clássicos para aumentar sua capacidade. Escala vertical: usar uma máquina mais potente. Escala horizontal: adicionar instâncias e distribuir o tráfego. Antes de ambos, existe uma pergunta ainda mais barata: podemos executar menos trabalho corrigindo consultas, índices, algoritmos, batching ou chamadas redundantes?
Vertical é simples: nada muda no código, você só paga mais. Mas tem teto (a maior máquina do mundo ainda é uma máquina) e mantém um ponto único de falha — se ela cair, tudo cai. Horizontal é elástica e resiliente, mas cobra um preço: seu código precisa aceitar que existe mais de uma instância rodando ao mesmo tempo (sessões, uploads e estado local viram problema).
Na vida real, muitos sistemas usam máquinas suficientemente potentes e múltiplas instâncias atrás de um balanceador. Escala horizontal reduz a dependência de uma instância, mas não elimina pontos únicos no load balancer, banco, cache, autenticação, região ou provedor. Redundância precisa atravessar a arquitetura.
CAPÍTULO 05
Latência & throughput: rápido não é a mesma coisa que aguentar volume
Duas métricas dominam qualquer conversa de performance, e elas medem coisas diferentes. Latência é o tempo que uma requisição leva para ser respondida — a experiência de um usuário. Throughput é quantas requisições o sistema processa por segundo — a capacidade do todo.
Um sistema pode ter latência ótima com pouca gente e desmoronar com volume. O padrão clássico: a latência fica estável enquanto há capacidade sobrando e explode de forma não-linear quando o sistema se aproxima da saturação. Arraste o slider e observe o joelho da curva:
Concorrência é quantas operações estão em andamento; throughput oferecido é o que chega; throughput processado é o que termina. Dez usuários simultâneos não significam dez requisições por segundo. Já o p95 é o valor abaixo do qual 95% das observações ficaram; os 5% restantes tiveram latência igual ou maior. O p99 faz o mesmo corte em 99%.
Quanto mais uma requisição demora, mais requisições ficam abertas ao mesmo tempo. Isso pressiona pools, filas e conexões — e faz a saturação alimentar a si própria.
CAPÍTULO 06
Dados, cache & CDN: o gargalo apenas mudou de lugar
Você escalou horizontalmente, tem cinco servidores atrás do load balancer… e o sistema continua lento. Por quê? Porque todos os cinco batem no mesmo banco de dados. Escalar a camada de aplicação só empurra o gargalo para baixo.
A primeira ferramenta contra isso é o cache: uma camada de memória rápida (Redis, Memcached) que guarda o resultado das consultas mais frequentes. O cardápio do restaurante não muda a cada segundo — então por que consultar o banco 12 mil vezes por minuto para responder a mesma pergunta?
Cache não é mágica. TTL é apenas uma estratégia; também existem invalidação por evento, cache-aside, write-through, write-behind e versionamento. Cada escolha pode introduzir dados inconsistentes, hot keys, stampede, avalanche de expirações e cold start. Cache não corrige uma consulta ruim; apenas evita executá-la em algumas ocasiões.
Antes ou junto do cache, o banco pode ganhar índices, consultas melhores, pooling, batching, materialized views e desnormalização. Quando um nó já não basta, entram decisões fundamentais:
- REPLICAÇÃOCopiar dados para tolerar falhas e escalar leituras; réplicas assíncronas podem servir dados atrasados.
- PARTICIONAMENTODividir dados entre nós para escalar armazenamento e escrita, pagando complexidade em consultas e rebalanceamento.
- CONSISTÊNCIADefinir quando uma escrita deve aparecer: forte, eventual ou garantias como read-your-writes.
- CDN & OBJETOSServir imagens, cardápios e arquivos na borda para que esse trabalho nem chegue à API de origem.
CAPÍTULO 07
Comunicação assíncrona: nem tudo precisa acontecer agora
Quando o usuário toca em “fazer pedido”, o sistema pode registrar a solicitação e devolver “recebido” ou “em processamento”. Pagamento, reserva de estoque e confirmação podem ocorrer de forma assíncrona — desde que a interface não prometa uma conclusão que ainda não aconteceu. “Pedido recebido” é diferente de “pedido confirmado”.
É para isso que usamos mensageria e streaming, como RabbitMQ, SQS ou Kafka, cada um com modelos e garantias diferentes. Kafka é uma plataforma distribuída de event streaming com retenção, replay, partições e grupos de consumidores — não apenas uma fila tradicional.
RESPOSTA AO CLIENTE EM —
O custo é projetar garantias de entrega e processamento: at-most-once, at-least-once e exatamente uma vez apenas em contextos delimitados; além de duplicidade, ordenação, retries, dead-letter queues, poison messages, particionamento e backpressure. Normalmente, não é “a mensagem” que falha: a entrega não é confirmada ou o consumidor não consegue processá-la.
CAPÍTULO 08
Resiliência: falhas vão acontecer — planeje a recuperação
Em escala, falha não é exceção, é rotina: um disco morre, um deploy quebra, um datacenter inteiro fica fora do ar. Sistema resiliente não é o que nunca falha — é o que continua atendendo enquanto falha.
O kit começa por timeout: sem um limite, uma chamada pode ficar presa indefinidamente. Depois vêm retry com backoff exponencial e jitter, circuit breaker, idempotência, isolamento ou bulkhead, degradação graciosa e failover. Retry deve tratar apenas falhas transitórias, com limite de tentativas e orçamento; caso contrário, multiplica o tráfego durante a sobrecarga.
O SERVIÇO ESTÁ INSTÁVEL · FAÇA UMA CHAMADA
A MESMA MENSAGEM CHEGOU DUAS VEZES (RETRY DA FILA)
Idempotência significa que execuções equivalentes não criam efeitos adicionais no estado observável; normalmente exige chave, deduplicação, constraint única e transação. Failover regional também não é instantâneo: depende de replicação, roteamento, capacidade secundária e testes. RPO define quanto dado pode ser perdido; RTO, quanto tempo de indisponibilidade é aceitável.
Durante sobrecarga, resiliência também significa proteger capacidade: rate limits, quotas, backpressure, load shedding e prioridade podem preservar criação e pagamento de pedidos enquanto recomendações e relatórios degradam.
CAPÍTULO 09
Observabilidade: encontre o problema antes do usuário
“O app tá lento” é o pior bug report do mundo — e é o que você vai receber sem instrumentação. Observabilidade é a capacidade de investigar o estado interno a partir dos sinais emitidos. Logs, métricas e traces são fundamentais, mas só ajudam com contexto, correlação, trace IDs, cardinalidade controlada, consultas, retenção, amostragem e alertas acionáveis.
Logs contam o que aconteceu, evento por evento. Métricas mostram o comportamento agregado ao longo do tempo (CPU, latência, taxa de erro). Traces seguem uma única requisição atravessando todos os serviços — e mostram exatamente onde ela gastou tempo. Use o trace abaixo para achar o culpado de uma lentidão:
SINTOMA: CHECKOUT LENTO · CAUSA: ?
O trace mostra que 2,4 segundos estão concentrados no serviço de pagamento. Para afirmar que a causa é um gateway externo, precisaríamos de um span filho dessa chamada; sem ele, ainda pode ser lock, banco, rede, garbage collection ou processamento interno. SLIs medem comportamento como latência e sucesso; SLOs definem objetivos como “99,9% das requisições válidas terão sucesso”.
CAPÍTULO 10
A arquitetura completa — e quando não usá-la
Junte tudo e surge uma arquitetura possível: cliente usando CDN para conteúdo estático, WAF e rate limiting protegendo a entrada, balanceador distribuindo tráfego, APIs sem estado, cache, dados replicados ou particionados e mensageria. Segurança, observabilidade, configuração e deploy não são caixas isoladas: atravessam cada componente.
Agora, a lição mais importante do artigo: esse diagrama não é uma meta, é um repertório. Cada caixa existe para resolver um problema específico que você talvez ainda não tenha. Começar um produto novo com Kafka, cinco microsserviços e três regiões é tão errado quanto atender um milhão de usuários com um servidor só.
System Design bem feito é isso: conhecer as ferramentas, entender os trade-offs, e ter a disciplina de aplicar a solução certa para o tamanho atual do problema — deixando o caminho aberto para evoluir quando o problema crescer.
FIM · OBRIGADO POR LER
Quer mais conteúdo interativo?
Tem também um artigo sobre cinco grandes ideias que influenciam a busca pela AGI, nesse mesmo formato.