...

Fragmentacja pamięci w hostingu internetowym: pułapka wydajnościowa dla PHP i MySQL

Fragmentacja pamięci W hostingu internetowym PHP-FPM i MySQL działają wolniej, mimo że wydaje się, że jest wystarczająca ilość pamięci RAM, ponieważ pamięć jest podzielona na wiele małych bloków, a większe alokacje kończą się niepowodzeniem. Pokażę w praktyce, jak fragmentacja podnosi koszty zapytań, uruchamia swap i dlaczego celowe dostrojenie PHP i MySQL wyraźnie poprawia czasy ładowania, niezawodność i skalowalność.

Punkty centralne

  • PHP-FPM Recykling: regularne ponowne uruchamianie procesów za pomocą pm.max_requests
  • Bufor Dozowanie: zachowaj konserwatywne ustawienia bufora MySQL na połączenie
  • Zamiana Unikaj: zmniejsz swappiness, zwróć uwagę na NUMA
  • Tabele Konserwacja: sprawdź Data_free, zoptymalizuj w sposób ukierunkowany
  • Monitoring Wykorzystaj: dostrzegaj trendy wcześnie i działaj

Co oznacza fragmentacja pamięci w codziennej pracy hostingu?

W hostingu spotyka się Fragmentacja na długotrwałe procesy, które nieustannie żądają i zwalniają pamięć, powodując luki w przestrzeni adresowej. Chociaż suma wolnej pamięci RAM wydaje się duża, brakuje spójnych bloków dla większych alokacji, co spowalnia próby alokacji. Obserwuję to w procesach PHP‑FPM i mysqld, które po kilku godzinach wydają się coraz bardziej „nadęte“. Efekt ten sprawia, że każde żądanie jest minimalnie droższe, a czasy odpowiedzi pod obciążeniem zauważalnie się wydłużają. W rezultacie szczyty, takie jak akcje sprzedażowe lub tworzenie kopii zapasowych, stają się hamulcem, mimo że procesor i sieć pozostają niezmienione.

Dlaczego PHP-FPM powoduje fragmentację

Każdy moduł roboczy PHP‑FPM ładuje kod, wtyczki i dane do własnego Przestrzeń adresowa, obsługuje najróżniejsze żądania i pozostawia rozproszone luki podczas zwalniania. Z czasem procesy rosną i zwalniają pamięć wewnętrzną, ale niekoniecznie do systemu operacyjnego, co powoduje wzrost fragmentacji. Różne skrypty, zadania importu i przetwarzanie obrazów wzmacniają tę mieszankę i prowadzą do zmiennych wzorców alokacji. Obserwuję to jako stopniowy wzrost pamięci RAM, mimo że obciążenie i ruch wydają się stałe. Bez recyklingu ta wewnętrzna fragmentacja spowalnia alokację i utrudnia planowanie przy dużej liczbie odwiedzających.

Wyraźny wpływ na czas ładowania i niezawodność

Fragmentowane procesy generują więcej Nad głową w zarządzaniu pamięcią, co objawia się spowolnieniem działania zaplecza administracyjnego i opóźnieniami przy realizacji transakcji. Szczególnie sklepy WordPress lub duże instancje CMS reagują opieszale, gdy wiele równoczesnych żądań trafia do fragmentarycznych procesów roboczych. Skutkuje to przekroczeniem limitów czasu, błędami 502/504 i zwiększoną liczbą ponownych prób po stronie NGINX lub Apache. Takie sytuacje odczytuję w metrykach, takich jak szczyty czasu odpowiedzi, rosnąca linia bazowa pamięci RAM i nagły wzrost wykorzystania pamięci wymiany. Ignorowanie tego powoduje utratę wydajności, pogorszenie komfortu użytkowania i wzrost wskaźnika rezygnacji w krytycznych lejkach.

Prawidłowe ustawienie PHP-FPM: limity, pule, recykling

