Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date

Notă. transl.: Jaana Dogan este un inginer cu experiență la Google, care lucrează în prezent la observabilitatea serviciilor de producție ale companiei scrise în Go. În acest articol, care a câștigat o mare popularitate în rândul publicului de limbă engleză, ea a adunat în 17 puncte detalii tehnice importante privind DBMS (și uneori sisteme distribuite în general) care sunt utile de luat în considerare pentru dezvoltatorii de aplicații mari/pretențioase.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date

Marea majoritate a sistemelor informatice țin evidența stării lor și, în consecință, necesită un fel de sistem de stocare a datelor. Am acumulat cunoștințe despre bazele de date pe o perioadă lungă de timp, pe parcurs făcând greșeli de proiectare care au dus la pierderi de date și întreruperi. În sistemele care procesează volume mari de informații, bazele de date se află în centrul arhitecturii sistemului și acționează ca un element cheie în alegerea soluției optime. În ciuda faptului că se acordă o atenție deosebită activității bazei de date, problemele pe care dezvoltatorii de aplicații încearcă să le anticipeze sunt adesea doar vârful aisbergului. În această serie de articole, împărtășesc câteva idei care vor fi utile dezvoltatorilor care nu sunt specializați în acest domeniu.

  1. Ai noroc dacă în 99,999% din timp rețeaua nu creează probleme.
  2. ACID înseamnă multe lucruri diferite.
  3. Fiecare bază de date are propriile mecanisme pentru asigurarea coerenței și izolării.
  4. Blocarea optimistă vine în ajutor atunci când este dificil să o menții pe cea obișnuită.
  5. Există și alte anomalii în afară de citirile murdare și pierderea de date.
  6. Baza de date și utilizatorul nu sunt întotdeauna de acord cu privire la cursul acțiunii.
  7. Sharding-ul la nivel de aplicație poate fi mutat în afara aplicației.
  8. Autoincrementarea poate fi periculoasă.
  9. Datele învechite pot fi utile și nu trebuie să fie blocate.
  10. Distorsiunile sunt tipice pentru orice sursă de timp.
  11. Întârzierea are multe semnificații.
  12. Cerințele de performanță ar trebui evaluate pentru o anumită tranzacție.
  13. Tranzacțiile imbricate pot fi periculoase.
  14. Tranzacțiile nu ar trebui să fie legate de starea aplicației.
  15. Planificatorii de interogări vă pot spune multe despre bazele de date.
  16. Migrarea online este dificilă, dar posibilă.
  17. O creștere semnificativă a bazei de date implică o creștere a impredictibilității.

Aș dori să mulțumesc lui Emmanuel Odeke, Rein Henrichs și altora pentru feedback-ul lor cu privire la o versiune anterioară a acestui articol.

Ai noroc dacă în 99,999% din timp rețeaua nu creează probleme.

Rămâne întrebarea cu privire la cât de fiabile sunt tehnologiile moderne de rețea și cât de des sistemele sunt întrerupte din cauza defecțiunilor rețelei. Informațiile despre această problemă sunt limitate și cercetarea este adesea dominată de organizații mari cu rețele, echipamente și personal specializat.

Cu o rată de disponibilitate de 99,999% pentru Spanner (baza de date Google distribuită la nivel global), Google susține că numai 7,6% problemele sunt legate de rețea. În același timp, compania numește rețeaua sa specializată „pilonul principal” al disponibilității ridicate. Studiu Bailis și Kingsbury, realizat în 2014, provoacă una dintre „concepții greșite despre calculul distribuit", pe care Peter Deutsch a formulat-o în 1994. Este rețeaua cu adevărat fiabilă?

Cercetarea cuprinzătoare în afara companiilor gigantice, efectuată pentru internetul mai larg, pur și simplu nu există. De asemenea, nu există suficiente date de la principalii jucători despre ce procent din problemele clienților lor sunt legate de rețea. Suntem bine conștienți de întreruperi în stiva de rețea a furnizorilor de cloud mari, care pot distruge o bucată întreagă a internetului timp de câteva ore, pur și simplu pentru că sunt evenimente de mare profil care afectează un număr mare de oameni și companii. Întreruperea rețelei poate cauza probleme în multe mai multe cazuri, chiar dacă nu toate aceste cazuri sunt în centrul atenției. De asemenea, clienții serviciilor cloud nu știu nimic despre cauzele problemelor. Dacă există o eroare, este aproape imposibil să o atribui unei erori de rețea din partea furnizorului de servicii. Pentru ei, serviciile terților sunt cutii negre. Este imposibil de evaluat impactul fără a fi un furnizor mare de servicii.

