fel sätt att skala upp Cassandra DB när sekundära index är på plats
Cassandra är min favorit (inte hanterad) databas av många anledningar: Den har inte en enda felpunkt (SPoF), stöder flera regioner, bra för Läs-och skriv ops, flexibel om läs-och skrivkonsistensnivåer, skalor linjärt och inte för komplicerat att hantera för den dagliga verksamheten.
som varje databas bör du använda Cassandra baserat på dina dataåtkomstmönster, så om du behöver en flexibel databas för ad hoc-frågor eller anpassningsbar nog för ständigt databasmodelländringar, bör du överväga andra alternativ.
Cassandra är en kolumnorienterad DB och det är verkligen kraftfullt när du har dina datafrågor redan definierade. Datastax, företaget som stöder Cassandra, rekommenderar att du börjar med att designa dina frågor och sedan din datamodell i Cassandra. Trots det faktum att din kolumnstruktur stöder Cassandra många datastrukturer som en kolumn(er) typ, till exempel kartor.
Cassandra är en primärnyckeldatabas, vilket innebär att dina data kvarstår och organiseras runt ett kluster baserat på hashvärdet (partitionsnyckeln) för primärnyckeln. För tabeller som har mer än en PK anser Cassandra endast den första delen av PK som partitionsnyckel. Läs mer om kompositnycklar här.
för att vara tydligare, låt oss komma tillbaka till en av de viktigaste egenskaperna hos en Cassandra DB: det är arkitektur och det faktum att det inte har en SPoF.
ett Cassandra-kluster består av noder (3 eller fler) och dessa noder komponerar tillsammans en ring av noder:
varje nod på en Cassandras kluster fungerar “oberoende”, men olika noder kan lagra samma data i enlighet därmed replikationsfaktorn (RF) konfiguration konfigurerad för klustret.
för att veta var (vilken nod) dina data kvarstår använder Cassandra hashvärdet (token) beräknat genom en konsekvent hashfunktion med hjälp av PK-kolumnen i en given tabell.
när du kör en fråga kommer koordinatornoden (normalt den närmaste av dina applikationsinstanser) att leta efter vilka noder i en ring som har dina data, på det sättet om en nod är nere av någon anledning, kan en annan nod tjäna dina data (RF GHz 2). Det är magin om ett mästerlöst tillvägagångssätt, där varje nod i en ring är lika när det gäller läsning och skrivning.
detta koncept om PK och replikationsfaktorn är mycket viktigt att förstå om hur du skalar ditt Cassandra-kluster när din applikation är under höga belastningsförhållanden.
sekundära index
Cassandra har också begreppet sekundära index. I relationsdatabaser kan du ha många index i en given tabell, kostnaden för har ett sekundärt index är associerat med skrivoperationer, inte för läsoperationer. I Cassandra är detta inte sant.
sekundära index i Cassandra kan vara användbara och frestande när din datamodell ändras och du måste fråga baserat på en ny kolumn.
på det sättet, med ett sekundärt index, kan du köra en fråga så:
välj * från my_table där SECONDARY_INDEX = ‘värde’;
problemet med att använda ett sekundärt Index
Föreställ dig scenariot: du befinner dig i en Blackfriday/CyberMonday och ditt Cassandra-kluster lider av topphändelser och du måste lägga till fler noder för att skala din databas, balansera bättre din trafik och… överleva. Bra, eller hur?
normalt är det en normal situation i en mycket skalbar applikation. Men vad händer om din applikation kör frågor med ett sekundärt index?
Ja, du har poängen.
kom ihåg när jag sa att Cassandra distribuerar data i en ring med partitionsnyckeln? Detta händer redan, men problemet är när du introducerar ett sekundärt index i din fråga. Sekundära index är inte en del av en partitionsnyckel, och Cassandra vet var dina data lever genom partitionsnyckeln. När du kör en fråga som använder denna typ av index, Letar Cassandra efter varje nod i din ring som försöker tillfredsställa din fråga.
verkligt Scenario
under en Blackfriday var våra applikationer med höga belastningar. Många och många kunder som vill dra nytta av de enorma rabatter som en Blackfriday händelse.
vi tittade på vår APM och all analys ledde oss till vår uthållighet, i detta fall en Cassandra DB. Vi har långa perioder av latens, men inte för varje begäran, bara för vissa.
försök att hålla saker tillbaka till det normala tillståndet igen, vår första manöver var att lägga till fler noder i vårt Cassandra-kluster.
vi lade till och vi lider fortfarande av latensproblem. Frågan var: varför händer detta fortfarande?
vi hade fel. Det var en förenklad slutsats och vi tog inte hand om en mycket viktig detalj: detta beteende hände inte i alla förfrågningar, men i vissa av dem.
om du tänkte på det sekundära indexet, bingo! Det var exakt problemet.
att lägga till noder skulle aldrig lösa problemet, eftersom problemet inte var relaterat till alla frågor som anlände till databasen, problemet var i vissa och de var de verkliga som försämrade databasens prestanda. Det var helt en Pareto sak.
detaljera problemet och hur vi mildrar det
vid ett ögonblick före Blackfriday-evenemanget behövde vi ändra vår datamodell. Vi regionaliserade vår applikation och kundens region började bli en viktig sak för oss, vi behövde söka data baserat på en produkt eller region.
när vi ser tillbaka och ansluter prickarna kunde vi inse att vi var mycket värdefulla med implementeringen eftersom vi ville återspegla detta nya beteende inte bara i API-lagret (new query param) utan också på det sätt som vi fick tillgång till data i Cassandra.
och varför var vi så värdefulla? För även med tanke på att vår frågestund inte ökade så mycket, gjorde vi förändringen.
den implementeringen ökade inte bara vår frågestund genom att använda ett sekundärt index, utan genererade också fler problem enligt att vi skalade upp Cassandras Infrastruktur. När vi lade till fler noder i vårt kluster, menade fler noder att leta upp för att hitta data, så ökade problemet exponentiellt.
för att mildra problemet var det vi gjorde att ta tillbaka antalet noder som vi tidigare hade och ökade replikationsfaktorn för majoriteten av våra noder i klustret.
vi ändrade också vår läskonsistensnivå för att vara mindre konsekvent. Vi använde *kvorum och istället bytte vi till en. Detta hjälpte oss att sänka belastningen i noder.
när vi frös våra ansökningar dagar före evenemanget visste vi att vi inte hade nya data (skrivoperationer) och uppgifterna skulle vara konsekventa i sitt nuvarande tillstånd.
dagarna efter och DB-modelllösningen
som en del av den slutliga lösningen behövde vi (åter)tänka på vår databasmodell och rulla tillbaka de förändringar som vi gjorde som en mildringsväg under evenemanget.
före händelsen använde vi Produkt-ID (PID) som partitionsnyckel, vilket var ett bra beslut, eftersom PID har goda attribut att vara en PK på grund av sin natur om att vara ett sekventiellt tal (hög kardinalitet) och på så sätt sprida data jämnt runt klustret.
om det nya fältet “region” utnyttjar vi Cassandra collections datatyp och använde en karta för varje region som en kolumn i vår produktabell.
sekundära index är alltid en dålig ide?
det korta svaret är nej.
förklara lite bättre, det finns två typer av index i Cassandra: lokala och globala index.
ett lokalt index som namnet säger är ett slags index som bara finns lokalt, det betyder i en nod. När du skapar ett sekundärt index skapar Cassandra en ny (dold) tabell där sekundären blir en primärnyckel i den här tabellen. Synligheten för denna nya tabell är i termer av en nod, inte en ring (kluster). Det är fallet med sekundära index.
å andra sidan har ett globalt index ringsynlighet genom sin partitionsnyckel, så Cassandra vet var du data finns i en ring genom den partitionsnyckeln.
sekundära index kan vara ett alternativ, när du har i en fråga både: primära och sekundära index. I så fall vet Cassandra var dina data finns (vilken nod) genom partitionsnyckeln och letar sedan upp den lokala tabellen i noden som refererar till de (lokala) sekundära indexen.
det finns också några andra nyanser om sekundära index som är mycket väl förklarade här, men den bästa praxisen är att undvika dem genom att denormalisera din datamodell.