Introdução: sem métricas, o problema parece "está tudo lento"

Cena conhecida. Aparece uma mensagem no chat da equipe: "os proxies estão lentos". Cinco minutos depois, a segunda: "e tem muitos erros". Aí começa o que os engenheiros chamam entre si de arqueologia. Alguém abre o terminal e roda curl na mão através de três endereços aleatórios. Outro olha para o gráfico de uso de CPU, que não tem nada a ver com proxy. Um terceiro escreve para o suporte do provedor um e-mail do tipo "tem algo errado aí", sem ter um único número na mão. Passa uma hora. Descobre-se que só um segmento do pool degradou, só em um país, e que isso começou não "agora", mas dois dias atrás, só ninguém percebeu.

A diferença entre uma equipe que gasta uma hora nessa história e uma que gasta um minuto não está na experiência nem no talento. Está no fato de que a segunda decidiu com antecedência o que vai medir, o que vai escrever nos logs e sobre o que vai disparar alertas. Quando você tem um gráfico da taxa de sucesso de requisições por país e operadora, a frase "está tudo lento" se transforma em uma afirmação concreta: "desde 14:32, o p95 do tempo até o primeiro byte nos proxies móveis da operadora A na região B subiu de 1,8 para 6,4 segundos, a taxa de erro não mudou, a taxa de retentativas triplicou". Com uma afirmação dessas, já dá para trabalhar: redistribuir carga, abrir um ticket com o provedor com dados em vez de emoções, ou entender que o problema não está nos proxies, mas no recurso de destino.

Neste guia, vamos destrinchar a observabilidade em torno de um pool de proxies do começo ao fim. Você vai aprender quais quatro sinais entregam quase toda a informação útil, quais campos são obrigatórios em cada linha de log e quais nunca devem aparecer nela, por que o tempo médio de resposta mente e como calcular percentis corretamente, como dimensionar os dados por atributos para enxergar a degradação de um recorte específico, como construir alertas que disparam raramente mas a propósito, e como distinguir no dashboard, em um minuto, três padrões típicos de problema. No final, vamos ver um stack tecnológico mínimo, casos reais com números e responder às perguntas frequentes.

Já deixamos claro desde agora os limites. A seleção de um IP específico do pool, os health checks dos proxies e a quarentena de endereços defeituosos não vamos detalhar aqui: isso é tema de um material separado. Aqui trata-se apenas de duas coisas: medição e alerta. Tudo o que está descrito abaixo se aplica a cenários legítimos de engenharia: monitoramento de preços em sites públicos, verificação de exibição de anúncios em diferentes regiões, testes de conteúdo geodependente dos seus próprios serviços, e coleta de dados abertos respeitando as regras dos sites.

Um pouco de fundamentos: o que é observabilidade aplicada a proxies

A definição clássica diz que observabilidade (observability) é a propriedade de um sistema que permite entender seu estado interno a partir de suas saídas externas. Traduzindo para a linguagem de engenharia: você consegue fazer ao sistema uma pergunta nova, que não foi prevista de antemão, e obter resposta sem fazer deploy de código novo. Monitoramento responde a perguntas previamente conhecidas ("o serviço está vivo?"), observabilidade responde a desconhecidas ("por que as requisições a esse domínio através de endereços móveis ficaram mais lentas justamente nas terças-feiras?").

Os três pilares da observabilidade são bem conhecidos: métricas (séries temporais numéricas, baratas de armazenar, fáceis de agregar), logs (eventos com contexto, caros de armazenar, mas cheios de detalhes) e traces (o caminho de uma requisição através dos componentes). Para um pool de proxies, o cenário é um pouco específico. Entre o seu código e o site de destino existe uma infraestrutura externa que você não controla: o endereço de saída, a operadora de telecom, a rota até o destino. Você não pode instalar um agente em uma torre de celular. Por isso, o único ponto onde você mede tudo é o seu próprio cliente, que envia a requisição através do proxy. Toda requisição pelo pool deve deixar dois rastros: um incremento de métrica e uma linha de log estruturado. Traces são úteis, mas para a maioria das equipes são excessivos, e só voltaremos a eles no FAQ.

Por que isso importa justamente para proxies? Porque a degradação de um proxy quase nunca se parece com uma queda. Servidor não responde — isso é simples. Já o proxy fica "um pouco mais lento", "erra um pouco mais em conexão", "às vezes corta o download no meio". Cada sintoma isolado fica abaixo do limiar de atenção humano. Somados, dobram o custo da coleta de dados e quebram prazos. Só medindo é possível perceber uma degradação lenta.

Quatro sinais para proxies: sucesso, latência, retentativas, tráfego

Na prática de engenharia de confiabilidade existe o conceito de "sinais de ouro": latência, tráfego, erros e saturação. Para um pool de proxies, adaptamos essa ideia e chegamos a quatro sinais, suficientes para 90% das situações. Por que quatro, e não vinte? Porque cada gráfico adicional no dashboard reduz a chance de o plantonista olhar o gráfico certo. Quatro sinais são o que cabe em uma linha de tela e se lê em dez segundos.

Sinal 1: taxa de sucesso das requisições

Taxa de sucesso (success rate) é a razão entre as requisições que terminaram com resultado útil e o total de requisições em uma janela de tempo. A palavra-chave é "útil". Sucesso para um proxy não é apenas HTTP 200. Sucesso é quando o recurso de destino respondeu o que você esperava, e a resposta chegou inteira. Conexão interrompida no meio do corpo não é sucesso, mesmo que os cabeçalhos tenham vindo com código 200. Um código 403 do site de destino também não é sucesso, mas provavelmente não é culpa do proxy, e nós distinguimos isso pela classificação do desfecho.

Regra prática: classifique o desfecho de cada requisição em uma das poucas categorias disponíveis. Por exemplo: ok (resposta esperada recebida), proxy_connect_error (não foi possível estabelecer conexão com o proxy), proxy_auth_error (o proxy recusou a autorização), upstream_timeout (o proxy respondeu, mas o recurso de destino não respondeu a tempo), upstream_4xx, upstream_5xx, truncated (conexão encerrada antes do fim do corpo). Essa classificação é a base de toda a análise posterior. A taxa de sucesso é calculada como ok sobre o total, mas você olha separadamente as taxas de cada categoria de erro, porque elas apontam para culpados diferentes.

Sinal 2: latência em percentis