Stawiam na realistyczne Ograniczenia, oddzielne pule i konsekwentny recykling w celu ograniczenia fragmentacji. pm.max_requests kończę w taki sposób, aby pracownicy regularnie rozpoczynali pracę od nowa, nie przeszkadzając obecnym użytkownikom. W przypadku profili ruchu z szczytami obciążenia często lepiej sprawdza się pm = dynamic, natomiast pm = ondemand pozwala zaoszczędzić pamięć RAM w przypadku spokojnych witryn. Celowo utrzymuję memory_limit na umiarkowanym poziomie dla każdej witryny i dostosowuję go do rzeczywistych skryptów; wprowadzenie do tego tematu znajduje się w sekcji Limit pamięci PHP. Dodatkowo dzielę projekty o dużym obciążeniu na osobne pule, aby jeden projekt zajmujący dużo pamięci nie wpływał negatywnie na wszystkie strony.

OPcache, preloading i PHP-Allocator w skrócie

Aby ograniczyć fragmentację w procesie PHP, stawiam na odpowiednio dobrany OPcache. Duża, ale nie przesadzona wartość opcache.memory_consumption i wystarczająca liczba internowanych ciągów znaków zmniejszają liczbę powtarzających się alokacji na żądanie. Obserwuję współczynnik trafień, marnotrawstwo i pozostałą pojemność; jeśli marnotrawstwo rośnie w czasie, lepiej jest zaplanować ponowne załadowanie niż pozwolić na niekontrolowany wzrost liczby pracowników. Wstępne ładowanie może utrzymać gorący kod stabilnie w pamięci, wyrównując w ten sposób wzorce alokacji, pod warunkiem, że baza kodu jest odpowiednio przygotowana. Dodatkowo zwracam uwagę na Wybór alokatora: W zależności od dystrybucji PHP‑FPM i rozszerzenia działają z różnymi implementacjami malloc. Alternatywne alokatory, takie jak jemalloc, w niektórych konfiguracjach znacznie zmniejszają fragmentację. Jednak wprowadzam takie zmiany tylko po przetestowaniu, ponieważ debugowanie, profilowanie DTrace/eBPF i zrzuty pamięci reagują różnie w zależności od alokatora.

W przypadku zadań wymagających dużej ilości pamięci, takich jak przetwarzanie obrazów lub eksportowanie, preferuję oddzielne pule z bardziej restrykcyjnymi limitami. Dzięki temu główna pula nie rośnie w sposób niekontrolowany, a fragmentacja pozostaje izolowana. Ponadto ograniczam rozszerzenia wymagające dużej ilości pamięci (np. poprzez zmienne środowiskowe) i stosuję backpressure: żądania wymagające dużych buforów są ograniczane lub przenoszone do asynchronicznych kolejek, zamiast obciążać jednocześnie wszystkich pracowników.

Zrozumienie pamięci MySQL: bufory, połączenia, tabele

W MySQL rozróżniam globalne Bufor takie jak bufor InnoDB, bufor na połączenie i struktury tymczasowe, które mogą rosnąć wraz z każdą operacją. Zbyt duże wartości powodują przy dużym obciążeniu połączeń gwałtowny wzrost zapotrzebowania na pamięć RAM i większą fragmentację na poziomie systemu operacyjnego. Ponadto tabele ulegają fragmentacji w wyniku aktualizacji/usuwania i pozostawiają części Data_free, które pogarszają wykorzystanie puli buforów. Dlatego regularnie sprawdzam rozmiar, współczynniki trafień i liczbę tymczasowych tabel dyskowych. Poniższy przegląd pomaga mi trafnie przyporządkować typowe objawy i rozważyć odpowiednie działania.

Objaw Prawdopodobna przyczyna Pomiar
RAM stale rośnie, swap zaczyna działać Zbyt duża pula buforów lub zbyt wiele buforów na połączenie Ogranicz rozmiar puli do odpowiedniej wielkości, zmniejsz bufor połączeń
Wiele powolnych sortowań/łączeń Brakujące indeksy, nadmierne bufory sortowania/łączenia Sprawdź indeksy, zachowaj konserwatywne sortowanie/łączenie
Duża ilość danych wolnych w tabelach Silne aktualizacje/usunięcia, rozdrobnione strony Ukierunkowana OPTYMALIZACJA, archiwizacja, usprawnienie schematu
Szczyty w tymczasowych tabelach dyskowych Zbyt mała wartość tmp_table_size lub nieodpowiednie zapytania Umiarkowane podwyższanie wartości, przebudowa zapytań

