den forkerte måde at opskalere Cassandra DB, når sekundære indekser er på plads
Cassandra er min favorit (ikke administreret) database af mange grunde: den har ikke et enkelt fejlpunkt (SPoF), understøtter multi-region, god til læse-og skriveops, fleksibel om læse-og skrivekonsistensniveauer, skalaer lineært og ikke for kompliceret til at styre til den daglige drift.
som enhver database skal du bruge Cassandra baseret på dine dataadgangsmønstre, så hvis du har brug for en fleksibel database til ad hoc-forespørgsler eller kan tilpasses nok til konstant databasemodelændringer, bør du overveje andre muligheder.
Cassandra er en kolonneorienteret DB, og den er virkelig kraftig, når du allerede har defineret dine dataforespørgsler. Virksomheden, der understøtter Cassandra, anbefaler, at du starter med at designe dine forespørgsler og derefter din datamodel i Cassandra. På trods af din kolonnestruktur understøtter Cassandra mange datastrukturer som en kolonne(er) type, såsom kort.
Cassandra er en primær nøgledatabase, hvilket betyder, at dine data er vedvarende og organiseret omkring en klynge baseret på hashværdien (partitionsnøglen) for den primære nøgle. For tabeller, der har mere end en PK, betragter Cassandra kun den første del af PK som det er partitionsnøgle. Se mere om sammensatte nøgler her.
for at være mere klar, lad os komme tilbage til en af de vigtigste egenskaber ved en Cassandra DB: det er arkitektur og det faktum, at det ikke har en SPoF.
en Cassandra-klynge er sammensat af noder (3 eller flere), og disse noder sammensætter en ring af noder:
hver node på en Cassandras klynge fungerer “uafhængigt”, men forskellige noder kunne gemme de samme data i overensstemmelse hermed replikationsfaktoren (RF) konfiguration konfigureret til klyngen.
for at vide, hvor (hvilken node) dine data vedvarer, bruger Cassandra hashværdien (token) beregnet gennem en konsistent hash-funktion ved hjælp af PK-kolonnen i en given tabel.
når du kører en forespørgsel, vil koordinatornoden (normalt den nærmeste af dine applikationsinstanser) se efter, hvilke noder i en ring der har dine data, på den måde, hvis en node er nede af en eller anden grund, kan en anden node tjene dine data (RF-2). Det er magien ved en masterløs tilgang, hvor hver knude i en ring er ens med hensyn til læsning og skrivning.
dette koncept om PK og replikationsfaktoren er meget vigtigt at forstå, hvordan du skalerer din Cassandra-klynge, når din applikation er under høje belastningsforhold.
sekundære indekser
Cassandra har også begrebet sekundære indekser. I relationsdatabaser kan du have mange indekser i en given tabel, omkostningerne ved har et sekundært indeks er forbundet med skriveoperationer, ikke til læseoperationer. I Cassandra er dette ikke sandt.
sekundære indekser i Cassandra kan være nyttige og fristende, når din datamodel ændres, og du skal forespørge baseret på en ny kolonne.
på den måde kan du med et sekundært indeks køre en forespørgsel Sådan:
vælg * fra my_table hvor SECONDARY_INDEKS = ‘værdi’;
problemet med at bruge et sekundært indeks
Forestil dig scenariet: du er i en Blackfriday/CyberMonday, og din Cassandra-klynge lider af tophændelser, og du skal tilføje flere noder for at skalere din database, balancere bedre din trafik og… overleve. Fint, ikke?
normalt er det en normal situation i en meget skalerbar applikation. Men hvad med, hvis din applikation kører forespørgsler ved hjælp af et sekundært indeks?
ja, du fik pointen.
Husk da jeg sagde, at Cassandra distribuerer data i en ring ved hjælp af partitionstasten? Dette sker allerede, men problemet er, når du introducerer et sekundært indeks i din forespørgsel. Sekundære indekser er ikke en del af en partitionsnøgle, og Cassandra ved, hvor dine data lever gennem partitionsnøglen. Når du kører en forespørgsel, der bruger denne type indeks, leder Cassandra efter hver node i din ring, der prøver at tilfredsstille din forespørgsel.
rigtigt Scenario
under en Blackfriday var vores applikationer med store belastninger. Mange og mange kunder, der ønsker at drage fordel af de enorme rabatter, der leveres af en Blackfriday-begivenhed.
vi kiggede på vores APM, og al analysen førte os til vores vedholdenhed, i dette tilfælde en Cassandra DB. Vi fik lange perioder med forsinkelse, men ikke for hver anmodning, bare for nogle.
forsøger at holde tingene tilbage til den normale tilstand igen, var vores første manøvre at tilføje flere noder til vores Cassandra-klynge.
vi tilføjede, og vi lider stadig af latensproblemer. Spørgsmålet var: hvorfor sker dette stadig?
vi tog fejl. Det var en forenklet konklusion, og vi tog os ikke af en meget vigtig detalje: denne adfærd skete ikke i alle anmodninger, men i nogle af dem.
hvis du tænkte på det sekundære indeks, bingo! Det var netop problemet.
tilføjelse af noder ville aldrig løse problemet, fordi problemet ikke var relateret til alle forespørgsler, der ankom i databasen, problemet var i nogle, og det var de rigtige, der forringede databasens ydeevne. Det var helt en Pareto ting.
detaljering af problemet og hvordan vi mildner det
et øjeblik før Blackfriday-begivenheden var vi nødt til at ændre vores datamodel. Vi regionaliserede vores applikation, og kundens region begyndte at være en vigtig ting for os, vi havde brug for at forespørge data baseret på et produkt eller en region.
når vi ser tilbage og forbinder prikkerne, kunne vi indse, at vi var meget dyrebare ved implementeringen, da vi ønskede at afspejle denne nye opførsel ikke kun i API-laget (ny forespørgsel param), men også på den måde, vi fik adgang til dataene i Cassandra.
og hvorfor var vi så dyrebare? Fordi selv i betragtning af at vores forespørgselstid ikke steg så meget, gjorde vi ændringen.
denne implementering øgede ikke kun vores forespørgselstid ved hjælp af et sekundært indeks, men genererede også flere problemer i henhold til, at vi opskalerede Cassandras infrastruktur. Da vi tilføjede flere noder i vores klynge, betød flere noder at se op for at finde dataene, således voksede problemet eksponentielt.
for at afbøde problemet var det, vi gjorde, at tage antallet af noder tilbage, som vi tidligere havde, og øgede replikationsfaktoren for størstedelen af vores noder i klyngen.
vi ændrede også vores læsekonsistensniveau for at være mindre konsistent. Vi brugte *kvorum og i stedet skiftede vi til en. Dette hjalp os med at sænke belastningen i noder.
da vi frøs vores applikationer dage før begivenheden, vidste vi, at vi ikke havde nye data (skriveoperationer), og dataene ville være konsistente i deres nuværende tilstand.
dagene efter og DB model solution
som en del af den endelige løsning var vi nødt til at (gen)tænke på vores databasemodel og rulle de ændringer tilbage, som vi gjorde som en afbødningssti under begivenheden.
før begivenheden brugte vi Produkt-ID (PID)som partitionsnøgle, hvilket var en god beslutning, da PID har gode egenskaber til at være en PK på grund af dens natur om at være et sekventielt nummer (høj kardinalitet) og på den måde sprede dataene jævnt omkring klyngen.
om det nye felt “region” udnytter vi Cassandra collections datatype og brugte et kort for hver region som en kolonne i vores produkttabel.
sekundære indekser er altid en dårlig ide?
det korte svar er nej.
forklarer lidt bedre, der er to slags indekser i Cassandra: lokale og globale indekser.
et lokalt indeks, som navnet siger, er en slags indeks, der kun findes lokalt, det betyder i en node. Når du opretter et sekundært indeks, opretter Cassandra en ny (skjult) tabel, hvor sekundæret bliver en primær nøgle i denne tabel. Synligheden af denne nye tabel er i form af en node, ikke en ring (klynge). Det er tilfældet med sekundære indekser.
på den anden side har et globalt indeks ringsynlighed gennem sin partitionsnøgle, så Cassandra ved, hvor dine data er i en ring gennem den partitionsnøgle.
sekundære indekser kan være et alternativ, når du har i en forespørgsel både: primære og sekundære indekser. I så fald ved Cassandra, hvor dine data ligger (hvilken node) gennem partitionstasten og ser derefter op på den lokale tabel i noden, der refererer til de (lokale) sekundære indekser.
der er også nogle andre nuancer om sekundære indekser, der er meget godt forklaret her, men den bedste praksis er at undgå dem ved at denormalisere din datamodel.