Para proxies, faz sentido medir latência em três pontos: tempo de estabelecimento de conexão com o proxy, tempo até o primeiro byte da resposta do recurso de destino (TTFB, time to first byte) e tempo total da requisição. O primeiro fala da disponibilidade do próprio nó proxy. O segundo, da qualidade da rota do proxy até o destino e da velocidade de resposta do destino. O terceiro, da capacidade do canal. Se só o terceiro cresce e os dois primeiros estão estáveis, o problema é de banda, não de disponibilidade. Se o primeiro cresce, o problema está entre você e o proxy. Latência precisa ser medida em percentis, não em média, e há uma seção dedicada a isso mais abaixo.

Sinal 3: taxa de retentativas

Taxa de retentativas (retry rate) é o sinal mais subestimado. A maioria dos clientes é escrita de forma que, em caso de erro, a requisição é automaticamente repetida através de outro endereço. Isso é correto do ponto de vista de confiabilidade e péssimo do ponto de vista de observabilidade: a taxa de sucesso continua 99%, mas por baixo dos panos cada requisição é feita três vezes. Você paga tráfego em triplo, o recurso de destino recebe carga tripla, e no dashboard está tudo verde. A taxa de retentativas revela a degradação oculta antes que ela se torne degradação visível. É calculada como a razão entre o número de tentativas e o número de requisições lógicas: valor 1,0 significa nenhuma retentativa, 1,5 significa que uma a cada duas requisições foi repetida.

Sinal 4: consumo de tráfego

Consumo de tráfego é um sinal diretamente ligado a dinheiro, porque muitos planos, incluindo os planos da Proxeon, cobram por volume de dados transmitidos. Mas ele também tem papel diagnóstico. Um salto no número de bytes por requisição bem-sucedida significa que o recurso de destino passou a entregar outras páginas: páginas pesadas de aviso, redirecionamentos para outras seções, layout alterado. Uma queda brusca significa que, em vez de páginas completas, você está recebendo respostas curtas de erro. Meça tráfego nas duas direções e sempre normalize pelo número de requisições bem-sucedidas: "kilobytes por requisição bem-sucedida" é uma métrica muito mais informativa do que "gigabytes por hora".

Como isso fica no código

Abaixo, um conjunto mínimo de métricas para Prometheus em Python. Repare no conjunto de labels: pool, país, operadora, tipo de proxy. O endereço do proxy específico não aparece nos labels de propósito, e vamos explicar por quê na seção sobre dimensões.

from prometheus_client import Counter, Histogram

LABELS = ["pool", "country", "operator", "proxy_type"]

REQUESTS = Counter("proxy_requests_total", "Логические запросы через пул", LABELS + ["outcome"])
ATTEMPTS = Counter("proxy_attempts_total", "Попытки, включая ретраи", LABELS + ["attempt_kind"])
TTFB = Histogram("proxy_ttfb_seconds", "Время до первого байта", LABELS, buckets=(0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 30))
CONNECT = Histogram("proxy_connect_seconds", "Установление соединения с прокси", LABELS, buckets=(0.05, 0.1, 0.25, 0.5, 1, 2, 5))
BYTES = Counter("proxy_bytes_total", "Байты через пул", LABELS + ["direction"])

def observe(ctx, outcome, ttfb, connect, bytes_in, bytes_out, attempt):
base = dict(pool=ctx.pool, country=ctx.country, operator=ctx.operator, proxy_type=ctx.proxy_type)
kind = "first" if attempt == 1 else "retry"
ATTEMPTS.labels(**base, attempt_kind=kind).inc()
if outcome in ("ok", "upstream_4xx", "upstream_5xx", "truncated", "upstream_timeout"):
TTFB.labels(**base).observe(ttfb)
CONNECT.labels(**base).observe(connect)
BYTES.labels(**base, direction="in").inc(bytes_in)
BYTES.labels(**base, direction="out").inc(bytes_out)
if outcome == "ok" or attempt == ctx.max_attempts:
REQUESTS.labels(**base, outcome=outcome).inc()

Repare no detalhe: o contador de requisições lógicas só incrementa em caso de sucesso ou quando as tentativas se esgotam. Assim, a taxa de sucesso reflete o resultado para o usuário do seu código, e o contador de tentativas reflete o trabalho real do pool. A diferença entre os dois é a taxa de retentativas.

Tabela: métrica, o que significa seu aumento, primeira ação

MétricaO que significa seu aumentoPrimeira ação
Taxa de proxy_connect_errorNós de proxy indisponíveis ou sobrecarregados; problema entre você e o provedorVerificar seu canal de saída e DNS; depois comparar por recortes: se for o pool inteiro, buscar a causa do seu lado; se for um recorte, avisar o provedor com dados
Taxa de proxy_auth_errorAcesso expirado, credenciais trocadas, limite de conexões excedidoVerificar a validade das credenciais e os limites no painel do provedor; não fazer retentativas nesses casos
Taxa de upstream_timeoutRecurso de destino respondendo devagar por essa rota, ou proxy sem capacidadeComparar TTFB por host de destino: se for um host só, o problema é do destino; se forem todos, é a rota do proxy
Taxa de upstream_4xx (exceto 404)Recurso de destino limitando a intensidade ou mudando regras de acessoReduzir a frequência de requisições ao recurso, verificar conformidade com as regras dele, não aumentar retentativas
Taxa de upstream_5xxProblema do lado do recurso de destinoEsperar, reduzir carga; provavelmente o proxy não tem culpa
Taxa de truncatedConexões caindo no meio: canal instável, timeouts de leitura curtos demaisVerificar timeouts de leitura e tamanho das respostas; comparar por operadora
p95 do tempo de conexão com o proxyDegradação de disponibilidade dos nós de proxy ou do seu stack de redeVerificar número de conexões simultâneas e esgotamento de portas do seu lado
p95 de TTFB com tempo de conexão estávelA rota do proxy até o destino ficou mais lenta, ou o destino desacelerouDecompor por host de destino e por recortes do pool
p99 do tempo total com TTFB estávelCaiu a capacidade de banda do canal do proxy, ou as respostas cresceramOlhar bytes por requisição; se o tamanho não mudou, é problema de banda
Taxa de retentativasDegradação oculta: o sucesso se sustenta com repetições, o custo cresceIdentificar qual categoria de erro está gerando as repetições e tratá-la
Bytes por requisição bem-sucedidaO recurso de destino está entregando outro conteúdo: avisos, redirecionamentos, estrutura alteradaVerificar o conteúdo das respostas em uma amostra, atualizar o parser e a validação
Tráfego total com número de requisições estávelAumento de retentativas ou de tamanho das respostas; impacto direto no orçamentoDecompor em dois fatores: número de tentativas e bytes por tentativa

