Flere udviklere burde vide dette om databaser

Bemærk. overs.: Jaana Dogan er en erfaren ingeniør hos Google, som i øjeblikket arbejder på observerbarhed af virksomhedens produktionstjenester skrevet i Go. I denne artikel, som vandt stor popularitet blandt det engelsktalende publikum, har hun i 17 punkter samlet vigtige tekniske detaljer vedrørende DBMS'er (og nogle gange distribuerede systemer generelt), som er nyttige at overveje for udviklere af store/krævende applikationer.

Flere udviklere burde vide dette om databaser

Langt de fleste computersystemer holder styr på deres tilstand og kræver derfor en form for datalagringssystem. Jeg opsamlede viden om databaser over en længere periode, og lavede undervejs designfejl, der førte til datatab og udfald. I systemer, der behandler store mængder information, ligger databaser i hjertet af systemarkitekturen og fungerer som et nøgleelement i valget af den optimale løsning. På trods af at der er meget opmærksomhed på databasens arbejde, er de problemer, som applikationsudviklere forsøger at forudse, ofte kun toppen af ​​isbjerget. I denne serie af artikler deler jeg nogle ideer, der vil være nyttige for udviklere, der ikke er specialiserede i dette område.

  1. Du er heldig, hvis netværket 99,999 % af tiden ikke forårsager problemer.
  2. SYRE betyder mange forskellige ting.
  3. Hver database har sine egne mekanismer til at sikre konsistens og isolation.
  4. Optimistisk blokering kommer til undsætning, når det er svært at opretholde den sædvanlige.
  5. Der er andre uregelmæssigheder udover beskidte læsninger og datatab.
  6. Databasen og brugeren er ikke altid enige om fremgangsmåden.
  7. Skæring på applikationsniveau kan flyttes uden for applikationen.
  8. Autoinkrementering kan være farligt.
  9. Forældede data kan være nyttige og behøver ikke at være låst.
  10. Forvrængninger er typiske for alle tidskilder.
  11. Forsinkelse har mange betydninger.
  12. Ydelseskrav bør evalueres for en specifik transaktion.
  13. Indlejrede transaktioner kan være farlige.
  14. Transaktioner bør ikke være bundet til ansøgningstilstand.
  15. Forespørgselsplanlæggere kan fortælle dig meget om databaser.
  16. Online migrering er vanskelig, men mulig.
  17. En markant stigning i databasen medfører en stigning i uforudsigeligheden.

Jeg vil gerne takke Emmanuel Odeke, Rein Henrichs og andre for deres feedback på en tidligere version af denne artikel.

Du er heldig, hvis netværket 99,999 % af tiden ikke forårsager problemer.

Spørgsmålet er stadig om, hvor pålidelige moderne netværksteknologier er, og hvor ofte systemerne er nede på grund af netværksfejl. Information om dette spørgsmål er sparsom, og forskning er ofte domineret af store organisationer med specialiserede netværk, udstyr og personale.

Med en tilgængelighedsgrad på 99,999 % for Spanner (Googles globalt distribuerede database) hævder Google, at kun 7,6 % problemer er relateret til netværket. Samtidig kalder virksomheden sit specialiserede netværk for "hovedsøjlen" for høj tilgængelighed. Undersøgelse Bailis og Kingsbury, udført i 2014, udfordrer en af ​​"misforståelser om distribueret databehandling", som Peter Deutsch formulerede i 1994. Er netværket virkelig pålideligt?

Omfattende forskning uden for gigantiske virksomheder, udført for det bredere internet, eksisterer simpelthen ikke. Der er heller ikke nok data fra de store aktører om, hvor stor en procentdel af deres kunders problemer, der er netværksrelaterede. Vi er godt klar over udfald i netværksstakken af ​​store cloud-udbydere, der kan tage en hel del af internettet ned i flere timer, blot fordi det er højprofilerede begivenheder, der påvirker et stort antal mennesker og virksomheder. Netværksudfald kan give problemer i mange flere tilfælde, selvom ikke alle disse sager er i søgelyset. Kunder af cloud-tjenester ved heller ikke noget om årsagerne til problemer. Hvis der er en fejl, er det næsten umuligt at tilskrive det en netværksfejl på tjenesteudbyderens side. For dem er tredjepartstjenester sorte bokse. Det er umuligt at vurdere påvirkningen uden at være en stor tjenesteudbyder.

