Uma página que responde em 200 ms contra um Redis local leva quatro segundos assim que o cliente fica atrás de uma VPN, enquanto os contadores do servidor permanecem inativos. O registo lento não mostra nada acima de um milissegundo, o gráfico de CPU é plano e a instância serve outros clientes sem queixas. O que mudou foi a distância, e o que multiplica a distância é o número de vezes que o seu código para e espera. Percorra o caminho do pedido desde o cliente para fora e a solução é quase sempre menos esperas sequenciais em vez de um servidor mais rápido.

Comece pelo cliente, não pelas métricas do servidor

Observe o processo do cliente entre o primeiro e o último comando. Se o traço mostra uma resposta, uma pausa e depois o comando seguinte, está a pagar uma ida e volta completa por comando e nenhum ajuste do servidor o mudará. O registo lento é o instrumento errado aqui, porque regista o tempo de execução e exclui deliberadamente a E/S de comunicação. Um registo lento limpo ao lado de um endpoint lento é a assinatura de um problema de idas e voltas.

Meça o próprio caminho antes de tocar no código. redis-cli -h redis.example.internal --latency envia PING repetidos e imprime o tempo médio de pedido e resposta, que é o seu custo por troca. Multiplique-o pelo número de comandos sequenciais numa ação de utilizador e tem um mínimo para o endpoint.

Conte as idas e voltas de que uma ação de utilizador realmente precisa

Pegue num único endpoint e anote cada chamada Redis por ordem, incluindo as que estão escondidas dentro de um ORM, de um middleware de sessão ou de uma pesquisa de feature flag. A maioria dos endpoints lentos acaba por fazer vinte ou trinta chamadas onde três bastariam, e as escondidas são normalmente a maioria.

Uma cadeia de dependências não pode ser colapsada: se o segundo comando precisa da chave devolvida pelo primeiro, a espera é real e as únicas opções são outro modelo de dados ou uma operação do lado do servidor que exprima todo o passo de uma vez. As leituras independentes são outro caso, porque nada o obriga a esperar pela primeira resposta antes de enviar a segunda. Marque cada chamada como dependente ou independente antes de editar seja o que for, e a forma da solução torna-se óbvia.

O número que importa são as esperas sequenciais, não os comandos totais. Trinta comandos num lote continuam a custar uma espera.

Agrupe apenas os comandos que não dependem uns dos outros

Uma pipeline põe em fila comandos independentes e recolhe as respostas por ordem, transformando N esperas numa só. A maioria dos clientes expõe-na como bloco, callback ou gestor de contexto em vez de um modo de ligação separado, por isso verifique a API que a versão instalada fornece. O ganho cresce com o tempo de ida e volta, e é exatamente por isso que conta numa VPN e quase não se nota em localhost.

import redis

r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
    pipe.get(key)
print(pipe.execute())

Passe transaction=False quando só quer agrupar. Por omissão o cliente envolve o lote em MULTI e EXEC, o que o torna atómico e altera o que outros clientes podem observar a meio. A atomicidade é uma decisão de correção, não um botão de desempenho, e uma pipeline não é uma transação só porque ambas reduzem idas e voltas.

Leia cada resposta em vez de apenas o envio do lote. Um comando em fila pode devolver um erro enquanto o lote como um todo termina, e um cliente que deita fora a lista de respostas transforma uma escrita falhada numa silenciosa. Compare o comprimento da lista de respostas com o número de comandos que pôs em fila.

Mantenha a ligação viva entre ações

Cada nova ligação paga resolução de nomes, um handshake TCP, um handshake TLS e um AUTH, e por VPN cada um desses é uma ida e volta que não pode agrupar. Crie o cliente uma vez por processo e reutilize-o, ou use o pool que a sua biblioteca oferece com um tamanho limitado. Reconectar por pedido é a segunda causa mais comum deste sintoma depois das esperas por comando.

Mantenha o nome de anfitrião privado na configuração em vez de um endereço literal. A validação do certificado e a identidade do serviço continuam a aplicar-se numa rede privada, e fixar um IP quebra no momento em que o fornecedor renumera uma sub-rede. Se a resolução é a parte lenta, corrija o caminho do resolvedor em vez da cadeia de ligação.

Defina um tempo limite de socket explícito para que um túnel bloqueado falhe depressa em vez de pendurar um worker. Depois confirme que o pool é realmente reutilizado registando a criação de ligações no cliente.

Dimensione o lote antes de se tornar um problema de memória

O Redis retém as respostas em fila durante todo o lote, e o cliente retém depois o resultado analisado. Uma pipeline de dez mil valores grandes troca esperas de rede por picos de memória e pausas longas nos dois extremos.

Escolha o tamanho do lote pelo volume de resposta e não pelo número de comandos. Algumas centenas de comandos pequenos num lote são normalmente confortáveis, enquanto cem cargas de vários kilobytes não são. Divida por bytes e também por número, e mantenha cada lote dentro do orçamento de latência que o endpoint tem de facto.

Meça de ponta a ponta após a alteração: redis-cli -h redis.example.internal --latency-history mostra se o caminho em si é estável, e o seu próprio temporizador de pedidos mostra se o endpoint melhorou. Se o endpoint continua lento enquanto o histórico de latência é plano, o custo restante é trabalho do servidor ou a sua própria serialização, e o passo seguinte é um perfil do gestor em vez de outra alteração de agrupamento.

Artigos relacionados