Razotkrivanje performansi baza podataka: izvan profiliranja, prema stvarnim rješenjima
Spora baza podataka rijetko je samo problem baze podataka. Obično je to problem sustava koji nosi masku u obliku baze podataka: API krajnja točka dohvaća previše podataka, ORM potajno izdaje desetke upita, indeks više ne odgovara obrascu pristupa ili se radnik reda natječe s web prometom za iste resurse.
Profiliranje je ključno jer vam pokazuje kamo odlazi vrijeme. No profil je polazište, a ne rješenje. Pravi posao jest pretvoriti spor upit ili preopterećeni skup veza u promjenu koja poboljšava ponašanje cijelog sustava, a da ga pritom ne učini težim za održavanje.
Počnite od zahtjeva, a ne od upita
Kada je krajnja točka spora, primamljivo je otvoriti konzolu baze podataka i optimizirati najskuplju SQL naredbu. Najprije postavite šire pitanje: što ovaj zahtjev zapravo treba postići?
Popis proizvoda može prikazivati samo naziv, cijenu i minijaturu, a aplikacija ipak može učitavati potpune zapise proizvoda, povezane zalihe, svaku sliku i nekoliko izračunatih odnosa. Čak je i dobro indeksiran upit rasipan kada dohvaća podatke koji će biti odbačeni.
Definirajte oblik odgovora prije podešavanja SQL-a. Zatim mu prilagodite pristup podacima. To često daje najsigurnije poboljšanje performansi jer smanjuje količinu posla umjesto da samo ubrzava nepotreban posao.
$products = $repository->findVisibleProducts(
categoryId: $categoryId,
fields: ['id', 'name', 'price', 'thumbnail_url'],
limit: 24
);
Važan dio nije naziv metode. To je disciplina: dohvatite najmanji koristan skup rezultata, namjerno koristite paginaciju i izbjegavajte tretirati retke baze podataka kao neograničenu zbirku u memoriji.
Čitajte planove upita kao objašnjenja
Vrijeme izvršavanja upita govori vam da je nešto sporo. Plan upita pomaže objasniti zašto. Upotrijebite alat svoje baze podataka za pregled plana, kao što je EXPLAIN, nad reprezentativnim upitima i realističnim vrijednostima parametara. Potražite operacije koje upućuju na nerazmjeran posao: potpuna skeniranja velikih tablica, skupa sortiranja, velike međuskupove rezultata ili spajanja koja neočekivano umnožavaju retke.
Indeks je koristan samo kada podržava stvarni obrazac predikata, spajanja i sortiranja. Reaktivno dodavanje indeksa može usporiti pisanja i prikriti stvarni obrazac pristupa. Umjesto toga, počnite s konkretnim upitom:
SELECT id, created_at, total
FROM orders
WHERE customer_id = ?
AND status = ?
ORDER BY created_at DESC
LIMIT 20;
Složeni indeks koji počinje s customer_id i status, nakon kojih slijedi created_at, može odgovarati ovom upitu. No treba ga provjeriti u odnosu na plan baze podataka i stopu pisanja radnog opterećenja. Dizajn indeksa nije kontrolni popis; to je kompromis između bržeg čitanja, pohrane i troška pisanja.
Pazite na upite koji poništavaju prednosti dobrih indeksa
Neki izrazi otežavaju učinkovito korištenje stupca u predikatu. Na primjer, filtriranje funkcijom primijenjenom na indeksiranu vremensku oznaku može prisiliti na više rada nego ograničeni raspon.
SELECT id
FROM events
WHERE created_at >= ?
AND created_at < ?;
O ovom je obliku općenito lakše razmišljati nego o transformiranju created_at unutar uvjeta. Načelo je jednostavno: sačuvajte izravnu usporedbu između pohranjene vrijednosti i zadane granice kad god to poslovno pravilo dopušta.
Uklonite umnožavanje upita
Klasični problem N+1 upita i dalje je čest jer se skriva iza aplikacijskog koda urednog izgleda. Stranica učita dvadeset narudžbi, a zatim svaka narudžba zasebno učitava svog kupca ili stavke. Pojedinačni upiti mogu biti brzi, ali mrežna putovanja, korištenje veza i ponavljano planiranje se zbrajaju.
Riješite to učitavanjem povezanih podataka u ograničenim skupovima, spajanjem kada je dobiveni oblik redaka prikladan ili upitom za podređene zapise s uvjetom IN i njihovim grupiranjem u aplikacijskom kodu. Koja je opcija najbolja ovisi o kardinalnosti. Spajanje narudžbi s malim odnosom kupca jedan-na-jedan razlikuje se od spajanja narudžbi s tisućama stavki.
- Koristite željno učitavanje kada su povezani podaci zasigurno potrebni.
- Koristite zaseban skupni upit kada bi spajanje proizvelo previše dupliciranih redaka nadređenog zapisa.
- Koristite eksplicitne projekcije kako se željno učitavanje ne bi pretvorilo u željno prekomjerno dohvaćanje.
- Mjerite broj upita po zahtjevu, kao i ukupno trajanje.
ORM-ovi su vrijedni, ali ne mogu zaključiti karakteristike performansi poslovnog ekrana. Generirani SQL tretirajte kao produkcijski kod: pregledajte ga kada se ponašanje promijeni, osobito oko odnosa, filtara i paginacije.
Odvojite transakcijski rad od svega ostalog
Transakcije baze podataka trebaju štititi malu, koherentnu poslovnu promjenu. Ne bi trebale uključivati HTTP pozive, sporu obradu datoteka, slanje e-pošte ili dugotrajne izračune. Držanje transakcije otvorenom dok čekate vanjski rad produljuje trajanje zaključavanja i povećava vjerojatnost sukoba.
Praktična granica često je sljedeća: validirajte unos, izvršite potrebne promjene u bazi podataka, potvrdite transakciju, a zatim pošaljite naknadni rad u red. Posao u redu treba biti osmišljen tako da podnosi ponovne pokušaje. To znači da mora izbjegavati stvaranje dupliciranih nuspojava kada se pokrene više puta.
Na primjer, posao koji šalje račun trebao bi zabilježiti ili provjeriti trajno poslovno stanje prije nego što račun tretira kao novo poslan. Mehanizam ponovnih pokušaja nije jamstvo izvršavanja točno jednom; on je razlog da svaku operaciju, gdje je moguće, učinite sigurno ponovljivom.
Skupovi veza, kontejneri i kapacitet dio su dizajna
Docker olakšava pokretanje mnogih aplikacijskih kontejnera. Također olakšava stvaranje više veza s bazom podataka nego što ih baza može ugodno podnijeti. Ako svaki PHP radnik može otvoriti veze i implementacija se vodoravno skalira, ukupan broj mogućih veza može rasti brže nego što se očekuje.
Broj aplikacijskih radnika, ograničenja veza i kapacitet baze podataka postavite kao jedan sustav. Ostavite rezervu za migracije, administrativni pristup, pozadinske radnike i privremene skokove prometa. Kada se veze iscrpe, dodavanje još jednog aplikacijskog kontejnera obično pojačava kvar umjesto da ga rješava.
Također razlikujte latenciju od zasićenja. Jedan loše indeksiran upit može uzrokovati povremene spore zahtjeve. Zasićena baza podataka može uzrokovati raširena prekoračenja vremena, zaostatak reda i oluje ponovnih pokušaja. Rješenja se razlikuju, stoga nadziranost treba uključivati trajanje upita, broj upita, aktivne veze, stope pogrešaka i dubinu reda.
Predmemorirajte rezultat odluke, a ne nejasnoću
Predmemoriranje može biti moćno kada je vrijednost skupa za izračun, opetovano se traži i prihvatljivo ju je poslužiti uz malu zastarjelost. Mnogo je manje korisno kada se doda prije razumijevanja modela podataka i obrasca upita.
Odaberite ključeve predmemorije koji odražavaju stvarne ulaze, definirajte ponašanje isteka i poništavanja te odlučite što se događa pri promašaju ili prekidu rada predmemorije. Predmemorija koja ne može elegantno otkazati postaje još jedan izvor problema s dostupnošću. Za podatke koji se često mijenjaju razmotrite je li jednostavniji upit ili unaprijed izračunati model čitanja lakši za upravljanje od složenih pravila poništavanja.
Učinite performanse svojstvom arhitekture
Najjače optimizacije baze podataka često su neupadljive: uži API odgovor, indeks opravdan stvarnim planom, jedan skupni upit umjesto mnogih, kraća transakcija ili ograničenje implementacije koje sprječava iscrpljivanje veza.
Profiliranje pokazuje najopterećeniji put. Inženjerska prosudba određuje pripada li trajno rješenje SQL-u, PHP-u, API ugovoru, redu, predmemoriji ili planiranju kapaciteta. Kada timovi dosljedno pitaju koji posao sustav obavlja i zašto, performanse prestaju biti hitna vježba i postaju namjeran dio dizajna koji se može održavati.