Având în vedere ceea ce raportează marii jucători despre sistemele lor, este sigur să spunem că aveți noroc dacă dificultățile de rețea reprezintă doar un mic procent din potențialele probleme legate de timpul de nefuncționare. Comunicațiile în rețea suferă în continuare de lucruri banale precum defecțiuni hardware, modificări de topologie, modificări ale configurației administrative și întreruperi de curent. Recent, am fost surprins să aflu că a fost adăugată lista cu posibile probleme mușcături de rechin (da, ai auzit bine).

ACID înseamnă o mulțime de lucruri diferite

Acronimul ACID înseamnă Atomicity, Consistency, Isolation, Reliability. Aceste proprietăți ale tranzacțiilor sunt menite să asigure valabilitatea acestora în cazul unor defecțiuni, erori, defecțiuni hardware etc. Fără ACID sau scheme similare, ar fi dificil pentru dezvoltatorii de aplicații să facă diferența între ceea ce sunt responsabili și pentru ce este responsabilă baza de date. Majoritatea bazelor de date tranzacționale relaționale încearcă să fie conforme cu ACID, dar noile abordări precum NoSQL au dat naștere la multe baze de date fără tranzacții ACID, deoarece sunt costisitoare de implementat.

Când am intrat prima oară în industrie, responsabilul nostru tehnic a vorbit despre cât de relevant a fost conceptul ACID. Pentru a fi corect, ACID este considerat mai degrabă o descriere grosieră decât un standard strict de implementare. Astăzi îl găsesc mai ales util, deoarece ridică o categorie specifică de probleme (și sugerează o serie de soluții posibile).

Nu orice SGBD este compatibil cu ACID; În același timp, implementările de baze de date care acceptă ACID înțeleg setul de cerințe în mod diferit. Unul dintre motivele pentru care implementările ACID sunt neregulate se datorează numeroaselor compromisuri care trebuie făcute pentru a implementa cerințele ACID. Creatorii își pot prezenta bazele de date ca fiind conforme cu ACID, dar interpretarea cazurilor marginale poate varia dramatic, la fel ca și mecanismul de gestionare a evenimentelor „improbabile”. Cel puțin, dezvoltatorii pot obține o înțelegere la nivel înalt a complexității implementărilor de bază pentru a obține o înțelegere adecvată a comportamentului lor special și a compromisurilor de proiectare.

Dezbaterea privind respectarea MongoDB cu cerințele ACID continuă chiar și după lansarea versiunii 4. MongoDB nu a fost suportat de mult timp Logare, deși în mod implicit datele au fost înregistrate pe disc nu mai mult de o dată la 60 de secunde. Imaginează-ți următorul scenariu: o aplicație postează două scrieri (w1 și w2). MongoDB stochează cu succes w1, dar w2 se pierde din cauza unei defecțiuni hardware.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Diagrama care ilustrează scenariul. MongoDB se blochează înainte de a putea scrie date pe disc

Angajarea pe disc este un proces costisitor. Evitând comiterile frecvente, dezvoltatorii îmbunătățesc performanța înregistrării în detrimentul fiabilității. MongoDB acceptă în prezent înregistrarea în jurnal, dar scrierile murdare pot afecta în continuare integritatea datelor, deoarece jurnalele sunt capturate la fiecare 100 ms în mod implicit. Adică, un scenariu similar este încă posibil pentru jurnalele și modificările prezentate în acestea, deși riscul este mult mai mic.

Fiecare bază de date are propriile sale mecanisme de consistență și izolare

Dintre cerințele ACID, consistența și izolarea se laudă cu cel mai mare număr de implementări diferite, deoarece gama de compromisuri este mai largă. Trebuie spus că consistența și izolarea sunt funcții destul de costisitoare. Acestea necesită coordonare și sporesc competiția pentru consistența datelor. Complexitatea problemei crește semnificativ atunci când este necesară scalarea orizontală a bazei de date pe mai multe centre de date (mai ales dacă acestea sunt situate în regiuni geografice diferite). Atingerea unui nivel ridicat de consistență este foarte dificilă, deoarece reduce disponibilitatea și crește segmentarea rețelei. Pentru o explicație mai generală a acestui fenomen, vă sfătuiesc să consultați teorema CAP. De asemenea, merită remarcat faptul că aplicațiile pot gestiona cantități mici de inconsecvență, iar programatorii pot înțelege nuanțele problemei suficient de bine pentru a implementa o logică suplimentară în aplicație pentru a gestiona inconsecvența fără a se baza foarte mult pe baza de date pentru a o gestiona.