Optymalizacja pamięci MySQL: wybieraj rozmiary zamiast przesadzać

Wybieram pulę buforów InnoDB tak, aby System operacyjny wystarczającą ilość pamięci dla pamięci podręcznej systemu plików i usług, zwłaszcza w przypadku serwerów kombinowanych z internetem i bazą danych. Bufory połączeń, takie jak sort_buffer_size, join_buffer_size i read-Buffer, skaluję konserwatywnie, aby wiele jednoczesnych połączeń nie prowadziło do przeciążenia pamięci RAM. tmp_table_size i max_heap_table_size ustawiam tak, aby nieistotne operacje nie wymagały ogromnych tabel w pamięci. Więcej opcji regulacyjnych można znaleźć pod adresem Wydajność MySQL Pomocne wskazówki do przemyśleń. Najważniejsze pozostaje to, że wolę ustawić nieco mniejszą wartość i dokonać pomiaru, niż ślepo zwiększać moc i ryzykować fragmentację oraz swap.

InnoDB w szczegółach: strategie przebudowy i instancje puli

Aby MySQL był bardziej „kompaktowy“ wewnętrznie, planuję regularne Przebudowy dla tabel z dużą ilością operacji zapisu. Celowe OPTIMIZE TABLE (lub przebudowa online za pomocą ALTER) łączy dane i indeksy oraz zmniejsza Data_free. Wybieram przedziały czasowe o niskim obciążeniu, ponieważ operacje przebudowy są intensywne pod względem operacji wejścia/wyjścia. Opcja innodb_file_per_table Uważam to za aktywne, ponieważ umożliwia kontrolowane przebudowy za pomocą tabeli i zmniejsza ryzyko, że pojedyncze „problematyczne elementy“ spowodują fragmentację całego pliku przestrzeni tabel.

Korzystam z kilku Instancje puli buforów (innodb_buffer_pool_instances) w stosunku do wielkości puli i rdzeni procesora, aby odciążyć wewnętrzne zatrzaski i rozłożyć dostęp. Poprawia to nie tylko równoległość, ale także wyrównuje wzorce alokacji w puli. Dodatkowo sprawdzam rozmiar dzienników redo i aktywność wątków czyszczących, ponieważ zgromadzona historia może zajmować pamięć i operacje wejścia/wyjścia, co zwiększa fragmentację na poziomie systemu operacyjnego. Ważne jest, aby zmieniać ustawienia stopniowo, mierzyć i zachowywać je tylko wtedy, gdy opóźnienia i wskaźniki błędów faktycznie spadają.

Unikanie swapowania: ustawienia jądra i NUMA

Gdy tylko Linux aktywuje swapowanie, czasy odpowiedzi wzrastają o rzędy wielkości, ponieważ dostęp do pamięci RAM staje się zbyt wolny. Znacznie obniżam vm.swappiness, aby jądro dłużej korzystało z fizycznej pamięci RAM. Na hostach z wieloma procesorami sprawdzam topologię NUMA i w razie potrzeby aktywuję interleaving, aby zmniejszyć nierównomierne wykorzystanie pamięci. Jeśli chodzi o tło i wpływ sprzętu, pomaga mi perspektywa Architektura NUMA. Dodatkowo planuję rezerwy bezpieczeństwa dla pamięci podręcznej stron, ponieważ wyczerpana pamięć podręczna przyspiesza fragmentację całego komputera.

Przejrzyste ogromne strony, nadmierne przydzielanie i wybór alokatora

Przejrzyste ogromne strony (THP) mogą powodować szczyty opóźnień w bazach danych, ponieważ scalanie/dzielenie dużych stron odbywa się w nieodpowiednim momencie. Ustawiam THP na „madvise“ lub wyłączam tę funkcję, jeśli MySQL reaguje zbyt wolno pod obciążeniem. Jednocześnie zwracam uwagę na Nadmierne zaangażowanie: Zbyt hojna konfiguracja vm.overcommit_memory grozi wystąpieniem błędów OOM właśnie wtedy, gdy fragmentacja sprawia, że duże, spójne bloki stają się rzadkością. Preferuję konserwatywne ustawienia overcommit i regularnie sprawdzam logi jądra pod kątem oznak obciążenia pamięci.

