▶ Slides ← Módulo
Módulo 2 · Ciclo Comum · IN02

Mergulhando nas Redes

Aula 11 — Material de Leitura Aprofundado

1. Por que redes para o backend

Tem uma piada antiga entre devs: "rede sempre cai". A piada nasce de uma verdade incômoda — a rede é a parte mais imprevisível de qualquer sistema distribuído. Pacotes se perdem, latências variam, DNS atrasa, certificados expiram, NATs fecham conexões, ISPs trocam rotas, e tudo isso acontece sem o seu código saber. Você pode escrever a melhor lógica de domínio do mundo, mas se ela trafega por uma rede mal compreendida, vai falhar de formas que parecem mágicas.

Esta aula é uma imersão na pilha de redes pela ótica do desenvolvedor de backend. Não vamos virar especialistas em BGP nem montar VLANs. Vamos entender o suficiente para tomar boas decisões: por que uma requisição às vezes leva 2 segundos para começar; por que o navegador faz um preflight CORS antes do POST; por que Cache-Control: max-age=3600 pode ser uma bomba-relógio; por que o time DevOps reclama quando você não respeita idempotência; o que mudar quando seu app vai pra trás de uma CDN.

A premissa desta aula

Vamos assumir o stack do projeto: TypeScript + Node.js + Express + EJS servindo HTML por SSR e endpoints JSON por API. Sempre que houver código, ele vai estar em TypeScript — e os exemplos focam em decisões que afetam o nosso código direto, não em RFCs por completude.

O que distingue um dev backend "que sabe redes"

Não é decorar o tamanho do header IP. É saber, na hora certa: que fetch sem timeout vai segurar workers do servidor; que POST sem idempotência num retry pode duplicar pedidos no banco; que Cache-Control sem private pode vazar dados de um usuário para outro via CDN; que 503 é diferente de 500 e que clientes inteligentes tratam os dois diferente. É sobre intuição calibrada, não memorização.

2. Modelo TCP/IP — quatro camadas

Antes do TCP/IP, o mundo aprendia redes pelo modelo OSI de 7 camadas. Hoje, o que efetivamente roda na Internet é o modelo TCP/IP de 4 camadas. As quatro estão empilhadas: cada camada superior usa serviços da inferior, sem precisar saber como ela faz seu trabalho. Isso é encapsulamento — o mesmo princípio que separa controllers, services e repositories no MVC.

Application HTTP, DNS, TLS, SMTP
Transport TCP, UDP, QUIC
Internet IP (v4/v6), ICMP, BGP
  • Link: cabos, Wi-Fi, MAC addresses. Move bits entre dois nós fisicamente conectados (mesmo segmento).
  • Internet: IP (Internet Protocol). Roteia pacotes entre redes — sabe como chegar do seu Wi-Fi de casa ao servidor em São Paulo passando pelo provedor.
  • Transport: TCP ou UDP. Adiciona portas, controle de erro e (no caso de TCP) entrega ordenada.
  • Application: HTTP, DNS, SMTP, gRPC. O que sua aplicação realmente fala.

Cada camada adiciona seu próprio header ao pacote. Quando seu Express envia { "ok": true }, isso passa por: HTTP body → envelope HTTP → segmento TCP → datagrama IP → quadro Ethernet. No outro lado, descasca tudo na ordem inversa. Saber que isso existe ajuda a entender ferramentas como tcpdump, wireshark e a maioria dos perfis de erro de rede.

OSI 7 camadas — quando ainda é útil

O modelo OSI (Físico, Enlace, Rede, Transporte, Sessão, Apresentação, Aplicação) sobrevive como vocabulário. Quando alguém diz "isso é problema de camada 7" (lê-se "camada de aplicação", como HTTP) ou "load balancer L4 vs L7", está falando OSI. Para o dia a dia de backend, o TCP/IP de 4 camadas é mais direto.

3. TCP vs UDP — confiável ou veloz

Na camada de transporte, dois protocolos disputam a maioria do tráfego. TCP é confiável, ordenado, com controle de fluxo e congestionamento. UDP é leve, sem garantia de entrega nem de ordem. A escolha depende do que você está mandando.

TCPUDP
Conexão3-way handshake antesSem handshake — manda direto
Garantia de entregaSim (retransmite perdidos)Não (perdeu, perdeu)
OrdemGarantidaPacotes podem chegar fora de ordem
Controle de congestãoSim (slow-start, AIMD)Não — você implementa se quiser
Casos de usoHTTP/1, HTTP/2, SSH, bancoDNS, streaming, jogos, VoIP
Custo da primeira conexão~1 RTT só pro handshake0 RTT

O 3-way handshake do TCP

Antes de transmitir dados, TCP estabelece uma conexão em três passos:

  1. SYN — cliente envia "quero conversar, número de sequência X".
  2. SYN-ACK — servidor responde "ok, e meu número é Y, confirmo X+1".
  3. ACK — cliente confirma "Y+1 recebido". Conexão aberta.