SGBD-urile oferă adesea diferite niveluri de izolare. Dezvoltatorii de aplicații îl pot alege pe cel mai eficient în funcție de preferințele lor. Izolarea scăzută permite o viteză crescută, dar crește și riscul unei curse de date. Izolarea ridicată reduce această probabilitate, dar încetinește munca și poate duce la concurență, ceea ce va duce la astfel de frâne în bază care încep defecțiunile.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Revizuirea modelelor de concurență existente și a relațiilor dintre ele

Standardul SQL definește doar patru niveluri de izolare, deși în teorie și practică există mult mai multe. Jepson.io oferă o imagine de ansamblu excelentă a modelelor de concurență existente. De exemplu, Google Spanner garantează serializabilitatea externă cu sincronizarea ceasului și, deși acesta este un strat de izolare mai strict, nu este definit în straturi de izolare standard.

Standardul SQL menționează următoarele niveluri de izolare:

  • Serializabil (cel mai strict și mai scump): execuția serializabilă are același efect ca o execuție secvențială a tranzacțiilor. Executarea secvenţială înseamnă că fiecare tranzacţie ulterioară începe numai după ce cea anterioară este finalizată. Trebuie remarcat faptul că nivelul Serializabil implementat adesea ca așa-numita izolație de instantanee (de exemplu, în Oracle) din cauza diferențelor de interpretare, deși izolarea de instantanee în sine nu este reprezentată în standardul SQL.
  • Citiri repetabile: Înregistrările neangajate din tranzacția curentă sunt disponibile pentru tranzacția curentă, dar modificările efectuate de alte tranzacții (cum ar fi rândurile noi) nu este vizibil.
  • Citiți angajat: Datele neangajate nu sunt disponibile pentru tranzacții. În acest caz, tranzacțiile pot vedea numai datele comise și pot apărea citiri fantomă. Dacă o tranzacție inserează și comite noi rânduri, tranzacția curentă le va putea vedea atunci când este interogată.
  • Citiți neangajat (nivelul cel mai puțin strict și costisitor): citirile murdare sunt permise, tranzacțiile pot vedea modificări necommitate făcute de alte tranzacții. În practică, acest nivel poate fi util pentru estimări aproximative, cum ar fi interogări COUNT(*) pe masă.

nivel Serializabil minimizează riscul curselor de date, fiind în același timp cel mai costisitor de implementat și rezultând cea mai mare sarcină competitivă a sistemului. Alte niveluri de izolare sunt mai ușor de implementat, dar cresc probabilitatea curselor de date. Unele SGBD vă permit să setați un nivel de izolare personalizat, altele au preferințe puternice și nu toate nivelurile sunt acceptate.

Suportul pentru nivelurile de izolare este adesea anunțat într-un anumit SGBD, dar numai un studiu atent al comportamentului acestuia poate dezvălui ceea ce se întâmplă de fapt.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Revizuirea anomaliilor de concurență la diferite niveluri de izolare pentru diferite SGBD

Martin Kleppmann în proiectul său schit Compară diferite niveluri de izolare, vorbește despre anomalii de concurență și dacă baza de date este capabilă să adere la un anumit nivel de izolare. Cercetarea lui Kleppmann arată cât de diferit gândesc dezvoltatorii de baze de date despre nivelurile de izolare.

Blocarea optimistă vine în ajutor atunci când este dificil să o menții pe cea obișnuită.

Blocarea poate fi foarte costisitoare, nu doar pentru că crește concurența în baza de date, ci și pentru că necesită ca serverele de aplicații să se conecteze constant la baza de date. Segmentarea rețelei poate exacerba situațiile de blocare exclusivă și poate duce la blocaje care sunt dificil de identificat și rezolvat. În cazurile în care blocarea exclusivă nu este potrivită, blocarea optimistă ajută.

Blocare optimistă este o metodă în care atunci când citește un șir, acesta ia în considerare versiunea, suma de control sau ora ultimei modificări. Acest lucru vă permite să vă asigurați că nu există nicio modificare a versiunii atomice înainte de a schimba o intrare:

UPDATE products
SET name = 'Telegraph receiver', version = 2
WHERE id = 1 AND version = 1

