Denormalizacija baze podataka: ubrzavanje čitanja bez žrtvovanja pisanja
Normalizacija je jedan od najboljih zadanih pristupa u dizajnu baza podataka. Smanjuje dupliciranje, čini ažuriranja predvidljivima i svakom podatku daje jasno mjesto. No savršeno normalizirana shema može postati skup način odgovaranja na jednostavna pitanja kada aplikacija dosegne produkcijsku razinu.
Denormalizacija je namjerna odluka o pohrani nekih izvedenih ili ponovljenih podataka kako bi uobičajena čitanja zahtijevala manje spajanja, agregiranja ili izračuna. Ako se provede nepažljivo, stvara zastarjele podatke i složena pisanja. Ako se provede namjerno, može ubrzati sustav uz zadržavanje kontrole nad ispravnošću.
Počnite s putanjom čitanja, a ne dijagramom sheme
Uobičajenu motivaciju lako je prepoznati: stranica ili API krajnja točka trebaju informacije raspoređene među nekoliko tablica, a njihov upit postaje usko grlo. Popis proizvoda može trebati pojedinosti o proizvodu, nazive kategorija, prosjeke recenzija, status zaliha i informacije o prodavatelju. Nadzorna ploča može trebati ukupne vrijednosti izračunate kroz veliku povijest događaja.
Normalizirani dizajn može uredno predstaviti te činjenice. To ne znači automatski da svaki zahtjev treba ponovno sastavljati te podatke od početka.
Prije dodavanja denormaliziranog stupca ili tablice, proučite stvarnu putanju čitanja. Zapitajte se koji je upit spor, koliko se često izvršava, koji su filtri i redoslijedi sortiranja važni te je li problem doista nedostajući indeks, neučinkovit upit, prekomjeran prijenos podataka ili N+1 upit u aplikacijskom sloju.
Denormalizacija treba slijediti mjerenje i jasan obrazac pristupa. Nije zamjena za razumijevanje plana izvršavanja upita.
Kako denormalizacija izgleda u praksi
Denormalizacija je šira od kopiranja naziva u drugu tablicu. Obuhvaća nekoliko korisnih obrazaca.
- Predmemorirane agregacije: pohranjivanje vrijednosti kao što su
comment_count,average_ratingiliopen_ticket_countna nadređenom zapisu. - Modeli za čitanje: održavanje tablice dizajnirane za određeni zaslon, API odgovor ili izvještajni upit.
- Duplicirana polja za prikaz: spremanje stabilne snimke, kao što su adresa dostave ili cijena artikla, povezane s narudžbom.
- Unaprijed izračunata polja za pretraživanje ili sortiranje: pohranjivanje vrijednosti koja izbjegava izračunavanje istog izraza za svaki upit.
Neko dupliciranje nije samo optimizacija performansi. Stavka narudžbe trebala bi u pravilu sačuvati naziv kupljenog artikla i jediničnu cijenu čak i ako se katalog kasnije promijeni. U tom slučaju kopirane vrijednosti izražavaju poslovnu povijest. Tretirati ih kao aktivnu predmemoriju tablice proizvoda bila bi pogreška u modeliranju.
Jasno odredite izvor istine
Središnje pitanje nije jesu li podaci duplicirani. Pitanje je koja je kopija mjerodavna i kako ostale ostaju ispravne.
Pretpostavimo da zapis posts pohranjuje comment_count. Tablica komentara ostaje izvor istine za pojedinačne komentare. Broj je izvedena vrijednost koja se održava radi brzog čitanja. Ta bi razlika trebala biti vidljiva u nazivima, dokumentaciji i vlasništvu nad kodom.
Korisno je pravilo jednostavno: svako denormalizirano polje treba imati deklariranu strategiju održavanja. Namjerno odaberite jednu:
- Ažurirajte ga u istoj transakciji baze podataka kao i izvorno pisanje.
- Ažurirajte ga asinkrono putem pouzdanog tijeka događaja ili poslova.
- Periodično ga ponovno izgradite kada je privremena zastarjelost prihvatljiva.
- Pohranite ga kao nepromjenjivu snimku koja se nikada ne bi trebala sinkronizirati.
Ako je odgovor „aplikacija će se vjerojatno sjetiti”, dizajn nije dovršen.
Koristite transakcije za vrijednosti koje moraju odmah biti ispravne
Za brojače i sažetke koji moraju odmah odražavati uspješno pisanje, ažurirajte normalizirani redak i njegovu izvedenu vrijednost u jednoj transakciji. U PHP-u to često znači zadržati logiku pisanja unutar male granice servisa, umjesto raspršivanja ažuriranja po kontrolerima i poslovima.
$pdo->beginTransaction();
try {
$statement = $pdo->prepare(
'INSERT INTO comments (post_id, body) VALUES (:post_id, :body)'
);
$statement->execute([
'post_id' => $postId,
'body' => $body,
]);
$statement = $pdo->prepare(
'UPDATE posts
SET comment_count = comment_count + 1
WHERE id = :id'
);
$statement->execute(['id' => $postId]);
$pdo->commit();
} catch (Throwable $exception) {
$pdo->rollBack();
throw $exception;
}
Povećanje se izvršava u SQL-u, umjesto čitanja broja u PHP i zapisivanja zamjenske vrijednosti. To je važno pri konkurentnom pristupu: povećanje unutar baze podataka izbjegava klasični obrazac izgubljenog ažuriranja.
Transakcije ne rješavaju svaki problem konzistentnosti, osobito kada promjena prelazi preko servisa ili baza podataka. Ipak pružaju snažnu i razumljivu granicu kada povezani podaci žive u jednoj bazi podataka.
Prihvatite eventualnu konzistentnost samo kada je proizvod može prihvatiti
Asinkrona ažuriranja mogu zaštititi putanju zahtjeva kada je ponovni izračun skup ili model za čitanje kombinira podatke iz više domena. Također uvode operativne obveze. Poslovi mogu ne uspjeti, izvršiti se dvaput, stići neispravnim redoslijedom ili kasniti.
To znači da potrošači moraju biti dizajnirani za projekciju s kašnjenjem. Novo poslana recenzija možda neće odmah utjecati na prikazani prosjek proizvoda. Izvještajna nadzorna ploča može prikazivati vrijeme posljednjeg osvježavanja. Prihvatljivo kašnjenje treba biti odluka o proizvodu, a ne slučajna nuspojava stavljanja posla u red čekanja.
Učinite obrađivače projekcija idempotentnima gdje je to moguće. Zabilježite dovoljno informacija za otkrivanje već primijenjenog rada i pružite siguran način za ponovnu izgradnju projekcije iz kanonskih podataka. Denormalizirani model bez putanje popravka s vremenom pretvara manji incident u ručnu istragu podataka.
Neka pisanja budu jednostavna i vidljiva
Denormalizacija premješta dio troška s čitanja na pisanja. To je često ispravna zamjena, ali samo ako putanja pisanja ostane razumljiva. Izbjegavajte dopustiti svakom pozivatelju da neovisno ažurira i izvornu tablicu i njezin predmemorirani prikaz.
Umjesto toga, centralizirajte operaciju koja mijenja temeljnu činjenicu. Primjerice, metoda koja objavljuje komentar može upravljati umetanjem komentara i ažuriranjem broja. Radnik koji obrađuje promjenu kataloga može upravljati ažuriranjima svojeg modela za čitanje. To čini pregled koda učinkovitijim jer ponašanje konzistentnosti ima vidljivo mjesto.
Također dodajte provjere koje otkrivaju odstupanja. Planirano usklađivanje može usporediti pohranjene brojeve s brojevima izračunatima iz osnovne tablice. Provjera se ne mora izvršavati pri svakom zahtjevu; njezina je zadaća rano otkriti pogrešne pretpostavke i podržati popravak.
Indeksirajte i denormaliziranu putanju
Kopirano polje pomaže samo ako ga baza podataka može učinkovito upotrijebiti. Ako API navodi proizvode prema average_rating i filtrira po dostupnosti, indeksirajte kombinaciju koja odgovara potrebama filtriranja i sortiranja upita. Potvrdite dobiveni plan izvršavanja upita reprezentativnim podacima, umjesto pretpostavke da će indeks biti odabran.
Slično tome, nemojte stvarati širok model za čitanje koji svakom klijentu vraća svako moguće polje. Denormalizacija smanjuje rad baze podataka, ali prijenos nepotrebnih podataka kroz API može jednostavno premjestiti usko grlo.
Znajte kada je normalizacija i dalje bolji izbor
Nemojte denormalizirati samo zato što postoje spajanja. Relacijske baze podataka dizajnirane su za spajanje povezanih podataka, a dobro indeksirano spajanje nad skupovima rezultata odgovarajuće veličine često je upravo pravo rješenje.
Zadržite normalizirani dizajn kada su pisanja česta, čitanja raznolika, svježina podataka nije predmet pregovora ili duplicirana vrijednost nema stabilnog, dobro razumljivog vlasnika. Prerano predmemorirana agregacija može nametnuti više složenosti nego što ju je izvorni upit ikada imao.
Najjači dizajni baza podataka nisu ideološki ni normalizirani ni denormalizirani. Čuvaju pouzdanu jezgru kanonskih činjenica, a zatim dodaju pažljivo održavane prečace za dokazane potrebe čitanja. Svaki prečac tretirajte kao mali dio infrastrukture: definirajte njegova vlasnika, ugovor o konzistentnosti, ponašanje pri neuspjehu i postupak popravka. Kada su ti odgovori jasni, brža čitanja ne moraju doći nauštrb pouzdanih pisanja.