Cada passo é um RTT (Round-Trip Time, latência ida-e-volta). Em redes próximas, isso é ~5ms. Entre Brasil e Estados Unidos, ~150ms. Multiplicado por dezenas de requisições, vira segundos visíveis ao usuário — daí toda a obsessão moderna por connection reuse, keep-alive e protocolos sem handshake.

Quando UDP é a melhor escolha

UDP brilha quando perder um pacote ocasional importa menos que latência. Em DNS, perder uma resposta é só perguntar de novo — abrir TCP para cada lookup atrasaria toda a navegação. Em streaming de vídeo, perder um frame ruim é melhor que pausar a tela esperando retransmissão. Em jogos online, posição do inimigo daqui a 50ms é mais útil que posição certa daqui a 500ms.

HTTP/3 e QUIC — UDP escondendo TCP

HTTP/3, padronizado em 2022, é HTTP sobre QUIC, e QUIC é um protocolo de transporte que roda em cima de UDP. Em vez de pegar TCP no kernel (lento de evoluir, fixo no S.O.), QUIC implementa em user-space: sem head-of-line blocking, com 0-RTT de retomada de conexão, criptografia obrigatória (TLS 1.3 embutido). É UDP com confiabilidade reescrita do zero — e é o que move boa parte do tráfego do Google e do Cloudflare hoje.

Você quase nunca escolhe TCP ou UDP diretamente

No dia a dia, sua app usa HTTP (TCP) ou WebSocket (TCP) ou banco (TCP). UDP só aparece quando você lida com DNS, NTP, multicast, ou stack próprio (gRPC sobre HTTP/2 segue TCP; WebRTC usa UDP). A camada que você escolhe é tipicamente a de aplicação.

4. IP, NAT, portas e sockets

IPv4 vs IPv6

O IPv4 usa endereços de 32 bits (~4,3 bilhões), escritos em formato decimal pontilhado: 192.168.0.1. Esse espaço acabou em 2011. IPv6 usa 128 bits (~3,4×10³⁸ endereços) em hexadecimal com dois-pontos: 2001:0db8:85a3::8a2e:0370:7334. A maior parte da Internet hoje convive com os dois — seu servidor escuta nos dois, seu provedor móvel já pode estar lhe entregando IPv6 nativo, e seu fetch sequer percebe a diferença.

NAT — por que IP local não é IP público

Seu computador em casa tem um IP da rede local (algo como 192.168.0.42) que não existe na Internet. O roteador faz NAT (Network Address Translation): quando você manda um pacote, ele troca o IP de origem pelo IP público dele e guarda numa tabela "esse fluxo veio de 192.168.0.42:54321". Quando a resposta volta, ele consulta a tabela e devolve para o computador certo.

Para o backend, NAT importa porque: (1) você não pode confiar cegamente que req.ip é o IP do usuário (pode ser o IP do NAT corporativo); (2) conexões NAT têm tempo de vida limitado — se ficarem ociosas, o roteador "esquece" do mapeamento e fecha; (3) servidores backend nunca rodam atrás de NAT residencial — precisam de IP público ou de um proxy reverso.

Portas e sockets

Um IP identifica uma máquina; uma porta identifica um processo dentro dela. A combinação IP:porta + protocolo é um socket. Seu Express escutando em 0.0.0.0:3000 aceita conexões em qualquer IP da máquina, na porta 3000. Quando um cliente se conecta vindo de 200.10.5.7:54912, o sistema operacional cria uma conexão única identificada pela quíntupla (TCP, 200.10.5.7, 54912, IP-do-servidor, 3000).

Well-known ports

PortaServiçoNotas
80HTTPTexto puro — em desuso para acesso direto
443HTTPSHTTP sobre TLS — padrão atual
22SSHAcesso remoto a servidores
5432PostgreSQLO banco do nosso projeto
3306MySQL
6379RedisCache em memória
27017MongoDB
53DNSUDP (e TCP em consultas grandes)

Portas abaixo de 1024 são "privilegiadas" — em Linux, só processos root conseguem escutar nelas por padrão. Por isso seu Express em dev usa 3000, e o produção fica atrás de um Nginx (que escuta na 443 e proxya pra 3000).

5. DNS — a agenda da Internet

Pessoas digitam inteli.edu.br; máquinas só falam IP. DNS (Domain Name System) é o protocolo que traduz nomes em IPs — uma agenda de telefones distribuída, hierárquica e cacheada em mil lugares.

Tipos de registros (records)

TipoPara que serveExemplo de valor
AMapeia nome para IPv4200.20.0.10
AAAAMapeia nome para IPv62001:db8::1
CNAMEApelido para outro nomewww → inteli.edu.br
MXServidor de e-mail do domínio10 mx.google.com
TXTTexto livre (SPF, verificação de domínio)"v=spf1 include:..."
NSServidores de nome autoritativosns1.cloudflare.com
SOAMetadados da zona (TTL, serial)

Como acontece uma resolução

