O que é system design?
Além de "fazer funcionar"
Qualquer desenvolvedor consegue construir uma aplicação que funciona no próprio laptop. Você digita bin/rails server, abre localhost:3000 e tudo roda. O banco responde em milissegundos. Só existe um usuário: você. Nada quebra.
System design é a disciplina de fazer software funcionar quando o laptop já não basta. Quando mil usuários acessam a app ao mesmo tempo. Quando o banco tem 50 milhões de linhas. Quando um servidor pega fogo às 3 da manhã e os clientes esperam que o site continue no ar. Quando um deploy precisa acontecer sem derrubar nenhuma requisição.
Escrever código melhor não resolve isso. System design trata de decisões de estrutura: como os componentes se organizam, como se comunicam, como falham e como escalam. Enquanto a app é pequena, essas decisões passam despercebidas. Quando ela cresce, nada importa mais do que elas.
A analogia da planta
Pense na construção de uma casa. Um carpinteiro constrói um barracão sem planta nenhuma: pega a madeira, prega, põe um telhado e pronto, funciona. Tente erguer um prédio de 40 andares do mesmo jeito e ele desaba.
Os materiais são os mesmos. O que muda é o planejamento. Um arranha-céu precisa de um arquiteto que decida: Qual a profundidade da fundação? Onde ficam as paredes estruturais? Como a água chega ao 40º andar? O que acontece num terremoto?
System design é arquitetura de software, e o arquiteto é você. Como o banco de dados aguenta 10.000 queries por segundo? Onde entra o cache? O que acontece quando o serviço de pagamento cai? Como o sistema sobrevive à queda de um data center?
O carpinteiro (desenvolvedor) e o arquiteto (system designer) precisam de habilidades diferentes. Este curso ensina você a pensar como o arquiteto.
Requisitos funcionais vs não funcionais
Todo sistema de software tem dois tipos de requisito. Confundir os dois é o primeiro erro de engenheiros juniores em system design.
Requisitos funcionais descrevem o que o sistema faz:
- Usuários podem se cadastrar e fazer login
- Estudantes podem se inscrever em cursos
- O sistema registra a conclusão de lições e concede XP
São as features. Respondem à pergunta "O que um usuário pode fazer?". A maioria dos desenvolvedores passa a carreira inteira aqui, e o Rails facilita: um model, um controller, uma view e pronto.
Requisitos não funcionais descrevem quão bem o sistema faz:
- A página carrega em menos de 200ms (performance)
- O site fica no ar 99,9% do tempo (disponibilidade)
- Dados de usuários nunca se perdem, nem durante falhas de servidor (durabilidade)
- O sistema suporta 10.000 usuários simultâneos (escalabilidade)
- Senhas são armazenadas com bcrypt, nunca em texto puro (segurança)
São qualidades. Respondem a "Quão rápido? Quão confiável? Quão grande?". Ninguém escreve user stories para elas, mas são elas que decidem se o sistema sobrevive em produção.
Requisitos funcionais são negociáveis. Os não funcionais, não. Dá para lançar sem a feature de "esqueci a senha". Não dá para lançar um sistema que perde dados ou cai sob carga.
Numa app Rails, os requisitos funcionais moram em controllers e services. Os não funcionais moram nos indexes do banco, nas estratégias de cache, nos background jobs, na configuração de deploy e nas decisões de infraestrutura. System design trata principalmente desse segundo grupo.
A mentalidade de trade-offs
A maior virada de chave em system design é esta: não existe resposta certa, só trade-offs.
Toda decisão de arquitetura abre mão de uma coisa para ganhar outra. O trabalho de quem projeta o sistema é entender o que você está trocando e decidir se a troca compensa. Solução perfeita não existe.
Três trade-offs governam qualquer sistema.
Consistência vs disponibilidade
Quando acontece uma partição de rede (e vai acontecer), você precisa escolher. O sistema mostra dados que podem estar desatualizados (disponibilidade) ou se recusa a responder até garantir dados atualizados (consistência)?
Um banco escolhe consistência, porque mostrar um saldo errado é pior do que ficar fora do ar por um instante. Um feed de rede social escolhe disponibilidade: mostrar um post de 2 segundos atrás não tem problema, desde que o site continue no ar.
Aplicações Rails costumam começar com consistência forte: PostgreSQL com um único banco primário. À medida que crescem, ganham réplicas de leitura e camadas de cache, e mais cedo ou mais tarde esbarram de frente nesse trade-off.
Latência vs throughput
Latência é o tempo que uma requisição leva para terminar. Throughput é quantas requisições o sistema processa por segundo. Otimizar um costuma prejudicar o outro.
Um worker do Puma que atende uma requisição de cada vez tem latência baixa, porque cada requisição recebe a atenção total da CPU. Pelo mesmo motivo, o throughput é baixo. Mais threads no Puma aumentam o throughput, mas podem aumentar a latência de cada requisição, por causa da troca de contexto e da disputa por conexões do banco.
O config/puma.rb real desta aplicação só define explicitamente o número de threads:
# config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
O arquivo não tem uma linha de workers. O próprio comentário gerado pelo Puma explica o motivo: "You can control the number of workers using ENV['WEB_CONCURRENCY']... The default is already 1." O config/deploy.yml desta aplicação define WEB_CONCURRENCY: 2 e RAILS_MAX_THREADS: 3 como variáveis de ambiente do deploy, e o Puma lê o número de workers de WEB_CONCURRENCY sem precisar de uma linha para isso.
WEB_CONCURRENCY=2 com RAILS_MAX_THREADS=3 dá 6 requisições simultâneas. Suba para 4 workers e 10 threads e você chega a 40, só que agora precisa de 40 conexões com o banco e de mais RAM. Esse é o trade-off.
Simplicidade vs flexibilidade
Um monolito Rails é simples: um codebase, um deploy, um banco. Em compensação, é menos flexível. Não dá para escalar a busca separadamente do checkout.
Microservices são flexíveis, porque cada serviço escala sozinho. Só que são complexos. Você precisa de service discovery, tracing distribuído, tratamento de falhas de rede e um time inteiro de plataforma.
A posição da comunidade Rails é clara: comece com um monolito. Basecamp, Shopify e GitHub começaram como monolitos Rails. Alguns evoluíram, outros continuam assim. O monolito não é gambiarra temporária. É uma arquitetura legítima que muitas empresas bilionárias rodam em produção.
A habilidade do engenheiro sênior está em saber quando os trade-offs do monolito deixam de funcionar, e nem um minuto antes.
A evolução de uma aplicação Rails
Aplicações Rails crescem por um caminho previsível. Conhecer esse caminho mostra quais problemas de system design você vai enfrentar, e em que momento.
Estágio 1: servidor único
Rails, PostgreSQL e os assets ficam na mesma máquina, e o Kamal faz deploy para esse único host. Isso aguenta mais do que muita gente imagina: uma app Rails bem ajustada num servidor razoável atende milhares de requisições por minuto.
Estágio 2: banco de dados separado
O PostgreSQL ganha um servidor próprio, ou vai para um serviço gerenciado como o RDS. O banco e o servidor da app disputam memória e CPU, e separar os dois dá fôlego a cada um. O deploy do Kamal passa a mirar só o servidor da app, e o banco é gerenciado à parte.
Estágio 3: background jobs
Operações lentas saem do ciclo da requisição. Enviar emails, processar uploads e conceder XP passam a rodar em workers do Solid Queue, no mesmo servidor ou num separado. O usuário recebe uma resposta rápida enquanto o trabalho acontece em segundo plano.
Estágio 4: cache
Queries que quase não mudam vão para o cache: fragment caching, Russian Doll caching, Solid Cache. Você troca memória por velocidade e aceita dados levemente desatualizados.
Estágio 5: réplicas de leitura
Um PostgreSQL primário recebe as escritas, e uma ou mais réplicas recebem as leituras. O Rails 6+ tem suporte nativo a múltiplos bancos. A maior parte do tráfego de leitura vai para as réplicas, o que alivia o primário.
Estágio 6: load balancing
Vários servidores de app ficam atrás de um load balancer, e o Kamal Proxy cuida disso nativamente. A app precisa ser stateless, então dados de sessão não podem ficar na memória do servidor. Use sessões no banco ou cookies.
Estágio 7: além do monolito
Extração de serviços, arquitetura orientada a eventos, CQRS. A maioria das aplicações Rails nunca chega aqui. As que chegam atendem milhões de usuários, com uma lógica de domínio complexa que não cabe mais num único bounded context.
Ninguém pula estágios, nem vai direto para o estágio 7 porque a Netflix fez isso. Cada estágio resolve um problema específico criado pelo anterior.
System design é tomada de decisão
No fim das contas, system design trata mais de decisões do que de tecnologia. Cada caixa num diagrama de arquitetura representa uma decisão que alguém tomou:
- "Escolhemos PostgreSQL em vez de MongoDB porque nossos dados são relacionais."
- "Escolhemos Solid Queue em vez de Sidekiq porque não queremos gerenciar Redis."
- "Escolhemos um monolito em vez de microservices porque o time tem quatro pessoas."
A tecnologia muda. PostgreSQL hoje, outra coisa amanhã. A habilidade está no raciocínio por trás de cada decisão: os trade-offs que você pesou, as restrições que encontrou, o crescimento que previu.
É essa habilidade que o curso ensina. Nas próximas seis lições, você vai aprender a estimar requisitos, projetar arquiteturas, escolher onde guardar os dados, planejar a escala, lidar com falhas e explicar decisões de design com clareza.
Resumo
| Conceito | O que guardar |
|---|---|
| System design | Fazer software funcionar além de um único laptop: estrutura, comunicação, falha, escala |
| Requisitos funcionais | O que o sistema faz (features); fáceis de adicionar, negociáveis |
| Requisitos não funcionais | Quão bem ele faz (performance, disponibilidade, durabilidade); difíceis de adicionar depois, inegociáveis |
| Trade-offs | Não existe resposta perfeita; toda decisão ganha algo e perde algo |
| Consistência vs disponibilidade | Bancos escolhem consistência; feeds de redes sociais escolhem disponibilidade |
| Latência vs throughput | Requisições individuais rápidas vs muitas requisições simultâneas; as threads do Puma mostram isso na prática |
| Simplicidade vs flexibilidade | Monolito vs microservices; comece simples e só divida quando for obrigado |
| Estágios de evolução | Servidor único → banco separado → background jobs → cache → réplicas → load balancing → extração |
Continue com a aula 2
Requisitos & restrições
Aborda perguntas de esclarecimento, estimativas de usuários, armazenamento e banda, e a diferença entre SLI, SLO e SLA.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.