Izvan baze podataka: projektiranje za integritet podataka u velikom opsegu
Većina kvarova podataka ne počinje s pokvarenom bazom podataka. Počinju ranije, kada aplikacija prihvati dvosmislen zahtjev, ponovi nuspojavu bez konteksta ili dopusti da se dvije valjane operacije međusobno utrkuju. Ograničenje baze podataka može uhvatiti neke od tih pogrešaka, ali integritet podataka u velikim razmjerima arhitektonsko je svojstvo: proizlazi iz načina na koji API-ji, radni procesi, transakcije, sheme i prakse implementacije djeluju zajedno.
Ta je razlika važna jer sustav može imati dobro osmišljenu shemu, a ipak stvarati duplicirane narudžbe, gubiti ažuriranja, prikazivati zastarjele iznose ili objavljivati događaje za transakcije koje se kasnije ponište. Baza podataka i dalje je ključna, ali ne može biti jedino mjesto na kojem postoji ispravnost.
Započnite definiranjem invarijanti
Invarijanta je uvjet koji mora ostati istinit bez obzira na vrijeme zahtjeva, ponavljanja ili kvarove. „Adresa e-pošte je jedinstvena” poznat je primjer. Korisnije poslovne invarijante često su konkretnije: račun se može platiti jednom, zaliha nikada ne smije pasti ispod nule, a korisnik ne smije dobiti pristup prije nego što se zabilježi uspješno plaćanje.
Napišite ta pravila običnim jezikom prije nego što odlučite gdje ih provesti. Zatim svako pravilo smjestite na najniži pouzdani sloj koji ima dovoljno konteksta da ga provede.
- Koristite ograničenja baze podataka za činjenice koje uvijek moraju biti istinite u pohranjenim podacima.
- Koristite aplikacijske usluge za tijekove rada koji obuhvaćaju više tablica ili vanjske sustave.
- Koristite API ugovore kako biste namjeru klijenta učinili izričitom.
- Koristite asinkronu obradu za posao koji se može obaviti kasnije, uz očuvanje trajnog zapisa o tome što se mora dogoditi.
Ovaj slojeviti pristup izbjegava uobičajenu zamku: smještanje svakog pravila u PHP zato što se čini praktičnim. Provjera u aplikaciji poboljšava povratne informacije korisniku, ali nije zamjena za jedinstveni indeks, strani ključ, ograničenje provjere ili transakciju. Drugi putovi koda s vremenom će je zaobići: uvozi, administratorski alati, pozadinski poslovi ili buduća usluga.
Dizajnirajte API-je za ponavljanja, a ne za idealne zahtjeve
Mreže otkazuju na načine koje korisnici ne mogu vidjeti. Klijent može poslati zahtjev, poslužitelj ga može dovršiti, a odgovor se može izgubiti. Ako klijent ponovi zahtjev „stvori plaćanje”, pozadinski sustav mora razlikovati ponavljanje od druge upute.
Za operacije s važnim nuspojavama prihvatite ključ idempotentnosti. Pohranite ključ zajedno s ishodom zahtjeva pod ograničenjem jedinstvenosti, a zatim vratite izvorni rezultat kada se isti ključ ponovno upotrijebi. Ključ mora biti primjereno ograničenog opsega, primjerice na račun i vrstu operacije, umjesto da se tretira kao globalni niz.
DB::transaction(function () use ($accountId, $key, $payload) {
$existing = IdempotencyRequest::query()
->where('account_id', $accountId)
->where('key', $key)
->lockForUpdate()
->first();
if ($existing) {
return $existing->response;
}
$payment = Payment::create($payload);
IdempotencyRequest::create([
'account_id' => $accountId,
'key' => $key,
'response' => ['payment_id' => $payment->id],
]);
return ['payment_id' => $payment->id];
});
Točna implementacija ovisi o okviru i bazi podataka, ali važna je ideja atomarnost. Pretraživanje nakon kojeg slijedi umetanje izvan transakcije može omogućiti da istodobni zahtjevi oba zaključe da zapis ne postoji. Baza podataka i dalje mora odbiti duplicirani ključ, a aplikacija mora obraditi to odbijanje učitavanjem već zabilježenog rezultata.
Zaštitite se od izgubljenih ažuriranja
Logika čitanja-izmjene-zapisivanja varljivo je opasna. Razmotrite smanjivanje zalihe. Dva radna procesa mogu oba pročitati količinu jedan, oba odlučiti da je dostupna i oba zapisati nulu. Baza podataka vidi valjane vrijednosti; poslovanje vidi preprodanu stavku.
Dajte prednost uvjetnom ažuriranju kada se pravilo može izravno izraziti u SQL-u:
UPDATE inventory
SET quantity = quantity - 1
WHERE sku = :sku
AND quantity > 0;
Ako je broj zahvaćenih redaka nula, nije bilo dostupne zalihe. To je obično jednostavnije i sigurnije nego prvo učitavanje retka u PHP. Za složenije tijekove rada koristite transakciju s odgovarajućom strategijom zaključavanja, neka transakcija bude kratka i izbjegavajte mrežne pozive dok su zaključavanja zadržana.
Optimistično zaključavanje još je jedna korisna mogućnost kada je konkurentnost niska. Dodajte stupac verzije, ažurirajte samo kada se verzija podudara te neka pozivatelj ponovi postupak ili riješi sukob kada se ne podudara. Ono pretvara tiho prepisivanje u izričitu odluku.
Učinite asinkroni rad trajnim
Slanje e-pošte, naplata putem pružatelja usluge, indeksiranje dokumenata za pretraživanje i obavještavanje drugih usluga rijetko bi se trebali odvijati unutar transakcije koja mijenja primarne podatke. Vanjski pozivi su spori, mogu nepredvidivo otkazati i vaša ih baza podataka ne može poništiti.
Obrazac transakcijskog outboxa daje toj granici trajan oblik. U istoj transakciji koja stvara ili mijenja poslovni zapis umetnite outbox redak koji opisuje događaj. Radni proces kasnije isporučuje događaj i bilježi njegovo stanje isporuke. Ako se proces sruši nakon potvrde, outbox unos ostaje dostupan za ponovno pokušavanje.
Potrošači također moraju biti idempotentni. Sustavi isporuke mogu proizvesti duplikate, osobito nakon isteka vremena ili ponovnog pokretanja radnog procesa. Potrošač bi trebao zabilježiti stabilni identifikator događaja ili na drugi način učiniti ponovljenu obradu bezopasnom. Isporuka „barem jednom” postaje upravljiva kada je svaki obrađivač za nju osmišljen.
Koristite bazu podataka kao partnera, a ne kao odlagalište
Ograničenja baze podataka izvršiva su dokumentacija. Strani ključ kaže da je zapis bez roditeljskog zapisa nevaljan. Jedinstveni indeks kaže da duplikati nisu samo nepraktični; zabranjeni su. Stupac koji ne dopušta null kaže da odsutnost nije valjano stanje. Ta jamstva pojednostavljuju nizvodni kod jer u sustav može ući manje nemogućih stanja.
Ipak, izbjegavajte pretvaranje pohranjenih procedura, okidača i neprozirnog ponašanja baze podataka u jedini dom poslovnih tijekova rada. Mogu biti prikladni za strogo lokalna pravila integriteta, ali logiku koja koordinira API-je, redove čekanja, autorizaciju i vidljive neuspjehe obično je lakše testirati i održavati u aplikacijskom sloju. Cilj nije smanjiti logiku baze podataka; cilj je učiniti svako pravilo vidljivim, provedivim i u vlasništvu odgovarajuće granice.
Izgradite vidljivost oko kvarova integriteta
Incidenti integriteta često se otkrivaju neizravno: podrška prijavi dvostruku naplatu, posao usklađivanja pronađe nepodudaranje ili zaostatak u redu čekanja otkrije zaglavljeni tijek rada. Tretirajte ih kao operativne signale prve klase.
- Zabilježite stabilne identifikatore zahtjeva, entiteta i događaja.
- Mjerite kršenja ograničenja, stope ponavljanja, poslove poslane u dead-letter red i starost outboxa.
- Zadržite polja za reviziju koja objašnjavaju tko je ili što promijenilo zapis i kada.
- Pokrećite provjere usklađivanja za kritične izvedene podatke, poput salda ili stanja vanjskih plaćanja.
Revizija nije izgovor za toleriranje korupcije. Njezina je vrijednost dijagnostička: kada se invarijanta dovede u pitanje, inženjeri trebaju dovoljno dokaza da razumiju slijed i sigurno ga isprave.
Integritet je navika dizajna sustava
U velikim razmjerima, ispravnost se ne postiže jednom savršenom transakcijom ili jednom pažljivo imenovanom tablicom. Proizlazi iz pretpostavke da će se zahtjevi duplicirati, radni procesi ponovno pokretati, implementacije preklapati i istodobni korisnici u isto vrijeme postavljati valjane zahtjeve.
Prvo dizajnirajte invarijantu. Provedite je na najjačoj praktičnoj granici. Učinite nuspojave ponovljivima, potrošače idempotentnima, a neuspjehe vidljivima. Baza podataka tada postaje ono što bi trebala biti: snažan temelj u sustavu namjerno izgrađenom da ispuni svoja obećanja.