Você digita inteli.edu.br no navegador. O caminho médio:

  1. Navegador checa seu próprio cache (TTL local).
  2. Sistema operacional checa cache do S.O.
  3. S.O. pergunta ao resolver configurado (provedor, ou 1.1.1.1, ou 8.8.8.8).
  4. Resolver, se não tiver cache, pergunta a um root server (".") quem cuida de .br.
  5. Root devolve os servidores de TLD (Top-Level Domain) .br.
  6. Resolver pergunta ao TLD .br quem é autoritativo para inteli.edu.br.
  7. Resolver pergunta ao autoritativo o registro A. Recebe o IP.
  8. Resolver guarda em cache pelo TTL e devolve para o S.O.

Esse vai-e-volta acontece em milissegundos quando há cache, e em centenas de ms quando não há. Por isso o primeiro acesso a um site novo "demora" — DNS frio.

TTL e propagação

Cada registro DNS tem um TTL (Time To Live) em segundos: por quanto tempo um cache pode reusar a resposta. Um TTL de 3600 (1 hora) significa "essa resposta é válida por uma hora; depois, pergunta de novo". TTL alto = poucas consultas, mas mudanças demoram a propagar. TTL baixo = mudanças rápidas, mas DNS lento. A prática consagrada é: TTL alto (1h-24h) em produção estável; abaixar para 60s antes de uma mudança de IP, fazer a mudança, e voltar ao TTL alto.

Bash — investigando DNS
# dig: o canivete suíço do DNS
dig inteli.edu.br
dig inteli.edu.br MX
dig inteli.edu.br TXT
dig +trace inteli.edu.br      # mostra a cadeia root → TLD → autoritativo

# nslookup: alternativa portátil
nslookup inteli.edu.br

# forçando um resolver específico
dig @1.1.1.1 inteli.edu.br
dig @8.8.8.8 inteli.edu.br
DNS é o suspeito mais comum em "está lento"

Quando uma página demora para abrir e ninguém entende por quê, DNS é o primeiro lugar para olhar. Resolvers ruins, TTLs baixíssimos, registros encadeados em CNAMEs profundos — qualquer um deles adiciona centenas de ms antes do primeiro byte chegar. chrome://net-internals/#dns e resolvectl statistics ajudam a investigar.

6. HTTP — métodos, status e headers

HTTP é o protocolo de aplicação da web. Texto puro (em HTTP/1.1), request/response, sem estado. Cada requisição é um pacote auto-contido que diz "fazer essa ação nesse recurso" e recebe uma resposta com código numérico + corpo.

Métodos (verbos)

MétodoSemânticaIdempotentePode ser cacheado
GETLer recursoSimSim
POSTCriar / executar açãoNão (em geral)Raramente
PUTSubstituir recurso inteiroSimNão
PATCHAtualizar parcialDependeNão
DELETEApagar recursoSimNão
HEADComo GET, mas só headersSimSim
OPTIONSCapacidades / CORS preflightSim

Status codes — recap rápido

  • 1xx informativo (raramente usado)
  • 2xx sucesso — 200 OK, 201 Created, 204 No Content
  • 3xx redirect — 301 Moved Permanently, 302 Found, 304 Not Modified
  • 4xx erro do cliente — 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 429 Too Many Requests
  • 5xx erro do servidor — 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

A diferença entre 500 e 503 é importante para clientes: 500 "tem bug, não retente cegamente"; 503 "estou ocupado, retente daqui a pouco". Bibliotecas modernas de retry usam essa distinção.

Headers que você precisa conhecer

HeaderPara que serve
Content-TypeTipo MIME do corpo (application/json, text/html)
Content-LengthTamanho do corpo em bytes
AuthorizationToken de autenticação (Bearer ...)
Cache-ControlPolítica de cache (max-age, no-store, etc.)
ETagHash do conteúdo para validação condicional
Last-ModifiedData da última modificação do recurso
AcceptTipos que o cliente aceita
User-AgentIdentificação do cliente
X-Forwarded-ForIP original quando há proxy/CDN entre cliente e servidor
TypeScript — fetch com headers explícitos
const res = await fetch('https://api.exemplo.com/users', {
  method: 'POST',
  headers: {
    'Content-Type':  'application/json',
    'Authorization': `Bearer ${token}`,
    'Accept':        'application/json',
  },
  body: JSON.stringify({ name: 'Ada', email: 'ada@x.com' }),
});

if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

7. HTTP/1.1 vs HTTP/2 vs HTTP/3

HTTP/1.1HTTP/2HTTP/3
TransporteTCPTCP + TLSQUIC (UDP) + TLS 1.3
FormatoTextoBinário (frames)Binário (frames)
MultiplexaçãoNão — 1 por conexãoSim — vários streams na mesma conexãoSim, sem head-of-line blocking
Header compressionNãoHPACKQPACK
Server pushNãoSim (pouco usado, agora obsoleto)Removido
0-RTT resumeNãoNãoSim

O grande salto de HTTP/1.1 para HTTP/2 foi multiplexação: numa única conexão TCP, vários requests/responses voam em paralelo (em "streams"), em vez de serem enfileirados. Isso elimina a necessidade do truque antigo de "domain sharding" (espalhar assets por subdomínios para abrir mais conexões TCP).

