PrincípioIntermediário7 min de leitura6 seções1994, Data aproximada

As 8 falácias dos sistemas distribuídos

Oito suposições que quase todo desenvolvedor faz ao chamar código pela rede — a rede é confiável, a latência é zero, a banda é infinita — e que a realidade desmente uma a uma, sempre em produção. Reconhecê-las é o começo de todo sistema distribuído que não desmorona: cada falácia ignorada vira um incidente esperando o pior momento.

Em 10 segundos

São oito suposições que a semelhança entre chamada local e chamada remota convida a fazer — a rede é confiável, a latência é zero, a banda é infinita, a rede é segura, a topologia não muda, há um administrador só, o transporte é grátis, a rede é homogênea. Todas são falsas, e ignorá-las produz sistemas que funcionam no dev e desmoronam em produção. O antídoto é negá-las por padrão: timeout, retry, erro de rede como caso esperado, contratos versionados.

Antes de ter nome

Bill Joy e Tom Lyon já haviam esboçado as primeiras quatro no início dos anos 1990, a partir das dores reais de construir sistemas em rede na Sun.

  • #principio
  • #distribuido
  • #rede
  • #latencia
  • #falhas
Analogia

Mandar carta não é falar cara a cara

Conversar na mesma sala é como uma chamada local: instantâneo, garantido, você vê a cara do outro. Mandar uma carta é a rede: pode se perder no caminho, demora, pode ser aberta por estranhos, o endereço pode ter mudado, e você não sabe se chegou até receber resposta. Quem programa sistemas distribuídos como se ainda estivesse na mesma sala está mandando cartas e esperando a resposta imediata de uma conversa — e se frustra toda vez.

01Conceito

As oito, e o que cada uma cobra

1. A rede é confiável — pacotes se perdem e conexões caem; sem retry, a operação some. 2. A latência é zero — cada salto custa milissegundos, e o N+1 de chamadas remotas vira segundos. 3. A banda é infinita — payloads gordos entopem o link e a conta. 4. A rede é segura — assuma hostil: autentique e cifre.

5. A topologia não muda — IPs e rotas mudam; não fixe endereço. 6. Há um administrador só — vários donos com prioridades diferentes exigem contratos versionados. 7. O custo de transporte é zero — serializar e mover dado gasta CPU e dinheiro. 8. A rede é homogênea — protocolos e versões divergem entre as pontas.

As três primeiras são as que mais derrubam sistema, porque são invisíveis no desenvolvimento. Com o servidor em localhost, a rede *é* confiável, a latência *é* quase zero e a banda *é* praticamente infinita — então o código ingênuo passa em todo teste e só falha quando o tráfego cruza a internet de verdade. É a origem do clássico 'na minha máquina funciona'.

A oitava falácia — 'a rede é homogênea' — foi a que Gosling acrescentou depois, e é a mais sutil: mesmo que tudo pareça HTTP e JSON, as pontas divergem em versões de protocolo, formatos de data, codificação, limites de tamanho. É o que torna 'contratos versionados' (a falácia 6) e 'validação na borda' não um luxo, e sim a única forma de duas partes que evoluem separadas continuarem se entendendo.

O antídoto não é decorar a lista, é a postura: toda chamada remota é uma aposta que pode falhar, demorar ou voltar diferente. Daí vêm as práticas concretas do catálogo — timeout para não esperar para sempre, retry com backoff para as falhas transitórias, idempotência para o retry ser seguro, e o CAP para o que fazer quando a rede parte de vez.

Código que assume rede local

Compila, roda, passa no code review. E quebra depois.

O código chama serviços remotos em sequência, um por item num laço, sem timeout e sem tratar falha de rede. Roda instantâneo no desenvolvimento, onde tudo é localhost, e passa em todos os testes — até ir para produção, onde cada chamada é uma travessia de rede e o laço vira segundos de espera que travam a tela inteira.

// Codigo escrito como se a rede fosse confiavel e a latencia, zero.
async function montarTela(userId: string) {
  const perfil = await api.getPerfil(userId);       // 1 chamada
  const pedidos = await api.getPedidos(userId);      // 2
  const enderecos = [];
  for (const p of pedidos) {                         // e agora o N+1:
    enderecos.push(await api.getEndereco(p.enderecoId)); // +N chamadas
  }
  return { perfil, pedidos, enderecos };
}

// No dev, com o servidor em localhost, isso responde em milissegundos.
// Em producao, cada await e uma travessia de rede de dezenas de ms.
// 1 + 1 + N chamadas SEQUENCIAIS viram segundos de espera.
// E nenhuma tem timeout: se uma trava, a tela inteira trava com ela.

O que quebra

  • Em produçãoAs chamadas sequenciais somam a latência real da rede, e uma tela que abria na hora passa a levar segundos para carregar.
  • Com muitos itensO laço faz uma chamada remota por item (o N+1), então o tempo cresce linearmente com os dados e estoura sob volume.
  • Quando uma chamada travaSem timeout, uma dependência lenta segura a requisição inteira, e o recurso fica preso esperando uma resposta que pode não vir.