O que escrever no log de cada requisição — e o que nunca escrever

Métricas respondem "o que está acontecendo"; logs respondem "com o quê exatamente". Quando o dashboard mostra aumento de erros no recorte "país X, operadora Y", você precisa entender se todos os endereços desse recorte são culpados ou apenas dois de trinta. Métricas com label de endereço vão explodir o armazenamento; logs não, porque você não os agrega continuamente, filtra sob demanda.

Campos obrigatórios da linha de log

Cada tentativa de requisição pelo pool deve gerar um registro estruturado, de preferência em formato JSON. Este é o conjunto mínimo de campos que consideramos suficiente:

  • ts — timestamp em UTC com milissegundos. Não hora local. Nunca.
  • request_id — identificador da requisição lógica, comum a todas as suas tentativas. Permite reunir toda a história de uma requisição em um único comando.
  • attempt e max_attempts — número da tentativa e limite. Sem o número da tentativa, você não distingue o primeiro erro do terceiro.
  • proxy_id — identificador interno do proxy. Não o endereço com porta nem a string de conexão. Um identificador interno que você consegue correlacionar com o endereço real via uma tabela separada e protegida.
  • proxy_type, country, operator, pool — as mesmas dimensões das métricas. Isso permite pular entre dashboard e logs sem recalcular nada.
  • target_host — o domínio do destino. Não a URL completa: em strings de consulta costuma haver tokens e dados pessoais.
  • method — método HTTP.
  • status — código HTTP da resposta, se houve; caso contrário, null.
  • outcome — aquela classificação de desfecho mencionada acima.
  • error_class — classe da exceção ou código curto do erro de transporte, se o desfecho não foi ok.
  • connect_ms, ttfb_ms, total_ms — as três latências em milissegundos.
  • bytes_in, bytes_out — tamanho do corpo da resposta e tamanho da requisição.
  • session_id — se você usa sessões sticky, o identificador da sessão; assim você vê que os erros se concentram em sessões longas.
  • retry_reason — para tentativas a partir da segunda: o desfecho da tentativa anterior.

Exemplo de linha:

{"ts":"2026-03-14T11:32:07.412Z","request_id":"7f3a9c1e","attempt":2,"max_attempts":3,"retry_reason":"upstream_timeout","proxy_id":"px-2841","proxy_type":"mobile","country":"RU","operator":"op_a","pool":"pricing","target_host":"shop.example","method":"GET","status":200,"outcome":"ok","error_class":null,"connect_ms":184,"ttfb_ms":1420,"total_ms":2310,"bytes_in":48211,"bytes_out":612,"session_id":"s-19b"}

Uma linha dessas pesa cerca de 400 bytes. A 50 requisições por segundo, isso dá aproximadamente 1,7 gigabyte por dia sem compressão e 200–300 megabytes com compressão. Perfeitamente viável mesmo com orçamento modesto.

O que não pode estar nos logs

Aqui saímos da engenharia e entramos na responsabilidade. A lista é curta, mas cada item importa:

  1. Credenciais de acesso ao proxy. O vazamento mais comum é a string de conexão no formato http://usuário:senha@host:porta, que entra inteira no log porque o desenvolvedor logou a config "só para depurar". Logs são copiados em tickets, repassados para colegas, armazenados por anos. Credenciais neles são credenciais disponíveis para qualquer um que já viu o log.
  2. O cabeçalho Proxy-Authorization e quaisquer outros cabeçalhos de autorização, incluindo os destinados ao recurso final.
  3. O corpo da resposta. É enorme, pode conter dados pessoais de terceiros, e quase nunca é necessário para diagnosticar proxy. Se você precisa entender o que o recurso de destino entregou, guarde o hash do corpo e seu tamanho; a resposta em si vai para um armazenamento separado, com tempo de vida curto e só por amostragem.
  4. Cookies nas duas direções.
  5. A URL completa com parâmetros de consulta. Basta o host e, quando necessário, o caminho sem parâmetros.
  6. O endereço real do proxy em texto claro, se o log vai para um armazenamento compartilhado. O identificador interno já basta.

Prática que recomendamos: implemente a redação em uma função única, por onde passa qualquer registro ligado a proxy. Não confie na disciplina dos desenvolvedores.

import re

CRED_IN_URL = re.compile(r"(https?|socks5h?)://[^/@:]+:[^/@]+@")
SENSITIVE_HEADERS = {"proxy-authorization", "authorization", "cookie", "set-cookie"}

def redact(record: dict) -> dict:
for key, value in list(record.items()):
if key.lower() in SENSITIVE_HEADERS:
record[key] = "[redacted]"
elif isinstance(value, str):
record[key] = CRED_IN_URL.sub(lambda m: m.group(1) + "://[redacted]@", value)
record.pop("body", None)
return record

Amostragem e retenção

É preciso registrar cada tentativa? Erros, sim, todos. Tentativas bem-sucedidas podem ser amostradas: com intensidade acima de cem requisições por segundo, 5–10% das linhas de sucesso já bastam para estimar latências, porque as métricas já contêm o quadro completo. É importante que a decisão de amostragem seja tomada por request_id, não aleatoriamente por tentativa: senão você terá a segunda tentativa sem a primeira. O tempo de retenção dos logs detalhados costuma ser de 7 a 14 dias; agregados e métricas vivem mais.

Percentis em vez de média: por que a média esconde o problema

Imagine um pool de cem endereços. Noventa respondem em 0,8 segundo. Dez respondem em 9 segundos, porque caíram em uma estação base sobrecarregada. O tempo médio de resposta é de 1,62 segundo. Uma semana antes, quando estava tudo bem, a média era 1,4. Um aumento de 15%. Quem vai reagir a isso? Ninguém. Enquanto isso, uma em cada dez das suas requisições espera nove segundos, e se você tem timeout de oito, uma em cada dez falha com erro.

A média aritmética descreve bem distribuições parecidas com um sino. Latências através de proxy nunca são parecidas com um sino. São distribuições de cauda longa à direita: a maioria das requisições é rápida, mas algumas são muito lentas, e são justamente elas que definem a experiência do usuário e o custo. Mais ainda, latências através de um pool frequentemente são bimodais: existe um grupo "normal" e um grupo de "endereços problemáticos". A média cai entre os dois, em um ponto onde quase não existem requisições de verdade.

O que são p50, p95, p99