În acest caz, actualizarea tabelului products nu va fi efectuată dacă o altă operație a făcut anterior modificări la acest rând. Dacă nu au fost efectuate alte operațiuni pe acest rând, se va produce schimbarea pentru un rând și putem spune că actualizarea a avut succes.

Există și alte anomalii în afară de citirile murdare și pierderea de date

Când vine vorba de consistența datelor, accentul se pune pe potențialul condițiilor de cursă care pot duce la citiri murdare și pierderi de date. Cu toate acestea, anomaliile de date nu se opresc aici.

Un exemplu de astfel de anomalii este distorsiunea de înregistrare (scrie deformări). Distorsiunile sunt greu de detectat deoarece de obicei nu sunt căutate activ. Acestea nu se datorează citirilor murdare sau pierderii de date, ci încălcărilor constrângerilor logice impuse datelor.

De exemplu, să luăm în considerare o aplicație de monitorizare care necesită ca un operator să fie disponibil în orice moment:

BEGIN tx1;                      BEGIN tx2;
SELECT COUNT(*)
FROM operators
WHERE oncall = true;
0                               SELECT COUNT(*)
                                FROM operators
                                WHERE oncall = TRUE;
                                0
UPDATE operators                UPDATE operators
SET oncall = TRUE               SET oncall = TRUE
WHERE userId = 4;               WHERE userId = 2;
COMMIT tx1;                     COMMIT tx2;

În situația de mai sus, se va produce o corupție a înregistrării dacă ambele tranzacții sunt efectuate cu succes. Deși nu au existat citiri murdare sau pierderi de date, integritatea datelor a fost compromisă: acum două persoane sunt considerate de gardă în același timp.

Izolarea serializabilă, proiectarea schemei sau constrângerile bazei de date pot ajuta la eliminarea corupției la scriere. Dezvoltatorii trebuie să fie capabili să identifice astfel de anomalii în timpul dezvoltării pentru a le evita în producție. În același timp, distorsiunile de înregistrare sunt extrem de greu de căutat în baza de cod. Mai ales în sistemele mari, când diferite echipe de dezvoltare sunt responsabile pentru implementarea funcțiilor bazate pe aceleași tabele și nu sunt de acord cu specificul accesului la date.

Baza de date și utilizatorul nu sunt întotdeauna de acord asupra a ceea ce trebuie să facă

Una dintre caracteristicile cheie ale bazelor de date este garantarea ordinii de execuție, dar această ordine în sine poate să nu fie transparentă pentru dezvoltatorul de software. Bazele de date execută tranzacțiile în ordinea în care sunt primite, nu în ordinea intenționată de programatori. Ordinea tranzacțiilor este dificil de prezis, mai ales în sistemele paralele foarte încărcate.

În timpul dezvoltării, mai ales când se lucrează cu biblioteci neblocante, stilul slab și lizibilitatea scăzută pot face utilizatorii să creadă că tranzacțiile sunt executate secvenţial, când de fapt ar putea ajunge în baza de date în orice ordine.

La prima vedere, în programul de mai jos, T1 și T2 sunt apelate secvențial, dar dacă aceste funcții sunt neblocante și returnează imediat rezultatul sub forma promisiune, atunci ordinea apelurilor va fi determinată de momentele în care au intrat în baza de date:

rezultat1 = T1() // rezultatele reale sunt promisiuni
rezultat2 = T2()

Dacă este necesară atomicitatea (adică fie toate operațiunile trebuie finalizate, fie anulate) și secvența contează, atunci operațiunile T1 și T2 trebuie efectuate într-o singură tranzacție.

Sharding-ul la nivel de aplicație poate fi mutat în afara aplicației

Sharding este o metodă de partiţionare orizontală a unei baze de date. Unele baze de date pot împărți automat datele pe orizontală, în timp ce altele nu pot sau nu sunt foarte bune la asta. Atunci când arhitecții/dezvoltatorii de date sunt capabili să prezică exact cum vor fi accesate datele, ei pot crea partiții orizontale în spațiul utilizatorului în loc să delege această muncă în baza de date. Acest proces se numește „sharding la nivel de aplicație” (sharding la nivel de aplicație).

Din păcate, acest nume creează adesea concepția greșită că sharding-ul trăiește în serviciile de aplicații. De fapt, poate fi implementat ca un strat separat în fața bazei de date. În funcție de creșterea datelor și de iterațiile schemei, cerințele de fragmentare pot deveni destul de complexe. Unele strategii pot beneficia de capacitatea de a repeta fără a fi nevoie să redistribuiți serverele de aplicații.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Un exemplu de arhitectură în care serverele de aplicații sunt separate de serviciul de fragmentare

