Projektiranje baza podataka za otpornost: Arhitektura predvidljivosti pod opterećenjem
Većina kvarova baza podataka ne počinje dramatičnim prekidom rada. Počinju kao mala odstupanja od očekivanog tijeka: spor upit tijekom skoka prometa, zaključani redak zadržan malo predugo, ponavljanje pokušaja koje duplicira rad ili replika koja kasni nekoliko sekundi dok aplikacija pretpostavlja da je ažurna.
Otporan dizajn baze podataka podrazumijeva da se ta odstupanja učine predvidljivima. Cilj nije obećati da se ništa neće pokvariti. Mreže se prekidaju, procesi se ponovno pokreću, diskovi se pune, a ovisnosti postaju spore. Cilj je osigurati da kvar ostane ograničen, razumljiv i oporavljiv.
Započnite s obećanjima koja vaši podaci moraju ispuniti
Shema je više od tablica i indeksa. Ona kodira poslovna obećanja. Prije odabira ograničenja ili granice transakcije utvrdite koje tvrdnje uvijek moraju biti istinite.
Za sustav narudžbi primjeri mogu uključivati: narudžba pripada jednom kupcu; stavka se ne može ispuniti dvaput; referenca plaćanja ne može se obraditi više od jednom; a zaliha ne može postati negativna ako poslovanje ne dopušta prekomjernu prodaju. To su invarijante, a baza podataka trebala bi provoditi one koje može pouzdano provoditi.
- Koristite primarne ključeve za uspostavljanje stabilnog identiteta.
- Koristite strane ključeve tamo gdje se odnos ne smije prekinuti.
- Koristite jedinstvena ograničenja za poslovne identifikatore i ključeve idempotentnosti.
- Koristite ograničenja provjere gdje su podržana i prikladna za lokalna pravila vrijednosti.
- Zadržite validaciju aplikacije, ali je nemojte smatrati jedinom linijom obrane.
Kôd aplikacije može validirati unos prije njegova zapisivanja, ali istovremeni zahtjevi i dalje mogu proći istu validaciju u isto vrijeme. Jedinstveno ograničenje rješava tu utrku na mjestu na kojem je važno: u zajedničkom izvoru istine.
Modelirajte za promjene bez napuštanja jasnoće
Previše krute sheme čine promjene skupima. Previše labave sheme čine ispravnost skupom. Otpornost proizlazi iz namjernog odabira strukture.
Normalizirajte podatke kada to sprječava proturječne kopije važnih činjenica. Adresa kupca pohranjena u nekoliko nepovezanih tablica može izgledati praktično sve dok se jedna kopija ne promijeni, a ostale ne. Istodobno, nemojte svaki put čitanja prisiljavati da rekonstruira složenu domenu iz desetaka spajanja. Izvedeni modeli čitanja, sažete tablice i predmemorirane projekcije mogu biti razumni kada se izričito tretiraju kao izvedeni podaci sa strategijom osvježavanja i oporavka.
Fleksibilna polja poput JSON-a mogu biti korisna za metapodatke koji se doista razlikuju. Manje su korisna kao izlaz u nuždi za ključna polja koja trebaju validaciju, indeksiranje, spajanje, izvještavanje ili pravila životnog ciklusa. Ako polje postane središnje za ponašanje aplikacije, njegovo promicanje u dobro definirani stupac često je odluka koju je lakše održavati.
Učinite zapise sigurnima za ponavljanje
U distribuiranim sustavima klijent možda neće znati je li zahtjev uspio. Baza podataka može potvrditi transakciju neposredno prije prekida veze. Potrošač reda može dovršiti svoj posao i srušiti se prije nego što potvrdi poruku. Ponovni pokušaji su nužni, ali ne smiju stvoriti drugo plaćanje, e-poruku, pošiljku ili zapis.
Idempotentnost pretvara “pokušaj ponovno” u sigurnu operaciju. Prihvatite ključ idempotentnosti koji klijent dostavlja za značajnu operaciju, pohranite ga uz jamstvo jedinstvenosti i vratite izvorni rezultat kada se isti ključ ponovno pošalje. Ključ mora predstavljati operaciju, a ne samo HTTP zahtjev.
CREATE TABLE payment_requests (
id BIGINT PRIMARY KEY,
idempotency_key VARCHAR(255) NOT NULL,
customer_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
status VARCHAR(32) NOT NULL,
UNIQUE (idempotency_key)
);
Ograničenje jedinstvenosti je ključno. Sam pristup “prvo provjeri, zatim umetni” ranjiv je na istovremene zahtjeve. U PHP-u uhvatite pogrešku baze podataka zbog dupliciranog ključa, učitajte postojeći zahtjev i vratite njegov poznati ishod. Nemojte široko ponavljati svaki izuzetak; ponovni pokušaji prikladni su za prolazne kvarove, a ne za nevaljan unos ili prekršena poslovna pravila.
Neka transakcije budu kratke i svrhovite
Transakcije štite dosljednost, ali duge transakcije također drže zaključavanja, troše resurse i pojačavaju konkurenciju. Dobra transakcija obavlja minimalni rad potreban za premještanje podataka iz jednog valjanog stanja u drugo.
Izbjegavajte pozive udaljenim API-jima, renderiranje dokumenata, slanje e-poruka ili čekanje korisničkog unosa unutar transakcije baze podataka. Najprije trajno zabilježite promjenu stanja, a zatim predajte vanjski rad putem pouzdanog mehanizma.
Koristite outbox za pouzdane popratne učinke
Obrazac transakcijskog outboxa rješava čest jaz: usluga ažurira svoju bazu podataka, ali ne uspije prije objave odgovarajućeg događaja. U istoj transakciji koja stvara narudžbu umetnite outbox redak koji opisuje događaj. Zaseban radnik čita retke na čekanju, isporučuje ih i označava dovršenima. Potrošači i dalje trebaju biti idempotentni, jer se isporuka može dogoditi više puta.
Ovaj obrazac čini granicu eksplicitnom. Transakcija baze podataka jamči poslovnu promjenu i namjeru objave; ne pretvara se da bazu podataka i posrednika za poruke čini jednim atomskim sustavom.
Dizajnirajte upite za neugodan dan
Upit koji je brz s malim skupom podataka kasnije može postati dominantan izvor opterećenja. Odgovor nije indeksirati svaki stupac. Svaki indeks ima trošak zapisivanja, trošak pohrane i trošak održavanja. Umjesto toga, indeksirajte pristupne putove koje aplikacija doista treba.
Pregledajte planove upita za operacije velikog opsega i osjetljive na latenciju. Uskladite redoslijed složenog indeksa s obrascem filtriranja i sortiranja. Odaberite samo potrebne stupce. Izbjegavajte neograničene upite u putanjama zahtjeva i koristite paginaciju po ključu za velike, sortirane skupove rezultata kada je važan stabilan prolazak.
SELECT id, created_at, status
FROM orders
WHERE customer_id = :customer_id
AND id < :last_seen_id
ORDER BY id DESC
LIMIT 50;
Indeks koji počinje s customer_id, a zatim s id, može podržati ovaj obrazac pristupa, ovisno o mehanizmu baze podataka i ostatku upita. Provjerite umjesto da pretpostavljate: plan izvršavanja je dokaz.
Planirajte konkurenciju, kašnjenje i degradirane ovisnosti
Kvarovi konkurentnosti zaslužuju prvoklasan tretman. Dva radnika mogu pokušati rezervirati posljednju stavku. Dva administratora mogu uređivati isti zapis. Pozadinski zadatak može se utrkivati sa zahtjevom kupca. Odaberite ispravnu strategiju za svaki slučaj: optimističku konkurentnost sa stupcem verzije, pažljivo ograničeno zaključavanje redaka ili poslovni tijek rada koji bilježi stanje na čekanju.
Replike za čitanje uvode još jedan kompromis. Mogu smanjiti pritisak čitanja, ali kašnjenje replike znači da se upravo zapisana vrijednost možda neće odmah pojaviti na replici. Usmjerite tijekove čitanja nakon zapisivanja na primarnu bazu kada je potrebna svježina ili dizajnirajte sučelje tako da potvrdi da se rezultat još obrađuje. Dosljednost je ponašanje proizvoda jednako kao i postavka pohrane.
Namjerno postavite vremenska ograničenja kroz cijeli stog: preuzimanje veze, izvršavanje upita, HTTP klijente, radnike i uravnoteživače opterećenja. Bez vremenskih ograničenja spora ovisnost može dovesti do iscrpljenog skupa veza, a zatim do prekida rada cijele aplikacije. Uz razumne granice sustav može brzo odbiti manji broj zahtjeva i oporaviti kapacitet.
Vježbajte oporavak prije nego što vam zatreba
Sigurnosne kopije nisu plan oporavka dok se obnova ne testira. Znajte koji se podaci moraju obnoviti, koliko oporavak može trajati, gdje se nalaze vjerodajnice i ključevi za šifriranje te kako se zapisi aplikacije kontroliraju tijekom oporavka. Testirajte postupak u izoliranom okruženju koristeći realističnu veličinu podataka i ovisnosti.
Operativna vidljivost dovršava dizajn. Pratite zasićenost baze podataka, korištenje veza, spore upite, čekanja zaključavanja, stanje replikacije, stope pogrešaka i uspjeh sigurnosnih kopija. Još važnije, učinite upozorenja djelotvornima. Upozorenje koje kaže “baza podataka je spora” manje je korisno od onoga koje utvrđuje trajnu iscrpljenost skupa veza, rastući red ili neuspješan zadatak sigurnosnog kopiranja.
Predvidljivost je prava značajka performansi
Otporne baze podataka rijetko definira jedan domišljat trik sa shemom ili moćna postavka poslužitelja. Izgrađene su od skromnih, međusobno osnažujućih izbora: jasnih invarijanti, ograničenja koja ih provode, ponovljivih zapisa, kratkih transakcija, izmjerenih upita, ograničenih ponovnih pokušaja i uvježbanog oporavka.
Ta disciplina daje sustavu njegovu najvrjedniju kvalitetu pod opterećenjem: ponaša se na načine koje tim može objasniti. Kada stigne sljedeći incident, predvidljivost pretvara paniku u niz odluka — a to je ono što bazu podataka čini pouzdanom dugo nakon prvog uspješnog postavljanja.