De flesta databaser växer i storlek med tiden. Tillväxten är inte alltid tillräckligt snabb för att påverka databasens prestanda, men det finns definitivt fall där det händer. När den gör det undrar vi ofta vad som kan göras för att minska den påverkan och hur vi kan säkerställa smidig databasoperation när vi hanterar data i stor skala.
Först och främst, låt oss försöka definiera vad betyder en "stor datavolym"? För MySQL eller MariaDB är det okomprimerad InnoDB. InnoDB fungerar på ett sätt som drar stor nytta av tillgängligt minne - främst InnoDB buffertpoolen. Så länge data får plats där, minimeras diskåtkomst till att endast hantera skrivningar - läsningar serveras ur minnet. Vad händer när data växer ur minnet? Mer och mer data måste läsas från disken när det finns ett behov av att komma åt rader som för närvarande inte är cachade. När mängden data ökar växlar arbetsbelastningen från CPU-bunden till I/O-bunden. Det betyder att flaskhalsen inte längre är CPU (vilket var fallet när data passade in i minnet - dataåtkomst i minnet är snabb, datatransformation och aggregering är långsammare) utan snarare är det I/O-undersystemet (CPU-operationer på data är mycket snabbare än att komma åt data från disk.) Med ökad användning av flash är I/O-bundna arbetsbelastningar inte så hemska som de brukade vara i tider med snurrande enheter (slumpmässig åtkomst är mycket snabbare med SSD) men prestandaträffen är fortfarande där .
En annan sak som vi måste komma ihåg att vi vanligtvis bara bryr oss om den aktiva datamängden. Visst, du kan ha terabyte med data i ditt schema, men om du bara måste komma åt de sista 5 GB är detta faktiskt en ganska bra situation. Visst, det innebär fortfarande operativa utmaningar, men prestationsmässigt borde det fortfarande vara ok.
Låt oss bara anta för syftet med denna blogg, och detta är inte en vetenskaplig definition, att vi med den stora datavolymen menar fall där aktiv datastorlek väsentligt växer ifrån storleken på minnet. Det kan vara 100 GB när du har 2 GB minne, det kan vara 20 TB när du har 200 GB minne. Tipppunkten är att din arbetsbelastning är strikt I/O-bunden. Håll ut med oss medan vi diskuterar några av alternativen som är tillgängliga för MySQL och MariaDB.
Partitionering
Det historiska (men helt giltiga) tillvägagångssättet för att hantera stora datamängder är att implementera partitionering. Tanken bakom det är att dela upp tabeller i partitioner, en sorts undertabeller. Uppdelningen sker enligt de regler som definieras av användaren. Låt oss ta en titt på några av exemplen (SQL-exemplen är hämtade från MySQL 8.0-dokumentationen)
MySQL 8.0 kommer med följande typer av partitionering:
- OMRÅDE
- LISTA
- KOLUMNER
- HASH
- NYCKEL
Det kan också skapa underpartitioner. Vi kommer inte att skriva om dokumentationen här men vi skulle ändå vilja ge dig lite inblick i hur partitioner fungerar. För att skapa partitioner måste du definiera partitioneringsnyckeln. Det kan vara en kolumn eller vid RANGE eller LIST flera kolumner som kommer att användas för att definiera hur data ska delas upp i partitioner.
HASH-partitionering kräver att användaren definierar en kolumn som kommer att hashas. Sedan kommer data att delas upp i användardefinierade antal partitioner baserat på det hashvärdet:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH( YEAR(hired) )
PARTITIONS 4;
I det här fallet kommer hash att skapas baserat på resultatet som genereras av YEAR()-funktionen i kolumnen "hyrt".
KEY-partitionering är liknande med undantaget att användaren definierar vilken kolumn som ska hashas och resten är upp till MySQL att hantera.
Medan HASH- och KEY-partitioner slumpmässigt fördelade data över antalet partitioner, låter RANGE och LIST användaren bestämma vad som ska göras. RANGE används ofta med tid eller datum:
CREATE TABLE quarterly_report_status (
report_id INT NOT NULL,
report_status VARCHAR(20) NOT NULL,
report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
Den kan också användas med andra typer av kolumner:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
LIST-partitionerna fungerar baserat på en lista med värden som sorterar raderna över flera partitioner:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LIST(store_id) (
PARTITION pNorth VALUES IN (3,5,6,9,17),
PARTITION pEast VALUES IN (1,2,10,11,19,20),
PARTITION pWest VALUES IN (4,12,13,14,18),
PARTITION pCentral VALUES IN (7,8,15,16)
);
Vad är poängen med att använda partitioner kan du fråga dig? Huvudpoängen är att uppslagningarna är betydligt snabbare än med icke-partitionerade tabeller. Låt oss säga att du vill söka efter raderna som skapades under en viss månad. Om du har flera års data lagrad i tabellen kommer detta att vara en utmaning - ett index måste användas och, som vi vet, hjälper index att hitta rader, men att komma åt dessa rader kommer att resultera i en massa slumpmässiga läsningar från hela bordet. Om du har partitioner skapade på årsbasis kan MySQL bara läsa alla rader från just den partitionen - inget behov av att komma åt index, inget behov av att göra slumpmässiga läsningar:läs bara all data från partitionen, sekventiellt, och vi är allt klart.
Partitioner är också mycket användbara för att hantera datarotation. Om MySQL enkelt kan identifiera rader att ta bort och mappa dem till en enskild partition, istället för att köra DELETE FROM table WHERE …, som använder index för att lokalisera rader, kan du trunkera partitionen. Detta är extremt användbart med RANGE-partitionering - om vi håller oss till exemplet ovan, om vi bara vill behålla data i 2 år, kan vi enkelt skapa ett cron-jobb som tar bort gammal partition och skapar en ny, tom för nästa månad.
InnoDB-komprimering
Om vi har en stor volym data (inte nödvändigtvis tänker på databaser) är det första som vi tänker på att komprimera det. Det finns många verktyg som ger en möjlighet att komprimera dina filer, vilket avsevärt minskar deras storlek. InnoDB har också ett alternativ för det - både MySQL och MariaDB stöder InnoDB-komprimering. Den största fördelen med att använda komprimering är minskningen av I/O-aktiviteten. Data, när de komprimeras, är mindre, så det går snabbare att läsa och skriva. En typisk InnoDB-sida är 16KB stor, för SSD är detta 4 I/O-operationer att läsa eller skriva (SSD använder vanligtvis 4KB-sidor). Om vi lyckas komprimera 16KB till 4KB minskade vi bara I/O-operationerna med fyra. Det hjälper egentligen inte mycket när det gäller datauppsättning till minnesförhållande. Egentligen kan det till och med göra det värre - MySQL måste, för att kunna arbeta med data, dekomprimera sidan. Ändå läser den komprimerad sida från disk. Detta resulterar i att InnoDB buffertpool lagrar 4KB komprimerad data och 16KB okomprimerad data. Naturligtvis finns det algoritmer för att ta bort onödiga data (okomprimerad sida kommer att tas bort när det är möjligt, bara en komprimerad sida behålls i minnet) men du kan inte förvänta dig för mycket av en förbättring på detta område.
Det är också viktigt att ha i åtanke hur komprimering fungerar när det gäller lagringen. Solid state-enheter är normen för databasservrar idag och de har ett par specifika egenskaper. De är snabba, de bryr sig inte mycket om trafiken är sekventiell eller slumpmässig (även om de fortfarande föredrar sekventiell åtkomst framför slumpmässig). De är dyra för stora volymer. De lider av "utslitna" eftersom de kan hantera ett begränsat antal skrivcykler. Komprimering hjälper avsevärt här - genom att minska storleken på data på disken, minskar vi kostnaden för lagringslagret för databasen. Genom att minska storleken på data vi skriver till disken, ökar vi livslängden på SSD:n.
Tyvärr, även om komprimering hjälper, kanske det fortfarande inte räcker för större datavolymer. Ett annat steg skulle vara att leta efter något annat än InnoDB.
MyRocks
MyRocks är en lagringsmotor tillgänglig för MySQL och MariaDB som bygger på ett annat koncept än InnoDB. Min kollega, Sebastian Insausti, har en trevlig blogg om att använda MyRocks med MariaDB. Kontentan är att, på grund av sin design (den använder Log Structured Merge, LSM), är MyRocks betydligt bättre vad gäller komprimering än InnoDB (som är baserad på B+Tree-struktur). MyRocks är designat för att hantera stora mängder data och för att minska antalet skrivningar. Det härstammar från Facebook, där datamängderna är stora och kraven för att komma åt datan är höga. Alltså SSD-lagring - ändå, i så stor skala är varje vinst i komprimering enorm. MyRocks kan leverera till och med upp till 2 gånger bättre komprimering än InnoDB (vilket innebär att du minskar antalet servrar med två). Den är också utformad för att minska skrivförstärkningen (antal skrivningar som krävs för att hantera en förändring av radinnehållet) - det kräver 10 gånger mindre skrivningar än InnoDB. Detta minskar uppenbarligen I/O-belastningen men, ännu viktigare, det kommer att öka livslängden för en SSD tio gånger jämfört med att hantera samma belastning med InnoDB). Ur prestandasynpunkt, mindre datavolym, desto snabbare åtkomst, så lagringsmotorer som den kan också hjälpa till att få ut data från databasen snabbare (även om det inte var högsta prioritet när man designade MyRocks).
Kolumndataarkiv
Relaterade resurser ClusterControl Performance Management Förstå effekterna av hög latens i hög tillgänglighet MySQL och MariaDB Solutions MySQL Performance Cheat SheetVid någon tidpunkt är allt vi kan göra att erkänna att vi inte kan hantera en sådan mängd data med MySQL. Visst, du kan skära sönder det, du kan göra olika saker men så småningom är det bara inte vettigt längre. Det är dags att leta efter ytterligare lösningar. En av dem skulle vara att använda kolumnära databutiker - databaser, som är designade med big data-analys i åtanke. Visst, de kommer inte att hjälpa till med OLTP-typ av trafiken men analys är i stort sett standard nuförtiden eftersom företag försöker vara datadrivna och fattar beslut baserat på exakta siffror, inte slumpmässiga data. Det finns många kolumnära databutiker men vi skulle vilja nämna två av dem här. MariaDB AX och ClickHouse. Vi har ett par bloggar som förklarar vad MariaDB AX är och hur kan MariaDB AX användas. Vad som är viktigt, MariaDB AX kan skalas upp i en form av ett kluster, vilket förbättrar prestandan. ClickHouse är ett annat alternativ för att köra analyser - ClickHouse kan enkelt konfigureras för att replikera data från MySQL, som vi diskuterade i ett av våra blogginlägg. Det är snabbt, det är gratis och det kan också användas för att bilda ett kluster och för att skära data för ännu bättre prestanda.
Slutsats
Vi hoppas att detta blogginlägg gav dig insikter i hur stora datamängder kan hanteras i MySQL eller MariaDB. Lyckligtvis finns det ett par alternativ till vårt förfogande och så småningom, om vi inte riktigt kan få det att fungera, finns det bra alternativ.