Meranie času psql cez VPN: oddelenie čakania siete od práce dotazu
psql hlási jedno číslo uplynutého času na príkaz, takže pomalá VPN ho nafúkne. Oddelte čas pripojenia, vykonania, prenosu a cesty tam a späť, skôr než začnete ladiť ktorúkoľvek stranu.
Váš report sa skončí približne za sekundu na hostiteľovi databázy a za štyri cez VPN, a číslo, ktoré psql vypíše, ukazuje priamo na databázu. To číslo sú stopky na strane klienta, nie meranie servera. Zahŕňa príkaz na ceste von, čakanie na odpoveď a riadky na ceste späť. Oddeliť tieto tri je jediný spôsob, ako zistiť, ktorú stranu ladiť.
Čo časovač psql skutočne meria
\timing on spustí stopky tesne predtým, než klient zapíše príkaz do socketu, a zastaví ich, keď je prečítaný posledný riadok výsledku. Vlastné vykonávanie servera leží niekde v tom okne, a rovnako každá milisekunda, ktorú pakety strávia v tuneli. Nič vo výstupe ich neoddeľuje. EXPLAIN tiež vypisuje číslo, ale to vytvára server a vylučuje čakanie klienta. Dve čísla, dve rôzne otázky.
Spustite jednu reláciu, nechajte meranie času zapnuté a spustite príkaz, ktorý vás zaujíma, zvnútra nej. Opätovné použitie relácie drží cenu pripojenia mimo prvého merania.
psql "host=db.internal port=5432 dbname=appdb user=app" \
-c "\timing on" \
-c "select count(*) from orders where created_at > now() - interval '1 day';"Číslo stále zahŕňa prenos výsledku, čo je tu to, čo chcete. Už nezahŕňa výmenu pripojenia a overenia, ktorú by pridal nový príkaz.
Porovnajte jeden príkaz na troch cestách
Spustite ten istý príkaz cez Unix socket, cez LAN adresu a cez názov hostiteľa VPN a porovnajte všetky tri. Ak je pomalá len posledná, dotaz nie je premenná.
Každé volanie sa najprv pripojí a až potom meria príkaz, takže cena pripojenia zostáva mimo čísla, kým prenos zostáva vnútri. Držte klauzulu WHERE, počet riadkov a klientsky stroj rovnaké, inak porovnávate pracovné zaťaženie, a nie cesty.
psql -h /var/run/postgresql -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h 10.40.0.12 -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h db.vpn.internal -U app -d appdb -c "\timing on" -c "select count(*) from orders;"V LAN je rozdiel zvyčajne dosť malý na to, aby sa zanedbal; cez VPN často nie. Zoradenie troch výsledkov vám potom povie, na ktoré čakanie zaútočiť.
Pomalý lokálny socket znamená prácu servera alebo plánovanie. Pomalý výsledok LAN znamená dotaz alebo objem, ktorý prenáša. Len pomalý výsledok VPN znamená latenciu, MTU alebo dostupnú šírku pásma na ceste, a žiadne zmeny indexov to nenapravia.
Vytiahnite vlastný čas vykonania servera
Server môže oznámiť, koľko času strávil, a práve toto číslo treba porovnávať s klientskym. EXPLAIN s ANALYZE spustí príkaz a vypíše čas plánovania a vykonania v pláne, zatiaľ čo BUFFERS pridáva správanie medzipamäte, ktoré často vysvetľuje teplý beh proti studenému. Neobracajte ho na zapisovacie príkazy, ak nemáte v úmysle zapisovať. Na replike iba na čítanie je to najbezpečnejšie miesto na začiatok, a referenčná príručka psql dokumentuje rozsah časovača na strane klienta.
Pre príkazy, ktoré aplikácia už spúšťa, pg_stat_statements agreguje vykonávanie na strane servera bez toho, aby čokoľvek spúšťal znova. Riadky sú na normalizovaný dotaz, takže jeden záznam môže pokryť viacero miest volania.
select calls, round(mean_exec_time::numeric, 2) as mean_ms, rows
from pg_stat_statements
order by mean_exec_time desc
limit 5;Názvy stĺpcov sa líšia podľa verzie: PostgreSQL 13 a novšie vystavujú mean_exec_time, kým staršie verzie ho nazývajú mean_time, takže skontrolujte pomocou \d pg_stat_statements, ak tento dotaz vráti chybu. Tieto hodnoty vylučujú plánovanie a vylučujú cestu k vášmu klientovi. Práve toto vylúčenie ich robí užitočnými pre porovnanie.
Počítajte cesty tam a späť, nielen milisekundy
Report zložený z dvesto malých závislých príkazov násobí akúkoľvek latenciu, ktorú cesta pridá, dvesto. Jediný príkaz vracajúci milión riadkov zaplatí tú latenciu raz a potom dlho platí šírku pásma. Oba vyzerajú v klientskom časovači rovnako a potrebujú opačné opravy.
Zmerajte počet príkazov namiesto toho, aby ste ho odhadovali. Prečítajte súčet, raz spustite report, potom ho prečítajte znova a odčítajte.
select sum(calls) as calls from pg_stat_statements;
-- run the report here
select sum(calls) as calls from pg_stat_statements;Ak je delta veľká a každý príkaz je na serveri lacný, dávkovanie je oprava, ktorá sa vyplatí. Skombinujte závislé dotazy, použite jediný príkaz založený na množinách alebo veďte slučku z kurzora. V interaktívnom psql, \set FETCH_COUNT 1000 núti veľký výsledok prichádzať v dávkach a mení správanie prenosu a klientskej pamäte. Neznižuje prácu servera, takže pomalý dotaz nezachráni.
Skontrolujte tunel na opakované prenosy a zaseknutia MTU
Keď sú malé príkazy rýchle a veľké výsledky sa zaseknú v polovici, dotaz je zvyčajne v poriadku a cesta nie. Tunel s nižším MTU, než má podkladové spojenie, v kombinácii s blokovaným ICMP vytvára presne taký vzorec: prvé pakety dorazia, veľké zmiznú a prenos visí, kým to neskúsi znova. Počítadlá opakovaných prenosov na strane klienta to ukazujú.
Sledujte socket a počítadlá jadra, kým beží zámerne pomalý prenos. Súčty opakovaných prenosov sú kumulatívne, takže si ich poznačte pred a po, namiesto čítania jednej vzorky.
ss -tin dst 10.40.0.12
netstat -s | grep -iE "retransmit|segments retransmitted"
ping -M do -s 1400 -c 3 10.40.0.12Opakované prenosy, ktoré počas prenosu rastú, spolu s pingom, ktorý uspeje pri 1400 bajtoch, ale zlyhá pri 1472, ukazujú na problém s MTU, nie s databázou. Zníženie MTU rozhrania tunela alebo stiahnutie MSS na bráne je obvyklá oprava a tie isté tri príkazy to potom potvrdia. Ak opakované prenosy zostanú ploché, kým je prenos stále pomalý, pozerajte na šírku pásma povolenú tunelu, a nie na straty.
Krátka postupnosť pokrýva ďalší pomalý report: porovnajte jeden príkaz lokálne, v LAN a cez VPN; prečítajte vykonávanie servera z EXPLAIN alebo pg_stat_statements; spočítajte volania pred jedným spustením a po ňom; a sledujte opakované prenosy, kým sa veľký výsledok pohybuje. Každý krok vylučuje jednu stranu cesty. Žiadny z nich nevyžaduje hádať, ktorý komponent reštartovať.