I betragtning af, hvad de store spillere rapporterer om deres systemer, er det sikkert at sige, at du er heldig, hvis netværksproblemer kun tegner sig for en lille procentdel af potentielle nedetidsproblemer. Netværkskommunikation lider stadig af så banale ting som hardwarefejl, topologiændringer, administrative konfigurationsændringer og strømafbrydelser. For nylig blev jeg overrasket over at høre, at listen over mulige problemer blev tilføjet hajbid (ja, du hørte rigtigt).

SYRE betyder mange forskellige ting

Akronymet ACID står for Atomicity, Consistency, Isolation, Reliability. Disse egenskaber ved transaktioner har til formål at sikre deres gyldighed i tilfælde af fejl, fejl, hardwarefejl mv. Uden ACID eller lignende ordninger ville det være svært for applikationsudviklere at skelne mellem, hvad de er ansvarlige for, og hvad databasen er ansvarlig for. De fleste relationelle transaktionsdatabaser forsøger at være ACID-kompatible, men nye tilgange som NoSQL har givet anledning til mange databaser uden ACID-transaktioner, fordi de er dyre at implementere.

Da jeg først trådte ind i branchen, talte vores tekniske leder om, hvor relevant ACID-konceptet var. For at være retfærdig betragtes ACID som en grov beskrivelse snarere end en streng implementeringsstandard. I dag finder jeg det mest nyttigt, fordi det rejser en bestemt kategori af problemer (og foreslår en række mulige løsninger).

Ikke alle DBMS er ACID-kompatible; Samtidig forstår databaseimplementeringer, der understøtter ACID, kravsættet forskelligt. En af grundene til, at ACID-implementeringer er usammenhængende, skyldes de mange afvejninger, der skal foretages for at implementere ACID-kravene. Skabere kan præsentere deres databaser som ACID-kompatible, men fortolkningen af ​​kanttilfælde kan variere dramatisk, ligesom mekanismen til håndtering af "usandsynlige" hændelser vil. Udviklere kan i det mindste opnå en forståelse på højt niveau af forviklingerne ved basisimplementeringer for at opnå en ordentlig forståelse af deres særlige adfærd og design-afvejninger.

Debatten om, hvorvidt MongoDB overholder ACID-kravene, fortsætter selv efter udgivelsen af ​​version 4. MongoDB har ikke været understøttet i lang tid logning, selvom data som standard ikke blev overført til disken mere end én gang hvert 60. sekund. Forestil dig følgende scenarie: en ansøgning sender to skrifter (w1 og w2). MongoDB gemmer w1 med succes, men w2 går tabt på grund af en hardwarefejl.

Flere udviklere burde vide dette om databaser
Diagram, der illustrerer scenariet. MongoDB går ned, før den kan skrive data til disk

At binde sig til disk er en dyr proces. Ved at undgå hyppige commits forbedrer udviklere optagelsesydelsen på bekostning af pålideligheden. MongoDB understøtter i øjeblikket logning, men snavsede skrivninger kan stadig påvirke dataintegriteten, da logfiler som standard fanges hver 100 ms. Det vil sige, at et lignende scenarie stadig er muligt for logfiler og ændringerne i dem, selvom risikoen er meget lavere.

Hver database har sin egen konsistens og isolationsmekanismer

Af ACID-kravene kan konsistens og isolation prale af det største antal forskellige implementeringer, fordi rækken af ​​afvejninger er bredere. Det skal siges, at konsistens og isolation er ret dyre funktioner. De kræver koordinering og øger konkurrencen om datakonsistens. Problemets kompleksitet øges markant, når det er nødvendigt at skalere databasen horisontalt på tværs af flere datacentre (især hvis de er placeret i forskellige geografiske områder). At opnå et højt niveau af konsistens er meget vanskeligt, da det også reducerer tilgængeligheden og øger netværkssegmenteringen. For en mere generel forklaring af dette fænomen råder jeg dig til at henvise til CAP-sætning. Det er også værd at bemærke, at applikationer kan håndtere små mængder af inkonsistens, og programmører kan forstå nuancerne af problemet godt nok til at implementere yderligere logik i applikationen til at håndtere inkonsistens uden at være stærkt afhængig af databasen til at håndtere det.

