Nos artigos anteriores da série System Design: da teoria à prática, vimos que todo sistema é um conjunto de trade-offs e que os gargalos quase sempre aparecem no armazenamento. Agora entramos em uma camada ainda mais crítica: como manter a confiança do usuário quando o sistema inevitavelmente falha?
É aqui que entram as famosas falácias da computação distribuída, listadas pela primeira vez por Peter Deutsch na Sun Microsystems nos anos 90:
- A rede é confiável.
- A latência é zero.
- A largura de banda é infinita.
- A rede é segura.
- A topologia não muda.
- Existe apenas um administrador.
- O transporte é barato.
- A rede é homogênea.
Essas falácias são, na prática, suposições incorretas que engenheiros fazem e que inevitavelmente levam a falhas em produção. A rede não é confiável, mensagens podem se perder ou duplicar. A latência não é zero, cada milissegundo se acumula. A largura de banda não é infinita, gargalos surgem sob pressão. É por isso que precisamos projetar sistemas com a certeza de que essas condições adversas vão acontecer — porque elas acontecem todos os dias.
Falhas não são exceções — são parte do funcionamento normal de sistemas distribuídos. Redes caem, nós ficam fora do ar, pacotes são duplicados. A questão não é se isso vai acontecer, mas quando. Por isso, confiabilidade e consistência precisam estar embutidas no design desde o início.
11. Idempotência salva você
Se um cliente clica duas vezes no botão de pagamento, ou se o frontend repete uma requisição porque recebeu timeout, o sistema precisa garantir que a operação não será duplicada. É aqui que entra a idempotência: a capacidade de executar a mesma operação múltiplas vezes sem alterar o resultado final.
A idempotência é especialmente poderosa porque resolve um problema estrutural de sistemas distribuídos: a inevitabilidade do retry. Tentativas repetidas podem vir do cliente, do load balancer, do SDK ou de filas intermediárias. Sem idempotência, cada retry é um risco de estado corrompido.
Exemplos do mundo real
- Stripe e chaves idempotentes: pagamentos só são aceitos se acompanhados de um
Idempotency-Key
. Isso garante que a cobrança não seja duplicada em casos de retry automático. - AWS SQS e Lambda: mensagens podem ser entregues mais de uma vez. Consumidores precisam ser idempotentes para evitar efeitos colaterais duplicados.
- E-commerce móvel: pedidos duplicados são comuns em redes instáveis. A solução é atrelar cada ordem a um identificador único (ex.:
order-intent-id
).
👉 No blog já exploramos o tema em detalhe: Idempotência em software: conceitos, importância e aplicações.
12. Fail fast, fail loud
Existe algo pior que um sistema caído: um sistema que parece funcionar, mas está falhando em silêncio. Esse é o cenário em que dados se perdem, inconsistências se espalham e o time de engenharia só descobre o problema horas depois.
O princípio do fail fast sugere que, ao detectar qualquer anomalia, o sistema deve falhar rapidamente em vez de tentar mascarar ou recuperar silenciosamente. Já o fail loud complementa: a falha deve ser visível, rastreável e, de preferência, notificada.
Exemplos do mundo real
- Netflix e circuit breakers: com Hystrix (e hoje Resilience4j/Envoy), a empresa implementou disjuntores que “abrem” após falhas repetidas, evitando cascatas.
- Google e deadlines encadeados: serviços internos abortam requisições cedo quando percebem que o SLA não será cumprido, reduzindo backpressure invisível.
- E-commerce em checkout: recomendações ou frete dinâmico podem falhar rápido e barulhento, preservando o fluxo de pagamento.
Esse princípio também se conecta a outros padrões de resiliência que já abordamos no blog, como back-pressure e bulkhead: Estratégias de resiliência para microservices.
13. Consistência eventual é um recurso, não um bug
Muitos engenheiros iniciantes encaram a consistência eventual como um problema. A realidade é que, em larga escala, ela se torna uma ferramenta de design.
Imagine o feed de uma rede social: você publica uma foto e, por alguns segundos, ela aparece para você mas não para todos os amigos. Isso é aceitável porque o SLA do negócio permite um pequeno atraso. O mesmo vale para métricas de analytics, relatórios e até carrinhos de compra (onde pequenas inconsistências são toleráveis por minutos).
Exemplos do mundo real
- Dynamo/DynamoDB: priorizam disponibilidade em face de partições, adotando consistência eventual por padrão. Leituras fortemente consistentes são opcionais e mais caras.
- DNS e CDNs: a propagação de conteúdo depende de TTLs. Durante minutos, partes diferentes do mundo podem enxergar versões distintas.
- E-commerce: catálogos são servidos com consistência eventual (escala barata), mas estoques e confirmações de pedido usam consistência forte.
O ponto é claro: consistência eventual não é defeito. É decisão consciente para balancear latência, custo e experiência.
14. Resolução de conflitos em sistemas ativos-ativos
O sonho de muitos arquitetos é ter sistemas ativos-ativos em múltiplas regiões, mas isso traz um desafio inevitável: conflitos de escrita simultânea.
Quando dois nós diferentes recebem updates concorrentes, a pergunta é: qual vence? A resposta não está na infraestrutura, mas no negócio.
Exemplos do mundo real
- Cosmos DB: permite configurar políticas de conflito (Last Write Wins ou merge customizado). Muitos times começam com LWW, mas descobrem perdas de dados e migram para regras customizadas.
- Figma e Google Docs: usam CRDTs (Conflict-free Replicated Data Types) para merges automáticos em edições colaborativas.
- Marketplaces e estoque: regra de negócio dita a resolução. Ex.: “primeiro lock válido vence” em reservas de estoque, com janelas temporárias curtas.
A lição é simples: a infra oferece mecanismos, mas a lógica de resolução precisa vir do domínio.
15. Durabilidade não é grátis
Outro mito comum é tratar durabilidade como atributo absoluto. Na prática, manter dados “para sempre” tem custos altos em latência, espaço e coordenação.
Cada confirmação síncrona entre regiões aumenta a latência da escrita. Protocolos de consenso como Paxos e Raft garantem que os nós concordem, mas adicionam overhead. Bancos financeiros aceitam essa troca: cada transação só é confirmada quando escrita em múltiplos nós. Já sistemas de analytics relaxam a exigência: perder alguns eventos não críticos pode ser aceitável para ganhar throughput.
Exemplos do mundo real
- Google Spanner: usa relógios atômicos (TrueTime) para garantir ordem global, mas isso aumenta a latência de commit.
- PostgreSQL:
synchronous_commit=on
com múltiplas réplicas dá mais durabilidade, mas custa RTT. Bancos usam, apps de telemetria relaxam. - Kafka:
acks=all
garante durabilidade, mas reduz throughput. Muitos times escolhemacks=1
em streams não críticos para ganhar velocidade.
Durabilidade precisa ser calibrada ao SLA do negócio: o custo de perder um dado nem sempre justifica o overhead de preservá-lo a qualquer preço.
Quando falamos de confiabilidade e consistência, não estamos apenas discutindo padrões técnicos: estamos falando sobre a base da confiança entre o sistema e quem o utiliza. Um feed que demora a atualizar é tolerável; um saldo bancário errado pode custar milhões. O usuário não enxerga circuit breakers, CRDTs ou idempotency keys — ele só percebe se o sistema faz o que prometeu, sempre, em qualquer circunstância.
O que vimos neste artigo é que resiliência não nasce da sorte, mas de decisões conscientes:
- Permitir retries sem idempotência é apostar no caos.
- Ignorar falhas silenciosas é condenar o sistema a se corromper lentamente.
- Exigir consistência forte em cenários que não precisam dela é gastar recursos à toa.
- Resolver conflitos sem pensar no domínio é transferir responsabilidade para quem não entende do negócio.
- Buscar “durabilidade infinita” sem calcular custo é comprometer latência e experiência do usuário.
Essas escolhas estão no coração do design de sistemas modernos — e é por isso que confiabilidade e consistência precisam ser tratadas como decisões de produto, não apenas de infraestrutura.
No artigo anterior sobre armazenamento, vimos onde os gargalos costumam aparecer. Neste, entendemos como lidar com eles de forma que os sistemas não quebrem diante do inevitável. Agora, no próximo capítulo da série System Design: da teoria à prática, vamos mudar de lente: em vez de pensar em dados ou falhas, vamos pensar em formas de organizar o próprio sistema.
Monólitos modulares, microservices, serverless, coreografia ou orquestração: cada padrão carrega seus próprios trade-offs. E, como vimos até aqui, não existe escolha perfeita, apenas a mais adequada ao contexto.
Este foi o Artigo 3 da série System Design: da teoria à prática.
No próximo, exploraremos Padrões de Arquitetura: do monólito modular aos microservices, passando por serverless, coreografia e orquestração.