The Wybór alokatora Warto przyjrzeć się temu na poziomie systemu. glibc‑malloc, jemalloc lub tcmalloc zachowują się różnie pod względem fragmentacji. Zawsze testuję alternatywy w izolacji, mierzę przebieg RSS i opóźnienia i wdrażam zmiany tylko wtedy, gdy wskaźniki pozostają stabilne w rzeczywistym ruchu. Korzyści różnią się znacznie w zależności od obciążenia, kombinacji rozszerzeń i wersji systemu operacyjnego.

Rozpoznawanie fragmentacji: wskaźniki i wskazówki

Zwracam uwagę na powolny wzrost linie bazowe w przypadku pamięci RAM, więcej odpowiedzi 5xx pod obciążeniem i opóźnienia w działaniach administratora. Spojrzenie na statystyki PM PHP-FPM pokazuje, czy procesy potomne osiągają limity lub działają zbyt długo. W MySQL sprawdzam współczynniki trafień, tymczasowe tabele na dysku i Data_free dla każdej tabeli. Równolegle pomocne są wskaźniki systemu operacyjnego, takie jak błędy stron, swap-in/out i wskaźniki fragmentacji pamięci, w zależności od wersji jądra. Łącząc te sygnały, można wcześnie rozpoznać wzorce i zaplanować odpowiednie działania.

Wzmocnienie monitorowania: jak łączę sygnały

Koreluję Metryki aplikacji (opóźnienia p95/p99, wskaźniki błędów) z wskaźniki procesowe (RSS na FPM‑Worker, pamięć mysqld) oraz Wartości OS (Pagecache, Slab, Major Faults). W PHP‑FPM używam interfejsu statusowego do sprawdzania długości kolejek, aktywnych/spawned Children oraz czasu życia workerów. W MySQL obserwuję Created_tmp_disk_tables, Handler_write/Handler_tmp_write oraz Buffer‑Pool‑Misses. Dodatkowo sprawdzam mapy procesów (pmap/smaps), aby dowiedzieć się, czy powstało wiele małych aren. Ważne jest dla mnie orientacja na trendy: to nie pojedynczy szczyt, ale stopniowe przesunięcie w ciągu godzin/dni decyduje o tym, czy fragmentacja stanie się prawdziwym zagrożeniem.

Praktyczna rutyna: konserwacja i pielęgnacja danych

Regularnie sprzątam Dane na: wygasłe sesje, stare logi, niepotrzebne rewizje i porzucone pamięci podręczne. W przypadku tabel podlegających znacznym zmianom planuję ukierunkowane okna OPTIMIZE, aby połączyć fragmentaryczne strony. Duże zadania importowe lub fale cron rozkładam w czasie, aby wszystkie procesy nie wymagały jednocześnie maksymalnych buforów. W przypadku rosnących projektów wcześnie oddzielam sieć i bazę danych, aby wyizolować wzorce wymagające dużej ilości pamięci. Ta dyscyplina pozwala zachować spójność pamięci roboczej i zmniejsza ryzyko wystąpienia opóźnień typu burst.

Dokładne obliczanie rozmiarów: wymiarowanie limitów i pul

Określam pm.max_children na podstawie faktycznie dostępnej pamięci RAM dla PHP. W tym celu mierzę średni RSS pracownika pod rzeczywistym obciążeniem (w tym rozszerzenia i OPcache) i dodaję margines bezpieczeństwa na szczyty. Przykład: na hoście 16 GB rezerwuję 4–6 GB dla systemu operacyjnego, pamięci podręcznej stron i MySQL. Pozostaje 10 GB dla PHP; przy 150 MB na pracownika daje to teoretycznie 66 potomków. W praktyce ustawiam pm.max_children na ~80–90% tej wartości, aby pozostawić margines na szczyty, czyli około 52–58. pm.max_requests Wybieram tak, aby pracownicy byli ponownie zatrudniani przed zauważalną fragmentacją (często w zakresie 500–2000, w zależności od kombinacji kodu).