A saída: Trate a rede como o que ela é: coloque timeout em toda chamada, agrupe as requisições (um endpoint que traz endereços em lote, ou paralelize com Promise.all em vez do laço sequencial) e trate erro de rede como caso esperado, com retry e um estado de falha na tela.

03No mundo real

Como isso aparece na prática

A tela que abria no dev e travava em produção

Cenário

Uma tela montava dados chamando três serviços em sequência e mais um por item de uma lista. No desenvolvimento, com tudo em localhost, abria instantânea; em produção levava oito segundos e às vezes estourava o timeout do navegador.

Como o padrão entra

As chamadas por item viraram uma única requisição em lote, o restante passou a rodar em paralelo com Promise.all, e cada chamada ganhou timeout com um estado de falha na interface.

Trade-off: O backend ganhou um endpoint de lote a mais para manter. Em troca, a tela voltou a abrir em milissegundos e deixou de travar quando um serviço ficava lento.

O contrato que quebrou sem ninguém mudar o código

Cenário

Uma integração com o serviço de outro time parou de funcionar de um dia para o outro. Ninguém do lado consumidor havia mexido em nada — o outro time subiu uma versão que mudou o formato de um campo de data.

Como o padrão entra

O contrato passou a ser versionado explicitamente e validado na borda: mudanças incompatíveis exigem uma versão nova, e o consumidor rejeita o que não bate com o schema esperado.

Trade-off: Passou a haver o custo de manter versões de contrato e validar toda resposta. Em troca, uma mudança unilateral do outro lado deixou de derrubar a integração em silêncio.

04Cuidado

Onde isto dá errado

  1. Validar só no localhost

    No desenvolvimento a rede é confiável, rápida e larga, então o código ingênuo passa. As três primeiras falácias só aparecem quando o tráfego cruza a internet — e aí viram incidente, não bug de teste.

  2. Chamada remota dentro de um laço

    Tratar chamada de rede como chamada local produz o N+1: uma requisição por item. O que era uma tela virou mil idas ao servidor, e a latência que era invisível vira o gargalo.

  3. Tratar erro de rede como exceção rara

    Falha de rede não é o caso excepcional, é o caso esperado. Código que só tem o caminho feliz e deixa o erro estourar para o usuário está apostando contra a primeira falácia — e perde.

05Implementação

As oito falácias e o antídoto

// As 8 falacias sao suposicoes que todo mundo faz e que a rede
// desmente, uma a uma, sempre no pior momento:
//
//   1. A rede e confiavel.          -> pacotes se perdem; precisa de retry.
//   2. A latencia e zero.           -> cada hop custa; cuidado com N+1.
//   3. A banda e infinita.          -> payload gordo entope o link.
//   4. A rede e segura.             -> assuma hostil; autentique e cifre.
//   5. A topologia nao muda.        -> IPs e rotas mudam; nao fixe endereco.
//   6. Ha um administrador so.       -> varios donos; versione contratos.
//   7. O custo de transporte e zero. -> serializar/mover dado custa CPU e $.
//   8. A rede e homogenea.          -> protocolos e versoes divergem.

// O antidoto pratico: timeout + retry com backoff em TODA chamada remota,
// tratar erro de rede como caso esperado (nao excecao rara),
// e nunca desenhar um loop que faz uma chamada remota por item.
06Decisão

Quando usar × quando evitar

Use quando

3
  • Ao desenhar qualquer integração entre processos separados por rede.
  • Como checklist em revisão de código que chama serviços remotos.
  • Ao diagnosticar por que algo rápido no dev arrasta em produção.

Evite quando

2
  • Dentro de um processo único, sem rede entre as partes.
  • Como pretexto para blindar contra falhas irrelevantes ao contexto.

Testar o que ficou

3 armadilhas de As 8 falácias dos sistemas distribuídos viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • retry sem timeoutAssumir que 'a rede é confiável' produz o código que espera para sempre — a primeira falácia batendo na porta.
  • app rápido no dev, lento em produção'A latência é zero' desmorona quando o servidor sai de localhost para o outro lado do país.
  • N+1 de chamadas remotasTratar chamada remota como chamada local ('o custo de transporte é zero') gera o loop que faz mil requisições.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// A rede NÃO é confiável — declare timeout.
await fetch(url, { signal: AbortSignal.timeout(2000) });

O que ele cobra

Nenhum salto a mais entre a chamada e o efeito.

  • Levá-las a sério adiciona timeout, retry, tratamento de erro e observabilidade a cada chamada remota
  • Cada falácia mitigada é mais código e mais casos de teste do que a versão ingênua que as ignora

Não vale se: tudo roda no mesmo processo, sem rede no meio — aí as falácias não se aplicam e proteger contra elas é peso morto.

Onde isto aparece

Continue por aqui