HTTP/3 resolve um problema sutil de HTTP/2: head-of-line blocking no nível do TCP. Se um pacote TCP é perdido, todos os streams ficam parados esperando retransmissão — porque o TCP entrega em ordem. QUIC entrega cada stream independentemente, então perder um pacote afeta só um stream. Em redes ruins (móvel, Wi-Fi público), HTTP/3 percebe ganhos visíveis.

Você (provavelmente) não escolhe a versão

Node.js falar HTTP/2 nativamente é fácil (http2.createSecureServer), mas a maioria dos projetos coloca um Nginx, Caddy ou Cloudflare na frente. Esses já negociam HTTP/2 ou HTTP/3 com o cliente automaticamente — seu Express continua falando HTTP/1.1 com o proxy. É o suficiente para 95% dos casos.

8. HTTPS e TLS — handshake conceitual

HTTPS é HTTP sobre TLS (Transport Layer Security, sucessor do SSL). TLS adiciona três coisas ao tráfego HTTP: confidencialidade (ninguém vê o conteúdo no caminho), integridade (ninguém altera no caminho) e autenticidade (você sabe que está falando com o servidor verdadeiro). Sem TLS, qualquer roteador entre você e o servidor pode ler e modificar suas requisições.

O handshake — versão simplificada

  1. ClientHello — cliente diz "aqui estão as versões TLS, suítes de cifras e extensions que sei usar; meu random é X".
  2. ServerHello + Certificate — servidor escolhe versão e suíte; envia seu certificado X.509.
  3. Cliente valida o certificado contra a CA raiz que conhece (cadeia de confiança).
  4. Key exchange — usando ECDHE (Elliptic Curve Diffie-Hellman Ephemeral), ambos derivam um segredo compartilhado sem transmiti-lo.
  5. Finished — ambos confirmam usando o segredo. A partir daqui, todo dado é criptografado.

Em TLS 1.3 (a versão atual), tudo isso acontece em 1-RTT — e em retomadas, em 0-RTT (o cliente já manda dados criptografados na primeira mensagem, usando o segredo da sessão anterior). É uma das razões para HTTP/3 ser tão rápido.

Por que ECDHE em vez de RSA

Antigamente, o cliente cifrava o segredo de sessão com a chave pública RSA do servidor e mandava. Funciona — mas se a chave RSA do servidor for comprometida no futuro, alguém que tenha gravado todo o tráfego antigo pode descriptografar tudo. ECDHE usa chaves efêmeras (uma por handshake), garantindo forward secrecy: comprometer a chave do servidor amanhã não te dá acesso ao tráfego de hoje.

9. Certificados e Let's Encrypt

