6.1 Bătălia abrevierilor: BASE vs. ACID

„În chimie, pH-ul măsoară aciditatea relativă a unei soluții apoase. Scara de pH variază de la 0 (substanțe puternic acide) la 14 (substanțe puternic alcaline); apa pură la 25°C are un pH de 7 și este neutră.

Inginerii de date au luat această metaforă pentru a compara bazele de date cu privire la fiabilitatea tranzacțiilor.”

Probabil, ideea a fost aceasta: cu cât pH-ul era mai mare, adică. cu cât baza de date este mai aproape de „alcalină” („BASE”), cu atât tranzacțiile sunt mai puțin fiabile.

Bazele de date relaționale populare, cum ar fi MySQL, au apărut doar pe baza ACID. Dar în ultimii zece ani, așa-numitele baze de date NoSQL, care combină mai multe tipuri foarte diferite de baze de date sub acest nume, s-au descurcat destul de bine fără ACID. De fapt, există un număr mare de dezvoltatori care lucrează cu baze de date NoSQL și nu le pasă deloc de tranzacții și de fiabilitatea acestora. Să vedem dacă au dreptate.

Nu poți vorbi în general despre baza de date NoSQL, pentru că este doar o abstractizare bună. Bazele de date NoSQL diferă unele de altele în proiectarea subsistemelor de stocare a datelor și chiar și în modelele de date: NoSQL este atât CouchDB orientat spre document, cât și graficul Neo4J. Dar dacă vorbim despre ele în contextul tranzacțiilor, toate tind să fie similare într-un singur lucru: oferă versiuni limitate de atomicitate și izolare și, prin urmare, nu oferă garanții ACID. Pentru a înțelege ce înseamnă asta, să răspundem la întrebarea: ce oferă ele, dacă nu ACID? Nimic?

Nu chiar. La urma urmei, ei, ca și bazele de date relaționale, trebuie să se vândă într-un pachet frumos. Și au venit cu propria lor abreviere „chimică” - BAZĂ.

6.2 BAZĂ ca antagonist

Și iar aici nu voi merge în ordinea literelor, ci voi începe cu termenul fundamental - consistență. Va trebui să vă nivelez efectul de recunoaștere, pentru că această consistență are puțin de-a face cu consistența de la ACID. Problema cu termenul de consistență este că este folosit în prea multe contexte. Dar această consistență are un context de utilizare mult mai larg și, într-adevăr, aceasta este exact consistența despre care se discută atunci când se discută despre sistemele distribuite.

Bazele de date relaționale despre care am vorbit mai sus oferă diferite niveluri de izolare a tranzacțiilor, iar cele mai stricte dintre ele asigură că o tranzacție nu poate vedea modificările nevalide făcute de o altă tranzacție. Dacă stai la casă într-un magazin, iar în acel moment banii pentru chirie sunt retrași din contul tău, dar tranzacția cu transferul de bani pentru chirie eșuează și contul tău revine la valoarea anterioară (banii sunt nedebitată), atunci tranzacția dvs. de plată la casă nu va observa tuturor aceste gesturi - la urma urmei, tranzacția respectivă nu a trecut niciodată și, pe baza cerinței de izolare a tranzacției, modificările sale temporare nu pot fi observate de alte tranzacții.

Multe baze de date NoSQL renunță la garanția de izolare și oferă „coerență eventuală” prin care veți vedea în cele din urmă date valide, dar există șansa ca tranzacția dvs. să citească valori nevalide - adică temporare, parțial actualizate sau depășite. Este posibil ca datele să devină consistente în modul „leneș” la citire („leneș la ora de citire”).

NoSQL a fost conceput ca o bază de date pentru analiză în timp real și, pentru a obține o viteză mai mare, au sacrificat consistența. Și Eric Brewer, același tip care a inventat termenul BASE, a formulat așa-numita „teoremă CAP”, conform căreia:

Pentru orice implementare a calculului distribuit, este posibil să se furnizeze nu mai mult de două dintre următoarele trei proprietăți:

  • consistenta datelor ( consistența ) - datele de pe diferite noduri (instanțe) nu se contrazic;
  • disponibilitate ( disponibilitate ) - orice cerere către un sistem distribuit se încheie cu un răspuns corect, dar fără o garanție că răspunsurile tuturor nodurilor de sistem sunt aceleași;
  • toleranță de partiție (toleranță de partiție ) - Chiar dacă nu există nicio legătură între noduri, acestea continuă să funcționeze independent unul de celălalt.

