Microsserviços - Parabéns trocou memória por latência de rede
Microsserviços. A palavra mágica que faz gente que nunca mediu latência na vida achar que vai ganhar escalabilidade, resiliência e paz interi
Tem gente que acha que microsserviços são só "serviços menores". Outros lunáticos dizem que um endpoint, um banco e um sonho.
Há quem afirme que é só quebrar o monólito em pedaços, colocar um HTTP entre eles, e pronto, agora você tem uma arquitetura moderna, escalável e linda no diagrama do PowerPoint.
Mas o que você fez, na prática, foi trocar uma chamada de memória por uma chamada de rede.
E rede não é memória. Rede é lenta e falha. Rede tem overhead em cada camada que você nem percebe. Esse overhead parece pequeno quando você olha uma chamada isolada.
- DNS? 0.1ms.
- TCP handshake? 0.5ms.
- TLS? 1ms.
- Serialização? 0.3ms.
- Header HTTP? 0.2ms.
- Parsing? 0.2ms.
Tudo baratinho, quase um cafezinho por camada. Aí você soma tudo e multiplica pelo caos.
Multiplica pelos hops. Multiplica por RPS. Multiplica pela vida real, aquela parte onde cache não tá quente, pool satura, deploy recicla instância, sidecar inventa de respirar pesado e o p99 vira um monstro.
Em 1994, Peter Deutsch e companhia já tinham escrito as 8 falácias da computação distribuída. Trinta anos depois, a indústria continua fazendo a mesma besteira, só que com nomes gourmet: service mesh, API gateway, sidecar proxy. O mesmo tropeço, só que com observabilidade e um bill de cloud mais caro.
De memória pra rede: parabéns, você comprou 12 etapas de sofrimento
No monólito você chama um método e pronto:
processPayment(order);
O CPU acessa a memória. Pega os dados. Executa a lógica. Retorna. Tempo total: 0.001ms, um microssegundo. O dado tá ali. Não saiu do processo. Não pediu licença pra ninguém.
Agora no "maravilhoso universo distribuído" você faz isso:
await httpClient.PostAsync("http://payment-service/api/process", content);
Essa linha não é código. É um contrato com o caos.
O hostname precisa resolver pra um IP (DNS). Aí vem o three-way handshake do TCP. Se usa HTTPS (e usa, né?), mais round-trips pro TLS. Aí monta os headers HTTP, serializa o objeto C# pra JSON, transmite os bytes, o outro lado deserializa, reconstrói o objeto, processa a lógica, serializa a resposta, manda de volta, você deserializa de novo, valida, faz o mapping.
São 12 etapas pra fazer o que antes era uma chamada de função. E ainda tem gente que monta o powerpoint para dizer que agora ficou mais "simples" e escalavel.
Eu já fiz essa graça. Já quebrei monólito em microsserviços porque era “melhor prática”. Ficou lindo no diagrama. Na prática uma operação que era 2ms virou 45ms e eu passei semanas fingindo surpresa.
Anatomia do overhead: cada milissegundo que você não vê
Vamos botar números, porque fé não reduz p99.
DNS resolution: 0.1ms (cached)
Você chama http://payment-service/api/process. Esse hostname precisa virar um IP. Se tiver cacheado, 0.1ms. Se não tiver? 1-10ms. Service discovery tossiu? 50 a 100ms.
E você continua achando que DNS é "instantâneo" porque na sua maquina funciona e é rápido.
TCP handshake: 0.5ms
Three-way handshake. SYN do cliente, SYN-ACK do servidor, ACK do cliente. Dentro do mesmo datacenter, 0.5ms. Entre regiões? 10-50ms.
"Mas eu uso connection pooling!" Sim, e funciona. Até a connection expirar, até o pool encher, até um novo deploy reciclar as instâncias. Aí o handshake volta.
TLS handshake: 1.0ms
Se você usa HTTPS entre serviços (e deveria), adicione 1ms pra negociação de cipher suite, troca de certificados, estabelecimento da sessão. TLS 1.3 é mais rápido que 1.2, mas não é zero.
"Mas dentro do cluster eu posso usar HTTP puro!" Pode. E qualquer pessoa com acesso ao cluster lê seus dados em trânsito. Sua escolha.
HTTP headers: 0.2ms (e alguns KB de autoestima)
Header não é só Content-Type. É Authorization, Accept, User-Agent, tracing, uns X-Qualquer-Coisa inventados, e quando tem mesh tipo Istio você ganha 2 a 5KB fácil.
Parece pouco, mas a 10.000 requests por segundo, são 50MB/s só de headers.
Serialização: 0.3ms
JSON é o imposto do pecado. Seu objeto Order precisa virar JSON. JsonSerializer.Serialize(order) leva 0.3ms pra um objeto médio com 20-30 propriedades. Objetos complexos com nested collections? 0.5-1ms fácil.
"Usa protobuf então!" Sim, por que não né? protobuf é mais rápido (~0.1ms) e menor em bytes. Mas agora você precisa manter .proto files, gerar código, e lidar com versionamento de schema. Bate aqui na minha mão, belo trade-off.
Transmissão de rede: 0.5ms
Os bytes viajam pelo wire. Dentro do mesmo rack, 0.1ms. Mesmo datacenter, 0.5ms. Entre availability zones, 1-2ms. Entre regiões, 10-100ms.
É aqui que você descobre que serviço distribuido não é só powerpoint bonito e conceito acadêmico, é os hidden cost da Nuvem vindo na fatura do cartão de crédito do seu chefe.
Deserialização + parsing: 0.5ms
O lado receptor lê os bytes, deserializa o JSON, valida o schema, mapeia pro modelo interno. Mais 0.5ms no caso otimista.
A conta
Soma tudo pra uma chamada dentro do mesmo datacenter:
DNS resolution: 0.1ms (cached)
TCP handshake: 0.5ms (se não reusou conexão)
TLS handshake: 1.0ms (se HTTPS)
HTTP headers: 0.2ms
Serialização (ida): 0.3ms
Transmissão (ida): 0.5ms
Processamento: 0.5ms
Serialização (volta): 0.3ms
Transmissão (volta): 0.5ms
Parsing (resposta): 0.2ms
─────────────────────────────
Total: ~4.1ms
Uma chamada de função: 0.001ms. Uma chamada HTTP: ~4ms.
4.000x mais lento. Pra mesma operação lógica.
Sim, é nessa ordem de grandeza. E ainda teve gente batendo palma porque "agora escala".
E agora? Quando o overhead vale a pena
Eu não tô dizendo "nunca use microsserviços". Tô dizendo: entenda o custo antes de pagar.
Microsserviços fazem sentido quando você tem 50+ engenheiros no mesmo produto e o merge conflict diário custa mais que a latência de rede. Ou quando possui aplicaçõoes genuinamente diferentes e que precisam escalar de maneira independente, tipo seu serviço de encoding de vídeo que não tem nada a ver com o serviço de busca. Ou quando compliance exige isolamento hard de dados de saúde, financeiros, PII. Ou quando tecnologias diferentes são necessárias, tipo ML model em Python, API em C#, stream processing em Go.
Se nenhuma dessas condições existe, o que você tem é um monólito distribuído. Todo o overhead de rede e nenhuma vantagem organizacional.
A alternativa que pouca gente menciona: o modular monolith. Módulos com boundaries claros, interfaces definidas, mas rodando no mesmo processo. Zero overhead de rede. ACID transactions. Um stack trace quando algo quebra. E quando (e se) você precisar extrair um módulo pra um serviço separado, a interface já existe.
Peter Deutsch e seus colegas não escreveram as 8 falácias pra assustar ninguém. Escreveram pra que você tome decisões informadas. Pra que, quando alguém propuser "vamos quebrar em microsserviços", você pergunte: quanto overhead de rede estamos dispostos a aceitar? E o que ganhamos em troca?
Se a resposta for "não sei", você não tá pronto pra microsserviços. E tudo bem. Monólitos bem feitos sustentam negócios bilionários. O Shopify é um monólito. O Stack Overflow rodou anos em dois servidores.
Rede não é memória. Nunca vai ser. Cada chamada HTTP entre serviços é uma decisão arquitetural com custo real, mensurável, composto. Meça antes de migrar.
Peter Deutsch avisou em 1994. É 32 anos depois você ainda não aprendeu.