Mutarea sharding-ului într-un serviciu separat extinde capacitatea de a utiliza diferite strategii de sharding fără a fi nevoie să redistribuiți aplicațiile. Vitess este un exemplu de astfel de sistem de sharding la nivel de aplicație. Vitess oferă sharding orizontal pentru MySQL și permite clienților să se conecteze la acesta prin protocolul MySQL. Sistemul segmentează datele în diferite noduri MySQL care nu știu nimic unul despre celălalt.

Autoincrementarea poate fi periculoasă

AUTOINCREMENT este o modalitate obișnuită de a genera chei primare. Există adesea cazuri când bazele de date sunt folosite ca generatoare de ID, iar baza de date conține tabele concepute pentru a genera identificatori. Există mai multe motive pentru care generarea cheilor primare folosind incrementarea automată este departe de a fi ideală:

  • Într-o bază de date distribuită, incrementarea automată este o problemă serioasă. Pentru a genera ID-ul, este necesară o blocare globală. În schimb, puteți genera un UUID: acest lucru nu necesită interacțiune între diferite noduri de bază de date. Incrementarea automată cu blocări poate duce la dispute și poate reduce semnificativ performanța la inserții în situații distribuite. Unele SGBD-uri (de exemplu, MySQL) pot necesita configurații speciale și o atenție mai atentă pentru a organiza corect replicarea master-master. Și este ușor să faci greșeli la configurare, ceea ce va duce la eșecuri de înregistrare.
  • Unele baze de date au algoritmi de partiționare bazați pe chei primare. ID-urile consecutive pot duce la puncte fierbinți imprevizibile și la o sarcină crescută pe unele partiții, în timp ce altele rămân inactive.
  • O cheie primară este cea mai rapidă modalitate de a accesa un rând dintr-o bază de date. Cu modalități mai bune de a identifica înregistrările, ID-urile secvențiale pot transforma cea mai importantă coloană din tabele într-o coloană inutilă plină cu valori fără sens. Prin urmare, ori de câte ori este posibil, alegeți o cheie primară unică și naturală la nivel global (de exemplu, numele de utilizator).

Înainte de a decide asupra unei abordări, luați în considerare impactul auto-incrementării ID-urilor și UUID-urilor asupra indexării, partiționării și sharding-ului.

Datele învechite pot fi utile și nu necesită blocare

Multiversion Concurrency Control (MVCC) implementează multe dintre cerințele de consistență care au fost discutate pe scurt mai sus. Unele baze de date (de exemplu, Postgres, Spanner) folosesc MVCC pentru a „alimenta” tranzacțiile cu instantanee – versiuni mai vechi ale bazei de date. Tranzacțiile instantanee pot fi, de asemenea, serializate pentru a asigura coerența. Când citiți dintr-un instantaneu vechi, sunt citite date învechite.

Citirea datelor ușor învechite poate fi utilă, de exemplu, atunci când se generează analize din date sau când se calculează valori agregate aproximative.

Primul avantaj al lucrului cu date vechi este latența scăzută (mai ales dacă baza de date este distribuită în diferite zone geografice). Al doilea este că tranzacțiile numai în citire sunt fără blocare. Acesta este un avantaj semnificativ pentru aplicațiile care citesc mult, atâta timp cât pot gestiona date învechite.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Serverul de aplicații citește date din replica locală care este învechită de 5 secunde, chiar dacă cea mai recentă versiune este disponibilă de cealaltă parte a Oceanului Pacific

SGBD-urile șterg automat versiunile mai vechi și, în unele cazuri, vă permit să faceți acest lucru la cerere. De exemplu, Postgres permite utilizatorilor să facă VACUUM la cerere și, de asemenea, efectuează periodic această operațiune în mod automat. Spanner conduce un colector de gunoi pentru a scăpa de instantaneele mai vechi de o oră.

Oricând sursele sunt supuse distorsiunii

Cel mai bine păstrat secret în informatică este că toate API-urile de sincronizare mint. De fapt, mașinile noastre nu știu exact ora curentă. Calculatoarele conțin cristale de cuarț care generează vibrații care sunt folosite pentru a păstra timpul. Cu toate acestea, ele nu sunt suficient de precise și pot fi în față/întârziere în urma orei exacte. Schimbarea poate ajunge la 20 de secunde pe zi. Prin urmare, ora de pe computerele noastre trebuie sincronizată periodic cu cea de rețea.