W przypadku MySQL obliczam Pula buforowa z aktywnej wielkości danych, a nie z całkowitej wielkości bazy danych. Ważne tabele i indeksy powinny się zmieścić, ale pamięć podręczna systemu operacyjnego potrzebuje miejsca na binlogi, gniazda i zasoby statyczne. W przypadku bufora połączeń obliczam maksymalną realistyczną równoległość. Jeśli możliwe jest 200 połączeń, nie wymiaruję tak, aby w sumie eksplodowało kilka gigabajtów na połączenie, ale ustalam twarde granice, które nawet w szczytowym momencie nie stanowią zagrożenia dla swapowania.

Oddzielenie kolejek, przetwarzania obrazów i zadań dodatkowych

Wiele problemów związanych z fragmentacją powstaje, gdy praca dorywcza te same pule, co żądania frontendowe. Do eksportów, indeksowania, konwersji obrazów lub aktualizacji indeksów wyszukiwania używam oddzielnych pul FPM lub zadań CLI z jasnymi ulimitOgraniczam operacje na obrazach za pomocą GD/Imagick dodatkowymi limitami zasobów, aby pojedyncze ogromne konwersje nie „rozbijały“ całej przestrzeni adresowej. Planuję zadania w czasie i nadaję im własne limity współbieżności, aby nie obciążały ścieżki frontendu.

Kontenery i wirtualizacja: Cgroups, OOM i efekty balonu

W kontenerach obserwuję Limity pamięci i OOM‑Killer szczególnie dokładnie. Fragmentacja powoduje, że procesy uruchamiają się wcześniej na granicach Cgroup, mimo że w hoście jest jeszcze wolna pamięć RAM. Ustawiam pm.max_children ściśle zgodnie z limitem kontenera i zachowuję wystarczającą rezerwę, aby złagodzić szczyty. Unikam swappingu w kontenerach (lub na hoście), ponieważ dodatkowa pośrednia operacja zwiększa szczyty opóźnień. W maszynach wirtualnych sprawdzam balonowanie i KSM/UKSM; agresywna deduplikacja oszczędza pamięć RAM, ale może powodować dodatkowe opóźnienia i zafałszować obraz fragmentacji.

Krótka lista kontrolna bez punktorów

Najpierw ustalam realistyczny pamięć_limit na stronę i obserwuję, jak zmienia się szczytowe wykorzystanie w ciągu dni. Następnie dostosowuję PHP-FPM za pomocą odpowiednich wartości pm i sensownego pm.max_requests, aby fragmentowane procesy działały zgodnie z planem. W przypadku MySQL skupiam się na odpowiedniej wielkości puli buforów i konserwatywnych buforach na połączenie zamiast na ogólnym zwiększaniu rozmiaru. Po stronie jądra zmniejszam swappiness, sprawdzam ustawienia NUMA i utrzymuję rezerwy dla pamięci podręcznej stron. Na koniec oceniam tabele z nietypowymi wartościami Data_free i planuję optymalizacje poza codzienną działalnością.

W skrócie: co liczy się w przedsiębiorstwie

Największy efekt w walce z fragmentacją pamięci osiągam dzięki konsekwentnemu Recykling pracownika PHP‑FPM, umiarkowanych limitów i czystych pul. MySQL korzysta z rozsądnych rozmiarów puli buforów i buforów na połączenie, a także z uporządkowanych tabel. Proaktywnie unikam swapowania, zwracając uwagę na swappiness i NUMA oraz rezerwując wolną pamięć RAM dla pamięci podręcznej plików. Monitorowanie pozwala wykryć niepożądane wzorce, zanim użytkownicy je zauważą, i umożliwia spokojne, zaplanowane interwencje. Kto dyscyplinarnie korzysta z tych narzędzi, utrzymuje PHP i MySQL szybsze, bardziej niezawodne i ekonomiczne bez konieczności natychmiastowej modernizacji sprzętu.

Artykuły bieżące