Quando o APM te engana: Um milhão de requisições e um gargalo invisível no TCP
Você entende as limitações que sua incrivel stack de observalidade tem? Não por descuido. É limitação técnica mesmo.
Um cliente processa milhões de transações de pagamento por dia. Só com um dos parceiros dele, são cerca de 1 milhão de requisições diárias.
E, deste volume, algo estranho acontecia.
Aproximadamente 1.000 requisições apresentavam mais de 3 segundos de resposta, algumas chegando a 10 segundos! E aparentemente sem nenhum padrão que pudesse determinar a causa.
No mesmo segundo em que uma transação demorava 10 segundos, outra era processada em 200 ms.
Clássico cenário de caos distribuído.
O parceiro dizia que não era problema dele.
Nosso cliente dizia que não era ele.
E ambos apontavam para um mesmo ponto de "verdade":
O APM disse que a requisição levou 10 segundos. Logo, é problema do outro lado.
E então fomos chamados.
War Room montada
Chegamos para ajudar a formar evidências sólidas o suficiente para que o parceiro dele não pudesse se "esconder".
Depois da explicação do cenário, fizemos a primeira pergunta:
– Qual linguagem vocês usam?
O cliente, já sem paciência e sem entender o motivo da pergunta:
– Python. Por quê?
– Como o Python gerencia conexões HTTP?
– Não estou entendendo como isso ajuda.
– Queremos saber se o Python está reutilizando conexões ou se está refazendo handshake TCP a cada requisição.
– Mas se isso fosse o problema, por que só algumas chamadas são lentas?
Excelente pergunta.
Respondemos:
– E se essas requisições lentas aconteceram justamente quando uma instância reiniciou e precisou refazer resolução DNS?
– E se, no momento do handshake, o servidor do parceiro estava sobrecarregado por meio segundo?
– Ou se houve uma retransmissão TCP na camada 4 que o APM não captura?
Nesse ponto o cliente parou, respirou e prestou atenção.
Finalmente estávamos falando de algo que ele nunca tinha considerado.
Afinal, qual dev olha pra rede?
O problema clássico da Observabilidade
A raiz desse problema pode estar na limitação estrutural dos sistemas de observabilidade:
O APM só vê o processo da aplicação.
Quando o código executa:
httpRequest.send()
O APM começa a contar o tempo.
Mas tudo que acontece fora do processo da aplicação e que compõe a maior parte da chamada HTTP ele não vê.
Isso inclui:
- Resolução DNS
- Handshake TCP (SYN, SYN/ACK, ACK)
- Abertura da porta efêmera (ex: 62340)
- Retransmissões de pacotes
- Concorrência no driver de rede
- Filas internas do kernel
- Latência real na camada 4 ou na camada 3
- Timeout de SYN/ACK
- Slow start do TCP
O APM só vê o que a linguagem vê.
E a linguagem só vê o que o processo da aplicação vê.
Resultado?
Se a requisição ficou presa dentro do sistema operacional, o APM vai mentir pra você ou, no mínimo, vai te induzir ao erro.
E o parceiro, claro, também via um APM que não mostrava nada suspeito.
O pior dos mundos:
cada um com sua meia verdade e ninguém com a verdade completa.
“Ok, então vamos observar a camada do SO”
Essa é a reação natural. Mas aqui surge outro problema:
Como saber qual socket e qual porta o sistema operacional usou exatamente na requisição que o APM diz ter demorado 10 segundos?
Você não sabe.
Seu monitoramento também não.
E essa correlação fina entre APM → SO → Rede simplesmente não existe.
Não é descuido.
É limitação técnica mesmo.
Monitoramos memórias, CPUs, discos, rede, GC, queries...
Mas quando precisamos rastrear uma única requisição por todas as camadas?
A observabilidade moderna não dá conta.
E isso é aceitável na maioria dos casos, pois geralmente o problema é grande o suficiente para aparecer em todas as métricas ao mesmo tempo.
Mas quando temos uma falha esporádica no meio de 1 milhão de requisições?
Aí começam os casos que podem levar meses para descobrir.
E na maioria das vezes, são ignorados, pois não geram um impacto critico.
Debug e Hands-on: indo para a lama
Voltamos à War Room.
O cliente abriu a VM → rodamos tcpdump.
Vimos algo mais ou menos assim e que já supeitavamos:
12:34:10.104512 IP 200.160.10.20.443 > 10.0.0.5.52144: Flags [S.], seq 987654321, ack 1234567891
12:34:10.410120 IP 200.160.10.20.443 > 10.0.0.5.52144: Flags [P.], seq 1:302, ack 517
12:34:10.411200 IP 200.160.10.20.443 > 10.0.0.5.52144: Flags [F.], seq 302, ack 518
12:34:11.022334 IP 200.160.10.20.443 > 10.0.0.5.52162: Flags [S.], seq 112233445, ack 765432101
12:34:11.260001 IP 200.160.10.20.443 > 10.0.0.5.52162: Flags [P.], seq 1:280, ack 498
12:34:11.909100 IP 200.160.10.20.443 > 10.0.0.5.52201: Flags [S.], seq 556677880, ack 443322111
Vamos entender esse exemplo de tcpdump:
12:34:10.104512 IP 200.160.10.20.443 > 10.0.0.5.52144: Flags [S.], seq 987654321, ack 1234567891
12:34:10.104512→ TimestampIP→ Indica que é um pacote IPv4 (o tcpdump mostraria "IP6" para IPv6).200.160.10.20.443→ Indica que o servidor do parceiro está respondendo, Ou seja: o servidor está enviando o pacote.>→ Direção do pacote, significa para.10.0.0.5.52144→ Endereço de destino + porta efêmera que o sistema operacional abriu para receber a resposta.Flags [S.]→ Flag do TCP para determinar o que significa o pacote
Perceba que o endereço de destino + porta efêmera muda constantemente, ou seja, muito provavelmente toda requisição abria um socket novo.
Rodamos netstat.
E a confirmação veio:
Mais de 1 mil sockets abertos, todos estavam em TIME_WAIT.
Quando vemos TIME_WAIT demais, significa:
- A aplicação não está reutilizando conexões HTTP
- Para cada chamada, ela abre um socket novo
- Portanto, refaz handshake TCP
- Portanto, paga latência extra
- Portanto, aumenta a chance de pegar um momento ruim na rede ou no parceiro
Isso explica tudo?
Não necessariamente.
Mas é um dos fatores que contribui para uma lentidão intermitente.
Como resolver?
Simples (mas exige código):
- Ativar HTTP keep-alive
- Usar Session / Connection Pool
- Manter o socket em estado ESTABLISHED
Assim a aplicação reaproveita conexões, evita bursts de DNS, reduz handshakes e estabiliza a latência.
Conclusão
No fim, parte do problema ainda pode estar do lado do parceiro.
Mas entregamos algo que muitas consultorias não entregam:
- Teoria clara
- Põe a teoria a prova (tcpdump, netstat, análise de sockets)
- Explicação das limitações de APM e observabilidade
- Evidências fortes para o cliente trabalhar com o parceiro
- Melhorias concretas para aplicar no próprio código
O cliente entrou na War Room convencido de que o problema era do parceiro.
Saiu dela entendendo que parte da culpa também poderia ser dele e que seu APM, sozinho, não podia ser tratado como verdade absoluta.
E, sinceramente?
Para quem processa 1 milhão de requisições por dia, essa lição vale ouro.