Um certificado X.509 é um documento assinado por uma autoridade certificadora (CA) que diz: "a chave pública desse domínio é essa". Seu navegador confia em algumas dezenas de CAs raiz de fábrica (Let's Encrypt, DigiCert, Sectigo, etc.). Se um certificado é assinado por uma CA confiável e o domínio bate, o cadeado verde aparece.

Let's Encrypt — TLS de graça

Antes de 2015, certificados custavam dezenas de dólares por ano. Let's Encrypt mudou isso emitindo certificados gratuitos via protocolo ACME (Automatic Certificate Management Environment). O fluxo:

  1. Você roda certbot (ou caddy, ou traefik) no seu servidor.
  2. Ele pede certificado para seusite.com.
  3. Let's Encrypt diz: "prove que você controla esse domínio — coloque esse arquivo em http://seusite.com/.well-known/acme-challenge/...".
  4. Você (ou o cliente ACME) coloca o arquivo. Let's Encrypt verifica e emite o cert.
  5. Cert vale 90 dias; o cliente ACME renova automaticamente a cada 60.

Resultado: HTTPS deixou de ser um "extra premium" e virou o padrão. Hoje, servir HTTP puro num site público é considerado má prática — Chrome inclusive marca como "Not Secure".

Mixed content quebra HTTPS

Se sua página HTTPS importa um <script src="http://..."> (sem S), o navegador bloqueia silenciosamente. O cadeado fica amarelo ou some. Sempre use URLs relativas ao protocolo (//cdn...) ou explicitamente HTTPS para todos os recursos.

10. Cache HTTP — Cache-Control

Cache é a otimização que mais entrega resultado por linha de configuração. O header Cache-Control diz a navegadores, CDNs e proxies por quanto tempo (e como) eles podem reusar uma resposta sem perguntar de novo ao servidor.

Diretivas que importam

DiretivaSignificado
max-age=NCache fresco por N segundos
s-maxage=NMesma coisa, mas só para CDNs/proxies (sobrescreve max-age)
publicQualquer cache (browser, CDN) pode armazenar
privateSó o cache do browser do usuário — CDN não armazena
no-cachePode armazenar, mas SEMPRE revalidar antes de usar
no-storeNão armazenar de jeito nenhum
must-revalidateQuando expirar, não usar versão velha mesmo se servidor estiver fora
immutablePromete nunca mudar — browser pula validação até max-age
TypeScript — Cache-Control no Express
// página HTML pública (landing page)
app.get('/', (req, res) => {
  res.setHeader('Cache-Control', 'public, max-age=300');
  res.render('home');
});

// página personalizada (depende de usuário logado) — NUNCA public
app.get('/perfil', (req, res) => {
  res.setHeader('Cache-Control', 'private, no-cache');
  res.render('perfil', { user: req.user });
});

// estáticos versionados (com hash no nome) — cache eterno
app.use('/static', express.static('public', {
  maxAge: '1y',
  immutable: true,
}));

// API que pode mudar — sem cache
app.get('/api/notificacoes', (req, res) => {
  res.setHeader('Cache-Control', 'no-store');
  res.json(notificacoes);
});

Camadas de cache

  • Browser — cache local do usuário. Mais perto, mais rápido.
  • CDN — armazena no edge, próximo geograficamente do usuário.
  • Reverse proxy (Nginx, Varnish) — cache na sua infraestrutura, na frente do app.
  • App layer — Redis, Memcached, ou cache em memória do processo.
Cache-Control: public num endpoint autenticado é uma catástrofe

Imagine uma página de perfil retornando Cache-Control: public, max-age=3600 com a CDN na frente. A primeira requisição cacheia o perfil do usuário A. As próximas requisições — de qualquer outro usuário — recebem o perfil do A. Vazamento de dados em escala. Em endpoints que dependem de cookie/Authorization, sempre private ou no-store.

11. ETag e validação condicional

Cache puro tem um problema: o que fazer quando o cache expirou, mas o conteúdo não mudou? Baixar tudo de novo é desperdício. Validação condicional resolve: o cliente pergunta "ainda é a mesma versão?", e o servidor responde com 304 Not Modified e corpo vazio se sim.

ETag

O servidor envia, junto da resposta, um header ETag com um identificador da versão do conteúdo (geralmente um hash). Da próxima vez, o cliente envia If-None-Match com esse ETag. Se o servidor verificar que o conteúdo continua o mesmo, responde 304 sem corpo. Economia: zero bytes de payload.

HTTP — fluxo de ETag
# 1. Primeira requisição
GET /api/produtos HTTP/1.1

HTTP/1.1 200 OK
ETag: "a8f93b1c"
Cache-Control: max-age=60
Content-Type: application/json

[...corpo...]

# 2. Cache expirou; cliente revalida
GET /api/produtos HTTP/1.1
If-None-Match: "a8f93b1c"

HTTP/1.1 304 Not Modified
ETag: "a8f93b1c"
# sem corpo — cliente reusa o que tinha em cache

Last-Modified — alternativa baseada em data

O servidor envia Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT. O cliente, na próxima vez, manda If-Modified-Since: .... Se nada mudou desde então, 304. Tem precisão de 1 segundo (versus ETag que pode ser arbitrário) e não funciona bem se o conteúdo é gerado dinamicamente — daí ETag costuma ser preferido.

Express já implementa ETag automaticamente para respostas estáticas. Para JSON dinâmico, gere você mesmo:

TypeScript — ETag manual
import { createHash } from 'crypto';

app.get('/api/produtos', async (req, res) => {
  const produtos = await service.listar();
  const body = JSON.stringify(produtos);
  const etag = createHash('sha1').update(body).digest('hex');

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }
  res.setHeader('ETag', etag);
  res.setHeader('Cache-Control', 'public, max-age=60');
  res.type('application/json').send(body);
});

12. CDN — entregando do edge

Uma CDN (Content Delivery Network) é uma rede de servidores espalhados pelo mundo que armazena cópias do seu conteúdo perto fisicamente do usuário. Em vez do navegador em São Paulo buscar uma imagem no seu servidor de Virginia (latência ~150ms), busca no PoP (Point of Presence) da CDN em São Paulo (latência ~5ms).

Anatomia

  • Origem: seu servidor real. A CDN só consulta a origem em cache miss.
  • Edge / PoP: servidor da CDN próximo do usuário. É onde o cache vive.
  • Hit: edge tinha o conteúdo. Resposta em ms.
  • Miss: edge não tinha. Vai até a origem buscar e cacheia para os próximos.
  • Purge: você invalida deliberadamente o cache (após um deploy, por exemplo).

Quem domina o mercado

  • Cloudflare — popular para sites públicos; tem plano grátis robusto.
  • Fastly — querida do mundo SaaS por flexibilidade (VCL).
  • AWS CloudFront — integrado ao stack AWS.
  • Akamai — pioneira; muito usada por empresas grandes.

Para o backend dev, pôr CDN na frente da aplicação afeta o que escrever no Cache-Control: a CDN obedece s-maxage antes de max-age; respeita private (não cacheia); e geralmente respeita Vary: Cookie para não vazar dados entre usuários. Estáticos com hash no nome (app.a8f9b1.js) podem ter cache eterno (max-age=31536000, immutable) porque uma mudança gera novo nome.

CDN também faz outras coisas

CDN moderna não é só cache. Ela faz terminação TLS (HTTPS para clientes, mantém HTTP para origem), DDoS mitigation, WAF (Web Application Firewall), bot management, image optimization, edge functions (executar código no PoP). Para o usuário final, o IP que ele "vê" é o da CDN; o seu servidor real fica protegido.

13. Retry, backoff exponencial, jitter, timeout

Toda chamada de rede pode falhar. Conexão é interrompida, servidor está sobrecarregado, DNS expirou, certificado mudou. Como reagir? Há quatro técnicas que, juntas, transformam "falhou" em "se recuperou silenciosamente".

Timeout — sempre

Sem timeout, uma requisição pode pendurar para sempre. Em Node.js, isso significa um worker preso, uma conexão de banco esperando, um usuário olhando para a tela girando. Toda chamada externa deve ter timeout. O fetch nativo do Node aceita um AbortSignal:

TypeScript — fetch com timeout via AbortController
async function fetchComTimeout(url: string, ms = 3000) {
  const controller = new AbortController();
  const t = setTimeout(() => controller.abort(), ms);
  try {
    const res = await fetch(url, { signal: controller.signal });
    return res;
  } finally {
    clearTimeout(t);
  }
}

Retry com backoff exponencial

Tentar de novo imediatamente quando falhou ajuda em problemas transitórios (pacote perdido). Mas se o servidor está caído, dezenas de retries em milissegundos só pioram. Backoff exponencial aumenta o intervalo entre tentativas: 100ms, 200ms, 400ms, 800ms... — cada falha dobra a espera. Isso reduz pressão no servidor que está se recuperando.

Jitter — quebrando a sincronia

Imagine 1000 clientes errarem ao mesmo tempo (servidor reiniciou). Todos esperam exatamente 100ms e tentam de novo... ao mesmo tempo. Trovoada. Jitter adiciona uma variação aleatória: cada cliente espera entre 50ms e 150ms, espalhando os retries no tempo. Algumas implementações usam jitter "full" (delay aleatório de 0 a 2× o backoff teórico) — costuma ser ainda mais resiliente.

TypeScript — retry com backoff + jitter
async function retry<T>(
  fn: () => Promise<T>,
  tentativas = 3,
  baseMs = 100,
): Promise<T> {
  for (let i = 0; i < tentativas; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === tentativas - 1) throw err;
      const exp = baseMs * 2 ** i;        // 100, 200, 400...
      const jitter = Math.random() * exp;  // 0..exp
      await new Promise(r => setTimeout(r, exp + jitter));
    }
  }
  throw new Error('unreachable');
}