Serverele NTP sunt folosite pentru sincronizare, dar procesul de sincronizare în sine este supus întârzierilor în rețea. Chiar și sincronizarea cu un server NTP din același centru de date durează ceva timp. Este clar că lucrul cu un server NTP public poate duce la o distorsiune și mai mare.

Ceasurile atomice și omologii lor GPS sunt mai bune pentru a determina ora curentă, dar sunt scumpe și necesită o configurare complexă, așa că nu pot fi instalate pe fiecare mașină. Din acest motiv, centrele de date folosesc o abordare pe niveluri. Ceasurile atomice și/sau GPS arată ora exactă, după care este difuzată către alte mașini prin servere secundare. Aceasta înseamnă că fiecare mașină va experimenta un anumit decalaj față de ora exactă.

Situația este agravată de faptul că aplicațiile și bazele de date sunt adesea localizate pe mașini diferite (dacă nu în centre de date diferite). Astfel, timpul va diferi nu numai pe nodurile DB distribuite pe diferite mașini. De asemenea, va fi diferit pe serverul de aplicații.

Google TrueTime adoptă o abordare complet diferită. Majoritatea oamenilor cred că progresul Google în această direcție se explică prin tranziția banală la ceasurile atomice și GPS, dar aceasta este doar o parte din imaginea de ansamblu. Iată cum funcționează TrueTime:

  • TrueTime folosește două surse diferite: GPS și ceasuri atomice. Aceste ceasuri au moduri de eroare necorelate. [vezi pagina 5 pentru detalii aici - aprox. traducere), astfel încât utilizarea lor în comun crește fiabilitatea.
  • TrueTime are un API neobișnuit. Returnează timpul ca un interval cu eroare de măsurare și incertitudine încorporate în el. Momentul real în timp se află undeva între limitele superioare și inferioare ale intervalului. Spanner, baza de date distribuită de Google, așteaptă pur și simplu până când este sigur să spună că ora actuală este în afara intervalului. Această metodă introduce o anumită latență în sistem, mai ales dacă incertitudinea asupra masterilor este mare, dar asigură corectitudinea chiar și într-o situație distribuită global.

Mai mulți dezvoltatori ar trebui să știe acest lucru despre bazele de date
Componentele Spanner folosesc TrueTime, unde TT.now() returnează un interval, astfel încât Spannerul pur și simplu dorm până în punctul în care poate fi sigur că timpul curent a depășit un anumit punct.

Precizia redusă în determinarea timpului curent înseamnă o creștere a duratei operațiunilor cheie și o scădere a performanței. De aceea este important să se mențină cea mai mare precizie posibilă, chiar dacă este imposibil să se obțină un ceas complet precis.

Întârzierea are multe semnificații

Dacă întrebi o duzină de experți despre ce este o întârziere, probabil vei primi răspunsuri diferite. În DBMS, latența este adesea numită „latența bazei de date” și este diferită de ceea ce este perceput de client. Faptul este că clientul observă suma întârzierii rețelei și a întârzierii bazei de date. Abilitatea de a izola tipul de latență este critică atunci când se depanează probleme în creștere. Când colectați și afișați valori, încercați întotdeauna să urmăriți ambele tipuri.

Cerințele de performanță ar trebui evaluate pentru o anumită tranzacție

Uneori, caracteristicile de performanță ale unui SGBD și limitările sale sunt specificate în termeni de debit de scriere/citire și latență. Aceasta oferă o prezentare generală a parametrilor cheie ai sistemului, dar atunci când se evaluează performanța unui nou SGBD, o abordare mult mai cuprinzătoare este evaluarea separată a operațiunilor critice (pentru fiecare interogare și/sau tranzacție). Exemple:

  • Scrieți debitul și latența atunci când introduceți un nou rând în tabelul X (cu 50 de milioane de rânduri) cu constrângeri specificate și umplutură de rând în tabelele asociate.
  • Întârziere în afișarea prietenilor prietenilor unui anumit utilizator atunci când numărul mediu de prieteni este de 500.
  • Latența în preluarea primelor 100 de intrări din istoricul unui utilizator atunci când utilizatorul urmărește alți 500 de utilizatori cu X intrări pe oră.