DBMS'er giver ofte forskellige niveauer af isolation. Applikationsudviklere kan vælge den mest effektive baseret på deres præferencer. Lav isolation giver mulighed for øget hastighed, men øger også risikoen for et dataræs. Høj isolering reducerer denne sandsynlighed, men bremser arbejdet og kan føre til konkurrence, hvilket vil føre til sådanne bremser i basen, at fejl begynder.

Flere udviklere burde vide dette om databaser
Gennemgang af eksisterende samtidighedsmodeller og sammenhænge mellem dem

SQL-standarden definerer kun fire isolationsniveauer, selvom der i teori og praksis er mange flere. Jepson.io giver et fremragende overblik over eksisterende samtidighedsmodeller. For eksempel garanterer Google Spanner ekstern serialiserbarhed med clock-synkronisering, og selvom dette er et strengere isolationslag, er det ikke defineret i standard isolationslag.

SQL-standarden nævner følgende isolationsniveauer:

  • serializable (mest stringente og dyre): Serialiserbar udførelse har samme effekt som en sekventiel transaktionsudførelse. Sekventiel eksekvering betyder, at hver efterfølgende transaktion først begynder, efter at den foregående er afsluttet. Det skal bemærkes, at niveauet serializable ofte implementeret som såkaldt snapshot isolation (for eksempel i Oracle) på grund af forskelle i fortolkning, selvom snapshot isolation i sig selv ikke er repræsenteret i SQL standarden.
  • Gentagelige læsninger: Ikke-forpligtede poster i den aktuelle transaktion er tilgængelige for den aktuelle transaktion, men ændringer foretaget af andre transaktioner (såsom nye rækker) ikke synlig.
  • Læs engageret: Uforpligtede data er ikke tilgængelige for transaktioner. I dette tilfælde kan transaktioner kun se forpligtede data, og fantomlæsninger kan forekomme. Hvis en transaktion indsætter og forpligter nye rækker, vil den aktuelle transaktion kunne se dem, når der forespørges.
  • Læs uforpligtende (mindst strengt og dyrt niveau): Beskidte læsninger er tilladt, transaktioner kan se uforpligtede ændringer foretaget af andre transaktioner. I praksis kan dette niveau være nyttigt til grove skøn, såsom forespørgsler COUNT(*) på bordet.

Level serializable minimerer risikoen for dataræs, samtidig med at den er den dyreste at implementere og resulterer i den højeste konkurrencebelastning på systemet. Andre isolationsniveauer er nemmere at implementere, men øger sandsynligheden for dataløb. Nogle DBMS'er giver dig mulighed for at indstille et tilpasset isolationsniveau, andre har stærke præferencer, og ikke alle niveauer understøttes.

Understøttelse af isolationsniveauer annonceres ofte i en given DBMS, men kun en omhyggelig undersøgelse af dens adfærd kan afsløre, hvad der rent faktisk sker.

Flere udviklere burde vide dette om databaser
Gennemgang af samtidighedsanomalier på forskellige isolationsniveauer for forskellige DBMS'er

Martin Kleppmann i sit projekt eremitage Sammenligner forskellige isolationsniveauer, fortæller om samtidighedsanomalier, og om databasen er i stand til at overholde et bestemt isolationsniveau. Kleppmanns forskning viser, hvor forskelligt databaseudviklere tænker om isolationsniveauer.

Optimistisk blokering kommer til undsætning, når det er svært at opretholde den sædvanlige.

Blokering kan være meget dyrt, ikke kun fordi det øger konkurrencen i databasen, men også fordi det kræver, at applikationsserverne konstant opretter forbindelse til databasen. Netværkssegmentering kan forværre eksklusive låsesituationer og føre til dødvande, der er svære at identificere og løse. I tilfælde, hvor eksklusiv låsning ikke er egnet, hjælper optimistisk låsning.

