W trakcie ostatnich kilku tygodni nie bardzo miałem czas na pisanie na blogu, chciałbym jednak zapewnić, że blog nie umarł. Dziś tak kilka przemyśleń dotyczących planowania struktury bazy danych i rodzajów zapytań. Może będzie to komuś przydatne.

Zacznijmy od stwierdzenia prostego faktu, który, choć wydawałoby się że oczywisty, możliwe że nie dla każdego developera takim jest w istocie.

MySQL nie jest w stanie obsłużyć jednego zapytania przy pomocy wielu rdzeni procesora. Kropka. To ograniczenie jest, wbrew pozorom, bardzo istotne i w żadnym wypadku nie można o nim zapominać. Dlaczego? Dzisiejsze procesory skalują się w górę przy pomocy ilości rdzeni. Taktowanie zegara pozostaje z grubsza na tym samym poziomie, co kilka lat temu. Owszem, wydajność samych układów pewnie też idzie w górę i z jednego megaherca wyciągniemy obecnie więcej niż wtedy, ale przyrost ten nie jest w żaden sposób porównywalny z przyrostem ilości rdzeni jaki nastąpił w tym samym okresie czasu. Dla osoby tworzącej zapytania oznacza to z grubsza tyle, że dane jedno zapytanie, wykonujące się na jednym rdzeniu, przez ostatnie parę lat nie wiele przyspieszyło. Owszem, jeśli zapytań mamy dziesiątki jednocześnie, to wtedy jak najbardziej – wykorzystujemy to, co postęp nam przyniósł. W przypadku pojedynczego zapytania – praktycznie nie.

Jeśli planujemy wykonywać duże ilości operacji na rekordach poprzez np. skomplikowane SELECTy generujące duże tabele tymczasowe, funkcje i procedury składowane, które realizują logikę aplikacji, kursory na kilku milionach rekordów, które potem są w dalszej części obrabiane – tego typu operacje wymagają dużych zasobów procesora. Oczywiście, zakładając że wszystko jest w pamięci i dysk nie stanowi problemu. Na potrzeby tego postu załóżmy że tak jest w istocie.

W przypadku tego typu operacji wąskim gardłem będzie właśnie ograniczenie do jednego rdzenia. Owszem, możemy napisać procedurę składowaną, która zrealizuje jakieś tam operacje na kilku tabelach, milionach rekordów i tak dalej. Efektem będzie to, że taka procedura będzie się wykonywała godzinami. Najgorsze jest połączenie tego typu stylu tworzenia aplikacji z typowym dla części programistów podejściem: “jak działa, to fajnie – zostawiamy”. Takie operacje zazwyczaj działają bez problemu na niewielkim zestawie danych. Dane mają jednak do siebie to, że najczęściej w jakimś tam tempie przyrastają. Po pewnym czasie okazuje się, że procedura nie jest w stanie wykonać się w sensownym czasie. Co wtedy można zrobić? Tu pojawia się problem – w zasadzie niewiele. Tak na prawdę, to należałoby przepisać wszystko w całości. Od strony sprzętu nie da się zrobić nic. Możemy kupić najszybszy i najdroższy procesor na rynku i za tysiące dolarów uzyskać przyrost prędkości rzędu 70 – 80%. Z dwóch godzin wykonywania się procedury zejdziemy do godziny z kawałkiem. W dodatku nie bardzo będziemy mieli już jakieś perspektywy na dalszy progres. Nie o taki postęp chodzi.

Pisząc aplikację wykorzystującą MySQL trzeba zawsze pamiętać o tym ograniczeniu. Jeśli tylko da się w jakiś sposób rozdzielić operację na części możliwe do wykonania jednocześnie, trzeba to zrobić. Każde dodatkowe zapytanie zwiększa możliwość wykorzystania zasobów procesora. Jeśli da się wykonać jednocześnie kilka UPDATE na tej samej tabeli zamiast jednego dużego – zróbmy to. Jeśli da się wywołać jednocześnie kilka instancji procedury składowanej, które łącznie zrealizują całą operację – zróbmy to. W ten sposób nasza aplikacja ma szansę skalować się na nowym sprzęcie – wraz z ilością rdzeni zwiększa się szybkość realizacji całego zestawu zapytań. Każdego pojedynczego, z osobna – nie, ale wszystkich łącznie – już tak. Oczywiście, o ile zapytań jest tyle samo bądź więcej niż rdzeni.

Pamiętajmy o tym drobnym niuansie podczas tworzenia zapytań. W przeciwnym wypadku może się okazać, że nasza aplikacja nie jest w stanie się skalować gdy potrzebna jest większa wydajność i szybsze działanie.