Evaluarea și experimentarea pot include astfel de cazuri critice până când sunteți sigur că baza de date îndeplinește cerințele de performanță. O regulă generală similară ia în considerare și această defalcare atunci când se colectează valorile de latență și se determină SLO-uri.

Fiți conștienți de cardinalitatea ridicată atunci când colectați valori pentru fiecare operațiune. Utilizați jurnalele, colectarea evenimentelor sau urmărirea distribuită pentru a obține date de depanare de mare putere. In articol "Doriți să depanați latența?» vă puteți familiariza cu metodologiile de depanare cu întârziere.

Tranzacțiile imbricate pot fi periculoase

Nu orice SGBD acceptă tranzacții imbricate, dar atunci când o fac, astfel de tranzacții pot duce la erori neașteptate care nu sunt întotdeauna ușor de detectat (adică ar trebui să fie evident că există un fel de anomalie).

Puteți evita utilizarea tranzacțiilor imbricate folosind biblioteci client care le pot detecta și ocoli. Dacă tranzacțiile imbricate nu pot fi abandonate, aveți grijă deosebită în implementarea lor pentru a evita situațiile neașteptate în care tranzacțiile finalizate sunt întrerupte accidental din cauza celor imbricate.

Încapsularea tranzacțiilor în diferite straturi poate duce la tranzacții imbricate neașteptate, iar din punct de vedere al lizibilității codului, poate îngreuna înțelegerea intențiilor autorului. Aruncă o privire la următorul program:

with newTransaction():
   Accounts.create("609-543-222")
   with newTransaction():
       Accounts.create("775-988-322")
       throw Rollback();

Care va fi rezultatul codului de mai sus? Va anula ambele tranzacții sau doar pe cea interioară? Ce se întâmplă dacă ne bazăm pe mai multe straturi de biblioteci care încapsulează crearea de tranzacții pentru noi? Vom putea identifica și îmbunătăți astfel de cazuri?

Imaginați-vă un strat de date cu operații multiple (de ex. newAccount) este deja implementat în propriile tranzacții. Ce se întâmplă dacă le rulați ca parte a logicii de afaceri de nivel superior care rulează în cadrul propriei tranzacții? Care ar fi izolarea și consistența în acest caz?

function newAccount(id string) {
  with newTransaction():
      Accounts.create(id)
}

În loc să cauți răspunsuri la astfel de întrebări nesfârșite, este mai bine să eviți tranzacțiile imbricate. La urma urmei, stratul dvs. de date poate efectua cu ușurință operațiuni de nivel înalt fără a-și crea propriile tranzacții. În plus, logica de afaceri în sine este capabilă să inițieze o tranzacție, să efectueze operațiuni asupra acesteia, să comite sau să anuleze o tranzacție.

function newAccount(id string) {
   Accounts.create(id)
}
// In main application:
with newTransaction():
   // Read some data from database for configuration.
   // Generate an ID from the ID service.
   Accounts.create(id)
   Uploads.create(id) // create upload queue for the user.

Tranzacțiile nu ar trebui să fie legate de starea aplicației

Uneori este tentant să folosiți starea aplicației în tranzacții pentru a modifica anumite valori sau a modifica parametrii de interogare. Nuanța critică de luat în considerare este domeniul corect de aplicare. Clienții reîncep adesea tranzacțiile atunci când există probleme de rețea. Dacă tranzacția depinde apoi de o stare care este schimbată de un alt proces, poate alege o valoare greșită, în funcție de posibilitatea unei curse de date. Tranzacțiile trebuie să ia în considerare riscul condițiilor de concurență a datelor în aplicație.

var seq int64
with newTransaction():
    newSeq := atomic.Increment(&seq)
    Entries.query(newSeq)
    // Other operations...

Tranzacția de mai sus va crește numărul de secvență de fiecare dată când este executată, indiferent de rezultatul final. Dacă commit-ul eșuează din cauza unor probleme de rețea, cererea va fi executată cu un număr de secvență diferit când încercați din nou.

Planificatorii de interogări vă pot spune multe despre o bază de date

Planificatorii de interogări determină modul în care va fi executată o interogare într-o bază de date. De asemenea, analizează cererile și le optimizează înainte de a le trimite. Planificatorii pot oferi doar câteva estimări posibile pe baza semnalelor de care dispun. De exemplu, care este cea mai bună metodă de căutare pentru următoarea interogare?

SELECT * FROM articles where author = "rakyll" order by title;