Percentil é o valor abaixo do qual está uma dada fração das observações. p50 (mediana) — metade das requisições é mais rápida que esse valor. p95 — 95% mais rápidas, 5% mais lentas. p99 — apenas um por cento mais lento. Para um pool de proxies, recomendamos olhar três percentis com objetivos diferentes:

  • p50 mostra a requisição "típica". Se o p50 sobe, degradou a maioria. É um sinal raro e sério.
  • p95 — métrica de trabalho para alertas. É sensível a segmentos problemáticos (no exemplo acima, o p95 mostra 9 segundos imediatamente), mas resistente a picos isolados.
  • p99 — métrica para timeouts e planejamento. Seu timeout deve ficar um pouco acima do p99 "saudável"; caso contrário, você vai cortar requisições normais e transformá-las em retentativas.

Como calcular com precisão

Se os dados são poucos (milhares de valores por janela), calcule com exatidão: ordene e pegue o elemento pelo índice.

def percentile(values, p):
if not values:
return None
ordered = sorted(values)
k = (len(ordered) - 1) * p / 100.0
lo = int(k)
hi = min(lo + 1, len(ordered) - 1)
return ordered[lo] + (ordered[hi] - ordered[lo]) * (k - lo)

ttfb_window = [0.8, 0.9, 0.7, 9.1, 0.85, 8.7, 0.78, 0.92, 0.81, 0.79]
print(percentile(ttfb_window, 50), percentile(ttfb_window, 95))

Cálculo exato não escala: para calcular p95 de uma semana com milhões de requisições, seria preciso armazenar todos os valores. Por isso, sistemas de produção usam estruturas aproximadas.

Como calcular em escala: histogramas

Prometheus e sistemas compatíveis armazenam o histograma como um conjunto de contadores por faixa: quantas observações ficaram abaixo de 0,1 s, abaixo de 0,25 s, abaixo de 0,5 s, e assim por diante. O percentil é reconstituído por interpolação linear dentro da faixa. A precisão depende de quão bem escolhidos foram os limites das faixas. Para latências através de proxy, uma escala logarítmica de 100 milissegundos a 30 segundos funciona bem: 0,1; 0,25; 0,5; 1; 2; 4; 8; 16; 30. Se o seu p95 saudável é em torno de 2 segundos e o timeout é 15, essa grade dá erro na casa dos 20–30% na cauda, o que é suficiente para alertas e diagnóstico.

histogram_quantile(0.95,
sum by (le, country, operator) (
rate(proxy_ttfb_seconds_bucket{pool="pricing"}[5m])
)
)

Repare no sum by (le, ...) dentro da expressão. Esse detalhe é crítico: histogramas de instâncias diferentes do seu cliente precisam ser somados antes de calcular o quantil. A operação inversa — calcular o p95 em cada instância e depois tirar a média — é matematicamente sem sentido. Média de percentis não é percentil. É um dos erros mais comuns, e esconde o problema de forma tão eficaz quanto a média simples.

Se você precisa de alta precisão na cauda (por exemplo, se oferece SLA de latência para clientes internos), use estruturas como t-digest ou HDR-histogramas, que entregam precisão de frações de por cento com memória fixa. Para a maioria das equipes, histogramas padrão são suficientes.

Dimensões: país, operadora, tipo de proxy

Eis um cenário que ilustra por que as dimensões são necessárias. Você tem um pool de 400 endereços, 5% deles são móveis, de uma operadora em uma região. Esse segmento degradou por completo: a taxa de sucesso ali caiu para 10%. O que o gráfico geral de taxa de sucesso do pool mostra? Uma queda de 99% para 94,5%. Perceptível, mas não crítico. O plantonista pensa "a rede está instável" e segue em frente. Já o cliente de dados dessa região recebe nada.

Dimensionar os dados (em terminologia Prometheus, labels) resolve exatamente esse problema: permite decompor o agregado em recortes e ver que o problema é localizado. Mas as dimensões têm custo, e é preciso entender isso.

Quais dimensões escolher

Uma boa dimensão tem duas propriedades: um número pequeno e limitado de valores, e uma decomposição que responde a uma pergunta real de diagnóstico. Para um pool de proxies, recomendamos quatro dimensões nas métricas:

  • proxy_type — móveis, residenciais, datacenter. Tipos diferentes degradam de formas diferentes e têm custos diferentes.
  • country — país do endereço de saída. Normalmente de 1 a 30 valores.
  • operator — operadora de telecom ou sistema autônomo pelo qual o endereço sai. Os problemas frequentemente se localizam aqui: uma estação base sobrecarregada ou um peering ruim de uma operadora.
  • pool — finalidade lógica: qual produto ou tarefa usa aquele segmento. Permite separar incidentes por impacto no negócio.

A quinta dimensão a considerar é target_host, o host de destino. Ela serve para diferenciar problemas de proxy de problemas do destino. Mas se você acessa milhares de domínios diferentes, a cardinalidade vai explodir. O compromisso: mantenha na label apenas os 20 hosts de maior volume e colapse o restante em um valor other.

A aritmética da cardinalidade

Cada combinação única de valores de labels é uma série temporal separada. Vamos contar: 3 tipos × 10 países × 15 operadoras × 4 pools × 7 desfechos = 12.600 séries apenas para o contador de requisições. Um histograma com 9 faixas multiplica seu número de séries por 10. Total de cerca de 50–60 mil séries. Para o Prometheus, isso é confortável. Agora imagine que você adicionou a label com o endereço do proxy (400 valores). O número de séries cresce 400 vezes — até 20 milhões. Isso deixa de ser "confortável", isso mata o armazenamento.

Daí a regra: o endereço do proxy vive nos logs, não nas métricas. As métricas respondem "qual recorte degradou"; os logs, "quais endereços dentro do recorte". Essa divisão de trabalho entrega velocidade e detalhe sem crescimento explosivo de custos.

Consultas que encontram o recorte

A pergunta central do diagnóstico é: "onde está pior?" A resposta vem da ordenação dos recortes por taxa de sucesso:

bottomk(5,
sum by (country, operator, proxy_type) (rate(proxy_requests_total{outcome="ok"}[10m]))
/
sum by (country, operator, proxy_type) (rate(proxy_requests_total[10m]))
)

A segunda pergunta: "é um problema novo ou sempre foi assim?" A resposta vem da comparação com o passado:

(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m])) / sum by (operator) (rate(proxy_requests_total[10m]))
)
-
(
sum by (operator) (rate(proxy_requests_total{outcome="ok"}[10m] offset 1d)) / sum by (operator) (rate(proxy_requests_total[10m] offset 1d))
)

