Skalowanie baz danych – Wprowadzenie do problemu
W życiu każdego administratora przychodzi taki czas, gdy musi podzielić jakiś monolit na wiele części pozostawiając go najczęściej w jednym kawałku. Coraz większymi krokami takie zadanie zbliża się do mnie. Postanowiłem, że nie zrobię wyjątku i do tego też przygotuję się z większym wyprzedzeniem… To wyprzedzenie pozwoli zachować nie tylko rezerwę na wypadek ewentualnych komplikacji, ale pozwoli także na spisanie całej drogi do sukcesu, którym operacja prędzej czy później musi się zakończyć 🙂
Klienci bazy danych
W jednym ze środowisk produkcyjnych posiadam bazę danych zawierającą 170 tabel, 211M rekordów i zajmującą 38,9GB. 6 podłączonych do niej serwerów aplikacyjnych generuje średnio 2,2M zapytań na godzinę, z czego 81% to SELECT, 10% to UPDATE, niecałe 2% to INSERT. Problem ze skalowaniem tej bazy to oprogramowanie jakie z niej korzysta…
Frontend jest napisany w PHP, a backend w C.
Kod PHP który zobaczyłem przyprawił mnie o chęć wydłubania sobie oczu i popełnienia rytualnego seppuku nad serwerami – by krew je zalała i wszystko stanęło w płomieniach. Pliki po 600kB, których pierwsze 350 linii to changelog, później kolejnych 300 linii to:
[code lang=”php”]if(isset($_GET[“klucz”])) {$klucz=$_GET[“klucz”];}
elseif (isset($_POST[“klucz”])) {$klucz=$_POST[“klucz”];}[/code]
Później wysyłka headerów, tablice ze słownikami, i…
[code lang=”php”]if($ACTION == ‘….’)[/code]
Widząc takie coś nie dziwi mnie, że ludzie uważają PHP za coś gorszego…
Powracając jednak do głównego problemu – frontend pomimo, iż tragicznie napisany, to nie stanowi dużego obciążenia dla maszyny która je serwuje i tu zapasy zasobów można zaryzykować twierdzenie, że są nieograniczone – zwłaszcza biorąc pod uwagę, że Apache i PHP są prawie w domyślnych konfiguracjach, a obciążenie CPU sięga 0.5%. Obciążenie RAIDa jest tak małe, że niemierzalne. RAM? Wykorzystane 1.9GB z 24GB dostępnych; większość przez rzeczy wrzucone by się maszyna nie nudziła 😉
Backend sam w sobie to nie mój problem jeżeli chodzi o jego konfigurację, ale mój jeżeli chodzi o bazę danych z którą się łączy. Skalowanie backendu polega na dokładaniu kolejnych serwerów z dokładnie tym samym oprogramowaniem i w 99% tą samą konfiguracją. Konfiguracja musi być spójna także po stronie bazy danych – i tu sytuacja się komplikuje. Niedopuszczalne jest stosowanie jakiegokolwiek cache po stronie bazy danych i dodatkowo każdy klient może zostać obsłużony przez dowolny z dostępnych serwerów backendowych.
Oprogramowanie które swoje początki ma w roku 1999 nie było niestety tworzone z myślą o prostej skalowalności. Oczywiście o korzystaniu z zewnętrznych, współdzielonych baz danych też nie. Dodając kilka lat później obsługę baz danych nie pomyślano jednak, że można ułatwić życie administratorom i nie wprowadzono prostych mechanizmów skalowania dostępu do baz danych.
Co robić?
Jak poradzić sobie z puchnącym kolosem, który ma zostać dodatkowo zapchany milionami zapytań i dziesiątkami kolejnych serwerów backendu? Problem z wydajnością? Nic to! 😉
Gdy informacje o tym co jest potrzebne już mam można zabrać się za analizowanie możliwości. Możliwości jest wiele – serwery proxy, replikacja, klastry, … Skalowanie baz nie jest tematem prostym i daje tak wiele możliwości, że ciężko się połapać w tym co i jak użyć, czy jakie rozwiązanie będzie najlepsze. Od samego początku pisania tego wpisu idea co, gdzie i jak zrobić ulega zmianom. Tym lepiej, że ją spiszę – przyda się przy wybieraniu końcowego rozwiązania. 🙂
MySQL Proxy
MySQL Proxy to proste rozwiązanie. Wstawiamy serwer pomiędzy klienta i serwer, później dokładamy serwerów i wszystko zdaje się działać. Fajne, proste rozwiązanie. Pozwala zarówno zrzucić na jego barki problem replikowania danych jak i pozwala wyeliminować problem korzystania z replikacji master-slave przy kliencie nie wspierającym takiego podziału. Pojedynczy punkt łączenia klienta z bazami danych.
Rozwiązanie z proxy jest czymś co mi się nie podoba. Choć wiem, że wskazane jest podejmowanie decyzji o dane, to czasem podejmuję decyzje w oparciu o przeczucie. Tak szczęśliwie się składa, że moje przeczucie ma się dobrze i jak do teraz podpowiada mi właściwe drogi.
Pomijając samo przeczucie, nie jestem przekonany do pojedynczego elementu odpowiedzialnego za całość komunikacji – zwłaszcza, że ilość serwerów ma w przyszłości zwielokrotnić się. Co by się stało, gdyby ten jeden element nagle zawiódł?
Replikacja
Replikacja bazy danych master-slave odpadła ze względu na specyfikę oprogramowania backendu i moją niechęć do wspomnianego wcześniej MySQL Proxy. Replikacja w MySQL od wersji 5.1.18 może jednak przebiegać w schemacie master-ring, lub circular replication – jak kto woli – czyli w schemacie gdzie każdy master jednocześnie działa jako slave innego mastera – i tak aż do zamknięcia okręgu. Rozwiązanie to niestety powstało jako obejście i nie spotkałem się z dobrymi opiniami na jego temat. Problemem jest także stabilność takiego rozwiązania – nie wiadomo bowiem jak zachowa się struktura gdy zabraknie pojedynczego jej elementu.
Galera Cluster
Rozwiązaniem na problem replikacji wydaje się być Galera Cluster. To synchroniczny klaster multi-master działający w oparciu o MySQL (oraz jego forki). Jest dostępny tylko i wyłącznie dla Linuksa i wspiera tylko XtraDB/InnoDB. Choć to pierwsze ograniczenie jest nieistotne, to to drugie wymaga już przeprowadzenia przeze mnie testów i późniejszej migracji z obecnie zastosowanego MyISAM. Pomimo tej niedogodności, to rozwiązanie wydaje się być idealne do serwowania baz dla środowisk niedostosowanych do prostego skalowania – a tym samym do rozwiązania mojego problemu.
Rozwiązanie brzmi dobrze. Pora zabrać się za instalację i sprawdzić o co dokładnie w nim chodzi 🙂
Inne rozwiązania
Innym rozwiązaniom niż powyższe przyglądałem się bardzo krótko, często kończąc na ich cenie 😉 Gdy Galera Cluster okaże się niewystarczający, lub z jakichś względów nieodpowiedni, to powrócę do poszukiwania możliwych rozwiązań.