Post ten piszę ze względu na sytuację, z którą ostatnio miałem okazję się zetknąć. Serwer fizyczny współdzielony był pomiędzy MySQL a PostgreSQL, zauważalne było znaczne obciążenie podsystemu dyskowego tego serwera. Po krótkiej analizie okazało się, że przyczyną jest kiepskie zgranie się MySQL i PostgreSQL jeśli chodzi o zarządzanie pamięcią, przez co PostgreSQL generował znaczny ruch na dysku. Koniecznie było przekonfigurowanie MySQL tak aby alokował mniejszą ilość pamięci.
Dlaczego piszę o kiepskim zgraniu? MySQL, szczególnie ten, gdzie silnikiem bazodanowym jest InnoDB, ma to do siebie, że alokuje sporą ilość pamięci. Wszystkiego rodzaju globalne bufory typu query cache, innodb buffer pool, a także bufory alokowane per połączenie (join buffer, sort buffer i tak dalej) alokowane są przez proces mysqld. Tego typu zachowanie jest dobre o tyle, że MySQL po pierwsze nie musi konkurować o tą pamięć z innymi procesami – dane raz wczytane do bufora pozostaną w nim tak długo jak tylko MySQL będzie chciał, po drugie dostęp do danych realizowany przy pomocy odwołań do pamięci zaalokowanej przez proces jest szybszy niż odwoływanie się do pamięci zaalokowanej przez system operacyjny (chodzi mi konkretnie o cache dyskowy obsługiwany przez system operacyjny).
PostgreSQL z kolei (a przynajmniej wersja 8.4, nie sprawdzałem jak to wygląda w 9.1) nie alokuje pamięci na cache danych, obsługę tego pozostawia mechanizmom systemowym. Jeśli teraz na jednym serwerze umieścimy zarówno MySQL jak i PostgreSQL, PostgreSQL zaczyna mieć problemy.
Mechanizm jest prosty. MySQL alokuje pamięć na bufory i cache. Jest jakiś ruch na bazach, tak więc dane do cache czy innodb buffer pool trafiają. Często może być tak, że trafiają tam dane, które nie są danymi aktywnymi – w buforze wylądowały np. podczas wykonywania kopii zapasowej. Nie są jednak usuwane, bo aktywny zestaw danych jest na tyle mały, że wszystko się ładnie w pamięci mieści. W efekcie alokujemy np. 10GB bufer pool z czego połowa to dane aktywne a połowa – dane wykorzystywane parę razy na dzień. Nadal jest to jednak 10GB, które zostało zaalokowane przez MySQL i do którego żadna inna usługa nie będzie miała dostępu. PostgreSQL, który korzysta z cache systemowego, będzie musiał zadowolić się tym, co MySQL pozostawił, pomimo tego że tak na prawdę spora część tej pamięci MySQL w danym momencie nie jest potrzebna. Brak cache skutkuje oczywiście skokiem obciążenia dysków. Gdyby MySQL dane trzymał w cache systemowym, współdzielenie serwera byłoby łatwiejsze – sam system operacyjny zadbałby o to, żeby w cache znajdowały się najbardziej przydatne dane, nie zależnie od tego który proces tam by je wrzucił. Jako że tak nie jest (co, jak pisałem, z punktu widzenia samego MySQL jest w sumie lepszym rozwiązaniem), trzeba sobie radzić inaczej – jeśli planujemy współdzielić serwer trzeba po prostu minimalizować wielkość buforów, które ma alokowac MySQL. Lepiej ustawić je mniejsze i ewentualnie zwiększać niż ustawić za duże. Czasem trudno się zorientować czy te 10GB to faktycznie dane potrzebne. Natomiast zorientować się czy bufor nie jest za mały jest prościej – MySQL udostępnia statystyki informujące o tym ile było odczytów, jaka część z nich poszła z bufora a jaka z dysku. Dodatkowo, jeśli stosujemy InnoDB, koniecznie trzeba włączyć tryb O_DIRECT. W przeciwnym razie dane będą buforowane dwukrotnie – raz w buffer pool, dwa w cache systemowym.
Jak widać, współdzielony serwer to już trochę bardziej problematyczna sprawa do administracji niż serwer pojedynczy, dedykowany dla danej usługi. Niestety, jako że najczęściej, ze względu na koszty, tak właśnie wygląda infrastruktura serwerowa, trzeba mieć na uwadze dokładniejsze niż zazwyczaj analizowanie stanu serwera – monitoring, okresowe zrzuty informacji o serwerze itp.
Komentarze