Um valor negativo mostra quanto o recorte piorou em relação a um dia atrás. Um gráfico desses no dashboard transforma "tem algo errado em algum lugar" em "operadora B no país C menos 12 pontos em relação a ontem".

De onde vêm os valores das dimensões

País e tipo de proxy normalmente vêm da configuração do pool. A operadora pode ser obtida dos metadados do provedor: por exemplo, no painel e na API da Proxeon, os endereços vêm com informações de geolocalização e operadora, que você carrega na inicialização do pool e mantém em cache junto ao identificador interno do proxy. Não vale a pena determinar a operadora no momento da requisição via bases externas: isso adiciona latência e cria uma dependência desnecessária.

Alertas que não fazem barulho: limiares, janelas, histerese

A pior coisa que pode acontecer com um sistema de alertas não é perder um incidente. A pior coisa é o plantonista receber 40 notificações por dia, das quais 39 não significam nada, e parar de ler a quadragésima. Um alerta ignorado é pior do que a ausência de alerta, porque cria a ilusão de controle.

Princípio 1: alerta no sintoma, não na causa

Não configure alerta para "o proxy px-2841 retornou erro". Um endereço específico tem o direito de ficar doente; para isso existe o mecanismo de quarentena dentro do pool, que não discutimos aqui. Configure o alerta para o que o consumidor dos seus dados sente: taxa de sucesso do pool abaixo da meta, p95 acima da meta, retentativas acima da meta. A causa você encontra no dashboard em um minuto, se tiver feito as dimensões corretamente. A função do alerta é acordar, não explicar.

Princípio 2: volume mínimo de tráfego

Taxa de sucesso de 50% com duas requisições em cinco minutos é uma requisição sem sorte, não um incidente. Toda regra sobre taxa precisa incluir uma condição de número mínimo de requisições na janela. Caso contrário, de madrugada, quando o tráfego cai, você vai se afogar em falsos positivos.

Princípio 3: janelas e velocidade de queima do orçamento

Se você definiu um nível-alvo de confiabilidade (por exemplo, 98% de requisições bem-sucedidas no mês), existe um orçamento de erro: 2% das requisições. Um bom alerta reage à velocidade de consumo do orçamento, não ao valor instantâneo. A prática que virou padrão na indústria de confiabilidade são alertas multi-janela: uma janela curta (5 minutos) pega quedas bruscas, uma longa (1 hora) pega degradação lenta, e ambas devem ser excedidas ao mesmo tempo para excluir picos passageiros.

Princípio 4: histerese

Histerese é usar limiares diferentes para disparar e para encerrar o alerta. Se o alerta dispara com taxa de sucesso abaixo de 95% e encerra ao voltar acima de 95%, uma métrica oscilando em torno de 95% vai gerar uma série de notificações "disparou — encerrou — disparou" a cada poucos minutos. Solução: disparar em 95%, encerrar em 97%. Ou, mais simples, exigir que a condição se mantenha ininterrupta por um tempo definido (no Prometheus, a palavra-chave for), e também definir um atraso para o encerramento.

Princípio 5: níveis de severidade e roteamento

Nem tudo precisa chegar por ligação. Divida os alertas em dois ou três níveis: críticos (impacto nos consumidores de dados agora — ligação ou push para o plantonista), avisos (degradação de recorte, aumento de retentativas, anomalia de tráfego — mensagem em canal, resolvido em horário comercial), informativos (ausência de dados de uma instância, aproximação do limite de tráfego — resumo diário).

Exemplo de conjunto de regras

groups:
- name: proxy-pool
rules:
- record: pool:success_ratio:5m
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[5m])) / sum by (pool) (rate(proxy_requests_total[5m]))
- record: pool:success_ratio:1h
expr: sum by (pool) (rate(proxy_requests_total{outcome="ok"}[1h])) / sum by (pool) (rate(proxy_requests_total[1h]))
- record: pool:request_rate:5m
expr: sum by (pool) (rate(proxy_requests_total[5m]))

- alert: ProxyPoolSuccessLow
expr: pool:success_ratio:5m < 0.95 and pool:success_ratio:1h < 0.97 and pool:request_rate:5m > 0.5
for: 5m
labels: { severity: critical }
annotations:
summary: "Доля успеха по пулу {{ $labels.pool }} ниже цели"

- alert: ProxyPoolLatencyHigh
expr: histogram_quantile(0.95, sum by (le, pool) (rate(proxy_ttfb_seconds_bucket[10m]))) > 4 and pool:request_rate:5m > 0.5
for: 10m
labels: { severity: warning }

- alert: ProxyPoolRetryRateHigh
expr: sum by (pool) (rate(proxy_attempts_total[15m])) / sum by (pool) (rate(proxy_requests_total[15m])) > 1.4
for: 15m
labels: { severity: warning }

- alert: ProxyPoolSliceDegraded
expr: (sum by (pool, country, operator) (rate(proxy_requests_total{outcome="ok"}[15m])) / sum by (pool, country, operator) (rate(proxy_requests_total[15m]))) < 0.85 and sum by (pool, country, operator) (rate(proxy_requests_total[15m])) > 0.2
for: 15m
labels: { severity: warning }

- alert: ProxyPoolNoData
expr: absent(proxy_requests_total) or pool:request_rate:5m == 0
for: 10m
labels: { severity: warning }

Preste atenção na última regra. Ausência de dados é a falha mais traiçoeira: o cliente caiu, não há métricas, os gráficos ficam planos e verdes, todos tranquilos. Alerta de silêncio é obrigatório.

Checklist para cada novo alerta

  1. A anotação diz o que o destinatário deve fazer? Se não, a regra não está pronta.
  2. Há condição de tráfego mínimo?
  3. Há atraso for ou histerese?
  4. A regra foi testada contra o histórico do último mês: quantas vezes teria disparado, e quantas eram incidentes reais?
  5. Está claro a qual nível de severidade pertence e para onde é roteado?
  6. Existe processo reverso: se o alerta disparou e foi falso positivo, quem e quando vai revisá-lo?

Diagnóstico rápido pelo dashboard: três padrões típicos

Dashboard não é galeria de gráficos, é ferramenta de decisão. Um bom dashboard para pool de proxies é montado como um funil. Linha de cima: os quatro sinais do pool inteiro nas últimas 6 horas, com sobreposição do dia anterior em linha fina. Segunda linha: os mesmos sinais decompostos por país, operadora e tipo — em formato de tabela ou mapa de calor, ordenado pelo pior valor. Terceira linha: taxa de sucesso e TTFB dos 10 principais hosts de destino. Quarta: relação entre tempo de conexão, TTFB e tempo total. Mais marcadores verticais de deploys e mudanças de configuração do pool.