// uso
const data = await retry(() => fetchComTimeout('https://api.x.com/...', 3000));
Não retentar tudo

Retry só faz sentido para erros transitórios: 5xx, timeouts, ECONNRESET, ENOTFOUND. Erros de cliente (4xx) são definitivos: 400 Bad Request não vira 200 OK com mais tentativas. Retentar 401 ou 404 só desperdiça tempo e pode disparar rate limit.

14. Idempotência

Uma operação é idempotente quando executar N vezes produz o mesmo efeito que executar 1 vez. GET /users/42, PUT /users/42 {nome:"Ada"}, DELETE /users/42 — todas idempotentes. POST /pedidos {...}, em geral, NÃO é: dois POSTs criam dois pedidos.

Por que importa: retries só são seguros em operações idempotentes. Se a rede caiu depois do servidor processar, mas antes do cliente receber a resposta, retentar um POST pode duplicar o efeito. Cobrar um cartão duas vezes. Enviar o e-mail duas vezes. Criar o usuário duas vezes.

Idempotency-Key

O padrão de mercado para tornar POSTs idempotentes é o Idempotency-Key. O cliente gera um UUID, manda no header, e o servidor armazena. Se chega um POST com a mesma Idempotency-Key e mesmo corpo dentro de uma janela (ex.: 24h), o servidor retorna a resposta original sem re-executar a operação.

TypeScript — middleware de idempotência
import type { Request, Response, NextFunction } from 'express';

const store = new Map<string, { status: number; body: unknown }>();

export function idempotency(req: Request, res: Response, next: NextFunction) {
  const key = req.headers['idempotency-key'] as string | undefined;
  if (!key) return next();

  const hit = store.get(key);
  if (hit) return res.status(hit.status).json(hit.body);

  const origJson = res.json.bind(res);
  res.json = (body: unknown) => {
    store.set(key, { status: res.statusCode, body });
    return origJson(body);
  };
  next();
}

Em produção, o storage deve ser Redis ou Postgres (não Map em memória), e a chave precisa de TTL. Mas a ideia é essa: a primeira requisição executa e salva; as próximas, com a mesma chave, recebem a resposta cacheada.

15. Circuit breaker, bulkhead, fallback

Quando uma dependência externa fica lenta ou falha, ficar tentando piora tudo: cada request espera o timeout, workers ficam presos, a CPU sobe, a UX morre. Circuit breaker é o padrão para parar de tentar quando a coisa está claramente quebrada — e só voltar a tentar de tempos em tempos para descobrir se ela voltou.