Dacă doriți o explicație foarte simplă a CAP, atunci iată.

Există opinii că teorema CAP nu funcționează și, în general, este formulată prea abstract. Într-un fel sau altul, bazele de date NoSQL refuză adesea consistența în contextul teoremei CAP, care descrie următoarea situație: datele au fost actualizate într-un cluster cu mai multe instanțe, dar modificările nu au fost încă sincronizate pe toate instanțele. Ține minte, am menționat exemplul DynamoDB de mai sus, care mi-a spus: modificările tale au devenit durabile - iată un HTTP 200 pentru tine - dar am văzut modificările doar după 10 secunde? Un alt exemplu din viața de zi cu zi a unui dezvoltator este DNS, Domain Name System. Dacă cineva nu știe, atunci acesta este exact „dicționarul” care traduce adresele http (s) în adrese IP.

Înregistrarea DNS actualizată este propagată către servere conform setărilor intervalului de stocare în cache - astfel încât actualizările nu sunt imediat vizibile. Ei bine, o inconsecvență temporală similară (adică, în cele din urmă consistență) se poate întâmpla unui cluster de baze de date relaționale (să zicem, MySQL) - la urma urmei, această consistență nu are nimic de-a face cu consistența din ACID. Prin urmare, este important să înțelegem că, în acest sens, este puțin probabil ca bazele de date SQL și NoSQL să fie foarte diferite atunci când vine vorba de mai multe instanțe dintr-un cluster.

În plus, consistența de la un capăt la altul poate însemna că cererile de scriere vor fi făcute din ordine: adică toate datele vor fi scrise, dar valoarea care va fi primită în cele din urmă nu va fi ultima din coada de scriere.

Bazele de date NoSQL non-ACID au așa-numita „stare soft” datorită modelului de consistență end-to-end, ceea ce înseamnă că starea sistemului se poate schimba în timp, chiar și fără intrare. Dar astfel de sisteme se străduiesc să ofere o mai mare accesibilitate. Asigurarea disponibilității 100% nu este o sarcină banală, așa că vorbim despre „disponibilitate de bază”. Și împreună aceste trei concepte: „disponibil în principiu”, „stare soft” („stare moale”) și „consistență eventuală” formează acronimul BASE.

Sincer să fiu, conceptul de BASE mi se pare un ambalaj de marketing mai gol decât ACID - pentru că nu dă nimic nou și nu caracterizează în niciun fel baza de date. Și atașarea etichetelor (ACID, BASE, CAP) la anumite baze de date nu poate decât să deruteze dezvoltatorii. M-am hotărât să vă introduc oricum acest termen, pentru că este greu să îl ocoliți când studiați baza de date, dar acum că știți ce este, vreau să uitați de el cât mai curând posibil. Și să revenim la conceptul de izolare.

6.3 Deci bazele de date BASE nu îndeplinesc deloc criteriile ACID?

În esență, unde bazele de date ACID diferă de non-ACID este că non-ACID renunță de fapt la izolare. Acest lucru este important de înțeles. Dar este și mai important să citiți documentația bazei de date și să le testați așa cum fac băieții din proiectul Hermitage. Nu este atât de important cum anume creatorii acestei sau acelei baze de date își numesc ideea - ACID sau BAZĂ, CAP sau nu CAP. Important este ce oferă exact cutare sau cutare bază de date.

Dacă creatorii bazei de date susțin că oferă garanții ACID, atunci probabil că există un motiv pentru aceasta, dar este indicat să o testați singur pentru a înțelege dacă este așa și în ce măsură. Dacă declară că baza lor de date nu oferă astfel de garanții, atunci aceasta poate însemna următoarele lucruri:

  • DB nu oferă nicio garanție de atomicitate. În timp ce unele baze de date NoSQL oferă un API separat pentru operațiuni atomice (ex. DynamoDB);

  • DB nu oferă nicio garanție de izolare. Aceasta poate însemna, de exemplu, că baza de date nu va scrie datele în ordinea în care au fost scrise.

În ceea ce privește garanția de durabilitate, multe baze de date fac compromisuri în acest punct de dragul performanței. Scrierea pe disc este o operațiune prea lungă și există mai multe modalități de a rezolva această problemă. Nu vreau să intru prea mult în teoria bazelor de date, dar pentru a înțelege aproximativ în ce fel să priviți, voi descrie în termeni generali cum diferite baze de date rezolvă problema cu durabilitatea.