Com um dashboard assim, o diagnóstico se resume a responder três perguntas: "degradou o pool inteiro ou um recorte?", "degradaram todos os destinos ou um só?", "qual das três latências cresceu?". As combinações de respostas geram três padrões típicos.

Padrão 1: todos os recortes degradaram ao mesmo tempo

Todos os países, todas as operadoras, todos os tipos de proxy mostram aumento de erros ou latência de forma síncrona, com precisão de minuto. Provedores externos e operadoras não quebram assim: eles não têm um ponto único de falha do seu lado. Portanto, o problema é seu: seu canal de saída, seu DNS, esgotamento de file descriptors ou de portas locais, um deploy recente que mudou timeouts ou o tamanho do pool de conexões, disputa de CPU com outro processo na mesma máquina. Primeira ação: olhar o marcador de deploy e o tempo de conexão com o proxy. Se o tempo de conexão cresceu e o TTFB (descontado o tempo de conexão) está estável, o problema está entre você e os nós de proxy, e quase certamente é do seu lado ou do seu provedor de rede.

Padrão 2: degradou um único recorte

Uma operadora em um país, ou um tipo de proxy, mostra queda, enquanto os outros recortes estão estáveis. Esse é o padrão mais frequente. A causa está na operadora de telecom (congestionamento de segmento de rede, manutenção programada) ou em um segmento da infraestrutura do provedor de proxies. Primeira ação: reduzir temporariamente o peso desse recorte na distribuição de carga (o mecanismo está fora do escopo deste artigo) e escrever para o suporte do provedor uma mensagem com dados concretos: recorte, horário de início, métricas antes e depois, exemplos de request_id. O suporte da Proxeon reage mais rápido a esse tipo de chamado do que a um "está tudo ruim", simplesmente porque recebe a localização já pronta. Segunda ação: olhar os logs do recorte para entender se todos os endereços internos degradaram ou apenas alguns.

Padrão 3: caiu a taxa de sucesso para um único host de destino, latências normais

Todos os recortes do pool continuam indo bem para todos os hosts, exceto um. Para esse host, cresce a taxa de upstream_4xx ou mudou o volume de bytes por requisição. Os proxies aqui não têm culpa nenhuma. O recurso de destino mudou de comportamento: introduziu limite de frequência, mudou a estrutura da página, começou a entregar conteúdo diferente para parte das requisições. Primeira ação: reduzir a intensidade das requisições a esse recurso e verificar se sua coleta está em conformidade com as regras de uso dele. Segunda: verificar o conteúdo em uma amostra e atualizar a validação das respostas. Categoricamente, não vale aumentar o número de retentativas nem trocar de endereço com mais frequência nessa situação — isso só aumenta a carga no recurso e seu gasto com tráfego.

Padrão 4 (bônus): retentativas sobem, sucesso se mantém

Taxa de sucesso em 99%, latências toleráveis, mas a taxa de retentativas subiu de 1,1 para 1,8. É degradação oculta. Está custando dinheiro agora e vai virar degradação visível assim que a reserva de tentativas se esgotar. Primeira ação: olhar qual desfecho passou a causar as repetições (campo retry_reason nos logs) e seguir por um dos três padrões anteriores.

Checklist de diagnóstico em 60 segundos

  1. Segundos 0–10: linha de cima do dashboard. Qual dos quatro sinais se afastou da linha de ontem? Quando exatamente?
  2. Segundos 10–25: tabela por recortes. Um recorte vermelho ou todos? Se todos — padrão 1, ir aos marcadores de deploy e à sua infraestrutura.
  3. Segundos 25–40: tabela por hosts de destino. Um host ou todos? Se for um — padrão 3, os proxies não têm culpa.
  4. Segundos 40–50: relação entre latências. Cresceu o tempo de conexão, o TTFB ou o tempo total?
  5. Segundos 50–60: formular uma frase: "desde [hora], no recorte [recorte], para [destino], cresceu [métrica] de [antes] para [depois], provavelmente por causa de [hipótese]". Essa frase vai para o canal de incidentes e para o ticket do provedor.

Erros típicos que anulam o valor da observabilidade

Já vimos dezenas de implementações de monitoramento em torno de proxies. Os erros se repetem com uma constância impressionante.

  • Tempo médio de resposta como métrica principal. Já discutimos: a média esconde a cauda, e a cauda é justamente o problema.
  • Média de percentis entre instâncias. Some histogramas, não p95 prontos.
  • Endereço do proxy como label de métrica. Explosão de cardinalidade e queda do armazenamento em um mês.
  • Credenciais de acesso nos logs. Uma linha "para depurar" e a senha fica em armazenamento compartilhado de logs por um ano.
  • Retentativas contadas como sucesso. Se você conta só o resultado final, nunca vai ver que o pool está trabalhando o triplo do necessário.
  • Código 429 ou 403 do recurso de destino registrados como erro de proxy. É erro de classificação, que desvia a investigação para o lado errado e estraga a relação com o provedor.
  • Uma latência em vez de três. Sem medir separadamente conexão, TTFB e tempo total, você não distingue problema de disponibilidade de problema de banda.
  • Alerta sem tráfego mínimo. Ruído noturno e perda de confiança nos alertas.
  • Alerta para cada endereço individual. Isso é tarefa de quarentena, não de alerta humano.
  • Nenhum alerta para ausência de dados. O cliente cai — gráficos verdes.
  • Hora local nos logs. Correlacionar com dados do provedor e de colegas em outro fuso vira tortura.
  • Dashboard sem marcadores de deploy. Metade dos incidentes coincide com mudanças de código ou configuração. Sem marcador, você vai procurar a causa fora quando ela está dentro.
  • Armazenar logs detalhados para sempre. Caro e inútil: em duas semanas os detalhes não são mais necessários, e os agregados já estão nas métricas.

Stack mínimo sem complicação

Observabilidade vira facilmente projeto por projeto. Uma equipe de três pessoas sobe uma fila distribuída, um cluster de banco colunar e um sistema de traces — para um cliente que faz 20 requisições por segundo. Não faça isso. O stack deve corresponder à escala.

Nível 1: um serviço, até 50 requisições por segundo

Basta log JSON estruturado em arquivo ou na saída padrão, e um script que, a cada minuto, agrega esses logs e escreve os quatro sinais em um banco simples ou até em um arquivo. Dashboard — qualquer ferramenta que consiga plotar um gráfico a partir de uma tabela. Alertas — um script cron que compara agregados com limiares e manda mensagem no messenger. Esse stack sobe em um dia e cobre 80% das necessidades.

