Jak powszechnie wiadomo, zanim człowiek jest w stanie osiągnąć maksymalny poziom wysiłku fizycznego dobrze jest się wcześniej rozgrzać. Podobnie jest z MySQL. Świeżo uruchomiony serwer MySQL to często dziesiątki gigabajtów cache i buforów, które czekają na zapełnienie. Dane znajdują się na dysku i muszą być dopiero wczytane do pamięci. W przypadku dużej ilości danych i dużych ilości pamięci ten proces trwa długo i przez ten czas serwer nie funkcjonuje z największą możliwą wydajnością. Przygotowałem krótki benchmark który, mam nadzieję, pokaże wyraźnie dlaczego ten cały „hot start” jest taki ważny.

Baza testowa to 35GB danych przygotowanych przez sysbench. Buffer pool ustawiony jest na 25G.

Oś x to czas w sekundach, oś y to liczba SELECTów na sekundę. Jak widać, przez ok. 20 minut wydajność jest poniżej średniej z okresu stabilnej pracy (który jest widoczny między 40 a 60 minutą). W tym czasie serwer nie funkcjonuje tak, jak powinien. To rodzi problemy – zapytania nie są wykonywane tak szybko jak zazwyczaj, zaczyna wisieć ich coraz więcej, serwer jest coraz bardziej przeciążony. Może to doprowadzić do sytuacji, w której serwer przez dłuższy czas nie będzie w stanie funkcjonować normalnie. Są na to różne sposoby, choćby wymuszenie mniejszej liczby zapytań przez ograniczenie maksymalnej liczby połączeń do serwera na czas rozgrzania wszystkich cache i buforów. Tak czy owak, wpływa to negatywnie na funkcjonowanie serwisu opartego o startujący serwer MySQL.

Problem ten jest na tyle poważny, że Percona przygotowała patch, który w pewien sposób umożliwia rozgrzanie buffer pool. W technikalia nie wchodzę, metoda ta bazuje na bebechach InnoDB i liście LRU (least recently used). Zasada działania jest prosta. Przed restartem serwera zrzucamy zawartość bufora:

mysql> SELECT * FROM information_schema.XTRADB_ADMIN_COMMAND /*!XTRA_LRU_DUMP*/;
+------------------------------+
| result_message               |
+------------------------------+
| XTRA_LRU_DUMP was succeeded. |
+------------------------------+
1 row in set (0.32 sec)

Po restarcie buffer pool jest praktycznie pusty:

mysql> SHOW GLOBAL STATUS LIKE '%pool_pages%';
+----------------------------------+---------+
| Variable_name                    | Value   |
+----------------------------------+---------+
| Innodb_buffer_pool_pages_data    | 17      |
| Innodb_buffer_pool_pages_dirty   | 0       |
| Innodb_buffer_pool_pages_flushed | 0       |
| Innodb_buffer_pool_pages_free    | 1638382 |
| Innodb_buffer_pool_pages_misc    | 0       |
| Innodb_buffer_pool_pages_total   | 1638399 |
+----------------------------------+---------+
6 rows in set (0.00 sec)

Wgrywamy więc zrzuconą zawartość:

mysql> SELECT * FROM information_schema.XTRADB_ADMIN_COMMAND /*!XTRA_LRU_RESTORE*/;
+---------------------------------+
| result_message                  |
+---------------------------------+
| XTRA_LRU_RESTORE was succeeded. |
+---------------------------------+
1 row in set (2 min 33.77 sec)

Zamiast 20 minut niestabilnej pracy przywrócenie zawartości bufora trwa 2,5 minuty. Dodatkowo dump można obrobić (posortować) udostępnianym na stronie Percony skryptem, tak aby jego wgrywanie było jeszcze bardziej efektywne. Efekt:

mysql> SHOW GLOBAL STATUS LIKE '%pool_pages%';
+----------------------------------+---------+
| Variable_name                    | Value   |
+----------------------------------+---------+
| Innodb_buffer_pool_pages_data    | 1576248 |
| Innodb_buffer_pool_pages_dirty   | 0       |
| Innodb_buffer_pool_pages_flushed | 0       |
| Innodb_buffer_pool_pages_free    | 62151   |
| Innodb_buffer_pool_pages_misc    | 0       |
| Innodb_buffer_pool_pages_total   | 1638399 |
+----------------------------------+---------+
6 rows in set (0.00 sec)

Buffer pool został zapełniony. Na wykresie wygląda to następująco:

Jak widać, serwer praktycznie od początku działa z pełną wydajnością, nie obserwujemy stopniowego, od zera, przyrostu wydajności, jak to było w poprzednim wypadku. Downtime został wydłużony o 2,5 minuty, ale dzięki temu serwer od razu po starcie jest w stanie obsłużyć normalny ruch. W przypadku, gdy serwery pracują na granicy swoich możliwości to jest jedyny sposób, aby w miarę bezbolesny sposób obsłużyć restart serwera.

Jeśli stosujemy jakikolwiek system cache – memcached, flashcache, wszystko co jest szybsze od nośnika, na którym znajdują się dane,  albo co odciąża CPU na serwerach bazodanowych, trzeba za planować jak ma wyglądać restart. Po restarcie cache może być pusty. Wtedy wszystkie dane będą pobierane z wolniejszego nośnika, a wyniki zapytań nie będą wystawiane przez memcached, tylko trafią w MySQL i będą potrzebować zasobów CPU . Powyższy przykład to rozwiązanie na problem z buffer pool. Rozgrzewanie innego rodzaju cache trzeba przemyśleć samodzielnie. Gdyby się to jednak nie udało i całość serwisu się wysypała, zawsze można się pocieszyć stwierdzeniem, że jest się w doborowym towarzystwie:

http://www.zdnet.co.uk/news/systems-management/2010/09/26/facebook-outage-due-to-internal-errors-says-company-40090273/