Opmerking. vert.: Jaana Dogan is een ervaren ingenieur bij Google en werkt momenteel aan observatieproblemen voor de productieservices van het bedrijf, geschreven in Go. In dit artikel, dat zeer populair is geworden bij Engelstalige lezers, heeft ze in 17 punten belangrijke technische details verzameld over DBMS (en soms ook over gedistribueerde systemen in het algemeen) die nuttig zijn voor ontwikkelaars van grote/veeleisende applicaties.

De overgrote meerderheid van de computersystemen houdt hun status bij en vereist daarom een vorm van gegevensopslag. Ik heb me gedurende een lange periode verdiept in databases en daarbij ontwerpfouten gemaakt die leidden tot gegevensverlies en uitval. In systemen die grote hoeveelheden informatie verwerken, vormen databases de kern van de systeemarchitectuur en een essentieel element bij het kiezen van de beste oplossing. Ondanks de grote aandacht die aan databases wordt besteed, zijn de problemen die applicatieontwikkelaars proberen te voorzien vaak slechts het topje van de ijsberg. In deze reeks artikelen deel ik een aantal ideeën die nuttig kunnen zijn voor ontwikkelaars die niet gespecialiseerd zijn in dit vakgebied.
- U heeft geluk als het netwerk in 99,999% van de gevallen niet de oorzaak van de problemen is.
- ACID heeft verschillende betekenissen.
- Elke database heeft zijn eigen mechanismen om consistentie en isolatie te garanderen.
- Optimistisch vergrendelen biedt uitkomst als het lastig is een normaal slot te onderhouden.
- Naast 'dirty reads' en gegevensverlies zijn er nog andere anomalieën.
- De database en de gebruiker zijn het niet altijd eens over de volgorde van de handelingen.
- Sharding op applicatieniveau kan buiten de applicatie worden verplaatst.
- Autoincrement kan gevaarlijk zijn.
- Verouderde gegevens kunnen nuttig zijn en hoeven mogelijk niet te worden vergrendeld.
- Vervormingen zijn kenmerkend voor elke tijdsbron.
- Vertraging heeft veel betekenissen.
- Prestatievereisten moeten per transactie worden beoordeeld.
- Geneste transacties kunnen gevaarlijk zijn.
- Transacties mogen niet aan de applicatiestatus worden gekoppeld.
- Queryplanners kunnen u veel vertellen over databases.
- Online migratie is moeilijk, maar mogelijk.
- Een aanzienlijke toename van de database brengt een grotere onvoorspelbaarheid met zich mee.
Ik wil Emmanuel Odeke, Rein Henrichs en anderen bedanken voor hun feedback op een eerdere versie van dit artikel.
Je hebt geluk als in 99,999% van de gevallen het netwerk niet de bron van de problemen is
De vraag blijft hoe betrouwbaar moderne netwerktechnologieën zijn en hoe vaak systemen uitvallen door netwerkstoringen. Informatie hierover is schaars en studies richten zich vaak op grote organisaties met gespecialiseerde netwerken, apparatuur en personeel.
Met een beschikbaarheidspercentage van 99,999% voor Spanner (de wereldwijd verspreide database van Google) beweert Google dat alleen Problemen zijn netwerkgerelateerd, waarbij het bedrijf zijn eigen netwerk de "steunpilaar" van hoge beschikbaarheid noemt. De studie , uitgevoerd in 2014, daagt een van de "", die Peter Deutsch in 1994 formuleerde. Is het netwerk echt betrouwbaar?
Er is simpelweg geen uitgebreid onderzoek buiten de giganten naar het bredere internet. Er zijn ook weinig gegevens van de grote spelers over welk percentage van de problemen van hun klanten netwerkgerelateerd is. We zijn ons terdege bewust van storingen in de netwerkstack van grote cloudproviders, die een heel deel van het internet urenlang kunnen platleggen, simpelweg omdat dit de meest opvallende gebeurtenissen zijn die een groot aantal mensen en bedrijven treffen. Netwerkstoringen zijn in veel meer gevallen de bron van problemen, ook al halen ze niet allemaal de krantenkoppen. Cloudklanten tasten ook in het duister over de oorzaak van de problemen. Wanneer er een storing optreedt, is het bijna onmogelijk om deze toe te schrijven aan een netwerkfout bij de serviceprovider. Voor hen zijn diensten van derden black boxes. Het is onmogelijk om de impact in te schatten zonder een grote serviceprovider te zijn.
Gezien wat de grote spelers over hun systemen melden, mag je van geluk spreken als netwerkproblemen slechts een klein percentage van de potentiële downtime uitmaken. Netwerkcommunicatie heeft nog steeds last van alledaagse problemen zoals hardwarestoringen, topologiewijzigingen, wijzigingen in de beheerconfiguratie en stroomuitval. Tot mijn verbazing hoorde ik onlangs dat de lijst met potentiële problemen is uitgebreid met: (ja, je hoort het goed).
ACID heeft verschillende betekenissen.
De afkorting ACID staat voor atomiciteit, consistentie, isolatie en duurzaamheid. Deze transactie-eigenschappen zijn ontworpen om hun validiteit te garanderen in geval van crashes, fouten, hardwarestoringen en dergelijke. Zonder ACID of vergelijkbare systemen zouden applicatieontwikkelaars moeite hebben om onderscheid te maken tussen hun verantwoordelijkheidsgebied en waarvoor de database verantwoordelijk is. De meeste relationele transactionele databases proberen ACID-compatibel te zijn, maar nieuwe benaderingen zoals NoSQL hebben veel databases zonder ACID-transacties voortgebracht, omdat ze duur zijn om te implementeren.
Toen ik net in de branche kwam, besprak onze technisch leider de relevantie van het ACID-concept. Om eerlijk te zijn, ACID wordt beschouwd als een ruwe richtlijn, geen strikte implementatiestandaard. Tegenwoordig vind ik het vooral nuttig omdat het een specifieke categorie vragen oproept (en een scala aan mogelijke oplossingen suggereert).
Niet elk DBMS is ACID-compatibel en ACID-compatibele database-implementaties hanteren verschillende interpretaties van de vereisten. Een reden voor de heterogeniteit van de ACID-implementatie zijn de vele afwegingen die moeten worden gemaakt om te voldoen aan de ACID-vereisten. Databaseleveranciers presenteren hun databases weliswaar als ACID-compatibel, maar hun interpretatie van randgevallen kan aanzienlijk verschillen, evenals het mechanisme voor de afhandeling van "onwaarschijnlijke" gebeurtenissen. Ontwikkelaars kunnen op zijn minst de complexiteit van database-implementaties op een hoog niveau begrijpen om een goed begrip te krijgen van hun specifieke gedrag en ontwerpafwegingen.
Het debat over hoe ACID-compatibel MongoDB is, gaat zelfs na de release van versie 4 door. MongoDB ondersteunde niet , hoewel de gegevens standaard niet vaker dan eens per 60 seconden naar schijf werden geschreven. Stel je het volgende scenario voor: de applicatie voert twee schrijfbewerkingen uit (w1 en w2). MongoDB slaat w1 succesvol op, maar w2 gaat verloren door een hardwarestoring.

