Większość baz danych powiększa się z czasem. Wzrost nie zawsze jest wystarczająco szybki, aby wpłynąć na wydajność bazy danych, ale zdecydowanie zdarzają się przypadki, w których tak się dzieje. Kiedy tak się dzieje, często zastanawiamy się, co można zrobić, aby zmniejszyć ten wpływ i jak zapewnić płynne działanie bazy danych w przypadku danych na dużą skalę.
Przede wszystkim spróbujmy zdefiniować, co oznacza „duża ilość danych”? W przypadku MySQL lub MariaDB jest to nieskompresowany InnoDB. InnoDB działa w sposób, który silnie korzysta z dostępnej pamięci - głównie z puli buforów InnoDB. Dopóki dane tam się zmieszczą, dostęp do dysku jest ograniczony do obsługi tylko zapisów - odczyty są obsługiwane z pamięci. Co się stanie, gdy dane przerosną pamięć? Coraz więcej danych musi być odczytanych z dysku, gdy istnieje potrzeba dostępu do wierszy, które nie są aktualnie buforowane. Gdy ilość danych wzrasta, obciążenie przełącza się z obciążenia procesora na powiązanie we/wy. Oznacza to, że wąskim gardłem nie jest już procesor (co miało miejsce, gdy dane mieszczą się w pamięci - dostęp do danych w pamięci jest szybki, transformacja i agregacja danych jest wolniejsza), ale raczej podsystem I/O (operacje procesora na danych są bardzo szybciej niż dostęp do danych z dysku). Wraz ze wzrostem popularności pamięci flash obciążenia związane z operacjami we/wy nie są tak straszne, jak w czasach wirujących dysków (dostęp losowy jest znacznie szybszy w przypadku dysków SSD), ale spadek wydajności nadal występuje .
Kolejną rzeczą, o której musimy pamiętać, jest to, że zazwyczaj zależy nam tylko na aktywnym zbiorze danych. Jasne, możesz mieć terabajty danych w swoim schemacie, ale jeśli musisz uzyskać dostęp tylko do ostatnich 5 GB, jest to całkiem dobra sytuacja. Jasne, nadal stanowi wyzwanie operacyjne, ale pod względem wydajności nadal powinno być w porządku.
Załóżmy tylko na potrzeby tego bloga, a nie jest to definicja naukowa, że przez dużą ilość danych rozumiemy przypadek, w którym aktywny rozmiar danych znacznie przewyższa rozmiar pamięci. Może to być 100 GB, gdy masz 2 GB pamięci, może to być 20 TB, gdy masz 200 GB pamięci. Punktem krytycznym jest to, że obciążenie jest ściśle związane z operacjami we/wy. Bądź z nami, gdy omawiamy niektóre opcje dostępne dla MySQL i MariaDB.
Partycjonowanie
Historycznym (ale całkowicie poprawnym) podejściem do obsługi dużych ilości danych jest implementacja partycjonowania. Ideą tego jest podzielenie stołu na partycje, coś w rodzaju podstolików. Podział odbywa się zgodnie z zasadami określonymi przez użytkownika. Rzućmy okiem na niektóre przykłady (przykłady SQL pochodzą z dokumentacji MySQL 8.0)
MySQL 8.0 zawiera następujące typy partycjonowania:
- ZAKRES
- LISTA
- KOLUMNY
- HASZ
- KLUCZ
Może również tworzyć podpartycje. Nie będziemy tutaj przepisywać dokumentacji, ale nadal chcielibyśmy dać ci wgląd w działanie partycji. Aby utworzyć partycje, musisz zdefiniować klucz partycjonowania. Może to być kolumna lub w przypadku RANGE lub LIST wiele kolumn, które zostaną użyte do zdefiniowania sposobu podziału danych na partycje.
Partycjonowanie HASH wymaga od użytkownika zdefiniowania kolumny, która będzie haszowana. Następnie dane zostaną podzielone na określoną przez użytkownika liczbę partycji na podstawie tej wartości skrótu:
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;
W tym przypadku hash zostanie utworzony na podstawie wyniku wygenerowanego przez funkcję YEAR() w kolumnie „zatrudniony”.
Partycjonowanie KEY jest podobne, z wyjątkiem tego, że użytkownik określa, która kolumna powinna być zahaszowana, a reszta zależy od obsługi MySQL.
Podczas gdy partycje HASH i KEY losowo rozdzielają dane na wiele partycji, RANGE i LIST pozwalają użytkownikowi zdecydować, co zrobić. RANGE jest powszechnie używany z godziną lub datą:
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)
);
Może być również używany z innymi typami kolumn:
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
);
Partycje LIST działają w oparciu o listę wartości, która sortuje wiersze w wielu partycjach:
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)
);
Jaki jest sens używania partycji, o które możesz zapytać? Najważniejsze jest to, że wyszukiwania są znacznie szybsze niż w przypadku tabeli niepartycjonowanej. Załóżmy, że chcesz wyszukać wiersze, które zostały utworzone w danym miesiącu. Jeśli masz dane przechowywane w tabeli z kilku lat, będzie to wyzwanie - trzeba będzie użyć indeksu i, jak wiemy, indeksy pomagają znaleźć wiersze, ale dostęp do tych wierszy będzie skutkować mnóstwem losowych odczytów z cały stół. Jeśli masz partycje tworzone co rok, MySQL może po prostu odczytać wszystkie wiersze z tej konkretnej partycji - nie ma potrzeby dostępu do indeksu, nie ma potrzeby wykonywania losowych odczytów:po prostu odczytuje wszystkie dane z partycji, sekwencyjnie, i jesteśmy wszystko gotowe.
Partycje są również bardzo przydatne w radzeniu sobie z rotacją danych. Jeśli MySQL może łatwo zidentyfikować wiersze do usunięcia i zmapować je na pojedynczą partycję, zamiast uruchamiać DELETE FROM table WHERE …, który użyje indeksu do zlokalizowania wierszy, możesz obciąć partycję. Jest to niezwykle przydatne przy partycjonowaniu RANGE - trzymając się powyższego przykładu, jeśli chcemy zachować dane tylko przez 2 lata, możemy łatwo utworzyć zadanie cron, które usunie starą partycję i utworzy nową, pustą na następny miesiąc.
Kompresja InnoDB
Jeśli mamy duży wolumen danych (niekoniecznie myślimy o bazach danych), pierwszą rzeczą, która przychodzi nam do głowy, jest ich skompresowanie. Istnieje wiele narzędzi, które umożliwiają kompresję plików, znacznie zmniejszając ich rozmiar. InnoDB również ma na to opcję - zarówno MySQL, jak i MariaDB obsługuje kompresję InnoDB. Główną zaletą stosowania kompresji jest zmniejszenie aktywności we/wy. Dane po skompresowaniu są mniejsze, dzięki czemu szybciej można je czytać i pisać. Typowa strona InnoDB ma rozmiar 16 KB, w przypadku dysku SSD są to 4 operacje we/wy do odczytu lub zapisu (SSD zwykle używa stron 4 KB). Jeśli uda nam się skompresować 16 KB do 4 KB, po prostu zredukowaliśmy operacje I/O o cztery. To naprawdę niewiele pomaga, jeśli chodzi o stosunek zestawu danych do pamięci. Właściwie może to nawet pogorszyć - MySQL, aby operować na danych, musi zdekompresować stronę. Jednak odczytuje skompresowaną stronę z dysku. Powoduje to, że pula buforów InnoDB przechowuje 4 KB skompresowanych danych i 16 KB nieskompresowanych danych. Oczywiście istnieją algorytmy usuwające niepotrzebne dane (nieskompresowana strona zostanie usunięta, jeśli to możliwe, zachowując w pamięci tylko jedną skompresowaną), ale nie można oczekiwać zbyt dużej poprawy w tym obszarze.
Ważne jest również, aby pamiętać, jak działa kompresja w odniesieniu do przechowywania. Dyski półprzewodnikowe są obecnie normą dla serwerów baz danych i mają kilka specyficznych cech. Są szybkie, nie dbają o to, czy ruch jest sekwencyjny, czy losowy (chociaż nadal wolą dostęp sekwencyjny od losowego). Są drogie w przypadku dużych ilości. Cierpią z powodu „zużycia”, ponieważ mogą obsłużyć ograniczoną liczbę cykli zapisu. Kompresja znacznie tu pomaga - zmniejszając rozmiar danych na dysku, zmniejszamy koszt warstwy przechowywania bazy danych. Zmniejszając rozmiar danych, które zapisujemy na dysku, zwiększamy żywotność dysku SSD.
Niestety, nawet jeśli kompresja pomaga, przy większych ilościach danych nadal może nie wystarczyć. Kolejnym krokiem byłoby poszukanie czegoś innego niż InnoDB.
MyRocks
MyRocks to silnik pamięci masowej dostępny dla MySQL i MariaDB, który opiera się na innej koncepcji niż InnoDB. Mój kolega, Sebastian Insausti, ma fajny blog o używaniu MyRocks z MariaDB. Istota jest taka, że ze względu na swoją konstrukcję (wykorzystuje Log Structured Merge, LSM), MyRocks jest znacznie lepszy pod względem kompresji niż InnoDB (który opiera się na strukturze B+Tree). MyRocks jest przeznaczony do obsługi dużych ilości danych i zmniejszenia liczby zapisów. Wywodzi się z Facebooka, gdzie wolumeny danych są duże, a wymagania dotyczące dostępu do danych są wysokie. Tak więc pamięć masowa SSD - jednak przy tak dużej skali każdy zysk w kompresji jest ogromny. MyRocks może zapewnić nawet do 2x lepszą kompresję niż InnoDB (co oznacza zmniejszenie liczby serwerów o dwa). Ma również na celu zmniejszenie wzmocnienia zapisu (liczba zapisów potrzebnych do obsługi zmiany zawartości wiersza) - wymaga 10x mniej zapisów niż InnoDB. To oczywiście zmniejsza obciążenie we/wy, ale co ważniejsze, wydłuży żywotność dysku SSD dziesięciokrotnie w porównaniu z obsługą tego samego obciążenia za pomocą InnoDB). Z punktu widzenia wydajności, im mniejsza ilość danych, tym szybszy dostęp, dzięki czemu takie silniki pamięci masowej mogą również pomóc w szybszym wydobyciu danych z bazy danych (nawet jeśli nie było to najwyższym priorytetem podczas projektowania MyRocks).
Kolumnowe magazyny danych
Zasoby pokrewne ClusterControl Performance Management Zrozumienie skutków dużych opóźnień w rozwiązaniach MySQL i MariaDB o wysokiej dostępności Arkusz informacyjny dotyczący wydajności MySQLW pewnym momencie wszystko, co możemy zrobić, to przyznać, że nie jesteśmy w stanie obsłużyć takiej ilości danych za pomocą MySQL. Jasne, możesz to shardować, możesz robić różne rzeczy, ale w końcu po prostu nie ma to już sensu. Czas poszukać dodatkowych rozwiązań. Jednym z nich byłoby wykorzystanie kolumnowych magazynów danych - baz danych, które zostały zaprojektowane z myślą o analityce big data. Jasne, nie pomogą one w ruchu typu OLTP, ale analityka jest obecnie prawie standardem, ponieważ firmy starają się opierać na danych i podejmować decyzje na podstawie dokładnych liczb, a nie losowych danych. Istnieje wiele kolumnowych baz danych, ale chcielibyśmy wspomnieć tutaj o dwóch z nich. MariaDB AX i ClickHouse. Mamy kilka blogów wyjaśniających, czym jest MariaDB AX i jak można korzystać z MariaDB AX. Co ważne, MariaDB AX można skalować w formie klastra, poprawiając wydajność. ClickHouse to kolejna opcja do prowadzenia analiz — ClickHouse można łatwo skonfigurować do replikacji danych z MySQL, o czym pisaliśmy w jednym z naszych wpisów na blogu. Jest szybki, bezpłatny i może być również używany do tworzenia klastra i dzielenia danych w celu uzyskania jeszcze lepszej wydajności.
Wniosek
Mamy nadzieję, że ten wpis na blogu dał Ci wgląd w to, jak duże ilości danych mogą być obsługiwane w MySQL lub MariaDB. Na szczęście mamy do dyspozycji kilka opcji i ostatecznie, jeśli naprawdę nie możemy sprawić, by to zadziałało, istnieją dobre alternatywy.