Rezultatele pot fi obținute în două moduri:

  • Scanare completă a tabelului: Puteți să vă uitați la fiecare intrare din tabel și să returnați articole cu un nume de autor potrivit, apoi să le comandați.
  • Scanarea indexului: Puteți utiliza un index pentru a găsi ID-uri care se potrivesc, pentru a obține acele rânduri și apoi pentru a le comanda.

Sarcina planificatorului de interogări este de a determina care strategie este cea mai bună. Merită să luați în considerare faptul că planificatorii de interogări au doar capacități predictive limitate. Acest lucru poate duce la decizii proaste. DBA sau dezvoltatorii le pot folosi pentru a diagnostica și a regla fin interogările cu performanțe slabe. Noile versiuni ale SGBD pot configura planificatorii de interogări, iar autodiagnoza poate ajuta la actualizarea bazei de date dacă noua versiune duce la probleme de performanță. Jurnalele de interogări lente, rapoartele privind problemele de latență sau statisticile privind timpul de execuție pot ajuta la identificarea interogărilor care necesită optimizare.

Unele valori prezentate de planificatorul de interogări pot fi supuse zgomotului (mai ales când se estimează latența sau timpul CPU). Un bun plus pentru programatori sunt instrumentele pentru urmărirea și urmărirea căii de execuție. Ele vă permit să diagnosticați astfel de probleme (din păcate, nu toate SGBD-urile oferă astfel de instrumente).

Migrarea online este dificilă, dar posibilă

Migrarea online, migrarea live sau migrarea în timp real înseamnă trecerea de la o bază de date la alta fără timp de nefuncționare sau corupție a datelor. Migrarea live este mai ușor de efectuat dacă tranziția are loc în același DBMS/motor. Situația devine mai complicată atunci când este necesară trecerea la un nou SGBD cu cerințe diferite de performanță și schemă.

Există diferite modele de migrare online. Iată una dintre ele:

  • Activați intrarea dublă în ambele baze de date. Noua bază de date în această etapă nu are toate datele, ci acceptă doar cele mai recente date. Odată ce sunteți sigur de acest lucru, puteți trece la pasul următor.
  • Activați citirea din ambele baze de date.
  • Configurați sistemul astfel încât citirea și scrierea să fie efectuate în principal pe noua bază de date.
  • Nu mai scrieți în vechea bază de date în timp ce continuați să citiți datele din ea. În această etapă, noua bază de date este încă lipsită de unele date. Acestea ar trebui copiate din vechea bază de date.
  • Vechea bază de date este doar pentru citire. Copiați datele lipsă din vechea bază de date în cea nouă. După ce migrarea este completă, comutați căile către noua bază de date și opriți-o pe cea veche și ștergeți-o din sistem.

Pentru informatii suplimentare, recomand sa contactati articol, care detaliază strategia de migrare a Stripe pe baza acestui model.

O creștere semnificativă a bazei de date implică o creștere a impredictibilității

Creșterea bazei de date duce la probleme imprevizibile asociate cu amploarea acesteia. Cu cât știm mai multe despre structura internă a unei baze de date, cu atât putem anticipa mai bine cum se va scala. Cu toate acestea, unele momente sunt încă imposibil de prevăzut.
Pe măsură ce baza crește, ipotezele și așteptările anterioare privind volumul de date și cerințele de lățime de bandă a rețelei pot deveni depășite. Acesta este momentul în care apare întrebarea privind revizuirile majore ale designului, îmbunătățirile operaționale la scară largă, regândirea implementărilor sau migrarea către alte SGBD-uri pentru a evita potențialele probleme.

Dar să nu credeți că cunoașterea excelentă a structurii interne a bazei de date existente este singurul lucru necesar. Noile scale vor aduce cu ei noi necunoscute. Punctele de durere imprevizibile, distribuția neuniformă a datelor, lățimea de bandă și problemele hardware neașteptate, traficul în continuă creștere și noile segmente de rețea vă vor obliga să vă regândiți abordarea bazei de date, modelul de date, modelul de implementare și dimensiunea bazei de date.

...

În momentul în care am început să mă gândesc la publicarea acestui articol, mai erau deja cinci articole pe lista mea inițială. Apoi a venit un număr mare idei noi despre ce altceva poate fi acoperit. Prin urmare, articolul atinge cele mai puțin evidente probleme care necesită atenție maximă. Totuși, asta nu înseamnă că subiectul a fost epuizat și nu voi mai reveni asupra lui în materialele mele viitoare și nu voi aduce modificări celui actual.

PS

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu