Det finns många artiklar online som beskriver databasskalbarhetsmönster, men de är mestadels spridda artiklar - bara tekniker som definieras på måfå utan mycket sammanhang. Jag tycker att de inte definieras steg för steg och diskuterar inte när man ska välja vilket skalningsalternativ, vilka skalningsalternativ som är genomförbara i praktiken och varför.
Därför planerar jag att diskutera några av teknikerna i detalj i framtida artiklar. Till att börja med tycker jag att det är bättre om jag diskuterar steg för steg-tekniker med något sammanhang på mitt eget sätt. Den här artikeln är en artikel på hög nivå - jag kommer inte att diskutera skalningstekniker i detalj här, men kommer att ge en översikt. Så låt oss komma igång.
En fallstudie
Anta att du har byggt en startup som erbjuder samåkning till en billig kostnad. Initialt när du börjar riktar du dig mot en stad och har knappt tiotals kunder efter din första annons.
Du sparar alla kunder, resor, platser, bokningsdata och kundresehistorik i samma databas eller troligen i en enda fysisk maskin. Det finns ingen fancy caching eller big data pipeline för att lösa problem eftersom din app är väldigt ny. Detta är perfekt för ditt användningsfall just nu eftersom det finns väldigt få kunder och ditt system till exempel knappt bokar en resa på 5 minuter.
Men allt eftersom börjar fler människor registrera sig i ditt system eftersom du är den billigaste tjänsten på marknaden och tack vare din marknadsföring och dina annonser. Du börjar boka, säg, 10 bokningar per minut, och sakta ökar antalet till 20, 30 bokningar per minut.
Vid denna tidpunkt inser du att systemet har börjat fungera dåligt:API-latens har ökat mycket, och vissa transaktioner låser sig eller svälter och så småningom misslyckas de. Din app tar längre tid att svara, vilket orsakar kundernas missnöje. Vad kan du göra för att lösa problemet?
Mönster 1 - Frågeoptimering och implementering av anslutningspool:
Den första lösningen som kommer i åtanke är att cachen ofta använder icke-dynamisk data som bokningshistorik, betalningshistorik, användarprofiler och så vidare. Men efter den här cachelagringen av applikationslagret kan du inte lösa latensproblemet med API:er som exponerar dynamisk data som den aktuella förarens plats eller närmaste hytter för en given kund eller aktuell reskostnad vid en viss tidpunkt efter att resan har börjat.
Du identifierar att din databas förmodligen är kraftigt normaliserad, så du introducerar några redundanta kolumner (dessa kolumner visas ofta i WHERE
eller JOIN ON
klausul i frågor) i mycket använda tabeller för denormaliseringens skull. Detta minskar anslutningsfrågor, delar upp en stor fråga i flera mindre frågor och lägger till deras resultat i applikationslagret.
En annan parallell optimering som du kan göra är att justera databasanslutningar. Databasklientbibliotek och externa bibliotek finns på nästan alla programmeringsspråk. Du kan använda anslutningspoolbibliotek för att cachelagra databasanslutningar eller kan konfigurera anslutningspoolstorlek i själva databashanteringssystemet.
Att skapa en nätverksanslutning är kostsamt eftersom det kräver viss kommunikation fram och tillbaka mellan klient och server. Pooling av anslutningar kan hjälpa dig att optimera antalet anslutningar. Anslutningspoolbibliotek kan hjälpa dig att multiplexa anslutningar — flera programtrådar kan använda samma databasanslutning. Jag ska se om jag kan förklara anslutningspooling i detalj i en separat artikel senare.
Du mäter fördröjningen av dina API:er och hittar förmodligen 20–50 % eller mer reducerad latens. Detta är bra optimering vid denna tidpunkt.
Du har nu skalat din verksamhet till en stad till, fler kunder registrerar sig, du börjar sakta göra 80–100 bokningar per minut. Ditt system kan inte hantera denna skala. Återigen ser du API-latensen har ökat, databaslagret har gett upp, men den här gången ger ingen frågeoptimering dig någon betydande prestandavinst. Du kontrollerar systemmåttet, du upptäcker att diskutrymmet är nästan fullt, CPU:n är upptagen 80 % av tiden, RAM-minnet fylls upp väldigt snabbt.
Mönster 2 – Vertikal skalning eller skala upp:
Efter att ha undersökt alla systemmått vet du att det inte finns någon annan enkel lösning än att uppgradera systemets hårdvara. Du uppgraderar din RAM-storlek med 2 gånger, uppgraderar diskutrymme med, säg, 3 gånger eller mer. Detta kallas vertikal skalning eller skala upp ditt system. Du informerar ditt infrastrukturteam eller anlitar team eller tredje parts datacenteragenter för att uppgradera din maskin.
Men hur ställer du in maskinen för vertikal skalning?
Du tilldelar en större maskin. Ett tillvägagångssätt är att inte migrera data manuellt från den gamla maskinen, istället ställ in den nya maskinen som replica
till den befintliga maskinen (primary
)-gör en tillfällig primary replica
konfiguration. Låt replikeringen ske naturligt. När replikeringen är klar, främja den nya maskinen till primär och ta den äldre maskinen offline. Eftersom den större maskinen förväntas betjäna alla förfrågningar kommer all läsning/skrivning att ske på denna maskin.
Häftigt. Ditt system är igång igen med ökad prestanda.
Ditt företag går väldigt bra och du bestämmer dig för att skala till ytterligare tre städer — du är nu verksam i totalt fem städer. Trafiken är 3 gånger än tidigare, du förväntas göra cirka 300 bokningar per minut. Innan du ens uppnår denna målbokning, träffar du prestandakrisen igen, databasindexstorleken ökar kraftigt i minnet, den behöver konstant underhåll, tabellskanning med index blir långsammare än någonsin. Du beräknar kostnaden för att skala upp maskinen ytterligare men är inte övertygad om kostnaden. Vad gör du nu?
Mönster 3 - Command Query Responsibility Segregation (CQRS):
Du identifierar att den stora maskinen inte kan hantera all read/write
förfrågningar. Också i de flesta fall behöver alla företag transaktionskapacitet på write
men inte på read
operationer. Du klarar dig också med lite inkonsekvent eller försenad read
drift och ditt företag har inga problem med det heller. Du ser en möjlighet där det kan vara ett bra alternativ att separera read
&write
operationer fysiskt maskinmässigt. Det kommer att skapa utrymme för enskilda maskiner att hantera mer read/write
operationer.
Du tar nu två stora maskiner till och ställer in dem som replica
till den aktuella maskinen. Databasreplikering kommer att ta hand om att distribuera data från primary
maskin till replica
maskiner. Du navigerar i alla lästa frågor (Query (Q
) i CQRS
) till replikerna — valfri replica
kan betjäna alla läsbegäranden, navigerar du i alla skrivfrågor (Kommando (C
) i CQRS
) till den primary
. Det kan vara lite fördröjning i replikeringen, men enligt ditt affärsanvändningsfall är det bra.
De flesta av de medelstora startups som betjänar några hundra tusen förfrågningar varje dag kan överleva med primära repliker, förutsatt att de regelbundet arkiverar äldre data.
Nu skalar du till ytterligare 2 städer ser du att din primary
kan inte hantera alla write
förfrågningar. Många write
förfrågningar har latens. Dessutom, fördröjningen mellan primary
&replica
påverkar ibland kunder och förare ex — när resan slutar betalar kunden föraren framgångsrikt, men föraren kan inte se betalningen eftersom kundens aktivitet är en write
begäran som går till den primary
, medan förarens aktivitet är en read
begäran som går till en av replikerna. Ditt övergripande system är så långsamt att föraren inte kan se betalningen på minst en halv minut - frustrerande för både förare och kund. Hur löser du det?
Mönster 4 - Multiprimär replikering
Du skalade riktigt bra med primary-replica
konfiguration, men nu behöver du mer skrivprestanda. Du kanske är redo att kompromissa lite med read
begära prestanda. Varför inte distribuera skrivbegäran till en replica
också?
I en multi-primary
konfiguration kan alla maskiner fungera som både primary
&replica
. Du kan tänka på multi-primary
som en cirkel av maskiner säger A->B->C->D->A
. B
kan replikera data från A
, C
kan replikera data från B
, D
kan replikera data från C
, A
kan replikera data från D
. Du kan skriva data till vilken nod som helst, medan du läser data kan du sända frågan till alla noder, den som svarar returnerar det. Alla noder kommer att ha samma databasschema, samma uppsättning tabeller, index etc. Så du måste se till att det inte finns någon kollision i id
över noder i samma tabell, annars skulle flera noder under sändning returnera olika data för samma id
.
I allmänhet är det bättre att använda UUID
eller GUID
för id. Ytterligare en nackdel med denna teknik är - read
frågor kan vara ineffektiva eftersom det innebär att sända frågor och få rätt resultat – i princip spridningsinsamling.
Nu skalar du till ytterligare 5 städer och ditt system har ont igen. Du förväntas hantera ungefär 50 förfrågningar per sekund. Du är i desperat behov av att hantera ett stort antal samtidiga förfrågningar. Hur uppnår du det?
Mönster 5 - Partitionering:
Du vet att din location
databas är något som blir hög write
&read
trafik. Förmodligen write:read
förhållandet är 7:3
. Detta sätter stor press på de befintliga databaserna. location
tabeller innehåller få primära data som longitude
, latitude
, timestamp
, driver id
, trip id
etc. Det har inte så mycket att göra med användarresor, användardata, betalningsdata etc. Vad sägs om att separera location
tabeller i ett separat databasschema? Vad sägs om att lägga den databasen i separata maskiner med korrekt primary-replica
eller multi-primary
konfiguration?
Detta kallas partitionering av data efter funktionalitet. Olika databas kan vara värd för data kategoriserad efter olika funktionalitet, vid behov kan resultatet aggregeras i backend-lagret. Med den här tekniken kan du fokusera på att skala de funktioner väl som kräver hög read/write
förfrågningar. Även om backend- eller applikationslagret måste ta ansvar för att sammanfoga resultaten när det behövs, vilket förmodligen leder till fler kodändringar.
Föreställ dig nu att du har utökat din verksamhet till totalt 20 städer i ditt land och planerar att expandera till Australien snart. Din ökande efterfrågan på app kräver snabbare och snabbare svar. Ingen av ovanstående metoder kan hjälpa dig till det yttersta nu. Du måste skala ditt system på ett sådant sätt att expansion till andra länder/regioner inte alltid kräver att du gör täta ingenjörs- eller arkitekturändringar. Hur gör man det?
Mönster 6 - Horisontell skalning:
Du googlar mycket, läser mycket om hur andra företag har löst problemet — och kommer fram till att du behöver skala horisontellt. Du tilldelar säg 50 maskiner — alla har samma databasschema som i sin tur innehåller samma uppsättning tabeller. Alla maskiner innehåller bara en del av data.
Eftersom alla databaser innehåller samma uppsättning tabeller, kan du designa systemet på ett sådant sätt att lokaliteten av data finns där, dvs; all relaterad data hamnar i samma maskin. Varje maskin kan ha sina egna repliker, repliker kan användas vid felåterställning. Var och en av databaserna kallas shard
. En fysisk maskin kan ha en eller flera shards
– det är upp till din design hur du vill. Du måste bestämma dig för sharding key
på ett sådant sätt att en enda sharding key
hänvisar alltid till samma maskin. Så du kan föreställa dig många maskiner som alla innehåller relaterad data i samma uppsättning tabeller, read/write
förfrågningar om samma rad eller samma uppsättning resursland i samma databasmaskin.
Sharding är i allmänhet svårt - åtminstone ingenjörer från olika företag säger det. Men när du tjänar miljoner eller miljarder förfrågningar måste du fatta ett så tufft beslut.
Jag kommer att diskutera sharding
mer detaljerat i mitt nästa inlägg, så håll tillbaka min frestelse att diskutera mer i det här inlägget.
Nu eftersom du har skärning på plats är du säker på att du kan skala till många länder. Ditt företag har vuxit så mycket att investerare driver dig att skala verksamheten över kontinenter. Du ser ett problem här igen. API-latens igen. Din tjänst är värd i USA och människor från Vietnam har svårt att boka resor. Varför? Vad gör du åt det?
Mönster 7 - Datacenter Wise Partition:
Ditt företag växer i Amerika, Sydasien och i några få länder i Europa. Du gör miljontals bokningar dagligen med miljarder förfrågningar som träffar din server. Grattis - det här är ett toppögonblick för ditt företag.
Men eftersom förfrågningar från appen måste resa över kontinenter genom hundratals eller tusentals servrar på internet, uppstår latensen. Hur är det med att distribuera trafik över datacenter? Du kan sätta upp ett datacenter i Singapore som hanterar alla förfrågningar från Sydasien, datacenter i Tyskland kan hantera alla förfrågningar från europeiska länder och ett datacenter i Kalifornien kan hantera alla förfrågningar från USA.
Du aktiverar också replikering över datacenter vilket hjälper till att återställa katastrofer. Så om Kaliforniens datacenter replikerar till Singapores datacenter, om Kaliforniens datacenter kraschar på grund av elektricitetsproblem eller naturkatastrofer, kan alla USA-förfrågningar falla tillbaka till Singapores datacenter och så vidare.
Denna skalningsteknik är användbar när du har miljontals kunder att betjäna i olika länder och du inte kan hantera någon dataförlust, du måste alltid upprätthålla tillgängligheten för systemet.
Det här är några allmänna steg för steg-tekniker för databasskalning. Även om de flesta av ingenjörerna inte får tillräckligt med chans att implementera dessa tekniker, men som helhet är det bättre att få en bredare uppfattning om ett sådant system som i framtiden kan hjälpa dig att göra bättre system- och arkitekturdesign.
I mina nästa artiklar kommer jag att försöka diskutera några av begreppen i detalj. Ge gärna feedback om det här inlägget om någon.
Artikeln är ursprungligen publicerad på författarens mediumkonto:https://medium.com/@kousiknath/understanding-database-scaling-patterns-ac24e5223522