Nível 2: vários serviços, de 50 a 1000 requisições por segundo

Conjunto padrão: Prometheus para métricas, Grafana para dashboards, Alertmanager para roteamento de alertas, Loki ou armazenamento equivalente para logs com índice por labels. Os clientes exportam métricas via endpoint HTTP interno e escrevem logs JSON na saída padrão; um agente os coleta. Recording rules pré-calculam taxas de sucesso para que os dashboards abram instantaneamente. Essa é a configuração descrita nos exemplos acima, e cobre a grande maioria das equipes.

Nível 3: dezenas de serviços, milhares de requisições por segundo

Aqui faz sentido usar OpenTelemetry como camada única de coleta, um banco colunar para logs com consultas analíticas arbitrárias sobre bilhões de linhas e, talvez, traces para cadeias complexas. Mas mesmo nesse nível, os quatro sinais e a estrutura dos logs não mudam. Só mudam o transporte e o armazenamento.

Dados do provedor como complemento

O provedor de proxies vê o lado dele: consumo de tráfego por porta, número de conexões ativas, hora da última rotação. O painel e a API da Proxeon entregam essas estatísticas, e é útil trazê-las ao lado das suas próprias métricas — pelo menos para conferir o consumo de tráfego: se o seu contador de bytes e o do provedor divergem mais de 10–15%, há problema na sua medição (na maioria das vezes, não são contados os cabeçalhos, os handshakes TLS ou as respostas interrompidas). Mas os dados do provedor não substituem os seus: só você sabe qual requisição foi bem-sucedida do ponto de vista da tarefa.

Wrapper de cliente como ponto único de medição

Independentemente do nível do stack, todas as medições devem passar por um único wrapper sobre o cliente HTTP. Nada de "aqui a gente mede, aqui esquece". Esquematicamente:

import time, json, sys, requests

def fetch_via_proxy(ctx, url, timeout=(5, 20)):
t0 = time.perf_counter()
outcome, status, err = "ok", None, None
ttfb = connect = total = 0.0
bytes_in = 0
try:
session = ctx.session # уже настроена на прокси по внутреннему proxy_id
resp = session.get(url, timeout=timeout, stream=True)
ttfb = time.perf_counter() - t0
status = resp.status_code
body = resp.content
bytes_in = len(body)
total = time.perf_counter() - t0
if status >= 500: outcome = "upstream_5xx"
elif status >= 400: outcome = "upstream_4xx"
elif not ctx.validate(body): outcome = "invalid_content"
except requests.exceptions.ProxyError as e:
outcome, err = "proxy_connect_error", type(e).__name__
except requests.exceptions.ReadTimeout as e:
outcome, err = "upstream_timeout", type(e).__name__
except requests.exceptions.ChunkedEncodingError as e:
outcome, err = "truncated", type(e).__name__
finally:
total = total or (time.perf_counter() - t0)
observe(ctx, outcome, ttfb, connect, bytes_in, len(url), ctx.attempt)
record = redact({"ts": ctx.now_utc(), "request_id": ctx.request_id, "attempt": ctx.attempt,
"proxy_id": ctx.proxy_id, "proxy_type": ctx.proxy_type, "country": ctx.country,
"operator": ctx.operator, "pool": ctx.pool, "target_host": ctx.host(url),
"status": status, "outcome": outcome, "error_class": err,
"ttfb_ms": int(ttfb * 1000), "total_ms": int(total * 1000), "bytes_in": bytes_in})
sys.stdout.write(json.dumps(record, ensure_ascii=False) + chr(10))
return outcome, status

Repare no desfecho invalid_content: resposta com código 200 que não passou na sua validação (por exemplo, uma página sem o bloco de dados esperado) não é sucesso. São exatamente essas respostas que costumam mascarar mudanças de comportamento do recurso de destino.

Casos: como a observabilidade funciona na prática

Caso 1: uma operadora, um país, menos duas horas de indisponibilidade por semana

Uma equipe de monitoramento de preços em sites públicos operava com um pool de cerca de 300 endereços móveis e residenciais a uma intensidade de aproximadamente 40 requisições por segundo. Antes de adicionar dimensionamento por operadora, eles viam uma coisa só: o p95 geral de TTFB subia de tempos em tempos de 1,6 para 3–4 segundos durante algumas horas, e a taxa de sucesso caía 3–5 pontos. Investigar cada episódio desses levava de uma a duas horas e quase sempre terminava em "passou sozinho". Depois de adicionar as labels country e operator, o primeiro episódio foi localizado em dois minutos: endereços móveis de uma operadora em uma região, p95 de 11 segundos contra norma de 2. Os outros 85% do pool estavam perfeitamente bem. A equipe reduziu o peso do recorte na distribuição, enviou ao provedor um relatório com janela de tempo e métricas, e em algumas horas recebeu confirmação de manutenção programada da operadora. Resultado no trimestre: o tempo de diagnóstico caiu de 90 minutos para 5, o número de episódios não mudou, mas o impacto deles no p95 geral ficou imperceptível.

Caso 2: retentativas ocultas e +38% na fatura de tráfego

Outra equipe detectou um aumento de 38% no consumo de tráfego em um mês, com o mesmo número de tarefas e taxa de sucesso estável em 98,5%. A primeira hipótese — as páginas de destino ficaram mais pesadas. A métrica de bytes por requisição bem-sucedida refutou: 46 kilobytes, como antes. Já a taxa de retentativas, que ninguém olhava até então, subiu de 1,08 para 1,52. A análise do campo retry_reason nos logs mostrou: quase todas as repetições eram causadas pelo desfecho upstream_timeout em um host de destino que passou a responder mais devagar, mas cabia no timeout na segunda tentativa. Os proxies não eram a causa. A equipe aumentou o timeout de leitura desse host de 8 para 15 segundos (o p99 saudável era 11), reduziu a intensidade das requisições a ele e devolveu o consumo de tráfego ao patamar anterior em três dias. Moral da história: taxa de retentativas é métrica de dinheiro.

Caso 3: de 200 notificações por semana para seis

