Redis su VPN sembra lento: meno andate e ritorni per richiesta
Una VPN non rende locale una chiamata Redis remota. Pianificate le andate e ritorni per richiesta, riutilizzate le connessioni e tenete le pipeline lontane dalle transazioni.
Una pagina che risponde in 200 ms contro un Redis locale impiega quattro secondi appena il client si trova dietro una VPN, mentre i contatori del server restano inattivi. Il log lento non mostra nulla sopra un millisecondo, il grafico della CPU è piatto e l’istanza serve altri client senza lamentarsi. Ciò che è cambiato è la distanza, e ciò che moltiplica la distanza è il numero di volte in cui il vostro codice si ferma e aspetta. Percorrete il percorso della richiesta dal client verso l’esterno e la soluzione è quasi sempre meno attese sequenziali anziché un server più veloce.
Partite dal client, non dalle metriche del server
Osservate il processo del client tra il primo e l’ultimo comando. Se la traccia mostra una risposta, una pausa e poi il comando successivo, state pagando un’andata e ritorno completa per comando e nessuna regolazione del server lo cambierà. Il log lento è lo strumento sbagliato qui, perché registra il tempo di esecuzione ed esclude deliberatamente l’I/O di comunicazione. Un log lento pulito accanto a un endpoint lento è la firma di un problema di andate e ritorni.
Misurate il percorso stesso prima di toccare il codice. redis-cli -h redis.example.internal --latency invia PING ripetuti e stampa il tempo medio di richiesta e risposta, che è il vostro costo per scambio. Moltiplicatelo per il numero di comandi sequenziali in un’azione utente e avete un minimo per l’endpoint.
Contate le andate e ritorni che un’azione utente richiede davvero
Prendete un solo endpoint e annotate ogni chiamata Redis in ordine, comprese quelle nascoste in un ORM, in un middleware di sessione o in una ricerca di feature flag. La maggior parte degli endpoint lenti finisce per fare venti o trenta chiamate dove tre basterebbero, e quelle nascoste sono di solito la maggioranza.
Una catena di dipendenze non può essere compattata: se il secondo comando ha bisogno della chiave restituita dal primo, l’attesa è reale e le uniche opzioni sono un altro modello di dati o un’operazione lato server che esprima l’intero passo in una volta. Le letture indipendenti sono un altro caso, perché nulla vi obbliga ad attendere la prima risposta prima di inviare la seconda. Marcate ogni chiamata come dipendente o indipendente prima di modificare qualsiasi cosa, e la forma della soluzione diventa ovvia.
Il numero che conta sono le attese sequenziali, non i comandi totali. Trenta comandi in un lotto costano comunque una sola attesa.
Raggruppate solo i comandi che non dipendono gli uni dagli altri
Una pipeline accoda comandi indipendenti e raccoglie le risposte in ordine, trasformando N attese in una sola. La maggior parte dei client la espone come blocco, callback o gestore di contesto anziché come modalità di connessione separata, quindi controllate l’API fornita dalla versione installata. Il guadagno cresce con il tempo di andata e ritorno, ed è proprio per questo che conta su una VPN e si nota appena su 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())
Passate transaction=False quando volete solo raggruppare. Per impostazione predefinita il client avvolge il lotto in MULTI ed EXEC, il che lo rende atomico e cambia ciò che gli altri client possono osservare in volo. L’atomicità è una decisione di correttezza, non una manopola delle prestazioni, e una pipeline non è una transazione solo perché entrambe riducono le andate e ritorni.
Leggete ogni risposta invece del solo invio del lotto. Un comando in coda può restituire un errore mentre il lotto nel suo insieme si completa, e un client che getta la lista delle risposte trasforma una scrittura fallita in una silenziosa. Confrontate la lunghezza della lista delle risposte con il numero di comandi che avete accodato.
Mantenete viva la connessione tra le azioni
Ogni nuova connessione paga la risoluzione dei nomi, un handshake TCP, un handshake TLS e un AUTH, e su una VPN ciascuno di questi è un’andata e ritorno che non potete raggruppare. Create il client una volta per processo e riutilizzatelo, oppure usate il pool fornito dalla vostra libreria con una dimensione limitata. Riconnettersi per ogni richiesta è la seconda causa più comune di questo sintomo dopo le attese per comando.
Tenete il nome host privato nella configurazione anziché un indirizzo letterale. La validazione del certificato e l’identità del servizio valgono ancora su una rete privata, e il pinning di un IP si rompe nel momento in cui il provider rinumera una sottorete. Se la risoluzione è la parte lenta, correggete il percorso del resolver anziché la stringa di connessione.
Impostate un timeout del socket esplicito così che un tunnel bloccato fallisca rapidamente invece di appendere un worker. Poi confermate che il pool venga davvero riutilizzato registrando la creazione delle connessioni nel client.
Dimensionate il lotto prima che diventi un problema di memoria
Redis trattiene le risposte in coda per tutta la durata di un lotto, e il client trattiene poi il risultato analizzato. Una pipeline di diecimila valori grandi scambia attese di rete con picchi di memoria e lunghe pause a entrambe le estremità.
Scegliete la dimensione del lotto in base al volume delle risposte e non al numero di comandi. Qualche centinaio di comandi piccoli in un lotto è di solito comodo, mentre un centinaio di carichi di diversi kilobyte non lo è. Dividete per byte e anche per numero, e tenete ogni lotto entro il budget di latenza che l’endpoint ha davvero.
Misurate da un capo all’altro dopo la modifica: redis-cli -h redis.example.internal --latency-history mostra se il percorso stesso è stabile, e il vostro timer delle richieste mostra se l’endpoint è migliorato. Se l’endpoint è ancora lento mentre la cronologia della latenza è piatta, il costo rimanente è lavoro lato server o la vostra serializzazione, e il passo successivo è un profilo dell’handler anziché un’altra modifica al raggruppamento.