Pentru a compara diferite baze de date, printre altele, trebuie să știți ce structuri de date stau la baza subsistemului de stocare și recuperare de date al unei anumite baze de date. Pe scurt: diferite baze de date au implementări diferite de indexare - adică organizarea accesului la date. Unele dintre ele vă permit să scrieți datele mai rapid, altele - mai rapid să le citiți. Dar nu se poate spune în general că unele structuri de date fac durabilitatea mai mare sau mai mică.

6.4 cum diferite baze de date indexează datele și cum afectează acest lucru durabilitatea și multe altele

Există două abordări principale pentru stocarea și preluarea datelor.

Cel mai simplu mod de a salva date este să adăugați operațiuni la sfârșitul fișierului într-o manieră asemănătoare unui jurnal (adică are loc întotdeauna o operație de adăugare): nu contează dacă vrem să adăugăm, să schimbăm sau să ștergem date - toate Operațiile CRUD sunt pur și simplu scrise în jurnal. Căutarea în jurnal este ineficientă și aici intervine indexul - o structură specială de date care stochează metadate despre exact locul în care sunt stocate datele. Cea mai simplă strategie de indexare a jurnalelor este o hartă hash care ține evidența cheilor și a valorilor. Valorile vor fi referințe la offset-ul de octeți pentru datele scrise în interiorul fișierului, care este jurnalul (jurnalul) și este stocat pe disc. Această structură de date este stocată în întregime în memorie, în timp ce datele în sine sunt pe disc și se numește arbore LSM (jungere structurată în jurnal).

Probabil te-ai întrebat: dacă ne scriem operațiunile în jurnal tot timpul, atunci va crește exorbitant? Da, și prin urmare a fost inventată tehnica de compactare, care „curăță” datele cu o anumită periodicitate, și anume, lasă doar cea mai relevantă valoare pentru fiecare cheie sau o șterge. Și dacă avem mai mult de un jurnal pe disc, dar mai multe și toate sunt sortate, atunci vom obține o nouă structură de date numită SSTable („tabel de șiruri sortate”), iar acest lucru ne va îmbunătăți, fără îndoială, performanța. Dacă dorim să sortăm în memorie, vom obține o structură similară - așa-numita MemTable, dar cu ea problema este că, dacă are loc o prăbușire fatală a bazei de date, atunci datele scrise ultimele (situate în MemTable, dar încă nu sunt scrise în disc) sunt pierdute. De fapt,

O altă abordare a indexării se bazează pe arbori B („arborii B”). Într-un arbore B, datele sunt scrise pe disc în pagini de dimensiune fixă. Aceste blocuri de date au adesea o dimensiune de aproximativ 4 KB și au perechi cheie-valoare sortate după cheie. Un nod B-tree este ca o matrice cu link-uri către o serie de pagini. Max. numărul de legături dintr-o matrice se numește factor de ramificare. Fiecare interval de pagini este un alt nod B-tree cu link-uri către alte intervale de pagini.

În cele din urmă, la nivel de foaie, veți găsi pagini individuale. Această idee este similară cu indicatorii din limbaje de programare de nivel scăzut, cu excepția faptului că aceste referințe de pagină sunt stocate mai degrabă pe disc decât în ​​memorie. Când apar INSERT și DELETE în baza de date, atunci un nod se poate împărți în doi subarbori pentru a se potrivi cu factorul de ramificare. Dacă baza de date eșuează din orice motiv în mijlocul procesului, integritatea datelor poate fi compromisă. Pentru a preveni acest lucru, bazele de date care folosesc arbori B mențin un „jurnal de scriere anticipată” sau WAL, în care este înregistrată fiecare tranzacție. Acest WAL este folosit pentru a restabili starea arborelui B dacă este corupt. Și se pare că acesta este ceea ce face ca bazele de date care folosesc arbori B să fie mai bune în ceea ce privește durabilitatea. Dar bazele de date bazate pe LSM pot menține și un fișier care în esență îndeplinește aceeași funcție ca WAL. Prin urmare, voi repeta ceea ce am spus deja, și poate de mai multe ori: înțelegeți mecanismele de funcționare ale bazei de date pe care ați ales-o.