Os três estados

  • Closed — funcionamento normal, requisições passam.
  • Open — taxa de falha passou do limite. Requisições falham imediatamente, sem chamar a dependência.
  • Half-open — depois de um intervalo, deixa passar uma requisição "de teste". Se passar, volta a Closed; se falhar, volta a Open.

Bulkhead

Como anteparas em navios: isolar pools para que uma dependência problemática não consuma todas as conexões. Por exemplo: dedicar 10 conexões para chamadas ao serviço X, 10 para Y, 30 para o banco — em vez de um pool único de 50 que o serviço X lento engole inteiro.

Fallback e degradação graciosa

Quando a dependência está fora, em vez de quebrar, devolver algo razoável. Exemplos: timeline do feed sem o serviço de recomendações? Mostre a timeline cronológica. Recomendações personalizadas falharam? Mostre as mais populares. Cache local com a última resposta válida? Use mesmo que esteja "velha".

Em Node, bibliotecas como cockatiel e opossum implementam circuit breaker, bulkhead, retry e timeout em uma API composável. Vale conhecer antes de escrever na mão.

Exemplo real — Netflix Hystrix

O padrão circuit breaker virou famoso com o Hystrix da Netflix (depois aposentado, mas conceitualmente vivo nos sucessores). A ideia explícita: "preferimos servir uma página com seções vazias a derrubar a página inteira porque um microserviço de comentários ficou lento". É o oposto da mentalidade "tudo ou nada" — e é o que mantém serviços grandes operando mesmo com partes da arquitetura em chamas.

16. Health checks — liveness vs readiness

Plataformas modernas (Kubernetes, ECS, Cloud Run) precisam saber se sua aplicação está saudável. Para isso, ela bate em endpoints chamados health checks. Há dois tipos, e confundi-los causa caos.

TipoPerguntaResposta a falha
Liveness"Esse processo está vivo?"Reiniciar o container
Readiness"Esse processo está pronto para receber tráfego?"Tirar do load balancer (não reinicia)

Liveness deve ser simples: respondeu OK = está vivo. Não chame banco, não chame Redis. Se você incluir o banco na liveness e o banco cai, o orquestrador reinicia toda a sua aplicação em loop — uma cascata desnecessária.

Readiness, sim, pode incluir dependências críticas: "o banco está respondendo?", "consigo ler o Redis?". Se essas falharem, o load balancer para de mandar tráfego, mas o processo continua rodando — quando a dependência voltar, ele volta sozinho ao pool.

TypeScript — liveness e readiness no Express
app.get('/healthz', (_req, res) => res.json({ status: 'ok' }));

app.get('/ready', async (_req, res) => {
  try {
    await pool.query('SELECT 1');
    res.json({ status: 'ready' });
  } catch {
    res.status(503).json({ status: 'db_down' });
  }
});

17. Observabilidade — logs, métricas, tracing

Quando algo falha em produção, você não tem o luxo de pôr console.log e esperar reproduzir. Precisa de observabilidade — três pilares que, juntos, permitem entender o que aconteceu post-mortem.

Logs estruturados

Logs em texto livre são caros de processar. Logs estruturados (JSON, idealmente) podem ser indexados, filtrados e agregados. Ferramentas como pino e winston emitem JSON nativamente.

TypeScript — pino, log estruturado
import pino from 'pino';

const log = pino({ level: 'info' });

log.info({ userId: 42, durationMs: 87 }, 'usuário criado');
log.error({ err, requestId: req.id }, 'falha ao criar pedido');

// saída: {"level":30,"time":...,"userId":42,"durationMs":87,"msg":"usuário criado"}

Métricas — RED

O modelo RED de Tom Wilkie é uma régua simples para o que medir em qualquer serviço:

  • Rate — requisições por segundo
  • Errors — erros por segundo
  • Duration — distribuição de latências (p50, p95, p99)

Com esses três, você cobre a maior parte das perguntas operacionais: "está crescendo?", "está quebrando?", "está lento para os usuários do percentil 99?". Prometheus + Grafana é o stack open-source canônico para coletar e visualizar.

Tracing distribuído

Quando uma requisição atravessa N serviços (controller → service → repo → cache → API externa), saber onde ela "demorou" exige um identificador único viajando junto. Cada serviço grava um span com o tempo de início/fim e o trace-id da requisição. OpenTelemetry é o padrão atual: instrumenta automaticamente Express, pg, fetch — e exporta para Jaeger, Tempo, Datadog, etc.

Por onde começar

Em projetos pequenos, dois passos já dão muito retorno: (1) trocar console.log por pino e adicionar requestId em todos os logs; (2) instalar @opentelemetry/sdk-node com auto-instrumentation. Você ganha tracing distribuído com pouquíssimas linhas — depois é só apontar para um backend (Jaeger local, ou Honeycomb, ou Datadog).

18. Implicações no nosso stack — TS + Express + EJS + SSR

Tudo que vimos cai diretamente em decisões do nosso projeto:

Cache-Control no HTML SSR