Optimistisk lås er en metode, hvor den, når den læser en streng, tager højde for dens version, kontrolsum eller tidspunkt for sidste ændring. Dette giver dig mulighed for at sikre, at der ikke er nogen atomversionsændring, før du ændrer en post:

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

I dette tilfælde, opdatering af tabellen products vil ikke blive udført, hvis en anden handling tidligere har foretaget ændringer i denne række. Hvis der ikke blev udført andre handlinger på denne række, vil ændringen for en række ske, og vi kan sige, at opdateringen var vellykket.

Der er andre uregelmæssigheder udover beskidte læsninger og datatab

Når det kommer til datakonsistens, er fokus på potentialet for raceforhold, der kan føre til beskidte læsninger og tab af data. Dataanomalier stopper dog ikke der.

Et eksempel på sådanne anomalier er optagelsesforvrængning (skriv skævheder). Forvrængninger er svære at opdage, fordi der normalt ikke søges aktivt efter dem. De skyldes ikke beskidte læsninger eller tab af data, men overtrædelser af logiske begrænsninger på dataene.

Lad os f.eks. overveje en overvågningsapplikation, der kræver, at én operatør altid er på vagt:

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;

I ovenstående situation vil der opstå en rekordkorruption, hvis begge transaktioner gennemføres med succes. Selvom der ikke var nogen beskidte læsninger eller tab af data, blev dataens integritet kompromitteret: nu betragtes to personer på vagt på samme tid.

Serialiserbar isolation, skemadesign eller databasebegrænsninger kan hjælpe med at eliminere skrivekorruption. Udviklere skal være i stand til at identificere sådanne uregelmæssigheder under udvikling for at undgå dem i produktionen. Samtidig er optagelsesforvrængninger ekstremt svære at se efter i kodebasen. Især i store systemer, hvor forskellige udviklingsteams er ansvarlige for at implementere funktioner baseret på de samme tabeller og ikke er enige om detaljerne i dataadgang.

Databasen og brugeren er ikke altid enige om, hvad de skal gøre

Et af nøglefunktionerne ved databaser er garantien for udførelsesordre, men denne ordre i sig selv er muligvis ikke gennemsigtig for softwareudvikleren. Databaser udfører transaktioner i den rækkefølge, de modtages, ikke i den rækkefølge, programmører har til hensigt. Rækkefølgen af ​​transaktioner er svær at forudsige, især i højt belastede parallelle systemer.

Under udvikling, især når der arbejdes med ikke-blokerende biblioteker, kan dårlig stil og lav læsbarhed få brugere til at tro, at transaktioner udføres sekventielt, mens de faktisk kan ankomme til databasen i en hvilken som helst rækkefølge.

Ved første øjekast, i programmet nedenfor, kaldes T1 og T2 sekventielt, men hvis disse funktioner er ikke-blokerende og straks returnere resultatet i formularen løfte, så vil rækkefølgen af ​​opkald blive bestemt af de øjeblikke, hvor de kom ind i databasen:

resultat1 = T1() // reelle resultater er løfter
resultat2 = T2()

Hvis atomicitet er påkrævet (det vil sige, at enten alle operationer skal fuldføres eller afbrydes) og sekvensen har betydning, så skal operationerne T1 og T2 udføres inden for en enkelt transaktion.

Skæring på applikationsniveau kan flyttes uden for applikationen

Sharding er en metode til vandret opdeling af en database. Nogle databaser kan automatisk opdele data vandret, mens andre ikke kan eller ikke er særlig gode til det. Når dataarkitekter/udviklere er i stand til at forudsige præcis, hvordan data vil blive tilgået, kan de oprette vandrette partitioner i brugerrummet i stedet for at uddelegere dette arbejde til databasen. Denne proces kaldes "skæring på applikationsniveau" (skæring på applikationsniveau).

Desværre skaber dette navn ofte den misforståelse, at sharding lever i applikationstjenester. Faktisk kan det implementeres som et separat lag foran databasen. Afhængigt af datavækst og skemagentagelser kan sharding-kravene blive ret komplekse. Nogle strategier kan drage fordel af muligheden for at iterere uden at skulle ominstallere applikationsservere.

