System Design/02 - Componentes/Communication2 min
TCP
- Que problema isso resolve?
- Quando usar / quando NÃO usar?
- Qual o principal trade-off?
- Como isso falha em produção?
Conceito
Protocolo de transporte orientado a conexão que garante entrega ordenada e sem duplicatas — construído sobre um IP que não garante nada.
O que ele adiciona sobre o IP
| Garantia | Mecanismo |
|---|---|
| Entrega | ACK + retransmissão por timeout |
| Ordem | Número de sequência |
| Sem duplicata | Deduplicação por número de sequência |
| Controle de fluxo | Janela anunciada pelo receptor |
| Controle de congestionamento | Slow start, AIMD, ajuste da janela |
| Integridade | Checksum |
Handshake e encerramento
Abertura (3 vias): SYN → SYN-ACK → ACK = 1 RTT antes do primeiro byte
Com TLS 1.3: +1 RTT = 2 RTT total
Encerramento: FIN → ACK → FIN → ACK
O custo de abertura é o motivo de connection pooling e keep-alive existirem: em API de alta frequência, o handshake pode custar mais que a requisição.
Estados que aparecem em produção
TIME_WAIT (espera ~2×MSL antes de liberar a porta) acumula em servidores com muitas conexões curtas e pode esgotar portas efêmeras. CLOSE_WAIT acumulado quase sempre indica bug de aplicação: o socket foi fechado do outro lado e o código nunca chamou close().
Trade-offs
As garantias custam latência. Head-of-line blocking é o efeito mais relevante: um pacote perdido bloqueia a entrega de todos os pacotes seguintes, mesmo que já tenham chegado. Em HTTP/2, que multiplexa vários streams numa conexão TCP, uma perda trava todos os streams — foi exatamente esse limite que motivou o HTTP/3 sobre QUIC (UDP).
Controle de congestionamento também tem custo: o slow start faz cada conexão nova começar devagar, o que penaliza requisições curtas.
E há o caso em que TCP simplesmente não serve: quando o dado envelhece mais rápido que a retransmissão. Retransmitir áudio de 200 ms atrás não ajuda ninguém — daí UDP.
Exemplo prático
Uma API interna que abre uma conexão TCP nova a cada chamada, entre serviços com 1 ms de RTT:
sem pool: handshake (1 RTT) + TLS (1 RTT) + requisição = ~3 ms
com pool: requisição = ~1 ms
Três vezes mais rápido sem tocar em nenhuma linha de lógica. E, sob carga alta, o servidor deixa de acumular TIME_WAIT — que é o modo pelo qual esse problema costuma aparecer primeiro: não como lentidão, mas como "esgotamento de portas efêmeras" em horário de pico.
Relacionado
Parte de Communication · roadmap.sh/system-design