Ваш извештај се завршава за око секунду на домаћину базе података и за четири преко VPN-а, а број који psql штампа показује право на базу података. Тај број је штоперица на страни клијента, а не мерење сервера. Обухвата наредбу на путу напоље, чекање одговора и редове на путу назад. Одвајање ова три је једини начин да се зна коју страну подесити.

Шта тајмер psql-а заправо мери

\timing on покреће штоперицу непосредно пре него што клијент упише наредбу у сокет и зауставља је када се прочита последњи ред тог резултата. Извршавање самог сервера налази се негде унутар тог прозора, као и свака милисекунда коју пакети проведу у тунелу. Ништа у излазу их не раздваја. EXPLAIN такође штампа број, али њега производи сервер и он искључује чекање клијента. Два броја, два различита питања.

Започните једну сесију, оставите мерење времена укључено и извршите наредбу која вас занима из њене унутрашњости. Поновна употреба сесије држи трошак повезивања ван првог мерења.

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';"

Број и даље укључује пренос резултата, што је овде жељено. Више не укључује размену повезивања и провере идентитета коју би додао нови позив.

Упоредите једну наредбу на три пута

Извршите исту наредбу преко Unix сокета, преко LAN адресе и преко имена домаћина VPN-а и упоредите три. Ако је само последњи спор, упит није променљива.

Сваки позив се прво повезује и тек онда мери наредбу, па трошак повезивања остаје ван броја, док пренос остаје унутра. Држите клаузулу WHERE, број редова и клијентску машину истим, иначе поредите радна оптерећења, а не путеве.

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;"

У LAN-у је разлика обично довољно мала да се занемари; преко VPN-а често није. Сортирање три резултата вам затим каже на које чекање да нападнете.

Спор локални сокет значи рад сервера или планирање. Спор LAN резултат значи упит или обим који он премешта. Само спор VPN резултат значи кашњење, MTU или доступну пропусност на путу, и никакве измене индекса то не поправљају.

Извуците сопствено време извршавања сервера

Сервер може да пријави колико је времена потрошио, и управо тај број треба упоредити са клијентским. EXPLAIN са ANALYZE извршава наредбу и штампа време планирања и извршавања унутар плана, док BUFFERS додаје понашање кеша које често објашњава топао ток наспрам хладног. Не усмеравајте га на наредбе писања осим ако не намеравате да пишете. На реплици само за читање то је најсигурније место за почетак, и референца psql-а документује обим тајмера на страни клијента.

За наредбе које апликација већ извршава, pg_stat_statements сажима извршавање на страни сервера без поновног покретања било чега. Редови су по нормализованом упиту, па један запис може да покрије неколико места позива.

select calls, round(mean_exec_time::numeric, 2) as mean_ms, rows
from pg_stat_statements
order by mean_exec_time desc
limit 5;

Имена колона се разликују по издању: PostgreSQL 13 и новији излажу mean_exec_time, док старије верзије то називају mean_time, па проверите са \d pg_stat_statements, ако тај упит врати грешку. Ове вредности искључују планирање и искључују пут до вашег клијента. Управо то искључивање их чини корисним за поређење.

Бројте одласке и повратке, не само милисекунде

Извештај састављен од две стотине малих зависних наредби множи свако кашњење које пут додаје са две стотине. Једна наредба која враћа милион редова плаћа то кашњење једном и затим дуго плаћа пропусност. Обе изгледају исто у клијентском тајмеру и траже супротне поправке.

Измерите број наредби уместо да га процењујете. Прочитајте укупно, извршите извештај једном, па га прочитајте поново и одузмите.

select sum(calls) as calls from pg_stat_statements;
-- run the report here
select sum(calls) as calls from pg_stat_statements;

Ако је делта велика и свака наредба је јефтина на серверу, груписање је поправка која се исплати. Комбинујте зависне упите, користите једну наредбу засновану на скуповима или водите петљу из курсора. У интерактивном psql-у, \set FETCH_COUNT 1000 велики резултат стиже у партијама и мења понашање преноса и клијентске меморије. То не смањује рад сервера, па неће спасити спор упит.

Проверите тунел на поновна слања и застоје MTU-а

Када су мале наредбе брзе, а велики резултати застану на пола пута, упит је обично у реду, а пут није. Тунел чији је MTU нижи од основне везе, заједно са блокираним ICMP-ом, производи управо такав образац: први пакети стижу, велики нестају, и пренос виси док поново не покуша. Бројачи поновних слања на страни клијента то показују.

Посматрајте сокет и бројаче језгра док траје намерно спор пренос. Збирови поновних слања су кумулативни, па их забележите пре и после уместо да читате један узорак.

ss -tin dst 10.40.0.12
netstat -s | grep -iE "retransmit|segments retransmitted"
ping -M do -s 1400 -c 3 10.40.0.12

Поновна слања која расту током преноса, заједно са ping-ом који успева на 1400 бајтова али не успева на 1472, указују на проблем са MTU-ом, а не са базом података. Смањење MTU-а интерфејса тунела или стезање MSS-а на мрежном пролазу је уобичајена поправка, и исте три команде то потврђују после. Ако поновна слања остану равна док је пренос још спор, гледајте пропусност дозвољену тунелу, а не губитке.

Кратак след покрива следећи спор извештај: упоредите једну наредбу локално, у LAN-у и преко VPN-а; прочитајте извршавање сервера из EXPLAIN-а или pg_stat_statements-а; избројте позиве пре и после једног извршавања; и посматрајте поновна слања док се велики резултат креће. Сваки корак искључује једну страну пута. Ниједан не захтева да погађате коју компоненту поново покренути.

Повезани чланци