Case study
MySQL slow_log: Pronadji najcesce i najsporije query digeste
Kada MySQL aplikacija uspori, gledanje samo najduzeg pojedinacnog upita cesto nije dovoljno.
Upit koji je jednom trajao 15 sekundi moze biti manje vazan od drugog upita koji traje 800 ms, ali se izvrsava 50,000 puta.
Tokom resavanja problema sa performansama baze podataka, obicno zelimo da odgovorimo na tri razlicita pitanja:
- Koji obrasci upita se pojavljuju najcesce?
- Koji obrasci upita trose najvise ukupnog vremena baze podataka?
- Koji obrasci upita imaju najlosije pojedinacno vreme izvrsavanja?
Ako MySQL upisuje svoj slow query log u TABLE, sva tri pitanja mogu da se odgovore direktno iz mysql.slow_log.
Problem sa gledanjem sirovih upita
Posmatrajmo upite kao sto su:
Ovo su tri razlicita SQL niza, ali operativno su to isti upit:
Grupisanje po originalnom sql_text bi zato sakrilo stvarnu ucestalost obrasca upita.
Ono sto nam treba jeste otisak upita, ili normalizovana reprezentacija nalik digestu.
Prvo proverite da li je slow log dostupan
Za ovaj pristup, MySQL treba da upisuje spore upite u tabelu mysql.slow_log.
Tipicna konfiguracija izgleda ovako:
Odgovarajuci long_query_time u velikoj meri zavisi od workload-a. Ako je previsok, sakricete vazne upite koji se cesto izvrsavaju; ako je prenizak, mozete dobiti veoma veliki slow log.
Zasto jednostavno ne koristiti STATEMENT_DIGEST()?
MySQL 8 pruza:
Na primer:
vraca normalizovanu reprezentaciju slicnu ovoj:
| |
Ovo izgleda idealno.
Postoji jedan praktican problem kada se ovo primenjuje na istorijske podatke iz mysql.slow_log: ove funkcije pozivaju MySQL SQL parser.
Ako makar jedan sacuvani statement ne moze da se parsira, sama analiza moze da propadne sa greskom kao sto je:
| |
To je posebno nezgodno kada se analiziraju hiljade ili milioni istorijskih slow-log zapisa.
Za istrazivacko resavanje problema, jednostavan SQL fingerprint zato moze da bude robusniji.
Napravite bezbedan query fingerprint
Sledeci primer:
- konvertuje
sql_textu tekst; - uklanja string literale;
- zamenjuje numericke literale;
- sabija beline;
- generise kratak SHA-256 fingerprint.
| |
Rezultat odmah pretvara veliki slow log u nesto mnogo korisnije:
Sada mozemo da razlikujemo veoma razlicite probleme sa performansama.
Pronadjite najcesce obrasce upita
Za upite koji generisu opterecenje kroz ponavljanje:
Ovo je posebno korisno za otkrivanje ponasanja aplikacije kao sto su:
- polling;
- N+1 upiti;
- preterani API pozivi;
- ponovljena trazenja sesija;
- neefikasni pozadinski worker-i;
- nedostajuce keširanje na strani aplikacije.
Upit ne mora da bude izuzetno spor pojedinacno da bi postao skup.
Na primer:
| |
Taj upit moze zasluziti vise paznje nego jedan 20-sekundni izvestaj.
Pronadjite upite koji stvaraju najvise ukupnog opterecenja
U mnogim ispitivanjima, ovo je najkorisnije rangiranje:
total_sec odgovara na drugo pitanje:
Koliko je ukupnog vremena izvrsavanja baze podataka potrosila ova familija upita tokom izabranog intervala?
Ovo cesto identifikuje najbolje ciljeve za optimizaciju jer kombinuje ucestalost i latenciju.
Upit sa:
je obicno mnogo zanimljiviji od:
Drugi upit izgleda mnogo gore kada se posmatraju pojedinacni slow-log zapisi, ali prvi znatno vise opterecuje bazu podataka.
Pronadjite obrasce upita sa najgorom latencijom
Za istrazivanje ekstremnih vremena odziva, koristite:
Alternativno, koristite prosecno vreme izvrsavanja:
Ova rangiranja su korisna za pronalazenje upita na koje uticu:
- losi planovi izvrsavanja;
- veliki scan-ovi;
- nedostajuci indeksi;
- privremene tabele;
- sortiranje;
- zakljucavanja;
- veoma promenljiva selektivnost parametara.
Ipak, uvek pogledajte i executions i total_sec. Veoma spor upit koji se izvrsava jednom nedeljno mozda nije problem sa najvisim prioritetom.
Iskljucite interval odrzavanja
Slow log cesto sadrzi backup-e, izvestaje, batch obradu, odrzavajuce poslove ili druga opterecenja koja ne bi trebalo da uticu na normalnu analizu aplikacije.
Na primer, da biste ignorisali upite izmedju ponoci i 04:00:
Ovaj mali korak filtriranja moze potpuno da promeni rangiranje na sistemima sa velikim nocnim workload-om.
Pregledajte stvarne primere nakon sto pronadjete digest
Normalizacija je korisna za rangiranje, ali optimizacija i dalje zahteva originalni SQL.
Nakon sto identifikujete interesantnu familiju upita, preuzmite nedavne primere iz mysql.slow_log i pregledajte ih:
Odavde se standardna istraga nastavlja alatima kao sto su:
| |
ili, tamo gde je bezbedno i odgovarajuce:
| |
Slow log nam govori koji upiti zasluzuju paznju. Plan izvrsavanja nam govori zasto su skupi.
Native MySQL digesti su i dalje korisni
Ako je poznato da se SQL sacuvan u logu moze parsirati, native MySQL digest funkcije pruzaju tacniju SQL-svesnu normalizaciju:
Ovo bi obicno trebalo preferirati kada radi pouzdano.
Fingerprint baziran na regularnim izrazima opisan ranije nije namenjen da tacno reprodukuje MySQL parser. Njegova svrha je drugacija: da obezbedi robusan nacin za grupisanje istorijskih slow-log zapisa kada digest na osnovu parsera nije pouzdan za svaki sacuvani statement.
Najsporije ne znaci i najvaznije
Korisni pregled performansi obicno cuva najmanje tri rangiranja:
| Rangiranje | Sta otkriva |
|---|---|
executions DESC | Previse cesti upiti |
total_sec DESC | Upiti koji trose najvise vremena baze |
max_sec DESC | Najgora pojedinacna latencija |
U praksi, ukupno vreme izvrsavanja je cesto najbolji prvi red za optimizaciju, zatim ucestalost, pa tek onda pojedinacni ekstremi.
Ovo sprecava cesto gresku pri resavanju problema: trosenje sati na optimizaciju vizuelno impresivnog 30-sekundnog upita dok mnogo manji upit tiho trosi redove velicine vise kapaciteta baze podataka.
Od jednokratnog upita do monitoringa
Kada ova analiza postane korisna tokom incidenta, obicno vredi pretvoriti je u mali Grafana dashboard.
Korisni raspored je:
Ovo daje inzenjerima brz put od:
| |
do:
| |
To je mnogo bolja polazna tacka za optimizaciju baze podataka.