Flere udviklere burde vide dette om databaser
Et eksempel på en arkitektur, hvor applikationsservere er adskilt fra sharding-tjenesten

Flytning af sharding til en separat tjeneste udvider muligheden for at bruge forskellige shardingstrategier uden behov for at ominstallere applikationer. Vitess er et eksempel på et sådant skæringssystem på applikationsniveau. Vitess leverer horisontal sharding til MySQL og giver klienter mulighed for at oprette forbindelse til det via MySQL-protokollen. Systemet segmenterer dataene i forskellige MySQL-noder, der ikke ved noget om hinanden.

Autoinkrementering kan være farligt

AUTOINCREMENT er en almindelig måde at generere primærnøgler på. Der er ofte tilfælde, hvor databaser bruges som ID-generatorer, og databasen indeholder tabeller designet til at generere identifikatorer. Der er flere grunde til, at generering af primærnøgler ved hjælp af automatisk inkrementering langt fra er ideelt:

  • I en distribueret database er auto-inkrementering et alvorligt problem. For at generere ID'et kræves en global lås. I stedet kan du generere et UUID: dette kræver ikke interaktion mellem forskellige databasenoder. Auto-inkrementering med låse kan føre til uenighed og betydeligt reducere ydeevnen på skær i distribuerede situationer. Nogle DBMS'er (f.eks. MySQL) kan kræve særlig konfiguration og mere omhyggelig opmærksomhed for korrekt at organisere master-master-replikering. Og det er nemt at lave fejl, når du konfigurerer, hvilket vil føre til registreringsfejl.
  • Nogle databaser har partitioneringsalgoritmer baseret på primærnøgler. Konsekutive id'er kan føre til uforudsigelige hot spots og øget belastning på nogle partitioner, mens andre forbliver inaktive.
  • En primær nøgle er den hurtigste måde at få adgang til en række i en database. Med bedre måder at identificere poster på kan sekventielle ID'er gøre den vigtigste kolonne i tabeller til en ubrugelig kolonne fyldt med meningsløse værdier. Vælg derfor, når det er muligt, en globalt unik og naturlig primær nøgle (f.eks. brugernavn).

Før du beslutter dig for en tilgang, skal du overveje virkningen af ​​auto-incrementing ID'er og UUID'er på indeksering, partitionering og sharding.

Forældede data kan være nyttige og kræver ikke låsning

Multiversion Concurrency Control (MVCC) implementerer mange af de konsistenskrav, der kort blev diskuteret ovenfor. Nogle databaser (for eksempel Postgres, Spanner) bruger MVCC til at "føde" transaktioner med snapshots - ældre versioner af databasen. Snapshot-transaktioner kan også serialiseres for at sikre konsistens. Når du læser fra et gammelt snapshot, læses forældede data.

Det kan være nyttigt at læse lidt forældede data, for eksempel når du genererer analyser ud fra dataene eller beregner omtrentlige aggregerede værdier.

Den første fordel ved at arbejde med ældre data er lav latenstid (især hvis databasen er fordelt på tværs af forskellige geografiske områder). Den anden er, at skrivebeskyttede transaktioner er låsefri. Dette er en væsentlig fordel for applikationer, der læser meget, så længe de kan håndtere forældede data.

Flere udviklere burde vide dette om databaser
Applikationsserveren læser data fra den lokale replika, der er 5 sekunder forældet, selvom den seneste version er tilgængelig på den anden side af Stillehavet

DBMS'er renser automatisk ældre versioner og giver dig i nogle tilfælde mulighed for at gøre dette på anmodning. For eksempel tillader Postgres brugere at gøre VACUUM efter anmodning, og udfører også periodisk denne operation automatisk. Spanner driver en skraldemand for at slippe af med snapshots, der er ældre end en time.

Enhver tidskilde er genstand for forvrængning

Den bedst bevarede hemmelighed inden for datalogi er, at alle timing-API'er lyver. Faktisk kender vores maskiner ikke det nøjagtige aktuelle tidspunkt. Computere indeholder kvartskrystaller, der genererer vibrationer, der bruges til at holde tiden. De er dog ikke nøjagtige nok og kan være foran/lagre efter det nøjagtige tidspunkt. Skiftet kan nå op på 20 sekunder om dagen. Derfor skal tiden på vores computere periodisk synkroniseres med netværket.