Ceea ce este cert despre arborii B, totuși, este că sunt buni pentru tranzacționalitate: fiecare cheie apare într-un singur loc în index, în timp ce subsistemele de stocare jurnalizate pot avea mai multe copii ale aceleiași chei în fragmente diferite (de exemplu, până când se efectuează următoarea compactare).

Cu toate acestea, designul indexului afectează direct performanța bazei de date. Cu un arbore LSM, scrierile pe disc sunt secvențiale, iar arborii B provoacă accesări multiple aleatorii pe disc, astfel încât operațiile de scriere sunt mai rapide cu LSM decât cu arborii B. Diferența este semnificativă în special pentru hard disk-urile magnetice (HDD), unde scrierile secvenţiale sunt mult mai rapide decât scrierile aleatorii. Citirea este mai lentă în arborii LSM, deoarece trebuie să vă uitați prin mai multe structuri de date diferite și tabele SS care se află în diferite stadii de compactare. Mai detaliat arată așa. Dacă facem o interogare simplă la baza de date cu LSM, vom căuta mai întâi cheia în MemTable. Dacă nu este acolo, ne uităm la cel mai recent SSTable; dacă nu există, atunci ne uităm la penultimul SSTable și așa mai departe. Dacă cheia solicitată nu există, atunci cu LSM vom ști aceasta din urmă. Arborii LSM sunt utilizați, de exemplu, în: LevelDB, RocksDB, Cassandra și HBase.

Descriu totul atât de detaliat, astfel încât să înțelegeți că atunci când alegeți o bază de date, trebuie să luați în considerare multe lucruri diferite: de exemplu, vă așteptați să scrieți sau să citiți mai multe date. Și nu am menționat încă diferența dintre modelele de date (trebuie să traversați datele, așa cum permite modelul grafic? Există vreo relație între diferite unități din datele dvs. - atunci bazele de date relaționale vor veni în ajutor?), și 2 tipuri de scheme de date - la scriere (ca în multe NoSQL) și citire (ca și în relațional).

Dacă revenim la aspectul durabilității, atunci concluzia va fi următoarea: orice bază de date care scrie pe disc, indiferent de mecanismele de indexare, poate oferi bune garanții pentru durabilitatea datelor dvs., dar trebuie să vă ocupați de fiecare bază de date specifică. , ce oferă mai exact.

6.5 Cum funcționează DB-urile în memorie

Apropo, pe lângă bazele de date care scriu pe disc, există și așa-numitele baze de date „în memorie” care funcționează în principal cu RAM. Pe scurt, bazele de date în memorie oferă de obicei o durabilitate mai mică de dragul vitezei de scriere și citire mai rapide, dar acest lucru poate fi potrivit pentru unele aplicații.

Cert este că memoria RAM a fost mult timp mai scumpă decât discurile, dar recent a început să se ieftinească rapid, ceea ce a dat naștere unui nou tip de bază de date - ceea ce este logic, având în vedere viteza de citire și scriere a datelor din RAM. Dar vă veți întreba pe bună dreptate: cum rămâne cu siguranța datelor din aceste baze de date? Aici, din nou, trebuie să vă uitați la detaliile implementării. În general, dezvoltatorii unor astfel de baze de date oferă următoarele mecanisme:

  • Puteți folosi RAM alimentată de baterii;
  • Este posibil să scrieți jurnalele de modificări pe disc (ceva ca WAL-urile menționate mai sus), dar nu și datele în sine;
  • Puteți scrie periodic copii ale stării bazei de date pe disc (care, fără a utiliza alte opțiuni, nu oferă o garanție, ci doar îmbunătățește durabilitatea);
  • Puteți replica starea RAM pe alte mașini.

De exemplu, baza de date în memorie Redis, care este folosită în principal ca coadă de mesaje sau cache, nu are durabilitate din partea ACID: nu garantează că o comandă executată cu succes va fi stocată pe disc, deoarece Redis șterge datele pe disc (dacă au persistența activată) numai în mod asincron, la intervale regulate.

Cu toate acestea, acest lucru nu este esențial pentru toate aplicațiile: am găsit un exemplu de editor online cooperativ EtherPad, care s-a spălat la fiecare 1-2 secunde și, eventual, utilizatorul ar putea pierde câteva litere sau un cuvânt, ceea ce nu era critic. În caz contrar, deoarece bazele de date în memorie sunt bune prin faptul că oferă modele de date care ar fi dificil de implementat cu indici de disc, Redis poate fi folosit pentru a implementa tranzacții - coada sa de prioritate vă permite să faceți acest lucru.