Izvan reda čekanja: Zašto dizajn baze podataka određuje brzinu vaše aplikacije
Kada se aplikacija čini sporom, red čekanja lako postaje sumnjivac. Poslovi se gomilaju, radnici izgledaju zauzeto, a nadzorne ploče čine zaostatak vidljivim. No red čekanja često otkriva problem s bazom podataka umjesto da ga uzrokuje. Ako svaki posao u redu obavlja skup upit, čeka zaključavanje ili ažurira tablicu s lošim indeksima, dodavanje radnika samo omogućuje većem broju procesa da se natječu za isto usko grlo.
Zato dizajn baze podataka uvelike određuje percipiranu brzinu aplikacije. Oblikuje trošak API zahtjeva, propusnost pozadinskog rada, pouzdanost ponovnih pokušaja i operativnu gornju granicu cijelog sustava. Redovi čekanja vrijedni su alati, ali ne mogu neučinkovit pristup podacima pretvoriti u učinkovit rad.
Red čekanja mehanizam je isporuke, a ne strategija performansi
Red čekanja odvaja rad od zahtjeva koji ga je pokrenuo. Slanje e-pošte, generiranje izvještaja, obrada prijenosa datoteke ili pozivanje vanjskog API-ja mogu se dogoditi kasnije, čime se zahtjev usmjeren korisniku može brzo dovršiti. To je odvajanje korisno, ali ne čini temeljni zadatak jeftinijim.
Razmotrite PHP posao koji obrađuje narudžbu. Učitava narudžbu, njezina kupca, njezine stavke, zapise o proizvodima, retke zaliha i prethodne događaje. Ako se ti odnosi dohvaćaju jedan po jedan, jedan posao može stvoriti dugačak niz odlazaka i povrataka do baze podataka. Pomnožite to stotinama poslova, ponovnim pokušajima ili istodobnim radnicima i red čekanja postaje koncentriran izvor opterećenja baze podataka.
Prvo pitanje ne bi trebalo biti „Koliko nam radnika treba?” Trebalo bi biti „Koliko košta jedna jedinica rada?”
Indeksi kodiraju pitanja koja vaša aplikacija postavlja
Indeks nije univerzalni gumb za ubrzanje. To je struktura podataka izgrađena za određene obrasce pristupa. Najkorisniji indeksi nastaju promatranjem upita koje aplikacija stvarno izvršava: filtriranjem, spajanjem, sortiranjem i odabirom sljedeće stavke za obradu.
Na primjer, radnik koji neprestano preuzima poslove na čekanju mogao bi se oslanjati na upit oblikovan ovako:
SELECT id
FROM outbound_messages
WHERE status = 'pending'
AND available_at <= NOW()
ORDER BY available_at, id
LIMIT 100;
Indeks koji počinje poljima za filtriranje i sortiranje može ovu rutinu učiniti predvidljivom. Bez njega baza podataka može opetovano pregledavati daleko više redaka nego što radniku treba. Rezultat se često pogrešno smatra problemom kapaciteta reda čekanja jer se simptom pojavljuje kao sporo preuzimanje poslova.
Indeksi također imaju cijenu. Svako umetanje, ažuriranje i brisanje mora ih održavati. Tablica sa svakim zamislivim indeksom može se brzo čitati u uskom testu, a pritom postati skupa za pisanje u produkciji. Praktični cilj nije „indeksiraj sve”; nego „indeksiraj obrasce pristupa koji su važni, a zatim provjeri kompromis.”
Složeni indeksi zahtijevaju razmišljanje prema obliku upita
Redoslijed stupaca važan je u složenom indeksu. Indeks dizajniran za WHERE tenant_id = ? AND status = ? ORDER BY created_at trebao bi odražavati način na koji upit sužava i sortira rezultate. Drugi upit koji počinje rasponom datuma možda treba drukčiji dizajn. Kopiranje indeksa iz druge tablice bez usporedbe oblika upita čest je put do lažnog osjećaja sigurnosti.
Tijekom razvoja i istraživanja incidenata koristite alate baze podataka za plan upita. Cilj nije zapamtiti terminologiju optimizatora. Cilj je potvrditi može li baza podataka pronaći mali relevantan skup redaka ili skenira, sortira i spaja daleko više podataka nego što značajka zahtijeva.
Modeliranje podataka određuje konkurenciju
Spori sustavi ne čitaju uvijek previše. Ponekad čekaju jedni na druge. Konkurencija nastaje kada mnogi zahtjevi ili radnici opetovano mijenjaju iste retke, drže transakcije otvorenima tijekom obavljanja nepovezanog rada ili serijaliziraju aktivnost kroz jedan zapis „trenutnog stanja”.
Poznat primjer su zalihe. Ako svaki proces naplate ažurira jedan zajednički agregatni redak, istodobne kupnje mogu se natjecati za taj redak čak i kada se ostatak aplikacije dobro skalira. Pravi odgovor ovisi o poslovnom pravilu: može uključivati pažljivo ograničene transakcije, optimistične provjere istodobnosti, zapise rezervacija ili model koji bilježi promjene kao događaje koji se samo dodaju. Ključno je izričito odrediti zahtjev konzistentnosti prije odabira mehanizma.
Transakcije neka budu uske. Pročitajte i provjerite podatke potrebne za odluku, napravite potrebne promjene u bazi podataka i potvrdite transakciju. Nemojte držati transakciju otvorenom tijekom prikazivanja dokumenta, slanja HTTP zahtjeva, čekanja operacije nad datotekom ili izvođenja dugog izračuna. Te operacije pripadaju prije ili nakon transakcijskog dijela, ovisno o tome što mora biti atomsko.
Dizajnirajte poslove za ponovne pokušaje prije nego što kvar to nametne
Pozadinski poslovi ne uspijevaju iz uobičajenih razloga: radnik se zaustavi, mrežni zahtjev istekne, veza s bazom podataka prekine se ili implementacija prekine obradu. Ponovni pokušaji nužni su, ali opasni kada posao nije idempotentan.
Idempotentan posao može se pokrenuti više puta bez stvaranja nenamjernog dupliciranog učinka. Za tijek rada povezan s plaćanjem to može značiti pohranjivanje i ponovnu upotrebu stabilnog vanjskog ključa idempotentnosti. Za tijek rada e-pošte to može značiti bilježenje identifikatora poruke i točno definiranje trenutka kada sustav poruku smatra poslanom. Za interne nadopune to može značiti nametanje jedinstvenog ograničenja koje predstavlja poslovni događaj.
CREATE TABLE report_requests (
id BIGINT PRIMARY KEY,
account_id BIGINT NOT NULL,
request_key VARCHAR(100) NOT NULL,
status VARCHAR(30) NOT NULL,
UNIQUE (account_id, request_key)
);
Jedinstveno ograničenje važno je jer štiti invarijantu ondje gdje ga je najteže zaobići: u bazi podataka. Provjere na razini aplikacije i dalje su korisne za prijateljski odgovor, ali dva istodobna procesa mogu oba proći provjeru „postoji li ovo?” prije nego što bilo koji od njih piše. Ograničenje baze podataka sustavu daje konačan, zajednički odgovor.
Dizajn API-ja može štititi ili kažnjavati bazu podataka
Svaka API krajnja točka ugovor je za rad baze podataka. Fleksibilna krajnja točka koja prihvaća proizvoljne filtre, duboko proširivanje odnosa i neograničenu paginaciju može slučajno potaknuti skupe upite. To ne znači da API-ji trebaju biti kruti; znači da njihova fleksibilnost treba promišljene granice.
- Koristite paginaciju sa stabilnim sortiranjem umjesto vraćanja neograničenih kolekcija.
- Dopustite samo filtre i sortiranja koji imaju jasan plan pristupa podacima.
- Odaberite polja koja odgovor treba umjesto da iz navike učitavate velike zapise.
- Grupirajte povezana čitanja kako biste izbjegli jedan upit po stavci u kolekciji.
- Postavite izričita ograničenja za izvoze, pretrage i veličine pozadinskih skupova.
U PHP aplikacijama objektno-relacijsko mapiranje može učiniti prolazak kroz odnose ugodno čitljivim. Također može sakriti ponovljene upite unutar petlji. Učitavanje unaprijed korisno je kada odgovara obliku odgovora, ali ne bi trebalo postati refleks. Dohvatite podatke potrebne za operaciju, pregledajte generirani SQL kada su performanse važne i održavajte vidljivom vezu između ponašanja krajnje točke i ponašanja upita.
Izmjerite rad prije skaliranja infrastrukture
Docker, više radnika, veće instance baze podataka i slojevi predmemoriranja svi imaju legitimne uloge. Najučinkovitiji su nakon što se razumije skupi put. Skaliranje sporog upita može odgoditi problem; rijetko ga uklanja. Predmemoriranje može smanjiti pritisak čitanja, ali također uvodi pravila poništavanja predmemorije i može prikriti model koji više ne odgovara opterećenju.
Pragmatična istraga počinje određenim sporim putem: jednom krajnjom točkom, jednom vrstom posla ili jednim izvještajem. Pratite SQL koji izvršava, prebrojite ponovljene obrasce pristupa, pregledajte plan upita i utvrdite retke koje čita i piše. Zatim napravite najmanju promjenu koja rješava uzrok: indeks, uži upit, skupnu operaciju, ograničenje ili drukčiju granicu transakcije.
Gradite za dosadan brzi put
Najjači pozadinski sustavi čine uobičajeni rad neupadljivim. Zahtjev pronalazi mali indeksirani skup redaka. Posao preuzima ograničenu skupinu. Ponovni pokušaj prepoznaje prethodni napredak. Transakcija štiti samo stanje koje se doista mora mijenjati zajedno. API skupe operacije čini namjernima umjesto slučajnima.
Redovi čekanja pomažu aplikacijama ostati responzivnima, ali baze podataka odlučuju je li rad iza tih redova prihvatljivog troška. Tretirajte dizajn sheme, oblik upita, ograničenja i granice transakcija kao dio razvoja proizvoda. Kada sloj podataka ima dosadan brzi put, ostatak arhitekture postaje lakše skalirati, razumjeti i pouzdati se u njega.