NTP-servere bruges til synkronisering, men selve synkroniseringsprocessen er underlagt netværksforsinkelser. Selv synkronisering med en NTP-server i samme datacenter tager noget tid. Det er klart, at arbejde med en offentlig NTP-server kan føre til endnu større forvrængning.

Atomure og deres GPS-modstykker er bedre til at bestemme den aktuelle tid, men de er dyre og kræver kompleks opsætning, så de kan ikke installeres på hver bil. På grund af dette bruger datacentre en trindelt tilgang. Atom- og/eller GPS-ure viser det nøjagtige tidspunkt, hvorefter det udsendes til andre maskiner gennem sekundære servere. Det betyder, at hver maskine vil opleve en vis offset fra det nøjagtige tidspunkt.

Situationen forværres af, at applikationer og databaser ofte er placeret på forskellige maskiner (hvis ikke i forskellige datacentre). Tiden vil således ikke kun afvige på DB-noder fordelt på forskellige maskiner. Det vil også være anderledes på applikationsserveren.

Google TrueTime har en helt anden tilgang. De fleste tror, ​​at Googles fremskridt i denne retning kan forklares med den banale overgang til atom- og GPS-ure, men dette er kun en del af det store billede. Sådan fungerer TrueTime:

  • TrueTime bruger to forskellige kilder: GPS og atomure. Disse ure har ikke-korrelerede fejltilstande. [se side 5 for detaljer her - ca. oversættelse), så deres fælles brug øger pålideligheden.
  • TrueTime har en usædvanlig API. Den returnerer tid som et interval med indbygget målefejl og usikkerhed. Det aktuelle tidspunkt er et sted mellem intervallets øvre og nedre grænser. Spanner, Googles distribuerede database, venter blot, indtil det er sikkert at sige, at det aktuelle tidspunkt er uden for rækkevidde. Denne metode introducerer en vis latency i systemet, især hvis usikkerheden på masterne er høj, men sikrer korrekthed selv i en globalt distribueret situation.

Flere udviklere burde vide dette om databaser
Skruenøgle-komponenterne bruger TrueTime, hvor TT.now() returnerer et interval, så Skruenøglen sover blot indtil det punkt, hvor den kan være sikker på, at den aktuelle tid er passeret et bestemt punkt

Reduceret nøjagtighed ved bestemmelse af den aktuelle tid betyder en stigning i varigheden af ​​skruenøgle-operationer og et fald i ydeevnen. Derfor er det vigtigt at opretholde den højest mulige nøjagtighed, selvom det er umuligt at opnå et helt præcist ur.

Forsinkelse har mange betydninger

Spørger du et dusin eksperter om, hvad en forsinkelse er, vil du sandsynligvis få forskellige svar. I DBMS kaldes latency ofte "databaselatens" og er forskellig fra, hvad klienten opfatter. Faktum er, at klienten observerer summen af ​​netværksforsinkelsen og databaseforsinkelsen. Evnen til at isolere typen af ​​latenstid er afgørende, når der skal fejlsøges voksende problemer. Når du indsamler og viser metrics, skal du altid prøve at holde øje med begge typer.

Ydelseskrav bør evalueres for en specifik transaktion

Nogle gange er ydeevneegenskaberne for et DBMS og dets begrænsninger specificeret i form af skrive/læsegennemløb og latens. Dette giver et generelt overblik over centrale systemparametre, men når man evaluerer ydeevnen af ​​et nyt DBMS, er en meget mere omfattende tilgang at evaluere kritiske operationer separat (for hver forespørgsel og/eller transaktion). Eksempler:

  • Skriv gennemløb og latenstid, når du indsætter en ny række i tabel X (med 50 millioner rækker) med specificerede begrænsninger og rækkeudfyldning i relaterede tabeller.
  • Forsinkelse med at vise venners venner af en bestemt bruger, når det gennemsnitlige antal venner er 500.
  • Latency i at hente de 100 bedste poster fra en brugers historie, når brugeren følger 500 andre brugere med X poster i timen.

