Jak już kilkukrotnie na łamach tego bloga pisałem, od kilku lat pracuję nad przeglądarkową grą MMORPG, której rozgrywka osadzona jest kosmosie. Cytując mój wpis sprzed półtorej roku:

W Pulsar Online to gra przeglądarkowa typu MMORPG w której wcielasz się pilota statku kosmicznego i zostajesz przeniesiony do wszechświata Pulsar. Wszechświat jest podzielony na systemy, a systemy na sektory. W systemach znajduję się sektory różnych typów (asteroidy, mgławica, itp.) różniące się parametrami oraz zasobami jakie w nich występują. Oprócz tego, w systemach są ulokowane stacje i porty kosmiczne. Gracz ma możliwość poruszania się wewnątrz systemów jak i pomiędzy nimi (używając Wrót Skoku), dokowania do portów i stacji, kupna i sprzedaży towarów, zbierania zasobów z przestrzeni kosmicznej. Dzięki handlowi i zbieractwu można kupować lepsze statki, wyposażenie i uzbrojenie. Posiadając odpowiedni statek można walczyć z innymi graczami.

Ostatnimi czasy znów przysiadłem fałd i zintensyfikowałem prace nad grą. Wielkimi krokami zbliża się kolejna (ostatnia) wersja Alfa i publiczna Beta. Jeśli wszystko pójdzie zgodnie z planem, gra ujrzy światło dzienne na początku wakacji 2011. Na dzień dzisiejszy, termin pierwszego lipca jest jak najbardziej realny.

Tworząc silnik Pulsara postawiłem sobie za zadanie, aby był jak najbardziej elastyczny i umożliwiał wdrożenie zarówno na zwykłym hostingu współdzielonym, jak i serwerze dedykowanym. Decyzja była więc (przynajmniej dla mnie) oczywista: MySQL 5.

Doświadczenia zebrane podczas prac na moją poprzednią grą, czyli Omricon Beta jasno wykazały, że najwęższym gardłem będzie, jak zwykle, baza danych. Optymalizacja zapytań i ograniczenie ich liczby ma kluczowe znaczenie dla płynności gry. Strata każdej milisekundy na zapytaniu może skutkować zablokowaniem dostępu do danych i w efekcie zatrzymać cały silnik. Z tego powodu odrzuciłem rozwiązania typu PDO, które ze względu na zbyt duży (relatywnie) narzut sterownika były za mało wydajne. Standardowy sterownik mysql_ do PHP, choć teoretycznie najszybszy, także został odrzucony. Nie dość, że nie jest rozwijany od dłuższego czasu, to w dodatku nie wspiera możliwości MySQL 5 takich jak prepared statements, transakcje, czy wielokrotne zapytania i procedury. Na placu boju pozostało tylko mysqli_ i to ono jest wykorzystywane przez silnik Pulsara.

Kolejną ważną decyzją jaką podjąłem projektując obsługę bazy danych, było zdecydowanie się na częściowe porzucenie modelu relacyjnego w warstwie aplikacji i stworzenie wrappera odwzorowującego pojedyncze tabele na model obiektowy. Takie rozwiązanie, nie dość, że pozwoliło na zwiększenie abstrakcyjności kodu i częściowe wyeliminowanie ręcznego składania zapytań SQL, to dzięki minimalnemu narzutowi obliczeniowemu nie spowalnia silnika. W dodatku, wrapper posiada wewnętrzny mechanizm pamięci podręcznej zmniejszający liczbę zapytań wysyłanych do serwera bazy danych (nawet w przypadku, gdyby żądanie mogło być zaspokojone z pamięci podręcznej zapytań serwera).

Baza danych Pulsara wykorzystuje 3 silniki składowania wbudowane w MySQL:

  • MyISAM - dla tabel, dla których przewidywany współczynnik zapisów jest niższy niż 0,1. W zasadzie, są to wszystkie tabele odpowiedzialne za opis świata gry i część tabel przechowujących informacje o rozgrywce
  • MEMORY - dla wybranych tabel tymczasowych o bardzo wysokim współczynniku zapisów i znacznej liczbie odczytów
  • InnoDB - dla często aktualizowanych tabel przechowujących informacje o rozgrywce

Jak widać, nie zdecydowałem się ma monolityczny model silników składowania. Wszędzie tam, gdzie zalety silnika InnoDB (blokada na poziomie rekordów, transakcje) mogą być wykorzystane, mechanizm jest w użyciu. Tabele tylko do odczytu są zarządzane przez MyISAM. Osobnym przypadkiem są tabele z danymi tymczasowymi z mechnizmem MEMORY. Dane w nich przechowywane są ważne tylko przez kilka minut, a przewidywana liczba równoczesnych rekordów nie przekracza kilkuset. Co więcej, są one często odczytywane i modyfikowane. Mechanizm MEMORY wydaje się dla nich najlepszym rozwiązaniem.

Ostatnim ważnym elementem silnika Pulsara z punktu widzenia bazy danych, są procedury utrzymaniowe świata gry takie jak generowanie towarów hadlowych w portach, zachowania NPC (Non Personal Character, boty), czy dodawanie przedmiotów w lokacjach. Silnik gry Omricon Beta wykonywał takie operacje w wyznaczonych interwałach czasowych globalnie dla całego świata gry. Takie podejście okazało się złe. Same operacje na lokacjach potrafiły na kilka minut obciążyć serwer w takim stopniu, że gra w czasie ‘resetów’ stawała się niemożliwa. Silnik Pulsara wykonuje te operacje ‘rozważniej’ i tylko w tych lokacjach, w których przebywają aktywni gracze. Przykładowo, ‘reset’ portu jest wykonywany tylko jeśli gracz pojawi się nad tym portem (nie częściej niż zdefiniowany interwał) lub wykona w nim dowolną czynność. Takie podejście pozwala na ograniczenie regularnych peaków obciążenia serwera baz danych. Co prawda, w łącznym rozrachunku, liczba operacji może być równa lub nawet większa, lecz jest rozłożona w czasie i nie skutkuje ‘przytkaniem’ silnika.