Zanim przystąpimy do problematyki skalowania baz danych SQL (a w szczególności MySQL, którego zawsze używamy w ramach praktycznych przykładów), warto poświęcić uwagę na kwestie związane z wydajnością czyli efektywnością działania baz danych.
Dlaczego przy skalowaniu prawie zawsze mówimy o wydajności?
Termin wydajność w szeroko pojętej Informatyce i oprogramowaniu prawie zawsze dotyczy ilości przetwarzanych danych w czasie, zaś każdy system informatyczny jest skalowany wówczas, kiedy z różnych względów potrzebujemy ich przetworzyć więcej. Aby zwiększyć ilość danych jakie system może przetwarzać twórcy skalowalnych baz danych mają zazwyczaj trzy wyjścia:
- Wyeliminować wąskie gardła a tym samym „pełniej” wykorzystać zasoby którymi dysponujemy.
- Zastosować techniki balansujące i łagodzące obciążenie, które rozłożą w czasie intensywność wykorzystywania zasobów obniżając w ten sposób zapotrzebowanie na zasoby oraz zwiększając (niejako przy okazji) dostępność i niezawodność.
- Zwiększyć ilość zasobów.
Skalowalność w kontekście baz danych
Proces skalowania ma miejsce w trzecim punkcie – w zwiększaniu ilości zasobów. Nie możemy jednak postawić znaku równości pomiędzy wydajnością a skalowalnością. Można mieć system wydajny ale bardzo mało skalowalny. Aby to zrozumieć należy zdefiniować skalowalność.
Skalowalność baz danych to właśnie ta możliwość zwiększenia ilości jej zasobów ale w taki sposób aby koszty jakie ponosimy w celu ich zwiększenia były (w najgorszym przypadku) liniowe do wzrostu wydajności jakie te zasoby zapewniają.
Warto zwrócić uwagę i postawić sprawę jasno, że początkowo, bazy SQL były zaprojektowane z myślą o skalowaniu wertykalnym (pionowym). Istnieje wiele technik, które pozwalają skalować je także horyzontalnie, w których za poprawne działanie tych mechanizmów jest odpowiedzialny programista aplikacji. Można też wykorzystać MySQL Cluster czyli z perspektywy serwera MySQL specjalny wewnętrzny system składowania danych (NDB), który został zaprojektowany z myślą o skalowaniu poziomym.
Teoria CAP
Teoria CAP autorstwa Erica Brewera mówi o tym, że baza danych może spełniać na raz tylko 2 z trzech własności, są nimi:
- Spójność (ang. Consistency) – oznacza, że wszystkie węzły bazy danych będą zwracać te same wyniki,
- Dostępność (ang. Availibility), oznacza że dane zawsze można odpisać i odczytać w racjonalnym czasie,
- Tolerancja na partycjonowanie (ang. Partition Tolerance)
Powszechne twierdzenie, że bazy NoSQL są łatwiejsze w horyzontalnym skalowaniu jest prawdziwe, jednak należy zaznaczyć, że w tych bazach świadomie rezygnuje się ze spójności dla większej wydajności i dostępności.
Wąskie gardła w bazach danych
Wąskie gardła w bazach danych mogą być spowodowane niemożliwością obsłużenia dużego ruchu lub niską efektywnością przetwarzania dużych zbiorów tabel.
To ile zapytań w danej jednostce czasu może obsłużyć baza danych zależy od jej konfiguracji max_connections oraz czasu jakie zabiera zapytanie MySQL.
Jeżeli baza danych ma jedno dostępne połączenie do wykorzystania a zapytanie SQL zajmuje 1 sekundę to baza danych podczas jednej minuty może obsłużyć 60 takich zapytań. W rzeczywistości problem ten się bardziej komplikuje, bo to oznacza, że jeden użytkownik może wysłać 60 zapytań lub 60 użytkowników może wysłać po jednym takim zapytaniu.
Wąskimi gardłami w bazie danych mogą być zatem:
- konfiguracja uniemożliwiająca nawiązanie większej ilości połączeń o zadanej wartości w konfiguracji bazy danych,
- zasoby sprzętowe (np. pamięć RAM), które pomimo konfiguracji nie są w stanie obsłużyć konkurujących danych,
- zapytania zajmujące zbyt wiele czasu, które blokują połączenie,
- struktura tabel i definicje poszczególnych komórek, która przyczynia się do efektywności zapytań.
Eliminacja wąskich gardeł może zatem polegać na:
- Optymalizacji bazy danych, np. za pomocą modyfikacji struktury tabel, wykorzystywania odpowiednich trybów składowania (MySQL), konfiguracji ustawień, wykorzystania pamięci podręcznej zapytań (query cache).
- Odciążeniu bazy danych za pomocą wdrażania pamięci podręcznej w warstwie aplikacji takiej jak memcached lub Redis,
- Zwiększenia ilości zasobów sprzętowych, za pomocą skalowania pionowego i poziomego,
- Zmiany syntaktyki zapytań SQL na semantycznie równoważne, które rokują jednak krótszy czas zwrócenia odpowiedzi,
- Wykorzystania denormalizacji baz danych.
Skalowanie pionowe bazy danych (vertical scaling)
Skalowanie pionowe to zapewnienie jej wzrostu w ramach jednej monolitycznej maszyny. Takie skalowanie jest efektywne do pewnego momentu ponieważ bazy takie jak MySQL są przechowywane w plikach w ramach konkretnego systemu operacyjnego. Większość systemów plików ma limity co do wielkości pojedynczych plików. Choć problem ten rozwiązuje partycjonowanie wprowadzone w MySQL to i tak wielkość takich baz danych będzie powodować inne problemy np. kosztowność ich konserwacji czy powolne działanie.
Takie skalowanie pionowe w pewnym momencie staje się nieopłacalne z powodu wykładniczego wzrostu kosztów w miarę zwiększania wydajności. Wymiana kolejnych dysków, rozbudowa ich macierzy RAID też w końcu stanie się wąskim gardłem Są też sztywne limit wielkości pojedynczej tabeli w ramach wewnętrznych systemów składowania:

