Redis przez VPN wydaje się wolny: mniej rund na żądanie
VPN nie czyni zdalnego wywołania Redis lokalnym. Zaplanuj rundy na żądanie, używaj połączeń ponownie i trzymaj potoki z dala od transakcji.
Strona, która odpowiada w 200 ms wobec lokalnego Redis, zajmuje cztery sekundy, gdy klient znajdzie się za VPN, a liczniki serwera pozostają bezczynne. Wolny log nie pokazuje nic powyżej milisekundy, wykres CPU jest płaski, a instancja obsługuje innych klientów bez skarg. Zmieniła się odległość, a odległość mnoży liczba zatrzymań i oczekiwań w kodzie. Przejdź ścieżkę żądania od klienta na zewnątrz, a rozwiązaniem prawie zawsze jest mniej kolejnych oczekiwań, nie szybszy serwer.
Zacznij od klienta, nie od metryk serwera
Obserwuj proces klienta między pierwszym a ostatnim poleceniem. Jeśli ślad pokazuje odpowiedź, pauzę, a potem następne polecenie, płacisz pełną rundę za każde polecenie i żadne strojenie serwera tego nie zmieni. Wolny log to tutaj niewłaściwe narzędzie, bo rejestruje czas wykonania i celowo pomija I/O komunikacji. Czysty wolny log obok wolnego endpointu to sygnatura problemu z rundami.
Zmierz samą ścieżkę, zanim dotkniesz kodu. redis-cli -h redis.example.internal --latency wysyła powtarzane PING-i i wypisuje średni czas żądania i odpowiedzi, czyli twój koszt na wymianę. Pomnóż to przez liczbę kolejnych poleceń w jednej akcji użytkownika, a otrzymasz dolny limit dla endpointu.
Policz rundy, których naprawdę wymaga jedna akcja użytkownika
Weź jeden endpoint i zapisz po kolei każde wywołanie Redis, w tym te ukryte w ORM, middleware sesji czy wyszukiwaniu flagi funkcji. Większość wolnych endpointów wykonuje dwadzieścia lub trzydzieści wywołań tam, gdzie wystarczyłyby trzy, a ukryte zwykle stanowią większość.
Łańcucha zależności nie da się złożyć: jeśli drugie polecenie potrzebuje klucza zwróconego przez pierwsze, oczekiwanie jest realne, a jedyne opcje to inny model danych albo operacja po stronie serwera wyrażająca cały krok naraz. Niezależne odczyty to inny przypadek, bo nic nie zmusza do czekania na pierwszą odpowiedź przed wysłaniem drugiej. Oznacz każde wywołanie jako zależne lub niezależne, zanim cokolwiek zmienisz, a kształt rozwiązania stanie się oczywisty.
Liczba, która się liczy, to kolejne oczekiwania, a nie łączne polecenia. Trzydzieści poleceń w jednej partii nadal kosztuje jedno oczekiwanie.
Grupuj tylko polecenia, które nie zależą od siebie
Potok kolejkuje niezależne polecenia i zbiera odpowiedzi po kolei, zamieniając N oczekiwań w jedno. Większość klientów udostępnia go jako blok, callback lub menedżer kontekstu, a nie osobny tryb połączenia, więc sprawdź API dostarczane przez zainstalowaną wersję. Zysk rośnie z czasem rundy i właśnie dlatego liczy się na VPN, a na localhost jest ledwo zauważalny.
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())
Przekaż transaction=False, gdy chcesz tylko grupowania. Domyślnie klient opakowuje partię w MULTI i EXEC, co czyni ją atomową i zmienia to, co inni klienci mogą zaobserwować w locie. Atomowość to decyzja o poprawności, a nie pokrętło wydajności, i potok nie jest transakcją tylko dlatego, że oba zmniejszają liczbę rund.
Czytaj każdą odpowiedź, a nie tylko wysłanie partii. Polecenie w kolejce może zwrócić błąd, podczas gdy partia jako całość się kończy, a klient, który wyrzuca listę odpowiedzi, zamienia nieudany zapis w cichy. Porównaj długość listy odpowiedzi z liczbą zakolejkowanych poleceń.
Utrzymuj połączenie żywe między akcjami
Każde nowe połączenie płaci za rozwiązywanie nazw, uścisk TCP, uścisk TLS i AUTH, a przez VPN każde z nich jest rundą, której nie zgrupujesz. Twórz klienta raz na proces i używaj go ponownie albo korzystaj z puli oferowanej przez bibliotekę o ograniczonym rozmiarze. Ponowne łączenie przy każdym żądaniu to druga najczęstsza przyczyna tego objawu po oczekiwaniach na polecenie.
Trzymaj prywatną nazwę hosta w konfiguracji, a nie dosłowny adres. Walidacja certyfikatu i tożsamość usługi nadal obowiązują w sieci prywatnej, a przypięcie IP psuje się w momencie, gdy dostawca przenumeruje podsieć. Jeśli rozwiązywanie nazw jest wolną częścią, napraw ścieżkę resolvera, a nie connection string.
Ustaw jawny limit czasu gniazda, aby zawieszony tunel szybko zawodził, zamiast wieszać proces roboczy. Następnie potwierdź, że pula jest naprawdę używana ponownie, logując tworzenie połączeń w kliencie.
Określ rozmiar partii, zanim stanie się problemem pamięci
Redis przechowuje odpowiedzi w kolejce przez cały czas trwania partii, a klient przechowuje potem przeanalizowany wynik. Potok dziesięciu tysięcy dużych wartości wymienia oczekiwania sieciowe na skoki pamięci i długie pauzy po obu stronach.
Wybieraj rozmiar partii według objętości odpowiedzi, a nie liczby poleceń. Kilkaset małych poleceń w jednej partii jest zwykle wygodne, a sto ładunków kilkukilobajtowych już nie. Dziel według bajtów i według liczby, i trzymaj każdą partię w budżecie opóźnienia, który endpoint naprawdę ma.
Zmierz od końca do końca po zmianie: redis-cli -h redis.example.internal --latency-history pokazuje, czy sama ścieżka jest stabilna, a twój własny stoper żądań pokazuje, czy endpoint się poprawił. Jeśli endpoint nadal jest wolny, a historia opóźnień jest płaska, pozostały koszt to praca po stronie serwera lub twoja własna serializacja, a następnym krokiem jest profil procedury obsługi, a nie kolejna zmiana grupowania.