Diagram dat het scenario illustreert: MongoDB crasht voordat er gegevens naar schijf kunnen worden geschreven
Vastleggen op schijf is een duur proces. Door frequente commits te vermijden, verbeteren ontwikkelaars de schrijfprestaties ten koste van de betrouwbaarheid. MongoDB ondersteunt momenteel journaling, maar dirty writes kunnen de data-integriteit nog steeds beïnvloeden, omdat journals standaard elke 100 ms worden vastgelegd. Dit betekent dat een vergelijkbaar scenario nog steeds mogelijk is voor journals en de wijzigingen die ze vertegenwoordigen, hoewel het risico veel lager is.
Elke database heeft zijn eigen mechanismen om consistentie en isolatie te garanderen.
Van de ACID-vereisten hebben consistentie en isolatie de meeste verschillende implementaties, omdat het spectrum aan afwegingen breder is. Het is belangrijk om op te merken dat consistentie en isolatie vrij dure functies zijn. Ze vereisen coördinatie en verhogen de concurrentie om dataconsistentie te waarborgen. De complexiteit van het probleem neemt aanzienlijk toe wanneer de database horizontaal moet worden geschaald over meerdere datacenters (vooral als deze zich in verschillende geografische regio's bevinden). Het bereiken van een hoge mate van consistentie is zeer lastig, omdat de beschikbaarheid afneemt en de netwerksegmentatie toeneemt. Voor een meer algemene uitleg van dit fenomeen raad ik aan om te verwijzen naar Het is ook belangrijk om te weten dat applicaties kleine inconsistenties kunnen verwerken en dat programmeurs de nuances van het probleem goed genoeg begrijpen om extra logica in de applicatie te implementeren om de inconsistenties te verwerken, zonder dat ze hiervoor al te afhankelijk zijn van de database.
DBMS'en bieden vaak verschillende isolatieniveaus. Applicatieontwikkelaars kunnen op basis van hun voorkeuren de meest effectieve kiezen. Lage isolatie zorgt voor hogere snelheid, maar verhoogt tegelijkertijd het risico op dataraces. Hoge isolatie vermindert dit risico, maar vertraagt de werkzaamheden en kan leiden tot conflicten, waardoor de database tot het punt van falen kan worden vertraagd.

Een overzicht van bestaande parallelismemodellen en de relaties daartussen
De SQL-standaard definieert slechts vier isolatieniveaus, hoewel het aantal in theorie en praktijk veel groter is. Biedt een uitstekend overzicht van bestaande gelijktijdigheidsmodellen. Zo garandeert Google Spanner externe serialiseerbaarheid met kloksynchronisatie. Hoewel dit een sterkere isolatielaag is, is deze niet gedefinieerd in de standaard isolatielagen.
De SQL-standaard vermeldt de volgende isolatieniveaus:
- serializable (de meest strikte en duurste): geserialiseerde uitvoering heeft een effect dat vergelijkbaar is met een sequentiële uitvoering van transacties. Sequentiële uitvoering betekent dat elke volgende transactie pas begint nadat de vorige volledig is voltooid. Opgemerkt dient te worden dat het niveau serializable Vaak geïmplementeerd als zogenaamde snapshot-isolatie (bijv. in Oracle) vanwege verschillen in interpretatie, hoewel snapshot-isolatie zelf niet is opgenomen in de SQL-standaard.
- Herhaalbare lezingen: Niet-vastgelegde records in de huidige transactie zijn beschikbaar voor de huidige transactie, maar wijzigingen die door andere transacties zijn aangebracht (zoals nieuwe rijen) .
- Lees toegewijd: Niet-vastgelegde gegevens zijn niet toegankelijk voor transacties. In dit geval kunnen transacties alleen vastgelegde gegevens zien en kunnen er spookleesbewerkingen plaatsvinden. Als een transactie nieuwe rijen invoegt en vastlegt, kan de huidige transactie deze zien wanneer deze een query uitvoert.
- Lees niet-vastgelegd (minst beperkende en dure niveau): dirty reads zijn toegestaan, transacties kunnen ongecommitteerde wijzigingen van andere transacties zien. In de praktijk kan dit niveau nuttig zijn voor ruwe schattingen, zoals query's.
COUNT(*)op tafel.
Niveau serializable Minimaliseert het risico op dataraces, maar is het duurst om te implementeren en resulteert in de hoogste gelijktijdige belasting van het systeem. Andere isolatieniveaus zijn eenvoudiger te implementeren, maar verhogen de kans op dataraces. Sommige DBMS'en bieden de mogelijkheid om een aangepast isolatieniveau te specificeren, terwijl andere sterke voorkeuren hebben en niet alle niveaus ondersteunen.
Ondersteuning voor isolatieniveaus wordt vaak aangekondigd in een DBMS, maar alleen door het gedrag ervan zorgvuldig te onderzoeken, kunt u achterhalen wat er daadwerkelijk gebeurt.

Overzicht van gelijktijdigheidsanomalieën op verschillende isolatieniveaus voor verschillende DBMS
Martin Kleppmann in zijn project Vergelijkt verschillende isolatieniveaus, bespreekt gelijktijdigheidsafwijkingen en of een database zich aan een bepaald isolatieniveau kan houden. Kleppmanns onderzoek laat zien hoe verschillend databaseontwikkelaars over isolatieniveaus denken.
Optimistisch vergrendelen komt te hulp als het moeilijk is om een regelmatig vergrendelsysteem te handhaven
Vergrendeling kan erg duur zijn, niet alleen omdat het de concurrentie in de database vergroot, maar ook omdat het een constante verbinding van applicatieservers met de database vereist. Netwerksegmentatie kan de situatie met exclusieve vergrendelingen verergeren en leiden tot deadlocks die moeilijk te identificeren en op te lossen zijn. In gevallen waar exclusieve vergrendeling niet geschikt is, helpt optimistische vergrendeling.
— is een methode waarbij rekening wordt gehouden met de versie, checksum of het tijdstip van de laatste wijziging van een rij bij het lezen ervan. Zo kunt u ervoor zorgen dat er geen atomaire versiewijziging plaatsvindt voordat u de record wijzigt:
UPDATE products
SET name = 'Telegraph receiver', version = 2
WHERE id = 1 AND version = 1 In dit geval het bijwerken van de tabel products Wordt niet uitgevoerd als er eerder een andere bewerking in deze rij is uitgevoerd. Als er geen andere bewerkingen op deze rij zijn uitgevoerd, wordt de wijziging voor één rij doorgevoerd en kunnen we zeggen dat de update succesvol is uitgevoerd.
Naast 'dirty reads' en gegevensverlies zijn er nog andere anomalieën.
Bij dataconsistentie ligt de nadruk op de kans op race-omstandigheden die kunnen leiden tot dirty reads en dataverlies. Data-anomalieën beperken zich echter niet tot deze categorie.
Een voorbeeld van dergelijke afwijkingen is vervorming van de opname. (schrijf scheef)Corrupties zijn moeilijk te detecteren omdat er doorgaans niet actief naar wordt gezocht. Ze worden niet veroorzaakt door dirty reads of dataverlies, maar door schendingen van logische beperkingen die aan de data zijn opgelegd.
Laten we bijvoorbeeld eens kijken naar een bewakingstoepassing waarbij er altijd één operator bereikbaar moet zijn:
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;In de bovenstaande situatie zou er een recordcorruptie optreden als beide transacties succesvol zouden worden uitgevoerd. Hoewel er geen dirty reads of dataverlies optraden, was de integriteit van de gegevens wel in gevaar: nu worden twee personen tegelijkertijd als oproepbaar beschouwd.
Serialiseerbare isolatie, schemaontwerp of databasebeperkingen kunnen schrijfcorruptie helpen voorkomen. Ontwikkelaars moeten dergelijke anomalieën tijdens de ontwikkeling kunnen identificeren om ze in productie te voorkomen. Schrijfcorrupties zijn echter extreem moeilijk te vinden in een codebase. Dit geldt met name voor grote systemen waar verschillende ontwikkelteams verantwoordelijk zijn voor de implementatie van functies op basis van dezelfde tabellen en de details voor gegevenstoegang niet coördineren.
De database en de gebruiker zijn het niet altijd eens over de volgorde van de handelingen.
Een van de belangrijkste kenmerken van databases is dat ze de uitvoeringsvolgorde garanderen, maar deze volgorde is mogelijk niet transparant voor de softwareontwikkelaar. Databases voeren transacties uit in de volgorde waarin ze worden ontvangen, niet in de volgorde die programmeurs voor ogen hebben. De uitvoeringsvolgorde van transacties is moeilijk te voorspellen, vooral in zwaar belaste parallelle systemen.
Tijdens de ontwikkeling, met name bij het werken met niet-blokkerende bibliotheken, kunnen slechte stijl en slechte leesbaarheid ertoe leiden dat gebruikers denken dat transacties sequentieel worden uitgevoerd, terwijl ze in werkelijkheid in willekeurige volgorde kunnen binnenkomen.
Op het eerste gezicht worden T1 en T2 in het onderstaande programma sequentieel aangeroepen, maar als deze functies niet-blokkerend zijn en het resultaat onmiddellijk als volgt retourneren: , dan wordt de volgorde van de aanroep bepaald door de momenten waarop ze de database binnenkomen:
result1 = T1() // werkelijke resultaten zijn beloften
resultaat2 = T2()
Als atomariteit vereist is (dat wil zeggen dat alle bewerkingen moeten worden voltooid of afgebroken) en sequentie van belang is, moeten de bewerkingen T1 en T2 binnen één enkele transactie worden uitgevoerd.
Sharding op applicatieniveau kan buiten de applicatie worden verplaatst
Sharding is een manier om een database horizontaal te partitioneren. Sommige databases kunnen gegevens automatisch horizontaal partitioneren, terwijl andere dat niet of niet goed kunnen. Wanneer data-architecten/ontwikkelaars kunnen voorspellen hoe gegevens worden benaderd, kunnen ze horizontale partities in de gebruikersruimte creëren in plaats van dit werk te delegeren aan de database. Dit proces wordt 'sharding op applicatieniveau' genoemd. (sharding op applicatieniveau).
Helaas wekt deze naam vaak de misvatting dat sharding zich afspeelt in de applicatieservices. Het kan echter als een aparte laag vóór de database worden geïmplementeerd. Afhankelijk van de groei van de data en de iteraties van het ontwerp kunnen de vereisten voor sharding behoorlijk complex worden. Sommige strategieën kunnen baat hebben bij de mogelijkheid om te itereren zonder de applicatieservers opnieuw te hoeven implementeren.

Een voorbeeldarchitectuur waarbij applicatieservers gescheiden zijn van de shardingservice
Door sharding naar een aparte service te verplaatsen, vergroot u de mogelijkheden voor het gebruiken van verschillende shardingstrategieën zonder dat u applicaties opnieuw hoeft te implementeren. — een voorbeeld van zo'n shardingsysteem op applicatieniveau. Vitess biedt horizontale sharding voor MySQL en stelt clients in staat er verbinding mee te maken via het MySQL-protocol. Het systeem segmenteert gegevens over verschillende MySQL-knooppunten die niets van elkaar weten.
Auto-increment kan gevaarlijk zijn
AUTOINCREMENT is een veelgebruikte manier om primaire sleutels te genereren. Het is niet ongebruikelijk om databases te gebruiken als ID-generators, en de database bevat tabellen die ontworpen zijn om identifiers te genereren. Er zijn verschillende redenen waarom het genereren van primaire sleutels met behulp van automatische ophoging verre van ideaal is:
- In een gedistribueerde database vormt automatisch ophogen een ernstig probleem. Het genereren van een ID vereist een globale vergrendeling. Het genereren van een UUID vereist daarentegen geen interactie tussen verschillende databaseknooppunten. Automatisch ophogen met vergrendelingen kan leiden tot conflicten en de invoegprestaties in gedistribueerde situaties aanzienlijk verminderen. Sommige DBMS'en (bijv. MySQL) vereisen mogelijk speciale afstemming en meer zorgvuldige aandacht om master-master-replicatie correct te organiseren. Bovendien is het gemakkelijk om een fout in de configuratie te maken, wat leidt tot schrijffouten.
- Sommige databases gebruiken partitioneringsalgoritmen op basis van primaire sleutels. Sequentiële ID's kunnen leiden tot onvoorspelbare hotspots en een verhoogde belasting op sommige partities, terwijl andere inactief blijven.
- Een primaire sleutel is de snelste manier om toegang te krijgen tot een rij in een database. Met betere manieren om records te identificeren, kunnen sequentiële ID's de belangrijkste kolom in uw tabellen veranderen in een nutteloze kolom vol betekenisloze waarden. Kies daarom, waar mogelijk, een wereldwijd unieke en natuurlijke primaire sleutel (bijv. gebruikersnaam).
Voordat u een aanpak kiest, moet u rekening houden met de impact van automatisch verhoogde ID's en UUID's op indexering, partitionering en sharding.
Verouderde gegevens kunnen nuttig zijn en vereisen geen vergrendeling
Multi-version concurrency control (MVCC) implementeert veel van de hierboven kort besproken consistentievereisten. Sommige databases (bijv. Postgres, Spanner) gebruiken MVCC om transactiesnapshots te voeden – oudere versies van de database. Transacties met snapshots kunnen ook worden geserialiseerd om consistentie te garanderen. Het lezen van een oude snapshot leest verouderde gegevens.
Het kan nuttig zijn om licht verouderde gegevens te lezen, bijvoorbeeld om analyses uit de gegevens te maken of geschatte totale waarden te berekenen.
Het eerste voordeel van het werken met verouderde data is de lage latentie (vooral als de database over verschillende geografische gebieden verspreid is). Het tweede voordeel is dat read-only transacties geen locks hebben. Dit is een aanzienlijk voordeel voor applicaties die veel lezen, mits verouderde data natuurlijk acceptabel is.

De applicatieserver leest gegevens van een lokale replica die 5 seconden verouderd is, zelfs als de nieuwste versie beschikbaar is aan de andere kant van de Stille Oceaan.
DBMS'en verwijderen automatisch oude versies en in sommige gevallen is dit op verzoek mogelijk. Postgres biedt gebruikers bijvoorbeeld de mogelijkheid om: VACUUM op aanvraag en voert deze bewerking ook periodiek automatisch uit. Spanner gebruikt een garbage collector om snapshots ouder dan een uur te verwijderen.
Elke tijdbron is onderhevig aan vervorming.
Het best bewaarde geheim in de informatica is dat API's voor alle tijden liegen. Sterker nog, onze machines kennen de exacte tijd niet. Computers bevatten kwartskristallen die oscillaties genereren die worden gebruikt om de tijd bij te houden. Ze zijn echter niet nauwkeurig genoeg en kunnen voor- of achterlopen op de exacte tijd. De afwijking kan oplopen tot wel 20 seconden per dag. Daarom moet de tijd op onze computers periodiek worden gesynchroniseerd met de netwerktijd.
NTP-servers worden gebruikt voor synchronisatie, maar het synchronisatieproces zelf is onderhevig aan netwerkvertragingen. Zelfs synchronisatie met een NTP-server in hetzelfde datacenter kost tijd. Het is duidelijk dat werken met een openbare NTP-server tot nog grotere verstoringen kan leiden.
Atoomklokken en hun GPS-tegenhangers zijn beter in het bepalen van de huidige tijd, maar ze zijn duur en vereisen een complexe installatie, waardoor ze niet op elke machine geïnstalleerd kunnen worden. Daarom gebruiken datacenters een gelaagde aanpak. Atoom- en/of GPS-klokken geven de exacte tijd aan, die vervolgens via secundaire servers naar andere machines wordt verzonden. Dit betekent dat elke machine een afwijking van de exacte tijd ondervindt.
De situatie wordt verergerd door het feit dat applicaties en databases zich vaak op verschillende machines bevinden (of zelfs in verschillende datacenters). De tijd zal dus niet alleen verschillen op de databaseknooppunten die over verschillende machines zijn verdeeld, maar ook op de applicatieserver.
Google TrueTime hanteert een heel andere aanpak. De meeste mensen schrijven Googles vooruitgang in deze richting toe aan de simpele overstap naar atoom- en gps-klokken, maar dat is slechts een deel van het verhaal. Zo werkt TrueTime:
- TrueTime gebruikt twee verschillende bronnen: GPS en atoomklokken. Deze klokken hebben een niet-gecorreleerde storingsmodus. [zie details op pagina 5 — ca. vert.), waardoor het gecombineerde gebruik de betrouwbaarheid verhoogt.
- TrueTime heeft een ongebruikelijke API. Deze retourneert de tijd als een interval met een ingebouwde meetfout en onzekerheid. De werkelijke tijd ligt ergens tussen de boven- en ondergrens van het interval. Spanner, de gedistribueerde database van Google, wacht simpelweg op het moment waarop met zekerheid kan worden gesteld dat de huidige tijd buiten het interval valt. Deze methode introduceert enige latentie in het systeem, vooral als de onzekerheid op de masters hoog is, maar garandeert correctheid, zelfs in een wereldwijd gedistribueerde situatie.

Spanner-componenten maken gebruik van TrueTime, waarbij TT.now() een interval retourneert, zodat Spanner gewoon in slaap valt totdat het er zeker van is dat de huidige tijd een bepaald punt is gepasseerd.
Een lagere nauwkeurigheid van de huidige tijd betekent langere Spanner-bewerkingen en lagere prestaties. Daarom is het belangrijk om de hoogst mogelijke nauwkeurigheid te behouden, ook al is het onmogelijk om een perfect nauwkeurige klok te hebben.
Vertraging heeft veel betekenissen
Vraag een dozijn mensen wat latentie is en je krijgt waarschijnlijk verschillende antwoorden. In een DBMS wordt latentie vaak "databaselatentie" genoemd en verschilt het van wat de client waarneemt. Dat komt doordat de client de som ziet van de netwerklatentie en de databaselatentie. Het kunnen isoleren van het type latentie is cruciaal bij het debuggen van escalerende problemen. Probeer altijd beide typen te monitoren bij het verzamelen en weergeven van statistieken.
Prestatievereisten moeten op transactiespecifieke basis worden beoordeeld.
Soms worden de prestatiekenmerken en -beperkingen van een DBMS gespecificeerd in termen van schrijf-/leessnelheid en latentie. Dit geeft een algemeen idee van de belangrijkste parameters van het systeem, maar bij het evalueren van de prestaties van een nieuw DBMS is een veel uitgebreidere aanpak om kritieke bewerkingen afzonderlijk te evalueren (voor elke query en/of transactie). Voorbeelden:
- Schrijfsnelheid en latentie voor het invoegen van een nieuwe rij in tabel X (met 50 miljoen rijen) met gegeven beperkingen en het vullen van rijen in gerelateerde tabellen.
- Vertraging bij het weergeven van vrienden van vrienden van een gebruiker wanneer het gemiddelde aantal vrienden 500 is.
- Vertraging bij het ophalen van de top 100 vermeldingen uit de gebruikersgeschiedenis wanneer de gebruiker 500 andere gebruikers volgt met X vermeldingen per uur.
Evaluatie en experimenten kunnen dergelijke kritieke gevallen omvatten totdat u er zeker van bent dat de database aan de prestatievereisten voldoet. Een vergelijkbare vuistregel houdt ook rekening met deze indeling bij het verzamelen van latentiegegevens en het definiëren van SLO's.
Houd rekening met een hoge kardinaliteit bij het verzamelen van metrische gegevens voor elke bewerking. Gebruik logboeken, gebeurtenisverzameling of gedistribueerde tracering om debuggegevens met een hoge kardinaliteit te verkrijgen. In het artikel «» kunt u meer lezen over methodologieën voor het opsporen van vertragingen.
Geneste transacties kunnen gevaarlijk zijn
Niet elk DBMS ondersteunt geneste transacties. Als dat wel het geval is, kunnen zulke transacties leiden tot onverwachte fouten die niet altijd eenvoudig te detecteren zijn (het zou bijvoorbeeld duidelijk moeten zijn dat er sprake is van een afwijking).
U kunt geneste transacties vermijden door clientbibliotheken te gebruiken die ze kunnen detecteren en omzeilen. Als u geneste transacties niet kunt vermijden, besteed dan speciale aandacht aan de implementatie ervan om onverwachte situaties te voorkomen waarin voltooide transacties per ongeluk worden onderbroken door geneste transacties.
Het inkapselen van transacties in verschillende lagen kan leiden tot onverwachte geneste transacties, en vanuit het oogpunt van leesbaarheid van de code kan het moeilijk zijn om de bedoelingen van de auteur te begrijpen. Neem bijvoorbeeld het volgende programma:
with newTransaction():
Accounts.create("609-543-222")
with newTransaction():
Accounts.create("775-988-322")
throw Rollback();Wat zal het resultaat zijn van bovenstaande code? Zal het beide transacties terugdraaien, of alleen de interne? Wat gebeurt er als we vertrouwen op meerdere lagen bibliotheken die de transactiecreatie voor ons inkapselen? Kunnen we dergelijke gevallen detecteren en verbeteren?
Stel je een gegevenslaag voor met meerdere bewerkingen (bijv. newAccount) zijn al geïmplementeerd in hun eigen transacties. Wat gebeurt er als je ze uitvoert binnen een hogere bedrijfslogica die binnen een eigen transactie draait? Wat zijn in dit geval de isolatie- en consistentieproblemen?
function newAccount(id string) {
with newTransaction():
Accounts.create(id)
}In plaats van te proberen zulke eindeloze vragen te beantwoorden, is het beter om geneste transacties te vermijden. Uw datalaag kan immers eenvoudig bewerkingen op hoog niveau uitvoeren zonder eigen transacties te creëren. Bovendien kan de bedrijfslogica zelf een transactie initiëren, er bewerkingen op uitvoeren, een transactie vastleggen of afbreken.
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.Transacties mogen niet aan de applicatiestatus worden gekoppeld.
Het is soms verleidelijk om de applicatiestatus in transacties te gebruiken om bepaalde waarden te wijzigen of queryparameters aan te passen. Een kritische nuance hierbij is de juiste scope. Clients proberen transacties vaak opnieuw wanneer er een netwerkprobleem is. Als een transactie afhankelijk is van een status die door een ander proces wordt gewijzigd, kan deze de verkeerde waarde kiezen, afhankelijk van de mogelijkheid van een datarace. Transacties moeten zich bewust zijn van het risico van dataracecondities in de applicatie.
var seq int64
with newTransaction():
newSeq := atomic.Increment(&seq)
Entries.query(newSeq)
// Other operations...De bovenstaande transactie verhoogt het volgnummer bij elke uitvoering, ongeacht het eindresultaat. Als de commit mislukt vanwege netwerkproblemen, wordt de aanvraag bij een nieuwe poging met een ander volgnummer uitgevoerd.
Queryplanners kunnen u veel vertellen over uw database
Queryplanners bepalen hoe een query in een database wordt uitgevoerd. Ze analyseren en optimaliseren query's ook voordat ze worden verzonden. Planners kunnen slechts enkele mogelijke schattingen geven op basis van de signalen waarover ze beschikken. Wat is bijvoorbeeld de meest optimale zoekmethode voor de volgende query?
SELECT * FROM articles where author = "rakyll" order by title;De resultaten kunnen op twee manieren worden verkregen:
- Volledige tabelscan: U kunt naar elke record in de tabel kijken en artikelen met een overeenkomende auteursnaam retourneren, en ze vervolgens ordenen.
- Indexscan:U kunt de index gebruiken om de bijbehorende ID's te vinden, de rijen op te halen en ze vervolgens te ordenen.
De taak van de queryplanner is om te bepalen welke strategie het beste is. Het is belangrijk om op te merken dat queryplanners beperkte voorspellende mogelijkheden hebben. Dit kan leiden tot slechte beslissingen. Databasebeheerders of -ontwikkelaars kunnen ze gebruiken om inefficiënte query's te diagnosticeren en te verfijnen. Nieuwere versies van het DBMS kunnen queryplanners optimaliseren en zelfdiagnostiek kan helpen bij het upgraden van de database als de nieuwe versie prestatieproblemen veroorzaakt. Langzame querylogs, meldingen over latentieproblemen of statistieken over de uitvoeringstijd kunnen helpen bij het identificeren van query's die optimalisatie behoeven.
Sommige statistieken die de queryplanner levert, kunnen onderhevig zijn aan ruis (met name bij het evalueren van latentie of CPU-tijd). Een goede aanvulling op planners zijn tools voor tracering en uitvoeringspadtracering. Hiermee kunt u dergelijke problemen diagnosticeren (helaas bieden niet alle DBMS'en dergelijke tools).
Online migratie is moeilijk, maar mogelijk
Online migratie, live migratie of realtime migratie betekent dat u van de ene database naar de andere migreert zonder downtime of datacorruptie. Live migratie is eenvoudiger uit te voeren als de migratie binnen dezelfde DBMS/engine plaatsvindt. De situatie wordt complexer bij de migratie naar een nieuwe DBMS met andere prestatie- en schemavereisten.
Er bestaan verschillende modellen voor online migratie. Hier is er één van:
- Schakel dubbele invoer in beide databases in. De nieuwe database bevat in dit stadium nog niet alle gegevens, maar accepteert alleen nieuwe gegevens. Zodra u hier zeker van bent, kunt u doorgaan naar de volgende stap.
- Schakel lezen uit beide databases in.
- Configureer het systeem zo dat er eerst naar de nieuwe database wordt gelezen en geschreven.
- Stop met schrijven naar de oude database, maar blijf gegevens uit de database lezen. Op dit moment ontbreken er nog gegevens in de nieuwe database. Deze moeten uit de oude database worden gekopieerd.
- De oude database is alleen-lezen. Kopieer de ontbrekende gegevens van de oude database naar de nieuwe. Nadat de migratie is voltooid, wijzigt u de paden naar de nieuwe database en stopt en verwijdert u de oude database uit het systeem.
Voor meer informatie raad ik u aan contact op te nemen waarin de migratiestrategie van Stripe op basis van dit model wordt beschreven.
Een aanzienlijke toename van de database brengt een grotere onvoorspelbaarheid met zich mee
Het laten groeien van een database brengt onvoorspelbare problemen met zich mee die verband houden met de schaal. Hoe meer we weten over de interne structuur van een database, hoe beter we kunnen voorspellen hoe deze zal schalen. Sommige problemen zijn echter nog steeds onmogelijk te voorspellen.
Naarmate de database groeit, kunnen eerdere aannames en verwachtingen over datavolume en netwerkbandbreedtevereisten achterhaald raken. In dat geval is het noodzakelijk om grote ontwerpwijzigingen, grootschalige operationele verbeteringen, heroverweging van implementaties of migratie naar andere DBMS'en te overwegen om potentiële problemen te voorkomen.
Maar denk niet dat een grondige kennis van de interne werking van uw bestaande database voldoende is. Nieuwe schaalgrootte brengt nieuwe onbekende factoren met zich mee. Onvoorspelbare knelpunten, ongelijkmatige datadistributie, onverwachte bandbreedte- en hardwareproblemen, steeds toenemend dataverkeer en nieuwe segmenten in het netwerk dwingen u om uw databaseaanpak, datamodel, implementatiemodel en omvang te heroverwegen.
...
Toen ik begon na te denken over het publiceren van dit artikel, bevatte mijn eerste lijst al vijf items. Daarna volgde een enorm aantal over wat er nog meer behandeld kan worden. Daarom behandelt het artikel de minst voor de hand liggende problemen die maximale aandacht vereisen. Dit betekent echter niet dat het onderwerp is uitgeput en ik zal er in mijn toekomstige materiaal niet op terugkomen en geen wijzigingen aanbrengen in het huidige materiaal.
PS
Lees ook op onze blog:
- «";
- «";
- «.
Bron: www.habr.com
