Guia de cenários
Cada cenário do System Design Playground é um pequeno laboratório: uma situação realista, um projeto inicial que falha nas metas por um motivo, e uma correção possível. Cada seção abaixo diz o que o cenário ensina, o que tentar, o que observar, as metas e perguntas para discutir em aula. Os simuladores embutidos são compactos (controles de execução, métricas e parâmetros); use Abrir no playground (↗) para editar a estrutura.
Como trabalhar um cenário
- Preveja antes de rodar. Estime a utilização (ρ = λ / μ) e o comportamento das filas com os números do enunciado.
- Rode o projeto inicial e leia o Relatório: que meta falha, e qual é o gargalo?
- Mude uma coisa por vez e rode de novo com a mesma semente — as diferenças vêm da sua mudança, não da sorte.
- Troque a semente no final: um bom projeto passa com qualquer semente.
- Explique os números com as fórmulas do manual.
Exercício
O exercício avaliado playground-design pede que você resolva três cenários e entregue o JSON exportado mais uma justificativa curta (o Relatório tem um botão Copiar relatório (Markdown)).
Todos os cenários
| Cenário | Lição |
|---|---|
| Monólito: encontre o ponto de ruptura | Todo sistema tem um ponto de ruptura, e o primeiro componente a saturar o define |
| Escalar horizontalmente atrás de um balanceador | Escala horizontal precisa de folga para falhas (N+1), e o balanceador só desvia de uma réplica morta depois de detectá-la |
| Catálogo com muitas leituras: adicione um cache | Um cache antes do banco reduz sua carga de leitura para leituras × (1 − taxa de acerto); um cache frio manda tudo de volta ao banco de uma vez (stampede) |
| Tire o conteúdo estático da origem com uma CDN | Conteúdo estático e cacheável fica na borda |
| Réplicas de leitura e atraso de replicação | Réplicas de leitura multiplicam a capacidade de leitura, não a de escrita, e servem dados um pouco antigos (atraso de replicação) |
| Partições e a chave quente | Particionar escala com o número de partições só enquanto as chaves se espalham; uma chave quente concentra a carga numa partição |
| Uma fila absorve o pico | Uma fila desacopla o usuário do trabalho lento |
| Publique um evento, deixe os consumidores reagirem | Com um log de eventos cada grupo de consumidores lê no seu ritmo |
| Falha em cascata: uma dependência lenta | Uma dependência lenta sem timeouts esgota as threads de quem chama (Lei de Little) e derruba requisições que nem a usam |
| Tempestade de retries: quando retentar piora | Retries imediatos multiplicam a carga de um serviço em apuros; quando as respostas passam do timeout, toda tentativa falha e o sistema continua fora mesmo após o gatilho (falha metaestável) |
| Rate limiting protege o backend | Rejeitar o excesso rapidamente (429) protege o backend e os usuários reais; deixá-lo enfileirar degrada todo mundo |
| Sobrevivendo à queda de uma região | Sobreviver à perda de uma região exige uma segunda pilha completa e um jeito de mover os usuários |
| Pico serverless: cold starts e limites | Funções escalam por requisição |
| Venda relâmpago: um lançamento com estoque limitado | Uma venda relâmpago é projetada para o pico |
| Encurtador de URLs (sho.rt/xyz) | Uma busca por chave com muitas leituras |
| Feed de notícias: fan-out na escrita vs na leitura | Fan-out na escrita move trabalho do caminho de leitura (frequente) para o de escrita (raro) |
| Chat de um evento ao vivo | Chat separa guardar mensagens (particionado, muitas escritas) de entregá-las (pub/sub para os servidores conectados) |
| Carona: um índice geográfico com muitas escritas | Atualizações de posição são um fluxo de muitas escritas |
| Streaming de vídeo: CDN + object store + transcodificação | Vídeo é bytes na borda mais trabalho pesado em segundo plano |
| Sistema de notificações | Notificações são assíncronas por natureza |
| Pagamentos: retries precisam de idempotência | Um retry após timeout pode repetir uma operação que já aconteceu |
| Autocompletar da busca | Autocompletar é um problema de latência |
| Assistente de suporte com IA (RAG + ferramentas) | Numa aplicação com LLM, latência e custo crescem com o número de chamadas ao modelo |
| Não se conserta o que não se mede | Observabilidade muda o resultado |
| Microsserviços no Kubernetes: pods vs nós | O HPA adiciona pods, só nós trazem espaço; cada salto síncrono soma à espera do usuário |
| Kubernetes: perdendo um nó | Planeje N+1 também para nós: um nó perdido leva vários pods de uma vez, e eles só voltam se houver espaço |
| Failover do banco: RTO e RPO | Um failover custa o tempo sem primário (RTO) e as escritas ainda não replicadas (RPO) |
Monólito: encontre o ponto de ruptura
Versão 1 da aplicação: um servidor de aplicação e um PostgreSQL. O perfil breakpoint aumenta a carga até os erros passarem de 5% ou o p99 explodir. Onde quebra, e qual componente quebra primeiro? Faça aguentar 2 000 req/s gastando no máximo 2 000 USD/mês.
- Lição: Todo sistema tem um ponto de ruptura, e o primeiro componente a saturar o define. Escalar uma camada leva o gargalo para a próxima.
- Configuração:
monolith· perfilbreakpoint· alvo 600 req/s · 90% leituras · semente 7 · caosoff - Metas: ponto de ruptura ≥ 2.000 req/s · custo/mês < US$ 2.000
- Tente: Aperte ▶ e leia o Relatório: ponto de ruptura e gargalo. Coloque um Load Balancer antes da aplicação e aumente as réplicas; rode de novo.
- Observe: O joelho (p99 dobra) vem bem antes da ruptura. Depois de escalar a aplicação, o banco vira o gargalo.
- Relacionado: O que é system design · Estimativas
Perguntas para discutir
- Por que a latência cresce devagar no começo e explode perto de ρ = 1?
- Com 6 réplicas, qual componente quebra primeiro, e em que taxa? Confira com ρ = λ / μ.
- O que você faria em seguida se a meta fosse 5 000 req/s?
Escalar horizontalmente atrás de um balanceador
O catálogo recebe 1 500 req/s. Duas réplicas não bastam, e em t = 80 s uma réplica cai. O balanceador continua mandando tráfego para ela até perceber (30 s sem health checks). Cumpra as metas com folga para uma falha (N+1).
- Lição: Escala horizontal precisa de folga para falhas (N+1), e o balanceador só desvia de uma réplica morta depois de detectá-la.
- Configuração:
scale-out· perfilload· alvo 1.500 req/s · 90% leituras · semente 11 · caosoff· eventos programados: t = 80 s — uma réplica do catálogo cai - Metas: taxa de erro < 1% · p99 < 250 ms · custo/mês < US$ 1.800
- Tente: Aumente as réplicas até ρ ≈ 0,6. Depois adicione um Health Check ligado ao Load Balancer.
- Observe: Em t = 80 s uma réplica morre: os erros duram ~30 s sem health checks e ~3 s com eles. As sobreviventes absorvem a parte dela.
- Relacionado: Tráfego & borda · Confiabilidade
Perguntas para discutir
- Quantas réplicas são necessárias para que perder uma mantenha ρ abaixo de 0,85?
- Por que uma réplica caída continua recebendo requisições por um tempo?
- O que o tempo de detecção faz com a disponibilidade (MTTR = detectar + reparar)?
Catálogo com muitas leituras: adicione um cache
Usuários navegam pelo catálogo: 3 000 req/s, 95% leituras. A camada de aplicação aguenta; o banco não. Em t = 100 s o cache (quando houver um) é esvaziado: o banco sobrevive à avalanche de misses?
- Lição: Um cache antes do banco reduz sua carga de leitura para leituras × (1 − taxa de acerto); um cache frio manda tudo de volta ao banco de uma vez (stampede).
- Configuração:
read-heavy-cache· perfilload· alvo 3.000 req/s · 95% leituras · semente 3 · caosoff· eventos programados: t = 100 s — cache esvaziado (deploy / reinício) - Metas: p99 < 100 ms · taxa de erro < 0,5% · custo/mês < US$ 3.000
- Tente: Insira um Cache entre a aplicação e o banco. Rode; depois ligue a coalescência e compare o que acontece em t = 100 s.
- Observe: A utilização do banco cai com a taxa de acerto. No flush, sem coalescência, ele satura por vários segundos.
- Relacionado: Cache
Perguntas para discutir
- Com 95% de leituras e 88% de acerto, qual é a carga no banco a 3 000 req/s?
- Por que o cache-aside reduz a taxa de acerto quando há muitas escritas?
- Cite duas outras formas de sobreviver a um stampede.
Tire o conteúdo estático da origem com uma CDN
As páginas de produtos têm imagens, estilos e scripts: 3 000 req/s, 98% leituras, servidos pela aplicação a partir do object storage. Usuários longe da região esperam, e a aplicação satura. Sirva-os pela borda.
- Lição: Conteúdo estático e cacheável fica na borda: a origem só vê misses e escritas, e o usuário recebe a resposta de perto.
- Configuração:
cdn-static· perfilload· alvo 3.000 req/s · 98% leituras · semente 5 · caosoff - Metas: p99 < 150 ms · taxa de erro < 0,5%
- Tente: Coloque uma CDN depois do DNS (dns → cdn → lb). Reduza as réplicas e compare latência e custo.
- Observe: A aplicação cai de saturada para ~35% com menos réplicas. A saída de dados da CDN vira a maior linha de custo.
- Relacionado: Tráfego & borda
Perguntas para discutir
- O que decide a taxa de acerto de uma CDN (TTL, chave de cache, conteúdo)?
- Por que a saída de dados (USD/GB) costuma ser a maior linha de um site de mídia?
- Como você invalidaria uma imagem que mudou?
Réplicas de leitura e atraso de replicação
4 000 req/s com 10% de escritas sobrecarregam o único primário PostgreSQL. Réplicas de leitura recebem leituras, nunca escritas, e ficam atrás do primário. Mantenha o atraso abaixo de 1 s.
- Lição: Réplicas de leitura multiplicam a capacidade de leitura, não a de escrita, e servem dados um pouco antigos (atraso de replicação).
- Configuração:
db-replicas· perfilload· alvo 4.000 req/s · 90% leituras · semente 13 · caosoff - Metas: p99 < 150 ms · taxa de erro < 0,5% · atraso de replicação < 1 s
- Tente: Adicione 2 réplicas de leitura ao banco. Depois aumente as escritas na barra para 30% e observe o primário.
- Observe: Leituras se espalham por 1 + réplicas; o primário fica com todas as escritas e seu atraso cresce com elas.
- Relacionado: Bancos de dados
Perguntas para discutir
- Quais leituras toleram atraso, e quais devem ir ao primário (read-your-writes)?
- Com que parcela de escritas as réplicas deixam de ajudar?
- O que vem depois das réplicas quando as escritas são o limite?
Partições e a chave quente
Consultas e atualizações de estoque (50% escritas) a 8 000 req/s em um banco chave-valor com 2 partições. Em t = 60 s uma celebridade posta o link de um produto: uma chave recebe uma fatia enorme do tráfego por um minuto. Mais partições resolvem a média — resolvem a chave quente?
- Lição: Particionar escala com o número de partições só enquanto as chaves se espalham; uma chave quente concentra a carga numa partição.
- Configuração:
sharding· perfilload· alvo 8.000 req/s · 50% leituras · semente 17 · caosoff· eventos programados: t = 60 s — efeito celebridade: uma chave quente - Metas: taxa de erro < 0,5% · p99 < 100 ms
- Tente: Aumente para 8 partições e rode. Depois adicione um Cache antes do NoSQL para as leituras quentes.
- Observe: Em t = 60 s começa o efeito celebridade: com 8 partições a mais ocupada ainda satura; o cache absorve as leituras quentes.
- Relacionado: Bancos de dados · Cache
Perguntas para discutir
- Se a chave mais quente recebe 45% do tráfego, qual é a carga da partição dela com P = 8? E com P = 64?
- Além do cache, como dividir uma chave quente (salting, réplicas da chave)?
- Por que se usa hashing consistente para posicionar as chaves?
Uma fila absorve o pico
Cada pedido gera um recibo em PDF (50 ms de trabalho). Hoje o serviço de pedidos espera por ele. O perfil spike multiplica a carga por 5 durante 30 s: os workers não acompanham e pedidos falham. Libere o usuário e faça o trabalho depois.
- Lição: Uma fila desacopla o usuário do trabalho lento: o produtor só espera o broker; o backlog absorve o pico e drena depois.
- Configuração:
async-queue· perfilspike· alvo 150 req/s · 0% leituras · semente 19 · caosoff - Metas: taxa de erro < 0,1% · p99 < 100 ms · atraso do consumidor < 60 s
- Tente: Troque a chamada app → worker por app → Message Queue → worker. Rode o perfil spike de novo.
- Observe: Os erros somem; o backlog cresce durante os 30 s de pico e drena depois. Atraso = backlog ÷ taxa de drenagem.
- Relacionado: Mensageria · Computação & serviços
Perguntas para discutir
- Quanto tempo o backlog leva para drenar com 4 workers? Calcule antes de rodar.
- O que acontece se o pico nunca acaba (produtores > consumidores para sempre)?
- Por que o consumidor deve ser idempotente com entrega at-least-once?
Publique um evento, deixe os consumidores reagirem
Quando um pedido é feito, a aplicação avisa o comprador, atualiza o analytics e reindexa a disponibilidade — hoje com três chamadas síncronas em sequência. Em t = 60 s o analytics fica 20× mais lento e os pedidos ficam lentos junto. Desacople o serviço de pedidos dos consumidores.
- Lição: Com um log de eventos cada grupo de consumidores lê no seu ritmo: um consumidor lento fica para trás sem atrasar o produtor nem os outros.
- Configuração:
event-fanout· perfilload· alvo 400 req/s · 0% leituras · semente 23 · caosoff· eventos programados: t = 60 s — analytics 20× mais lento - Metas: p99 < 120 ms · taxa de erro < 0,1% · atraso do consumidor < 60 s
- Tente: Publique no Kafka (app → kafka) e deixe notification, analytics e o indexador consumirem (kafka → cada um).
- Observe: Quando o analytics fica lento em t = 60 s, só o atraso dele cresce; a latência dos pedidos fica estável.
- Relacionado: Mensageria
Perguntas para discutir
- O que define o paralelismo máximo de um grupo de consumidores?
- Que garantias você perde em relação a chamadas síncronas (ordem, read-your-writes)?
- Como publicar o evento e gravar o pedido de forma atômica (outbox)?
Falha em cascata: uma dependência lenta
Metade das páginas do catálogo chama uma API externa de recomendações. Em t = 60 s ela fica 20× mais lenta (3 s). Sem timeouts cada requisição segura uma thread esperando; os pools de threads se esgotam e o catálogo inteiro falha — embora a CPU da aplicação esteja quase ociosa (Lei de Little: threads = λ × W).
- Lição: Uma dependência lenta sem timeouts esgota as threads de quem chama (Lei de Little) e derruba requisições que nem a usam.
- Configuração:
cascading-failure· perfilload· alvo 800 req/s · 90% leituras · semente 29 · caosoff· eventos programados: t = 60 s — API de recomendações 20× mais lenta - Metas: taxa de erro < 1% · p99 < 500 ms
- Tente: Rode como está e leia o gargalo no Relatório. Depois defina timeout de 300 ms em app → recommendations, e então fallback e circuit breaker.
- Observe: Sem timeout a aplicação fica sem threads com a CPU ociosa. Com o timeout só falham as chamadas de recomendação; com o fallback, ninguém.
- Relacionado: Confiabilidade
Perguntas para discutir
- A 800 req/s, metade chamando uma dependência de 3 s, quantas threads ficam ocupadas? (L = λ × W)
- Por que só o timeout não zera os erros aqui?
- O que um bulkhead mudaria?
Tempestade de retries: quando retentar piora
O serviço de inventário retenta cada consulta falha 3 vezes, na hora, com timeout de 100 ms. Em t = 60 s o banco reinicia (8 s). Quando volta, recebe até 4× a carga normal, a fila enche, as respostas passam do timeout… e ele nunca se recupera, embora o gatilho já tenha passado (uma falha metaestável).
- Lição: Retries imediatos multiplicam a carga de um serviço em apuros; quando as respostas passam do timeout, toda tentativa falha e o sistema continua fora mesmo após o gatilho (falha metaestável).
- Configuração:
retry-storm· perfilload· alvo 2.000 req/s · 90% leituras · semente 31 · caosoff· eventos programados: t = 60 s — o banco reinicia - Metas: taxa de erro < 15% · p99 < 300 ms
- Tente: Rode e observe a aresta app → db após o reinício de 8 s. Depois ligue o circuit breaker; teste também 1 retry com backoff exponencial + jitter.
- Observe: A taxa na aresta salta para ~4× e os erros não se recuperam. Com o breaker o banco esvazia a fila e o sistema volta.
- Relacionado: Confiabilidade
Perguntas para discutir
- Com p = 0,9 de falha por tentativa e 3 retries, qual é o multiplicador de carga?
- Por que um timeout maior ou uma fila menor também quebram o ciclo?
- O que é um orçamento de retries (retry budget)?
Rate limiting protege o backend
1 000 req/s de usuários reais — mais bots raspadores enviando o dobro disso. O catálogo derrete para todos. Rejeite o excesso rapidamente no gateway (429) em vez de deixá-lo enfileirar atrás dos usuários reais.
- Lição: Rejeitar o excesso rapidamente (429) protege o backend e os usuários reais; deixá-lo enfileirar degrada todo mundo.
- Configuração:
rate-limiter· perfilload· alvo 1.000 req/s · 90% leituras · semente 37 · caosoff - Metas: taxa de erro < 1% · p99 < 200 ms
- Tente: Defina um limite no API Gateway abaixo da capacidade da aplicação (ex.: 1 500 req/s). Ou coloque um WAF na frente.
- Observe: Os bots são cortados primeiro (token buckets por cliente), os usuários reais mantêm latência baixa e a aplicação fica abaixo da saturação.
- Relacionado: Tráfego & borda
Perguntas para discutir
- Token bucket vs. janela deslizante: qual permite rajadas?
- Onde deve ficar o limite num sistema com muitas réplicas de gateway?
- Por que um 429 é melhor para o cliente que um timeout?
Sobrevivendo à queda de uma região
A aplicação roda em uma região de nuvem. Em t = 60 s a região inteira cai. Construa uma segunda região e deixe o DNS levar os usuários para lá. Tempo de failover = detecção + TTL do DNS: confira os dois.
- Lição: Sobreviver à perda de uma região exige uma segunda pilha completa e um jeito de mover os usuários: o failover por DNS leva detecção mais TTL.
- Configuração:
multi-region· perfilload· alvo 1.000 req/s · 90% leituras · semente 41 · caosoff· eventos programados: t = 60 s — região sa-east-1 fora do ar - Metas: disponibilidade ≥ 95% · p99 < 80 ms
- Tente: Desenhe uma segunda VPC (outra região) com LB, aplicação e banco; conecte o DNS a ela, ligue um Health Check ao DNS e reduza o TTL.
- Observe: Em t = 60 s a primeira região some. Os erros duram cerca de (3 s + TTL) para os usuários roteados para lá.
- Relacionado: Confiabilidade · Tráfego & borda
Perguntas para discutir
- Ativo-ativo ou ativo-passivo: qual perde menos requisições aqui?
- O que acontece com dados gravados na região perdida (RPO)?
- Por que cada aplicação deve chamar o banco da própria região?
Pico serverless: cold starts e limites
A verificação de cupom de desconto roda como função. O perfil spike salta de 200 para 2 000 req/s. Instâncias novas partem a frio (600 ms) e a conta tem limite de concorrência de 50. Instâncias necessárias = λ × W (Lei de Little).
- Lição: Funções escalam por requisição: instâncias = λ × W, cold starts acontecem justo quando o tráfego salta, e o limite de concorrência estrangula o resto.
- Configuração:
serverless-burst· perfilspike· alvo 200 req/s · 90% leituras · semente 43 · caosoff - Metas: taxa de erro < 0,5% · p99 < 300 ms · custo/mês < US$ 5.000
- Tente: Aumente o limite de concorrência, rode o spike; depois adicione instâncias provisionadas e compare p99 e custo.
- Observe: O throttling (429) some com o limite maior; os cold starts aparecem no p99 no início do pico até existirem instâncias quentes.
- Relacionado: Computação & serviços
Perguntas para discutir
- Quantas instâncias 2 000 req/s × 40 ms exigem?
- Por que cold starts aumentam o número de instâncias necessárias?
- Quando um contêiner com autoescala é mais barato que funções?
Venda relâmpago: um lançamento com estoque limitado
Um produto com estoque limitado entra à venda em t = 60 s: o tráfego salta 20× (~6 000 req/s) por 90 s. 80% das requisições leem a página do produto, 20% tentam comprar. O banco guarda o estoque (nada de vender o que não há!) e o provedor de pagamento aceita 500 req/s. Hoje tudo desaba. Projete para o pico: sirva leituras da borda e de caches, e coloque os compradores numa sala de espera (fila) drenada na velocidade que o inventário e o provedor aguentam.
- Lição: Uma venda relâmpago é projetada para o pico: leituras são absorvidas pela borda e por caches, e compradores esperam numa fila drenada no ritmo do inventário e do provedor de pagamento.
- Configuração:
flash-sale· perfilload· alvo 300 req/s · 80% leituras · semente 2027 · caosoff· eventos programados: t = 60 s — abre a venda - Metas: p99 < 300 ms · taxa de erro < 1% · atraso do consumidor < 300 s
- Tente: Siga as dicas uma a uma: CDN, cache do catálogo, depois API Gateway mandando escritas para uma fila (sala de espera) consumida por workers de pedidos.
- Observe: Com a sala de espera os erros somem e o p99 fica baixo; o atraso do consumidor é quanto o comprador espera na fila.
- Relacionado: Estudos de caso · Mensageria · Cache
Perguntas para discutir
- Quantos itens podem ser vendidos por segundo se o pagamento aceita 500 req/s e cada pedido faz uma chamada?
- Como evitar vender mais itens do que há em estoque (reservas, SELECT … FOR UPDATE)?
- Como mostrar ao usuário sua posição na fila?
Encurtador de URLs (sho.rt/xyz)
Cada e-mail de pedido e post compartilhado leva um link curto. 5 000 redirecionamentos/s, 99% leituras, poucas escritas (novos links). O clássico de entrevista: qual é o caminho de leitura, e que banco serve para chave → URL?
- Lição: Uma busca por chave com muitas leituras: cache + banco chave-valor, dimensionados com aritmética simples.
- Configuração:
url-shortener· perfilload· alvo 5.000 req/s · 99% leituras · semente 47 · caosoff - Metas: p99 < 50 ms · taxa de erro < 0,1% · custo/mês < US$ 3.500
- Tente: Troque o SQL por Cache → NoSQL e dimensione a aplicação.
- Observe: O p99 cai para poucas dezenas de ms; a maioria dos redirecionamentos nem chega ao banco.
- Relacionado: Estudos de caso
Perguntas para discutir
- Quantos códigos cabem em 7 caracteres base-62?
- Como gerar códigos únicos sem coordenação?
- Redirecionamento 301 ou 302: qual permite contar cliques?
Feed de notícias: fan-out na escrita vs na leitura
O feed mostra posts das contas que o usuário segue. Hoje o feed é montado na leitura: uma consulta pesada (40 ms) que junta centenas de fontes. 2 000 req/s, 95% leituras. Em vez disso, pré-calcule as timelines quando algo é postado.
- Lição: Fan-out na escrita move trabalho do caminho de leitura (frequente) para o de escrita (raro): ler o feed vira uma busca por chave.
- Configuração:
news-feed· perfilload· alvo 2.000 req/s · 95% leituras · semente 53 · caosoff - Metas: p99 < 100 ms · taxa de erro < 0,5% · atraso do consumidor < 10 s
- Tente: Mande leituras para um cache de timelines sobre um NoSQL, e escritas para o banco de posts mais Kafka → workers de fan-out → timelines.
- Observe: Leituras ficam baratas; o atraso do fan-out mostra quanto um post leva para chegar às timelines.
- Relacionado: Estudos de caso · Mensageria
Perguntas para discutir
- O que quebra quando uma conta tem 10 milhões de seguidores?
- Como uma abordagem híbrida trata celebridades?
- Que consistência um feed realmente precisa?
Chat de um evento ao vivo
Durante um evento ao vivo, usuários conversam na sala: 2 000 req/s, metade são mensagens novas. Cada mensagem é gravada no PostgreSQL e entregue de forma síncrona antes de o remetente ter resposta. As escritas sobrecarregam o banco e o remetente espera cada entrega.
- Lição: Chat separa guardar mensagens (particionado, muitas escritas) de entregá-las (pub/sub para os servidores conectados).
- Configuração:
chat· perfilload· alvo 2.000 req/s · 50% leituras · semente 59 · caosoff - Metas: p99 < 80 ms · taxa de erro < 0,1% · atraso do consumidor < 2 s
- Tente: Guarde mensagens num NoSQL e publique-as num Pub/Sub consumido pelo serviço de entrega.
- Observe: A carga de escrita se espalha pelas partições; quem envia não espera mais a entrega.
- Relacionado: Estudos de caso · Tráfego & borda
Perguntas para discutir
- Como uma mensagem chega a um usuário conectado a outro servidor websocket?
- Como ordenar as mensagens de uma sala?
- O que acontece com mensagens para usuários offline?
Carona: um índice geográfico com muitas escritas
Motoristas enviam a posição a cada 4 s: 20 000 req/s, 90% escritas, enquanto passageiros buscam motoristas próximos. Um banco relacional não absorve essa taxa de escrita. Guarde só a última posição por motorista num banco particionado e transmita o histórico.
- Lição: Atualizações de posição são um fluxo de muitas escritas: guarde só a última posição num banco particionado e mande o histórico de forma assíncrona.
- Configuração:
ride-sharing· perfilload· alvo 20.000 req/s · 10% leituras · semente 61 · caosoff - Metas: p99 < 100 ms · taxa de erro < 0,5% · custo/mês < US$ 6.000
- Tente: Troque o SQL por um índice geográfico NoSQL particionado; amostre posições no Kafka para o analytics.
- Observe: O primário relacional saturava com as escritas; o banco particionado as absorve com folga.
- Relacionado: Estudos de caso · Bancos de dados
Perguntas para discutir
- Quantas escritas/s geram 1 milhão de motoristas enviando a cada 4 s?
- Como achar motoristas num raio de 2 km (geohash, quadtree, H3)?
- O que acontece com o índice quando um estádio esvazia?
Streaming de vídeo: CDN + object store + transcodificação
Criadores enviam vídeos; usuários assistem. 2 000 requisições/s de segmentos passam pela aplicação, e cada upload é transcodificado de forma síncrona (20 s por vídeo) enquanto quem enviou espera. Sirva segmentos pela borda e transcodifique em segundo plano.
- Lição: Vídeo é bytes na borda mais trabalho pesado em segundo plano: CDN + object store para quem assiste, fila + workers para transcodificar.
- Configuração:
video-streaming· perfilload· alvo 2.000 req/s · 99% leituras · semente 67 · caosoff - Metas: p99 < 150 ms · taxa de erro < 0,5% · atraso do consumidor < 120 s
- Tente: Viewers → CDN → Object Store; uploads → API Gateway → app → queue → transcoders.
- Observe: A aplicação quase some do caminho de quem assiste; o backlog de transcodificação drena a réplicas × 1/20 s.
- Relacionado: Estudos de caso · Tráfego & borda
Perguntas para discutir
- Por que segmentos de vídeo são ideais para cache?
- Quantos transcoders 0,5 upload/s de jobs de 20 s exigem (Little)?
- Por que gerar várias taxas de bits (streaming adaptativo)?
Sistema de notificações
Cada compra manda um e-mail de confirmação (50 req/s), e um agendador manda 6 000 e-mails de resumo a cada 2 minutos — todos no mesmo instante. O provedor de e-mail aceita 200 envios/s. Hoje compradores esperam o provedor e falham sempre que chega a rajada do resumo.
- Lição: Notificações são assíncronas por natureza: enfileire, dose na cota do provedor e nunca dispare jobs agendados todos juntos.
- Configuração:
notification-system· perfilload· alvo 50 req/s · 0% leituras · semente 71 · caosoff - Metas: taxa de erro < 0,5% · p99 < 150 ms · atraso do consumidor < 120 s
- Tente: Coloque uma fila antes do serviço de notificações e espalhe os jobs do agendador em 60 s.
- Observe: Os compradores não sentem mais a rajada do resumo; a cota do provedor define a drenagem.
- Relacionado: Estudos de caso · Computação & serviços
Perguntas para discutir
- Por que uma rajada agendada é um pico autoinfligido?
- Como dar prioridade a e-mails transacionais sobre resumos?
- Como evitar mandar o mesmo e-mail duas vezes?
Pagamentos: retries precisam de idempotência
Pedidos chamam o provedor de pagamento de forma síncrona com timeout de 1 s e 2 retries imediatos. Em t = 60 s o provedor fica 4× mais lento: chamadas estouram o timeout, e cada retry após um timeout pode cobrar o cartão DE NOVO — o provedor recebeu a primeira tentativa. Torne os pagamentos seguros e mantenha os compradores rápidos.
- Lição: Um retry após timeout pode repetir uma operação que já aconteceu: pagamentos precisam de chaves de idempotência, e o comprador não deve esperar o provedor.
- Configuração:
payment-system· perfilload· alvo 300 req/s · 50% leituras · semente 73 · caosoff· eventos programados: t = 60 s — provedor de pagamento 4× mais lento - Metas: cobranças duplicadas < 1 · taxa de erro < 1% · p99 < 1.500 ms
- Tente: Ligue a chave de idempotência na aresta do pagamento; depois mova a chamada para trás de uma fila e de um worker de pagamento.
- Observe: As cobranças duplicadas zeram com a chave; com a fila, a latência do comprador não depende mais do provedor.
- Relacionado: Estudos de caso · Computação & serviços
Perguntas para discutir
- Por que um timeout não distingue "falhou" de "deu certo devagar"?
- Como o provedor usa a chave de idempotência?
- Como webhooks e conciliação fecham o ciclo?
Autocompletar da busca
Cada tecla na caixa de busca pede sugestões: 10 000 req/s, todas leituras, e precisam parecer instantâneas (< 50 ms). O cluster de busca não aguenta. A maioria dos prefixos é popular e se repete muito.
- Lição: Autocompletar é um problema de latência: prefixos populares se repetem, então um cache de prefixos responde a maioria das teclas.
- Configuração:
search-autocomplete· perfilload· alvo 10.000 req/s · 100% leituras · semente 79 · caosoff - Metas: p99 < 50 ms · taxa de erro < 0,1% · custo/mês < US$ 5.000
- Tente: Insira um Cache entre a aplicação e a busca; dimensione a aplicação. Reduza o RPS-alvo para simular o debounce do cliente.
- Observe: A carga da busca cai pela taxa de acerto e o p99 cumpre o orçamento de 50 ms.
- Relacionado: Estudos de caso · Cache
Perguntas para discutir
- Quantas requisições o debounce de 100 ms economiza ao digitar "metallica"?
- Trie em memória vs. motor de busca: trade-offs?
- Com que frequência as sugestões devem ser reconstruídas?
Assistente de suporte com IA (RAG + ferramentas)
O assistente de suporte responde perguntas sobre produtos e pedidos. Seu laço de agente faz 6 passos (6 chamadas ao LLM + ferramentas) por pergunta, a cota do provedor é de 10 chamadas/s, e nada verifica prompt injection. 2 perguntas/s já estouram a cota, levam segundos e custam uma fortuna.
- Lição: Numa aplicação com LLM, latência e custo crescem com o número de chamadas ao modelo: menos passos, cache semântico e roteamento ciente da cota importam mais que capacidade bruta; guardrails ficam no caminho.
- Configuração:
ai-assistant· perfilload· alvo 2 req/s · 100% leituras · semente 83 · caosoff - Metas: taxa de erro < 1% · p99 < 10.000 ms · custo/mês < US$ 60.000 · respostas inseguras < 0,1%
- Tente: Reduza os passos do agente, ligue o cache semântico e um modelo reserva, e coloque um Safety Mesh entre o gateway e o assistente.
- Observe: Os erros (429) somem abaixo da cota, o custo cai com as chamadas por pergunta e a taxa de respostas inseguras despenca.
- Relacionado: Sistemas de IA
Perguntas para discutir
- Custo por pergunta = passos × tokens × preço: calcule para 6 e para 3 passos.
- O que um cache semântico arrisca (respostas velhas ou erradas)?
- Onde os guardrails devem rodar: entrada, saída ou ambos?
Não se conserta o que não se mede
A utilização fica oculta ("?") até o serviço ser ligado ao Metrics. Em t = 50 s uma réplica do catálogo morre; sem health checks o balanceador só percebe após 30 s, e sem alertas ninguém a repara logo. Monte a pilha de observabilidade: métricas em todo serviço, health checks, alertas, logs e tracing.
- Lição: Observabilidade muda o resultado: health checks encurtam a detecção, alertas encurtam o reparo, e métricas tornam a utilização visível.
- Configuração:
observability-stack· perfilload· alvo 600 req/s · 90% leituras · semente 89 · caosoff· eventos programados: t = 50 s — uma réplica do catálogo morre - Metas: disponibilidade ≥ 99% · p99 < 200 ms · serviços medidos ≥ 100%
- Tente: Conecte Metrics ao LB, à aplicação e ao banco; adicione Health Check no LB, Alerting no Metrics, além de Logs e Tracing na aplicação.
- Observe: Os "?" viram números; a queda em t = 50 s custa segundos em vez de meio minuto; a aba Trace mostra onde uma requisição lenta gasta seu tempo.
- Relacionado: Observabilidade
Perguntas para discutir
- Disponibilidade = MTTF / (MTTF + MTTR): que parte cada nó melhorou?
- Que quatro sinais de ouro você poria no painel?
- Por que alertar por taxa de queima do SLO e não por CPU?
Microsserviços no Kubernetes: pods vs nós
A aplicação foi dividida nos microsserviços catalog, inventory, order e notify, cada um com seu banco, atrás de um API gateway, num cluster Kubernetes de 2 nós × 6 pods. Com 2 000 req/s o HPA do catálogo pede mais pods do que o cluster consegue alocar: eles ficam Pending, e o catálogo afoga. Comprar é uma cadeia síncrona (order → inventory, catalog, notify, seu banco): cada salto soma sua latência à espera do comprador.
- Lição: O HPA adiciona pods, mas só nós trazem espaço: pods além de nós × pods por nó ficam Pending e não atendem nada. Numa cadeia de microsserviços síncronos cada salto soma sua latência à espera do usuário, então o trabalho que o usuário não precisa esperar vai para trás de uma fila.
- Configuração:
microservices· perfilload· alvo 2.000 req/s · 80% leituras · semente 2031 · caosoff - Metas: taxa de erro < 1% · p99 < 200 ms · custo/mês < US$ 8.500
- Tente: Selecione a moldura do cluster: pods = nós × pods por nó. Adicione nós ou ligue o cluster autoscaler (um nó leva ~90 s, então mantenha alguma folga), aumente o mínimo de réplicas do catálogo e coloque uma fila entre order e notify.
- Observe: Como está, erros ≈ 25% e p99 ≈ 3,8 s: o HPA do catálogo quer ~14 pods, mas o cluster de 2 × 6 está cheio (6 catalog + 2 order + 2 inventory + 2 notify), então 9 pods ficam Pending; e o comprador ainda espera o notify (mediana de 120 ms, σ 0,6). A solução possível — 3 nós com o cluster autoscaler (máx. 6; ele adiciona um 4º nó em ~45 s, pronto em ~135 s), réplicas e mínimo do catálogo em 10, e order → fila confirmations → notify — passa com erros ≈ 0%, p99 ≈ 60 ms e ≈ 8 000 USD/mês. Leia as linhas de custo: o preço por requisição do API gateway, não os nós, domina a conta.
- Relacionado: Computação & serviços · Plataformas cloud-native
Perguntas para discutir
- O catálogo recebe as leituras (80% de 2 000 req/s) mais uma chamada por pedido (400 req/s), a 250 req/s por pod. Quantos pods ele precisa com ρ = 0,6, e eles cabem em 2 nós × 6 pods?
- Que chamadas da cadeia de compra precisam terminar antes de o comprador ter resposta, e quais podem virar eventos?
- O gateway cobra 1 USD por milhão de requisições: quanto isso dá por mês a 2 000 req/s, e como você o reduziria?
Kubernetes: perdendo um nó
Catalog e pricing rodam 6 pods cada num cluster de 3 nós × 4 pods: completamente cheio. Em t = 60 s um nó morre e leva um terço dos pods. Quem chama continua mandando requisições aos pods mortos até perceber (30 s atrás do balanceador, 40 s na chamada direta catalog → pricing), e os pods perdidos não têm onde ser realocados. Sobreviva à perda de qualquer nó.
- Lição: N+1 vale para nós, não só para réplicas: um nó perdido leva vários pods de uma vez, quem chama continua chamando-os até perceber, e o Kubernetes só consegue realocá-los onde houver espaço. Uma chamada direta entre serviços precisa de descoberta de serviços e de timeout para parar de esperar por pods mortos.
- Configuração:
k8s-node-failure· perfilload· alvo 1.700 req/s · 90% leituras · semente 2033 · caosoff· eventos programados: t = 60 s — um nó de trabalho morre - Metas: taxa de erro < 1% · p99 < 150 ms · custo/mês < US$ 2.000
- Tente: Deixe espaço no cluster (mais pods por nó ou um nó a mais) e dimensione as réplicas para que as sobreviventes aguentem a carga (ρ após perder um terço abaixo de ~0,85). Coloque timeout com retries em catalog → pricing e conecte um Service Registry a catalog e pricing.
- Observe: Como está, o nó leva 2 pods do catalog + 2 do pricing. As chamadas catalog → pricing continuam indo aos pods mortos do pricing por 40 s; cada uma trava 10 s, as threads do catálogo acabam e a falha cascateia (~90% de erros nessa janela); após 40 s os pods perdidos são recriados mas ficam Pending (sem espaço): ≈ 27% de erros e p99 > 4 s na execução. A solução possível — 8 pods por nó (3 × 8 = 24 vagas: após perder um nó, 16 vagas para os 16 pods — N+1 para nós), 8 réplicas cada, um Service Registry conectado a catalog e pricing, e timeout de 300 ms + 2 retries em catalog → pricing — passa com erros ≈ 0,8% e p99 ≈ 43 ms: pods mortos são descartados em 3 s, uma chamada que cai num deles desiste após 300 ms e é retentada num pod vivo, os pods sobreviventes aguentam a carga, e os 6 pods perdidos são todos realocados após 40 s.
- Relacionado: Plataformas cloud-native · Confiabilidade
Perguntas para discutir
- Com 3 nós × 4 pods e 12 pods, quantos pods uma falha de nó leva, e onde o Kubernetes pode colocá-los? E com 8 pods por nó e 8 réplicas cada?
- Por que a chamada direta catalog → pricing machuca mais que a aresta balanceador → catalog? Use a Lei de Little com uma trava de 10 s.
- Por que os pods de um serviço devem se espalhar por nós e zonas diferentes (topology spread constraints)?
Failover do banco: RTO e RPO
Checkout da venda relâmpago: 1 500 req/s, metade escritas (pedidos), num DB Cluster com um primário e um seguidor replicado de forma assíncrona. Em t = 90 s o primário morre. Até um seguidor ser promovido (30 s) toda escrita falha, e as escritas ainda dentro do atraso de replicação se perdem de vez: pedidos pagos e esquecidos. Faça o failover não perder nada e doer pouco.
- Lição: Um failover tem dois custos: o tempo sem primário (RTO) e as escritas ainda não replicadas (RPO). Replicação síncrona ou por quorum zera o RPO ao preço de uma ida e volta por escrita; um failover rápido e mais shards reduzem o RTO e a parcela de escritas que ele atinge — e toda cópia é paga.
- Configuração:
db-failover· perfilload· alvo 1.500 req/s · 50% leituras · semente 2035 · caosoff· eventos programados: t = 90 s — o primário de orders-db morre - Metas: escritas perdidas < 1 · disponibilidade ≥ 99% · p99 (escritas) < 100 ms
- Tente: Troque a replicação para sync ou quorum, reduza o tempo de failover e divida as chaves em shards. Depois injete uma degradação de latência no cluster (ela deixa um seguidor lento) e compare sync e quorum no p99 de escrita.
- Observe: Como está, ≈ 1 570 escritas se perdem quando o primário morre, as escritas falham por 30 s (disponibilidade ≈ 90,6%), e após a promoção um único nó carrega leituras e escritas, então o p99 de escrita chega a ≈ 375 ms. A solução possível — 2 shards, 2 seguidores cada, quorum, failover de 5 s — passa com 0 escritas perdidas, disponibilidade ≈ 99,2% e p99 de escrita ≈ 34 ms; mas a conta do banco vai de 900 para 2 700 USD/mês (2 shards × 3 nós × 450).
- Relacionado: Bancos de dados · Confiabilidade
Perguntas para discutir
- Escritas perdidas ≈ taxa de escrita × atraso: estime-as pelo inspetor logo antes de t = 90 s.
- Com um seguidor lento, por que o sync sofre e o quorum com 2 seguidores não?
- RPO 0 e RTO de 5 s triplicam a conta do banco: para que dados vale a pena (pedidos, sessões, analytics)?