Uma terceira equipe herdou um sistema de alertas com a regra "taxa de sucesso de qualquer recorte em 1 minuto abaixo de 90%", sem limite de tráfego. De madrugada, quando alguns recortes recebiam duas ou três requisições por minuto, a regra disparava dezenas de vezes. Os plantonistas desligaram o som. Um incidente real — queda de disponibilidade de um segmento inteiro por quatro horas — só foi percebido de manhã, pelas reclamações. Refazer as regras levou um dia: adicionada condição de tráfego mínimo, introduzidas duas janelas (5 minutos e 1 hora), adicionado atraso for de 10 minutos, recortes movidos para nível warning, e mantido um único critical no nível do pool. Resultado: cerca de seis notificações por semana, das quais quatro ou cinco exigiam ação real. Os plantonistas voltaram a ligar o som.

FAQ: perguntas frequentes sobre observabilidade de pool de proxies

Preciso de traces se já tenho métricas e logs?

Para a maioria das equipes, não. Uma requisição via proxy não passa por dez microsserviços; ela tem uma única transição. O papel do trace aqui é cumprido pelo request_id nos logs: por ele você reúne todas as tentativas de uma requisição. Traces ficam úteis quando o cliente de proxy é apenas parte de uma cadeia longa (fila de tarefas, parser, validador, carregador no banco) e você precisa entender onde exatamente o tempo se perde.

Como medir TTFB se o cliente HTTP não entrega isso diretamente?

A maioria dos clientes devolve o controle após receber os cabeçalhos, se você pedir leitura em streaming (stream=True ou equivalente). O momento do retorno é a aproximação do TTFB. O tempo de conexão é medido com mais precisão no nível do transporte: muitas bibliotecas têm hooks para eventos de conexão, outras já trazem informação de fases da requisição. Se não houver hooks, pode-se medir o tempo até o envio do primeiro byte da requisição com uma medição de baixo nível separada, mas, para fins práticos, a aproximação pela leitura em streaming basta.

Como distinguir erro do proxy de erro do recurso de destino?

Pelo ponto onde surgiu. Se a conexão com o proxy não se estabeleceu, ou se o proxy retornou um código próprio (por exemplo, 407 em problemas de autorização, ou uma resposta específica do gateway), é erro de proxy. Se o proxy aceitou a requisição e depois você recebeu um código do servidor de destino ou um timeout esperando resposta, é erro entre proxy e destino, ou do próprio destino. Classifique nessa fronteira e depois compare por recortes: erros de destino aparecem em todos os recortes ao mesmo tempo, erros de proxy aparecem em recortes específicos.

Qual deve ser um nível saudável de taxa de retentativas?

Depende das metas e do tipo de proxy. Para endereços móveis, pode-se considerar normal 1,05–1,15: rede sem fio é naturalmente menos previsível. Para data center, 1,01–1,05. Mais importante do que o valor absoluto é a estabilidade dele: um aumento de 30% em relação ao nível basal semanal é motivo para investigar, mesmo que o valor absoluto pareça modesto.

Como escolher limiares de alerta sem ter histórico?

Comece observando sem alertas: colete duas semanas de dados, olhe a distribuição da taxa de sucesso e do p95 por dias e horas. O limiar de critical deve ser posto de forma que, nessas duas semanas, tivesse disparado no máximo uma ou duas vezes, e nas duas você concordasse que era incidente. O limiar de warning, de forma que disparasse algumas vezes por semana. Depois revise todo mês: um alerta que nunca levou a uma ação é endurecido ou removido.

Vale a pena guardar o corpo da resposta pelo menos para erros?

Para erros de transporte não há corpo. Para respostas com conteúdo inesperado (invalid_content), vale guardar os primeiros algumas centenas de bytes ou o hash e o tamanho, em armazenamento separado com tempo de vida de um a dois dias, com limpeza obrigatória de dados pessoais. Corpos completos não precisam ser guardados: são pesados, podem conter dados de terceiros, e para diagnosticar proxy quase sempre basta entender que tipo de página veio.

Como conferir sua contagem de tráfego com os dados do provedor?

Conte bytes nas duas direções, incluindo cabeçalhos, e compare somas diárias por porta ou sessão com o que mostra o painel do provedor. Divergência de 5–10% é normal: do seu lado não estão sendo contados os custos de TLS e as conexões interrompidas. Divergência acima de 20% é motivo para checar se você está contando apenas o corpo da resposta e perdendo tentativas que terminaram em exceção antes da leitura do corpo.

Quanto tempo leva para implementar a abordagem descrita do zero?

Para um cliente com wrapper HTTP já existente: um dia para métricas e estrutura de logs, um dia para o dashboard, um dia para as regras de alerta e mais duas semanas de observação antes de ligar os alertas critical. Se você tem vários clientes em linguagens diferentes, o prazo principal vai para a negociação de um conjunto único de desfechos e labels — aqui é importante não ter pressa, porque renomear uma métrica depois de um ano acumulando histórico dói muito.

O que fazer se o provedor não tiver dados de operadora para parte dos endereços?

Crie um valor unknown e acompanhe sua proporção. Se unknown passar de 10–15% do pool, seus recortes por operadora perdem sentido — solicite ao provedor metadados completos ou marque os endereços manualmente uma vez, na inclusão no pool.

Conclusão: um minuto em vez de uma hora

Voltemos à cena do início. "Os proxies estão lentos" é uma frase que não contém informação. Tudo o que está descrito neste guia serve a um único objetivo: transformá-la em uma frase com números, recorte, horário e hipótese. Os quatro sinais — taxa de sucesso, latência em percentis, taxa de retentativas, consumo de tráfego — dão o quadro do estado do pool. O log estruturado com identificador de requisição, identificador de proxy, número da tentativa, código, três latências e tamanho — dá os detalhes para a investigação. Percentis em vez de média mostram a cauda, onde vivem os problemas reais. Dimensionamento por país, operadora e tipo localiza a degradação até o recorte. Alertas com tráfego mínimo, duas janelas e histerese disparam raramente e a propósito. O dashboard-funil responde a três perguntas em um minuto.

Por onde começar amanhã? Primeiro passo: um wrapper sobre o cliente com classificação única de desfechos e uma função única de registro. Segundo: histograma de TTFB e contador de tentativas com labels por país, operadora e tipo. Terceiro: função de redação de logs, por onde tudo passa sem exceção. Quarto: dashboard com quatro gráficos e uma tabela por recortes. Quinto: duas semanas de observação e só depois os alertas. Em um mês, você vai olhar o gráfico da taxa de retentativas e, quem sabe, se surpreender: o pool estava trabalhando bem mais intensamente do que parecia. Em um trimestre, a frase "está tudo lento" vai desaparecer do seu chat — não porque os problemas vão acabar, mas porque cada um deles você vai chamar pelo nome.