Iznad indeksa: otključajte brzinu baze podataka dizajnom usmjerenim na upite
Većina problema s performansama baze podataka ne počinje nedostajućim indeksom. Počinju ranije: aplikacijom koja bazi podataka postavlja nejasna, skupa ili nepotrebna pitanja.
Indeksi su, naravno, važni. Oni su jedan od najučinkovitijih alata dostupnih backend inženjeru. No tretiranje svake spore krajnje točke kao problema indeksiranja može stvoriti sustav s mnogo indeksa, sporim upisima i upitima koji i dalje imaju suštinski loš oblik.
Dizajn usmjeren na upite polazi od drukčijeg pitanja: koje točno informacije ovaj zahtjev treba, koliko često su potrebne i koji je najjeftiniji pouzdan način njihova dohvaćanja? To pitanje povezuje dizajn API-ja, modeliranje podataka, SQL, predmemoriranje, paginaciju i aplikacijski kod. Također vodi do poboljšanja koja bolje podnose rast od zbirke reaktivnih popravaka.
Dizajnirajte API-je prema obrascima pristupa podacima
Shema baze podataka predstavlja pohranjene činjenice. Upit predstavlja pitanje o tim činjenicama. Shema može biti stabilna godinama, dok se pitanja mijenjaju kako se značajke proizvoda razvijaju. Rad na performansama postaje lakši kada su ta pitanja izričita.
Razmotrite API krajnju točku koja prikazuje nedavne narudžbe kupca. Široka implementacija mogla bi učitati kupca, dohvatiti svaku narudžbu, hidratizirati povezane zapise i prepustiti PHP-u filtriranje i oblikovanje rezultata. To je u početku praktično, ali preko veze šalje daleko više podataka nego što zaslon zahtijeva.
Alternativa usmjerena na upite najprije definira ugovor odgovora: primjerice, posljednjih 20 narudžbi, njihove identifikatore, status, ukupni iznos i vrijeme kreiranja. Zatim upit odabire samo ta polja i ograničava skup rezultata.
SELECT id, status, total_amount, created_at
FROM orders
WHERE customer_id = :customer_id
AND created_at < :cursor_created_at
ORDER BY created_at DESC, id DESC
LIMIT 20;
Korisni indeks proizlazi iz obrasca pristupa, a ne iz općeg uvjerenja da svaki strani ključ zasebno treba indeks. Ovisno o bazi podataka i postojećim ograničenjima, indeks koji počinje s customer_id i nastavlja se stupcima za sortiranje može učinkovito podržati ovaj upit. Provjerite izbor pomoću alata baze podataka za plan upita, umjesto da pretpostavite da se indeks koristi.
Učinite skupo pitanje vidljivim
Spori upiti često se skrivaju iza ugodnih apstrakcija: ORM relacija, metoda repozitorija, API serijalizatora i pomoćnih funkcija. Te su apstrakcije vrijedne, ali ne uklanjaju trošak SQL-a koji generiraju.
Klasičan je primjer problem N+1 upita. Zahtjev učita 50 narudžbi, a zatim pristupa relaciji kupca svake narudžbe. Ako se relacija učitava lijeno, jedan upit za popis može postati 51 upit prema bazi podataka. Kod može djelovati prirodno dok se latencija, pritisak na veze i rad baze podataka naglo povećavaju.
Pravo rješenje ovisi o tome što krajnja točka treba. Spajanje, grupno žurno učitavanje ili namjerno oblikovana projekcija mogu biti prikladni. Važan je korak pregledati izvršene upite i prebrojiti ih pri realističnim veličinama odgovora.
$orders = $orderRepository->findRecentForCustomer(
customerId: $customerId,
cursor: $cursor,
limit: 20,
);
Naziv metode poput ovoga bolji je od generičkog findByCustomerId() kada prenosi redoslijed, opseg i granice. Budućim održavateljima daje korisno mjesto za razmišljanje o upitu, umjesto da potiče pozivatelje da dohvate neograničenu kolekciju i improviziraju.
Odabirite manje, prenosite manje, hidratizirajte manje
Dohvaćanje cijelog retka zato što bi jedno polje možda kasnije moglo biti korisno navika je koju je lako steći. Na širokim tablicama postaje skupa. Veliki tekstualni stupci, JSON dokumenti, binarni podaci i rijetko korištena revizijska polja troše I/O, memoriju, mrežnu propusnost i vrijeme PHP hidratizacije.
Za krajnje točke s mnogo čitanja, promatrajte SQL rezultat kao model čitanja specifičan za API. Ne mora se savršeno preslikavati na domensku cjelinu. Kartica nadzorne ploče može trebati agregat i oznaku, dok administrativna stranica s detaljima može trebati bogatiji objekt.
- Odaberite polja koja odgovor zaista koristi.
- Izračunajte jednostavne agregate u SQL-u kada se tako izbjegava prijenos mnogo redaka u aplikacijski kod.
- Izbjegavajte učitavanje relacija samo zato što bi ih serijalizator mogao pregledati.
- Velike sadržaje, poput tijela dokumenata, držite iza zasebnih krajnjih točaka kada ih korisnici ne trebaju u prikazima popisa.
Ovo nije argument za raspršivanje sirovog SQL-a po PHP aplikaciji. Ovo je argument za promišljen pristup podacima. Repozitorij, objekt upita ili dobro omeđen sloj postojanosti mogu očuvati održivost, a istodobno omogućiti upitima da odražavaju stvarna opterećenja.
Paginacija je značajka performansi
Offset paginacija je poznata:
SELECT id, created_at
FROM events
ORDER BY created_at DESC
LIMIT 50 OFFSET 50000;
Korisnicima i klijentima jednostavna je za razumjeti, ali duboki offseti mogu prisiliti bazu podataka da prođe pokraj mnogo redaka prije vraćanja male stranice. Također se može ponašati nezgrapno kada novi reci stignu između zahtjeva.
Za podatke orijentirane na dodavanje, kursorska paginacija često bolje odgovara. Upotrijebite stabilan redoslijed s kriterijem za razrješavanje izjednačenja, vratite vrijednosti konačnog retka kao sljedeći kursor i zatražite retke nakon tog položaja. Par created_at, id čest je oblik kada vremenske oznake same po sebi nisu jedinstvene.
Kursorska paginacija nije univerzalno nadmoćna. Sučelje za administraciju koje treba proizvoljne skokove između stranica može opravdano koristiti offsete. Praktična je pouka odabrati paginaciju na temelju očekivane dubine, jamstava redoslijeda i ponašanja korisnika, umjesto primjene jednog obrasca posvuda.
Indeksi su ugovori s troškom upisivanja
Indeks ubrzava neka čitanja stvaranjem dodatne strukture koju baza podataka mora održavati. Svako umetanje, ažuriranje i brisanje možda će trebati ažurirati tu strukturu. Široki ili redundantni indeksi mogu trošiti pohranu i usporiti putanje s mnogo upisivanja.
Prije dodavanja indeksa utvrdite predikat upita, redoslijed sortiranja, uvjete spajanja, očekivanu kardinalnost i volumen upisivanja. Zatim pregledajte plan prije i nakon promjene. Dobar indeks temelji se na dokazima i vezan je uz poznati upit. Nekorišten indeks predstavlja trajni trošak održavanja.
Redoslijed stupaca složenog indeksa važan je jer određuje koji oblici upita mogu imati koristi. Ne postoji univerzalno pravilo redoslijeda koje može zamijeniti razumijevanje opterećenja. Filtri jednakosti, uvjeti raspona i sortiranje utječu na odluku, stoga testirajte točan upit umjesto oslanjanja na prepisani recept.
Mjerite zahtjev, ne samo naredbu
Brz upit i dalje može stvoriti sporu krajnju točku ako aplikacijski kod izvodi ponovljene upite, serijalizira goleme strukture, nepotrebno pokušava ponovno ili čeka usluge nizvodno. Obratno, upit koji izgleda zahtjevno može biti potpuno prihvatljiv ako se rijetko izvršava i ostaje unutar proračuna latencije krajnje točke.
Mjerite na nekoliko razina: latenciju zahtjeva, broj upita, trajanje upita, broj pregledanih ili vraćenih redaka gdje je dostupan, korištenje memorije i opterećenje baze podataka. Zabilježite dovoljno konteksta da povežete spor upit s rutom i operacijom koja ga je pokrenula, pritom izbjegavajući osjetljive parametre i podatke kupaca.
Zatim optimizirajte najznačajnije usko grlo. Malo poboljšanje indeksa manje je vrijedno od uklanjanja tisuća nepotrebnih prijenosa redaka. Predmemorija je manje korisna od upita koji dosljedno dohvaća pravih 20 redaka. Replika za čitanje nije zamjena za krajnju točku koja zahtijeva cijelu povijest kako bi prikazala sažetak.
Gradite za pitanja koja se mijenjaju
Dizajn usmjeren na upite nije odbacivanje indeksa, ORM-ova, normaliziranih shema ili čiste arhitekture. To je disciplina promatranja svakog poziva bazi podataka kao odluke o proizvodu s profilom troška.
Zapamtiva je promjena jednostavna: ne pitajte kako ubrzati spor upit dok niste pitali je li to uopće pravi upit. Kada API-ji zahtijevaju ograničene, svrhovite podatke; kada paginacija odgovara načinu korištenja; i kada indeksi podržavaju dokazane obrasce pristupa, performanse baze podataka prestaju biti operacija spašavanja u kasnoj fazi. Postaju dio načina na koji je sustav dizajniran.