Evaluering og eksperimenter kan omfatte sådanne kritiske tilfælde, indtil du er sikker på, at databasen opfylder ydeevnekravene. En lignende tommelfingerregel tager også højde for denne opdeling, når der indsamles latency-metrics og SLO'er.

Vær opmærksom på høj kardinalitet, når du indsamler metrics for hver operation. Brug logfiler, hændelsesindsamling eller distribueret sporing til at opnå højeffektive fejlretningsdata. I artiklen "Vil du fejlsøge latency?» du kan sætte dig ind i delay debugging-metoder.

Indlejrede transaktioner kan være farlige

Ikke alle DBMS understøtter indlejrede transaktioner, men når de gør det, kan sådanne transaktioner resultere i uventede fejl, som ikke altid er nemme at opdage (det vil sige, at det burde være indlysende, at der er en form for anomali).

Du kan undgå at bruge indlejrede transaktioner ved at bruge klientbiblioteker, der kan registrere og omgå dem. Hvis indlejrede transaktioner ikke kan opgives, skal du være særlig omhyggelig med implementeringen af ​​dem for at undgå uventede situationer, hvor afsluttede transaktioner ved et uheld afbrydes på grund af indlejrede transaktioner.

Indkapsling af transaktioner i forskellige lag kan føre til uventede indlejrede transaktioner, og fra et kodelæsbarhedssynspunkt kan det gøre det svært at forstå forfatterens intentioner. Tag et kig på følgende program:

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

Hvad bliver outputtet af ovenstående kode? Vil det rulle begge transaktioner tilbage, eller kun den indre? Hvad sker der, hvis vi stoler på flere lag af biblioteker, der indkapsler oprettelsen af ​​transaktioner for os? Vil vi være i stand til at identificere og forbedre sådanne tilfælde?

Forestil dig et datalag med flere operationer (f.eks. newAccount) er allerede implementeret i sine egne transaktioner. Hvad sker der, hvis du kører dem som en del af forretningslogik på højere niveau, der kører inden for sin egen transaktion? Hvad ville isolationen og sammenhængen være i dette tilfælde?

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

I stedet for at søge efter svar på sådanne endeløse spørgsmål, er det bedre at undgå indlejrede transaktioner. Dit datalag kan trods alt nemt udføre operationer på højt niveau uden at oprette sine egne transaktioner. Derudover er forretningslogikken selv i stand til at starte en transaktion, udføre operationer på den, begå eller afbryde en transaktion.

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.

Transaktioner bør ikke være bundet til ansøgningstilstand

Nogle gange er det fristende at bruge applikationstilstand i transaktioner til at ændre bestemte værdier eller justere forespørgselsparametre. Den kritiske nuance at overveje er det korrekte anvendelsesområde. Klienter genstarter ofte transaktioner, når der er netværksproblemer. Hvis transaktionen så afhænger af en tilstand, der bliver ændret af en anden proces, kan den vælge den forkerte værdi afhængigt af muligheden for et dataræs. Transaktioner skal tage højde for risikoen for dataraceforhold i applikationen.

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

Ovenstående transaktion vil øge sekvensnummeret hver gang den udføres, uanset det endelige resultat. Hvis commit mislykkes på grund af netværksproblemer, vil anmodningen blive udført med et andet sekvensnummer, når du prøver igen.

Forespørgselsplanlæggere kan fortælle dig meget om en database

Forespørgselsplanlæggere bestemmer, hvordan en forespørgsel vil blive udført i en database. De analyserer også anmodninger og optimerer dem, før de sender dem. Planlæggere kan kun give nogle mulige estimater baseret på de signaler, de har til rådighed. Hvad er f.eks. den bedste søgemetode til følgende forespørgsel?

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

Resultaterne kan hentes på to måder:

  • Fuld bordscanning: Du kan se på hver post i tabellen og returnere artikler med et matchende forfatternavn og derefter bestille dem.
  • Indeks scanning: Du kan bruge et indeks til at finde matchende id'er, hente disse rækker og derefter bestille dem.