Se a página é pública e igual para todos (landing, lista de produtos sem filtro), Cache-Control: public, max-age=300 + CDN na frente economiza 95% das renderizações. Se a página depende do usuário logado (perfil, dashboard), Cache-Control: private, no-cache — nunca public.

Estáticos em public/

Use hash no nome do arquivo (main.a8f9b1.css) e cache eterno: Cache-Control: public, max-age=31536000, immutable. Um deploy gera novo hash → nova URL → bypass de cache automático. Sem invalidações manuais, sem stale assets.

Endpoints JSON — caso a caso

  • Listagens públicas (catálogo): cache curto (60s), ETag, public.
  • Mutáveis personalizadas (notificações): no-store.
  • Idempotentes seguras (consulta a CEP, blog): cache mais agressivo, public, max-age=3600.

Sempre timeout em fetch externo

Toda chamada para API externa, gateway de pagamento, serviço de e-mail: 3-5s de timeout, retry com backoff em erros transitórios, fallback razoável quando exaurir.

Checagens de saúde

Mesmo que você não esteja em Kubernetes ainda, expor /healthz e /ready agora prepara o caminho. Custa 6 linhas. E, se sua aplicação roda atrás de um load balancer (até mesmo um Nginx), ele já consome esses endpoints.

19. Anti-padrões — o que evitar

Retry infinito

"Se falhar, tenta de novo" sem limite, sem backoff. Em incidentes, vira amplificador: 1000 clientes errando ao mesmo tempo geram 100.000 retries por segundo, o que mata o servidor que estava se recuperando. Sempre tenha limite de tentativas e backoff exponencial com jitter.

Sem timeout

Um fetch sem timeout pode ficar pendurado por minutos quando a rede tem problema. Em servidor, isso prende workers, segura conexões de banco, derruba a aplicação inteira. Timeout não é uma "boa prática" — é parte do contrato. Sem ele, sua dependência manda no seu uptime.

Side effects em GET

GET /pedidos/42/cancelar é um anti-padrão clássico. GETs devem ser idempotentes e seguros (sem efeito colateral). Se o usuário recarregar a página, o pedido cancela duas vezes. Se o navegador prefetcher acessar o link, cancela sozinho. Use POST/DELETE para ações que mudam estado.

POST sem idempotência

Cliente perde a conexão depois do servidor processar mas antes de receber a resposta. Cliente retenta. Resultado: dois pedidos no banco, dois e-mails enviados, duas cobranças. Idempotency-Key resolve. Mesmo se você não implementar tudo agora, projete a API com a possibilidade.

Cache-Control: public em endpoint autenticado

Já mencionado, mas vale repetir: a CDN cacheia a resposta do usuário A; usuário B recebe os dados do A. Vazamento de dados em escala. Em qualquer endpoint que depende de cookie/Authorization, nunca public.

"Funciona no meu computador" é quase sempre rede

Quando algo funciona em dev e quebra em produção, e ninguém vê erro óbvio, em 80% dos casos é rede: timeout diferente, certificado faltando, DNS diferente, NAT bloqueando, CDN cacheando o que não devia. Antes de culpar o código, abra DevTools → Network. Mais cedo ou mais tarde, todo bug de produção tem rede como suspeita razoável.

20. Checklist de estudo

Marque conforme dominar:

  • Sei nomear as 4 camadas do TCP/IP e dar 1 protocolo de cada.
  • Diferencio TCP de UDP e dou casos de uso para cada.
  • Explico o 3-way handshake do TCP em uma frase.
  • Sei o que é NAT e por que req.ip nem sempre é o IP real do usuário.
  • Reconheço portas well-known (80, 443, 5432, 22, 53, 6379).
  • Diferencio registros A, AAAA, CNAME, MX, TXT, NS.
  • Descrevo a cadeia de resolução DNS (browser → resolver → root → TLD → autoritativo).
  • Sei usar dig/nslookup para investigar problemas de DNS.
  • Conheço os principais headers HTTP (Cache-Control, ETag, Authorization, Content-Type).
  • Diferencio HTTP/1.1, HTTP/2 e HTTP/3 em pelo menos 2 pontos cada.
  • Explico o handshake TLS em alto nível (ClientHello → cert → key exchange → secure).
  • Sei o que é Let's Encrypt e ACME, e por que renovação automática mudou o jogo.
  • Configuro Cache-Control apropriado para HTML SSR público, autenticado e estáticos.
  • Implemento ETag manual num endpoint dinâmico para responder 304.
  • Explico o que é PoP, hit/miss e purge em uma CDN.
  • Implemento retry com backoff exponencial + jitter em TypeScript.
  • Adiciono timeout em todo fetch usando AbortController.
  • Sei o que é idempotência e como Idempotency-Key resolve POSTs duplicados.
  • Explico circuit breaker (Closed/Open/Half-open) em uma frase.
  • Diferencio liveness de readiness e o impacto de errar essa diferença.
  • Conheço o modelo RED (Rate, Errors, Duration) para métricas.
  • Sei por que tracing distribuído (OpenTelemetry) ajuda em sistemas com várias chamadas.

Referências

Inteli Logo