Više od predmemoriranja: projektiranje baza podataka za korisnička putovanja bez latencije
Predmemoriranje je često prvi alat za performanse za kojim timovi posežu. Taj je instinkt razumljiv: dodajte predmemoriju, promatrajte kako spora krajnja točka postaje brža i krenite dalje. No predmemorija može samo sakriti latenciju koja već postoji. Ne može popraviti korisnički put čiji podatkovni model, oblici upita, granice API-ja i izbori implementacije prisiljavaju sustav na nepotreban rad.
Bez latencije ne znači da se svaki zahtjev dovršava trenutačno. To znači da korisnik rijetko čeka rad koji je bilo moguće izbjeći, odgoditi, unaprijed izračunati ili ukloniti iz kritičnog puta samim dizajnom. To je arhitektonski cilj, a ne Redis konfiguracija.
Počnite s korisničkim putem, a ne s bazom podataka
Upit nad bazom podataka nikada nije spor sam za sebe. Spor je u odnosu na korisničku radnju. „Otvori nadzornu ploču”, „dodaj artikl u košaricu” i „pogledaj status narudžbe” imaju različita očekivanja, tolerancije na pogreške i zahtjeve za dosljednošću.
Mapirajte put zahtjeva prije podešavanja pojedinačnih upita. Za PHP API taj put može uključivati autentikaciju, provjeru zahtjeva, aplikacijske servise, ORM hidraciju, čitanja iz baze podataka, pozive drugom servisu, serijalizaciju i odgovor. Upit baze podataka od 250 ms ima drukčiju važnost kada je jedina operacija nego kada se nalazi iza tri uzastopna mrežna poziva.
Za svaku interakciju postavite izravno pitanje: koje informacije moraju biti aktualne da bi odgovor bio koristan? Odgovor često smanjuje opseg rada na sinkronom putu. Nadzornoj ploči možda trebaju aktualne dozvole računa, ali može prikazati agregirane brojeve s malim kašnjenjem. Stranici s potvrdom narudžbe potreban je trajno zapisan zapis narudžbe, ali potvrda e-poštom ne mora biti poslana prije nego što preglednik primi potvrdu o uspjehu.
Dizajnirajte za upite koje doista poslužujete
Dizajn sheme počinje ispravnošću, ali i obrasci čitanja moraju ga oblikovati. Normalizirani model često je pravi izvor istine. Iz toga ne slijedi da svaki zaslon treba rekonstruirati svoj prikaz kroz dubok lanac spajanja i izračune po retku.
Prepoznajte visokofrekventne upite i učinite ih eksplicitnima. Za svaki definirajte filtre, redoslijed sortiranja, očekivanu veličinu rezultata i potrebne stupce. Zatim provjerite može li baza podataka učinkovito izvršiti taj obrazac pristupa odgovarajućim indeksom. Indeks nije ukras koji se dodaje nakon incidenta; to je podatkovna struktura odabrana za poznato pitanje.
Na primjer, nedavni računi nekog računa mogu se tražiti po računu, poredani prema vremenu stvaranja i raspoređeni na stranice. Složeni indeks usklađen s tim obrascem obično je vredniji od neovisnog indeksiranja svakog stupca:
CREATE INDEX invoices_account_created_at_idx
ON invoices (account_id, created_at DESC);
Precizna sintaksa i učinkovitost ovise o mehanizmu baze podataka i upitu, stoga pregledajte plan izvršavanja u okruženju koje nalikuje produkciji. Također odaberite samo polja koja krajnja točka treba. Dohvaćanje velikih tekstualnih stupaca ili hidratacija složenih grafova objekata za sažeti odgovor popisa istodobno povećava opterećenje baze podataka, PHP-a i mreže.
Učinite paginaciju predvidivom
Paginacija s pomakom postaje postupno skuplja na velikim skupovima podataka koji se često mijenjaju. Paginacija temeljena na kursoru može bolje odgovarati kronološkim feedovima i revizijskim zapisima jer nastavlja od poznate granice umjesto da od baze podataka traži preskakanje sve većeg broja redaka.
SELECT id, created_at, status
FROM invoices
WHERE account_id = :account_id
AND created_at < :cursor_created_at
ORDER BY created_at DESC
LIMIT :page_size;
Stvarni kursor također treba stabilan kriterij za razrješavanje izjednačenja kada se vremenske oznake mogu podudarati. Važno je načelo stabilnog poretka: korisnici ne bi smjeli vidjeti duplikate ni zapise koji nedostaju dok prolaze kroz stranice.
Odvojite pisanja od skupih modela čitanja
Ne bi svaka krajnja točka trebala izravno upitivati model pisanja. Kada zaslon treba ukupne iznose, sažetke statusa ili podatke sastavljene iz nekoliko entiteta, namjenski model čitanja može pretvoriti ponavljano izračunavanje u jednostavno dohvaćanje.
To ne zahtijeva veliku redizajniranu arhitekturu vođenu događajima. Pragmatičan pristup mogao bi održavati tablicu account_dashboard kada se promijeni stanje računa ili plaćanja. Zahtjev tada čita jedan sažeti zapis umjesto da ponovno izračunava agregate u više tablica.
Kompromis je izričit: modeli čitanja uvode logiku ažuriranja i mogu nakratko biti zastarjeli. To je prihvatljivo samo kada korisnički put to dopušta. Učinite svježinu vidljivom u dizajnu. Upotrebljavajte transakcijski put pisanja za radnje koje zahtijevaju trenutačnu dosljednost, a asinkrono održavane projekcije ondje gdje malo kašnjenje ne šteti.
- Neka autoritativni zapis bude jasan.
- Učinite ažuriranja projekcija idempotentnima kako bi ponovni pokušaji bili sigurni.
- Prikažite smislenа stanja poput čekanja ili obrade umjesto da se pretvarate da je asinkroni rad dovršen.
- Planirajte kako ponovno izgraditi projekciju ako se njezina logika promijeni.
Držite spor rad izvan puta zahtjeva
Mnogi problemi s latencijom zapravo su problemi tijeka rada. Generiranje izvješća, promjena veličine prijenosa, slanje obavijesti, izračun preporuka ili sinkronizacija s trećom stranom rijetko bi trebali blokirati web-zahtjev.
U PHP aplikaciji trajno zabilježite korisnikovu namjeru i stavite naknadni rad u red putem pouzdanog mehanizma primjerenog sustavu. Odgovor može potvrditi prihvaćenu radnju, dok radnik obrađuje nekritični korak. Radnik mora pretpostaviti da se isporuka može dogoditi više puta i da vanjski pozivi mogu zakazati nakon isteka vremena čak i ako je udaljeni sustav dovršio posao.
To znači da idempotentnost nije opcionalna. Operacijama koje imaju vanjsko značenje dodijelite ključ idempotentnosti, trajno zabilježite stanje obrade, upotrebljavajte ograničene ponovne pokušaje s postupnim povećanjem čekanja i pošaljite neuspjehe na put koji je moguće pregledati nakon što se ponovni pokušaji iscrpe. Izbjegavajte petlje ponovnih pokušaja unutar jednog HTTP zahtjeva; one troše PHP radnike upravo kada ovisnost nije zdrava.
Granice API-ja tretirajte kao granice latencije
Čista interna granica servisa i dalje može stvoriti loše korisničko iskustvo ako jedan zahtjev preglednika pokreće lanac sinkronih poziva servisa. Svaki skok dodaje trošak povezivanja, čekanje u redu, serijalizaciju i još jedno mjesto za istek vremena.
Preferirajte API-je koji vraćaju koristan odgovor oblikovan za zaslon kada to smanjuje broj povratnih putovanja bez stvaranja neodržive krajnje točke „sve”. Grupirajte neovisna dohvaćanja gdje je praktično. Namjerno postavite vremenska ograničenja, razlikujte privremeni neuspjeh ovisnosti od neispravnog unosa i odlučite je li djelomičan odgovor bolji od potpunog neuspjeha.
Za implementacije temeljene na Dockeru, performanse ovise i o operativnim osnovama: veze s bazom podataka moraju biti ograničene, konkurentnost radnika mora odgovarati dostupnom kapacitetu baze podataka, a spremnicima treba dati dovoljno vremena da tijekom gašenja dovrše rad u tijeku. Skaliranje PHP spremnika bez kontrole skupova veza može samo premjestiti usko grlo u bazu podataka.
Mjerite cijeli put
Instrumentacija bi timu trebala omogućiti odgovor na pitanje kamo je otišlo vrijeme za određeni zahtjev. Bilježite trajanje zahtjeva, broj i trajanje upita nad bazom podataka, vremena vanjskih ovisnosti, starost reda, stopu pogrešaka i signale zasićenja poput iscrpljenih radnika ili veza s bazom podataka. Identifikatori korelacije čine te signale korisnima u API zahtjevu i njegovu asinkronom naknadnom radu.
Podatke upotrijebite da najprije popravite najveći ponavljajući trošak. Uklanjanje nepotrebnog upita iz svakog zahtjeva često je bolje od skraćivanja rijetkog pozadinskog zadatka za nekoliko milisekundi. Testiranje opterećenja trebalo bi uključivati realističnu konkurentnost i količinu podataka; upit koji je elegantan na malom razvojnom skupu podataka kasnije može postati operativni teret.
Izgradite put koji korisnici osjećaju
Predmemorije ostaju vrijedne. Štite skupa čitanja, apsorbiraju nagle poraste opterećenja i smanjuju pritisak na primarnu pohranu. No najbolje funkcioniraju kao jedan sloj u sustavu koji već postavlja razumna pitanja, ključni rad obavlja jednom i sve ostalo uklanja s puta.
Trajna navika za performanse jednostavna je: pratite korisnički put, odredite minimalnu sinkronu istinu koja mu je potrebna i arhitektonski uskladite svaki sloj s pouzdanom isporukom te istine. Kada baza podataka, API, red i model implementacije zajedno podržavaju taj put, brzina prestaje biti pogodak u predmemoriji i postaje svojstvo dizajna.