Forespørgselsplanlæggerens opgave er at bestemme, hvilken strategi der er bedst. Det er værd at overveje, at forespørgselsplanlæggere kun har begrænsede forudsigelsesmuligheder. Dette kan føre til dårlige beslutninger. DBA'er eller udviklere kan bruge dem til at diagnosticere og finjustere underpræsterende forespørgsler. Nye versioner af DBMS kan konfigurere forespørgselsplanlæggere, og selvdiagnose kan hjælpe ved opdatering af databasen, hvis den nye version fører til ydeevneproblemer. Langsomme forespørgselslogfiler, forsinkelsesproblemer eller statistik over eksekveringstid kan hjælpe med at identificere forespørgsler, der har brug for optimering.

Nogle metrics præsenteret af forespørgselsplanlæggeren kan være genstand for støj (især ved estimering af latenstid eller CPU-tid). En god tilføjelse til planlæggere er værktøjer til at spore og spore udførelsesstien. De giver dig mulighed for at diagnosticere sådanne problemer (ak, ikke alle DBMS'er giver sådanne værktøjer).

Online migrering er vanskelig, men mulig

Online-migrering, live-migrering eller migrering i realtid betyder, at du flytter fra en database til en anden uden nedetid eller datakorruption. Live migrering er nemmere at udføre, hvis overgangen sker inden for samme DBMS/motor. Situationen bliver mere kompliceret, når det er nødvendigt at flytte til et nyt DBMS med andre krav til ydeevne og skema.

Der er forskellige online migrationsmodeller. Her er en af ​​dem:

  • Aktiver dobbeltindtastning i begge databaser. Den nye database har på nuværende tidspunkt ikke alle data, men accepterer kun de seneste data. Når du er sikker på dette, kan du gå videre til næste trin.
  • Aktiver læsning fra begge databaser.
  • Konfigurer systemet, så læsning og skrivning primært udføres på den nye database.
  • Stop med at skrive til den gamle database, mens du fortsætter med at læse data fra den. På dette stadium er den nye database stadig blottet for nogle data. De skal kopieres fra den gamle database.
  • Den gamle database er skrivebeskyttet. Kopier de manglende data fra den gamle database til den nye. Når migreringen er fuldført, skal du skifte stierne til den nye database og stoppe den gamle og slette den fra systemet.

For yderligere information anbefaler jeg at kontakte artiklen, som beskriver Stripes migrationsstrategi baseret på denne model.

En markant stigning i databasen medfører en stigning i uforudsigeligheden

Væksten i databasen fører til uforudsigelige problemer forbundet med dens skala. Jo mere vi ved om den interne struktur i en database, jo bedre kan vi forudsige, hvordan den vil skalere. Nogle øjeblikke er dog stadig umulige at forudse.
Efterhånden som basen vokser, kan tidligere antagelser og forventninger vedrørende datavolumen og krav til netværksbåndbredde blive forældede. Det er, når spørgsmålet opstår om større designeftersyn, store operationelle forbedringer, nytænkende implementeringer eller migrering til andre DBMS'er for at undgå potentielle problemer.

Men tro ikke, at fremragende viden om den interne struktur i den eksisterende database er det eneste, der er nødvendigt. Nye skalaer vil bringe nye ubekendte med sig. Uforudsigelige smertepunkter, ujævn datafordeling, uventede båndbredde- og hardwareproblemer, stadigt stigende trafik og nye netværkssegmenter vil tvinge dig til at genoverveje din databasetilgang, datamodel, implementeringsmodel og databasestørrelse.

...

På det tidspunkt, hvor jeg begyndte at tænke på at udgive denne artikel, var der allerede fem elementer mere på min oprindelige liste. Så kom der et stort antal nye ideer om hvad der ellers kan dækkes. Derfor berører artiklen de mindst åbenlyse problemer, der kræver maksimal opmærksomhed. Dette betyder dog ikke, at emnet er udtømt, og jeg vil ikke længere vende tilbage til det i mine fremtidige materialer og vil ikke foretage ændringer i det nuværende.

PS

Læs også på vores blog:

Kilde: www.habr.com

Tilføj en kommentar