Partycjonowanie pionowe wykorzystujemy wtedy, kiedy wiemy że aplikacja nie będzie rosła w nieskończoność a zapotrzebowanie na zasoby nie będzie na tyle duże, ze dojdziemy do załamania krzywej obrazującej wzrost kosztów w miarę wzrostu nakładów na wydajność.

Partycjonowanie funkcjonalne baz danych (functional partitioning)
Technika ta ma inną nazwę np. podział na klastry. Niektórych tabel nie będziemy ze sobą łączyć w zapytaniach. Jest to idealny warunek do stworzenia klastrów zawierających podgrupy tabel. Partycjonowanie funkcjonalne powoduje, że konkretnie zapytanie wysyłamy do konkretnych baz danych. Przykładem może tutaj być tutaj aplikacja złożona np. z trzech głównych funkcji. Poszczególne pod-aplikacje mogą być przechowywane w osobnych bazach danych. Jeżeli zajdzie konieczność złączenia danych pochodzących z więcej niż jednej bazy danych należy to obsłużyć w back-endzie aplikacji.
Często odseparowanymi tabelami od reszty aplikacji są tabele odpowiedzialne za:
- przechowywanie logów,
- bazy wykorzystywane do zapisu sesji,
- ustawień aplikacji i funkcji administracyjnych.
Ta strategia też ma skończone możliwości wzrostu ponieważ dojdziemy do momentu w którym nie będzie można tworzyć kolejnych podziałów. Technika ta jest przydatna kiedy część funkcji np. zapis logów obciąża bazę ale nie jest ściśle związana z kluczową funkcjonalnością aplikacji.
Skalowanie poziome bazy danych (horizontal scaling)
Skalowanie poziome baz danych polega na dodawaniu kolejnych baz danych. Dane takie możemy klonować w ramach replikacji baz danych, lub tworzyć poszczególne klastry lub odłami (ang shards).
Replikacja baz danych
Replikację już omówiliśmy w artykule o replikacji baz danych. W tej technice wykorzystujemy fakt, że z bazy danych częściej odczytujemy dane niż zapisujemy. Zwielokrotniając bazy danych typu Slave umożliwiamy podłączenie się coraz to większej liczby użytkowników.
Data-sharding baz danych (podział na federacje)
W przypadku techniki polegającej na tworzeniu kolejnych odłamków (ang. data-sharding), wykorzystujemy fakt, że rekordy w ramach tabel są od siebie w pewien sposób niezależne. Przykładowo, użytkownicy w aplikacji e-commerce nie potrzebują danych dotyczących innych użytkowników. W ten sposób możemy zapisywać dane o użytkownikach z nieparzystym ID w bazie A a użytkowników z parzystym – w bazie B.
if ($user_id % 2 ==0){ connectdb_b(); $query = "SELECT * FROM users JOIN products..."; } else{ connectdb_a(); $query = "SELECT * FROM users JOIN products..."; }
Można też zrobić tak, że w kolejnych bazach przechowujemy np. po milionie rekordów i za pomocą klucza głównego wiemy do której bazy wysłać zapytanie. Wyciąganie danych z konkretnych odłamków trzeba zapewnić w back-endzie aplikacji. Możemy to zrobić na własną rękę lub wykorzystać wiele gotowych systemów, które w pewien sposób wyręczają programistów z obsługiwania odłamków „na niskim poziomie abstrakcji” i zajmują się w tym w niejako „wyższej warstwie”. Technika jest przydatna kiedy tabele zawierają bardzo dużą ilość rekordów (setki tysięcy i miliony).
W przypadku intensywnego skalowania horyzontalnego można wykorzystać MySQL NDB Cluster, czyli technologię (tak naprawdę specjalną wersję MySQL) wykorzystującą wewnętrzny system składowania NDB: https://dev.mysql.com/doc/refman/8.0/en/mysql-cluster.html rozwiązanie to doprowadza do redundancji danych. Istnieje też kilka innych rozwiązań w tym komercyjnych, które ułatwiają i standaryzują podział bazy na odłamki.
Autoscaling w rozwiązaniach DBaaS
Warto wspomnieć, że istnieją gotowe systemy bazodanowe, które „same się skalują”. Replikację możemy wykonać za sprawą jednego kliknięcia (i oczywiście opłaceniu rachunku) Jednak koszt tych usług jak zresztą innych chmurowych opcji hostingowych w przypadku aplikacji dużej skali będzie pozostawiał wiele do życzenia. Przykłady:
- Amazon RDS (w tym Serverless Amazon Aurora),
- Oracle Database Cloud Service,
- Digital Ocean Managed Databases.
Zbyt szybka przesiadka na tego typu rozwiązania (w połączeniu z brakiem doświadczenia przy budowaniu serwisów dużej skali) może powodować wysoki koszt ich utrzymywania. Wąskie gardła zamiast być wyeliminowane są ukryte pod płaszczem dodawania kolejnych jednostek, których koszt będzie rósł nieliniowo do rozmiaru baz danych. Dzięki znajomości technik optymalizacyjnych i zbudowaniu odpowiedniej architektury, koszt utrzymania danych można w skrajnych przypadkach zmniejszyć nawet o rząd wielkości.
Podsumowanie
Skalowanie baz danych to konieczny problem do rozwiązania w przypadku aplikacji, która osiągnie duży sukces i będzie wykorzystywana przez wielu użytkowników. Inny przypadek to zwiększenie się ilości przetwarzanych przez nią danych lub wystąpienia obu tych okoliczności jednocześnie. Choć stara zasada mówi, że nie nie powinno się optymalizować aplikacji na zapas. Warto przy tworzeniu pierwszych linijek kodu wiedzieć, że w przyszłości konkretnie zapytania trzeba będzie rozesłać do wielu baz i na każdym kroku eliminować potencjalne wąskie gardła.
Zobacz też:
https://galeracluster.com/products/
Źródła
https://www.mysql.com/products/enterprise/scalability.html
https://www.oracle.com/technetwork/oem/grid-control/overview/wp-rapid-1.pdf
https://aws.amazon.com/blogs/database/sharding-with-amazon-relational-database-service/
Odpowiedz lub skomentuj