Prawidłowa konfiguracja zarządzania procesami PHP-FPM: wyjaśnienie pm.max_children & Co.

Optymalizacja php-fpm decyduje, ile procesów PHP-FPM może działać jednocześnie, jak szybko uruchamiane są nowe procesy i jak długo obsługują one zapytania. Pokażę Ci, jak pm.max_children, pm, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers i pm.max_requests tak, aby Twoja aplikacja szybko reagowała pod obciążeniem, a serwer nie przechodził w tryb swappingu.

Punkty centralne

  • tryb pm: wybierz właściwą opcję – statyczną, dynamiczną lub na żądanie – aby procesy były dostosowane do Twojego ruchu.
  • pm.max_children: Dostosuj liczbę równoczesnych procesów PHP w pamięci RAM do rzeczywistego zużycia procesów.
  • Wartości początkowe/rezerwowe: pm.start_servers, pm.min_spare_servers, pm.max_spare_servers w rozsądny sposób.
  • Recykling: Zmniejsz wycieki pamięci za pomocą pm.max_requests bez generowania niepotrzebnego obciążenia.
  • Monitoring: Obserwuj logi, status i pamięć RAM, a następnie stopniowo dostosowuj ustawienia.

Dlaczego zarządzanie procesami ma znaczenie

Kieruję PHP-FPM wykonanie każdego skryptu PHP jako osobnego procesu, a każde równoległe zapytanie wymaga własnego modułu roboczego. Bez odpowiednich limitów zapytania blokują kolejki, co prowadzi do Limity czasu i błędów. Jeśli ustawisz zbyt wysokie limity, pula procesów zużyje całą pamięć operacyjną, a jądro zacznie swappen. Ta równowaga nie jest zgadywanką: opieram się na rzeczywistych wartościach pomiarowych i zachowuję margines bezpieczeństwa. Dzięki temu opóźnienie pozostaje niskie, a przepustowość stabilna, nawet gdy obciążenie gwałtownie wzrasta.

Ważna jest dla mnie jasna wartość docelowa: Ile jednoczesnych wykonań PHP chcę umożliwić bez wyczerpania pamięci RAM? Jednocześnie sprawdzam, czy wąskie gardła występują raczej w Baza danych, w zewnętrznych interfejsach API lub na serwerze internetowym. Tylko znając wąskie gardło, mogę wybrać odpowiednie wartości dla pm, pm.max_children i innych. Zaczynam ostrożnie, mierzę, a następnie stopniowo zwiększam wartości. W ten sposób unikam twardych restartów i nieoczekiwanych awarii.

Trzy tryby pm: static, dynamic, ondemand

Tryb statyczny zawsze utrzymuje dokładnie pm.max_children procesów. Zapewnia to bardzo przewidywalne opóźnienia, ponieważ nie jest wymagany żaden proces uruchamiania. Używam static, gdy obciążenie jest bardzo równomierne i dostępna jest wystarczająca ilość pamięci RAM. Jednak w przypadku zmiennego zapotrzebowania static powoduje niewielkie straty. Pamięć. Dlatego stosuję static celowo tam, gdzie potrzebuję stałego wykonania.

Z dynamiczny uruchamiam ilość startową i pozwalam, aby rozmiar puli wahał się między min_spare a max_spare. Tryb ten nadaje się do ruchu o charakterze falowym, ponieważ pracownicy są tworzeni i likwidowani w zależności od potrzeb. Zawsze utrzymuję wystarczającą liczbę procesów bezczynnych, aby bez czekania obsługiwać szczyty. Zbyt duża liczba bezczynnych pracowników powoduje jednak niepotrzebne obciążenie. RAM, dlatego staram się utrzymywać niewielki margines bezpieczeństwa. Dzięki temu pula pozostaje elastyczna, nie ulegając nadmiernemu rozszerzeniu.

W trybie na żądanie Na początku nie ma żadnych procesów roboczych, PHP-FPM uruchamia je dopiero po otrzymaniu zapytania. Pozwala to zaoszczędzić pamięć w okresach bezczynności, ale pierwsze trafienie wiąże się z pewnym opóźnieniem. Wybieram opcję ondemand dla rzadko wywoływanych pul, narzędzi administracyjnych lub punktów końcowych cron. W przypadku często odwiedzanych stron internetowych opcja ondemand zazwyczaj zapewnia gorszy czas reakcji. W takich przypadkach zdecydowanie preferuję opcję dynamic z odpowiednio ustawionymi wartościami rezerwowymi.

Prawidłowe wymiarowanie pm.max_children

Myślę, że pm.max_children z dostępnej pamięci RAM dla PHP i średniej pamięci na pracownika. W tym celu najpierw rezerwuję pamięć dla systemu, serwera WWW, bazy danych i pamięci podręcznej, aby system nie wpadł w Outsourcing działa. Pozostałą pamięć RAM dzielę przez rzeczywiste zużycie procesora. Z teorii odejmuję 20–30 % margines bezpieczeństwa, aby wyeliminować wartości odstające i szczyty obciążenia. Wynik wykorzystuję jako wartość początkową, a następnie obserwuję efekt.

Średnie zużycie procesu ustalam za pomocą narzędzi takich jak ps, top lub htop i sprawdzam RSS/RES. Ważne: mierzę pod typowym obciążeniem, a nie w stanie spoczynku. Gdy ładuję wiele wtyczek, frameworków lub dużych bibliotek, zużycie na każdy worker wyraźnie rośnie. Ponadto procesor ogranicza krzywą: więcej procesów nie pomaga, gdy Jednowątkowy-Wydajność procesora ograniczona na każde zapytanie. Osoby zainteresowane bardziej szczegółowymi informacjami na temat charakterystyki procesora znajdą dodatkowe informacje na stronie Wydajność pojedynczego wątku.

Moje założenia są przejrzyste: ile pamięci RAM jest faktycznie dostępne dla PHP? Jak duże są typowe żądania? Jakie są wartości szczytowe? Jeśli odpowiedzi są prawidłowe, ustawiam pm.max_children, wykonuję łagodne ponowne ładowanie i sprawdzam pamięć RAM, czasy odpowiedzi oraz wskaźniki błędów. Dopiero potem przechodzę małymi krokami do wyższych lub niższych wartości.

Wartości orientacyjne według wielkości serwera

Poniższa tabela pokazuje mi Wartości początkowe . Nie zastępuje ona pomiarów, ale zapewnia solidną orientację dla wstępnych ustawień. Dostosowuję wartości do poszczególnych zastosowań i sprawdzam je za pomocą monitorowania. Jeśli rezerwy pozostają niewykorzystane, ostrożnie je zwiększam. Jeśli serwer osiąga limit pamięci RAM, zmniejszam wartości.

Pamięć RAM serwera Pamięć RAM dla PHP Ø MB/pracownik pm.max_children (Start) Użycie
1–2 GB ~1 GB 50–60 15–20 Małe strony internetowe, blogi
4–8 GB ~4–6 GB 60–80 30–80 Biznes, małe sklepy
16+ GB ~10–12 GB 70–90 100–160 Wysokie obciążenie, API, sklepy

Czytam tabelę od prawej do lewej: Czy pasuje Użycie W ramach projektu sprawdzam, czy pamięć RAM jest realistycznie zarezerwowana dla PHP. Następnie wybieram rozmiar procesora, który pasuje do bazy kodu i rozszerzeń. Następnie ustawiam pm.max_children i obserwuję efekt w czasie rzeczywistym. Skuteczność i stabilność wzrastają, gdy dokładnie dokumentuję te kroki.

Ustawianie wartości startowych, rezerwowych i żądań

Z pm.start_servers Określam, ile procesów ma być natychmiast gotowych. Zbyt niska wartość powoduje zimne starty pod obciążeniem, zbyt wysoka niepotrzebnie zajmuje pamięć RAM. Często kieruję się wartością 15–30 % z pm.max_children i zaokrąglam, jeśli obciążenie zaczyna się raczej spokojnie. W przypadku szczytów ruchu wybieram nieco wyższą wartość początkową, aby zapytania nie napływały, zanim wystarczająca liczba pracowników będzie gotowa do pracy. Ta precyzyjna regulacja znacznie skraca czas pierwszej odpowiedzi.

Wartości pm.min_spare_servers i pm.max_spare_servers definiują zakres bezczynności. Zarezerwowałem tyle wolnych procesów, aby nowe żądania miały bezpośredni dostęp, ale nie tak dużo, aby procesy bezczynności marnowały pamięć. W przypadku sklepów internetowych lubię stosować węższe okno, aby wyrównać szczyty. Dzięki pm.max_requests Recyklinguję procesy po kilkuset żądaniach, aby ograniczyć dryft pamięci. W przypadku niepozornych aplikacji wybieram 500–800, a w przypadku podejrzenia wycieków celowo wybieram mniejszą wartość.

Monitorowanie i rozwiązywanie problemów

Regularnie sprawdzam Dzienniki, strony statusowe i pamięć RAM. Ostrzeżenia dotyczące osiągnięcia limitów pm.max_children są dla mnie wyraźnym sygnałem, aby podnieść górną granicę lub zoptymalizować kod/bazę danych. Jeśli pojawiają się częste błędy 502/504, sprawdzam logi serwera WWW i kolejki. Znaczne wahania opóźnień wskazują na zbyt małą liczbę procesów, blokujące operacje wejścia/wyjścia lub zbyt wysokie koszty procesów. Najpierw sprawdzam twarde fakty, a następnie reaguję małymi krokami, nigdy nie podejmując radykalnych działań.

Szybciej dostrzegam wąskie gardła, gdy Czas oczekiwania mierzę wzdłuż całego łańcucha: serwer WWW, PHP-FPM, baza danych, usługi zewnętrzne. Jeśli czas backendu wzrasta tylko w przypadku określonych tras, izoluję przyczyny za pomocą profilowania. Jeśli czasy oczekiwania występują wszędzie, zajmuję się rozmiarem serwera i puli. Pomocne jest również przyjrzenie się kolejkom pracowników i procesom w stanie D. Dopiero gdy zrozumiem sytuację, zmieniam limity – i dokładnie dokumentuję każdą zmianę.

Współpraca serwera WWW i PHP-FPM

Upewniam się, że Serwer sieciowy-Limity i PHP-FPM współgrają ze sobą. Zbyt wiele jednoczesnych połączeń na serwerze WWW przy zbyt małej liczbie pracowników powoduje kolejki i przekroczenia limitów czasu. Jeśli liczba pracowników jest wysoka, ale serwer WWW ogranicza przyjmowanie połączeń, wydajność pozostaje na niskim poziomie. Parametry takie jak worker_connections, event-Loop i Keep-Alive mają bezpośredni wpływ na obciążenie PHP. Praktyczne wprowadzenie do precyzyjnego dostrajania zapewniają wskazówki dotyczące Pule wątków w serwerze WWW.

Zatrzymam Keep-Alive-Okno czasowe w widoku, aby połączenia bezczynne nie blokowały niepotrzebnie pracowników. W przypadku zasobów statycznych stosuję agresywne buforowanie przed PHP, aby utrzymać obciążenie z dala od puli. Pamięci podręczne odwrotnego proxy dodatkowo pomagają, gdy identyczne odpowiedzi są często wywoływane. Dzięki temu mogę utrzymać pm.max_children na niższym poziomie i nadal dostarczać szybciej. Mniejsza ilość pracy na żądanie jest często najskuteczniejszym czynnikiem regulacyjnym.

Precyzyjne śruby regulacyjne w php-fpm.conf

Wychodzę poza podstawowe wartości i dostosowuję Parametry basenu dobrze. Z pm.max_spawn_rate ograniczam szybkość tworzenia nowych procesów roboczych, aby serwer nie uruchamiał zbyt agresywnie procesów w momentach szczytowego obciążenia i nie popadał w thrashing procesora. Dla ondemand ustawiam pm.process_idle_timeout określono, jak szybko nieużywane procesy znikają – zbyt krótki czas powoduje obciążenie startowe, zbyt długi zajmuje pamięć RAM. W przypadku słuchać-Socket wybieram między Unix-Socket a TCP. Unix-Socket zmniejsza obciążenie i zapewnia przejrzystą przypisanie praw za pomocą listen.owner, listen.group oraz listen.mode. Dla obu wariantów ustawiam listen.backlog wystarczająco wysoka, aby nadchodzące pakiety trafiały do bufora jądra, zamiast być natychmiast odrzucane. Dzięki rlimit_files W razie potrzeby zwiększam liczbę otwartych plików na każdego pracownika, co zapewnia stabilność przy wielu równoczesnych operacjach pobierania i wysyłania danych. A jeśli potrzebne są priorytety, używam priorytet procesu, aby nieco mniej krytyczne pule były traktowane nieco mniej priorytetowo po stronie procesora.

Slowlog i ochrona przed zawieszaniem się systemu

Aby wyświetlić trudne żądania, aktywuję Slowlog. Z request_slowlog_timeout definiuję próg (np. 2–3 s), od którego ślad stosu jest zapisywany w slowlog jest zapisywane. W ten sposób znajduję blokujące operacje wejścia/wyjścia, kosztowne pętle lub nieoczekiwane blokady. W przypadku prawdziwych zawieszeń stosuję request_terminate_timeout, które gwałtownie się przerywa, gdy żądanie trwa zbyt długo. Uważam, że te przedziały czasowe są zgodne z max_execution_time z PHP i limitów czasu serwera WWW, aby jedna warstwa nie została przerwana wcześniej niż druga. W praktyce zaczynam ostrożnie, analizuję logi spowolnień pod obciążeniem i stopniowo dostosowuję progi, aż sygnały będą miarodajne, nie powodując zalewania logów.

Opcache, memory_limit i ich wpływ na rozmiar workerów

Odnoszę się do Opcache do mojego planowania pamięci RAM. Jego obszar pamięci współdzielonej nie jest liczony na pracownika, ale jest wspólnie wykorzystywany przez wszystkie procesy. Wielkość i fragmentacja (opcache.memory_consumption, interned_strings_buffer) mają znaczący wpływ na czas rozgrzewania i współczynnik trafień. Dobrze skalowany Opcache zmniejsza obciążenie procesora i pamięci RAM na każde zapytanie, ponieważ mniej kodu jest ponownie kompilowane. Jednocześnie zwracam uwagę na pamięć_limit: Wysoka wartość chroni przed brakiem pamięci w pojedynczych przypadkach, ale zwiększa teoretyczny budżet w najgorszym przypadku na każdego pracownika. Dlatego planuję z uwzględnieniem średniej wartości pomiarowej plus bufor, a nie samej wartości memory_limit. Funkcje takie jak wstępne ładowanie lub JIT zwiększają zapotrzebowanie na pamięć – testuję je celowo i uwzględniam dodatkowe zużycie w obliczeniach pm.max_children.

Rozdzielanie i ustalanie priorytetów pul

Dzielę aplikacje na kilka basenów gdy profile obciążenia znacznie się różnią. Jedna pula dla ruchu frontendowego, jedna dla administracji/backendu, trzecia dla cron/przesyłania plików: w ten sposób izoluję szczyty i przypisuję zróżnicowane limity. Dla rzadko odwiedzanych punktów końcowych ustawiam na żądanie z krótkim czasem bezczynności dla interfejsu użytkownika dynamiczny z niewielkim marginesem bezpieczeństwa. O użytkownik/grupa i w razie potrzeby. chroot Dbam o czystą izolację, podczas gdy uprawnienia gniazda regulują, który proces serwera WWW ma dostęp. Tam, gdzie wymagane są priorytety, frontend otrzymuje więcej pm.max_children i ewentualnie neutralną priorytet procesu, podczas gdy Cron/Reports działają przy mniejszym budżecie i niższym priorytecie. Dzięki temu interfejs użytkownika pozostaje responsywny, nawet jeśli w tle działają ciężkie zadania.

Prawidłowe wykorzystanie punktów końcowych statusu

W celu przeprowadzenia diagnostyki działania aktywuję pm.status_path i opcjonalnie ping.path na pulpit. W statusie widzę Active/Idle-Worker, które Kolejka list, liczniki przepustowości i metryki powolnych żądań. Stale rosnąca kolejka list lub ciągle 0 bezczynnych pracowników są dla mnie sygnałami alarmowymi. Chronię te punkty końcowe za pomocą uwierzytelniania i sieci wewnętrznej, aby żadne szczegóły operacyjne nie przedostały się na zewnątrz. Dodatkowo aktywuję catch_workers_output, gdy chcę w krótkim czasie zebrać stdout/stderr z workerów – na przykład w przypadku trudnych do odtworzenia błędów. Sygnały te łączę z metrykami systemowymi (RAM, CPU, I/O), aby zdecydować, czy zwiększyć pm.max_children, dostosować wartości rezerwowe, czy też zająć się aplikacją.

Cechy szczególne kontenerów i maszyn wirtualnych

Na stronie kontenerowanie W przypadku małych maszyn wirtualnych zwracam uwagę na limity cgroup i ryzyko wystąpienia OOM-killera. Ustawiam pm.max_children ściśle zgodnie z Limit pamięci kontenera i testuję szczyty obciążenia, aby żaden pracownik nie został wyłączony. Bez swapowania w kontenerach margines bezpieczeństwa jest szczególnie ważny. W przypadku limitów CPU skaluję liczbę pracowników do dostępnej liczby vCPU: jeśli aplikacja jest ograniczona przez CPU, większa równoległość powoduje raczej kolejki niż przepustowość. Obciążenia ograniczone przez IO tolerują więcej procesów, o ile wystarcza pamięć RAM. Dodatkowo ustawiam emergency_restart_threshold oraz emergency_restart_interval dla procesu głównego, aby zapobiec spirali awarii w przypadku, gdy rzadki błąd spowoduje awarię kilku procesów potomnych w krótkim czasie. Dzięki temu usługa pozostaje dostępna, podczas gdy ja analizuję przyczynę.

Płynne wdrożenia i ponowne ładowanie bez przestojów

Planuję Ponowne ładowanie tak, aby bieżące żądania były poprawnie realizowane. A elegantne ponowne ładowanie (np. poprzez systemd reload) przejmuje nowe konfiguracje bez gwałtownego zamykania otwartych połączeń. Utrzymuję ścieżkę gniazda stabilną, aby serwer WWW nie widział przerwania połączenia. W przypadku zmian wersji, które unieważniają wiele Opcache, podgrzewam pamięć podręczną (preloading/warmup-requests), aby ograniczyć szczyty opóźnień bezpośrednio po wdrożeniu. Większe zmiany testuję najpierw na mniejszej puli lub w instancji Canary o identycznej konfiguracji, zanim wprowadzę zmiany na szeroką skalę. Każda zmiana trafia do mojego dziennika zmian wraz z sygnaturą czasową i zrzutami ekranu z metrykami – skraca to czas wyszukiwania błędów w przypadku wystąpienia nieoczekiwanych skutków ubocznych.

Zachowanie podczas przepływu danych i kolejki

Szczyty obciążenia kompensuję za pomocą odpowiednio dobranego Projekt kolejki ab. Stawiam listen.backlog tak wysoka, że jądro może krótkotrwale buforować więcej prób połączeń. Po stronie serwera WWW ograniczam maksymalną liczbę jednoczesnych połączeń FastCGI na pulę, tak aby wynosiła ona pm.max_children pasuje. Dzięki temu bursty wolą gromadzić się krótkotrwale na serwerze internetowym (korzystne), niż głęboko w PHP (kosztowne). Mierzę Kolejka list w statusie FPM: jeśli wzrasta regularnie, zwiększam liczbę pracowników, optymalizuję współczynniki trafień pamięci podręcznej lub obniżam agresywne wartości Keep-Alive. Celem jest, aby w przypadku szczytów Czas do pierwszego bajtu utrzymywać stabilność zamiast pozostawiać żądania w niekończących się kolejkach.

Praktyczny przebieg pracy przy dopasowywaniu

Zaczynam od Audyt: budżet pamięci RAM, rozmiar procesu, profile wejścia/wyjścia. Następnie ustawiam konserwatywne wartości początkowe dla pm.max_children i trybu pm. Potem przeprowadzam testy obciążenia lub obserwuję rzeczywiste godziny szczytu. Rejestruję wszystkie zmiany wraz z metrykami i przedziałami czasowymi. Po każdej regulacji sprawdzam pamięć RAM, opóźnienie P50/P95 i wskaźniki błędów – dopiero wtedy przechodzę do następnego kroku.

Kiedy wielokrotnie osiągam granicę wytrzymałości, nie eskaluję od razu sytuacji. Pracownik-Liczba. Najpierw optymalizuję zapytania, współczynniki trafień w pamięci podręcznej i kosztowne funkcje. Przenoszę zadania obciążające IO do kolejek i skracam ścieżki odpowiedzi. Dopiero gdy aplikacja działa wydajnie, zwiększam rozmiar puli. Ten proces oszczędza zasoby i pozwala uniknąć szkód w innych obszarach.

Typowe scenariusze: przykładowe wartości

Na serwerze wirtualnym o pojemności 2 GB rezerwuję około 1 GB dla PHP-FPM i ustaw zużycie pracownika na około 50–60 MB. W ten sposób zaczynam od pm.max_children około 15–20 i używam dynamic z małą ilością początkową. min_spare utrzymuję na poziomie 2–3, max_spare na poziomie 5–6. pm.max_requests ustawiam na 500, aby procesy były regularnie wymieniane. Te ustawienia zapewniają stabilny czas reakcji w przypadku małych projektów.

Przy 8 GB pamięci RAM zazwyczaj planuję 4–6 GB dla PHP i ustawiam rozmiar procesów roboczych na 60–80 MB. Daje to 30–80 procesów potomnych jako zakres startowy. pm.start_servers wynosi 15–20, min_spare 10–15, a max_spare 25–30. pm.max_requests wybieram między 500 a 800. Pod obciążeniem sprawdzam, czy szczyt pamięci RAM pozostawia margines, a następnie ostrożnie zwiększam wartość.

W konfiguracjach o wysokim obciążeniu z 16+ GB pamięci RAM rezerwuję 10–12 GB dla FPM. Przy 70–90 MB na pracownika szybko osiągam 100–160 procesów. To, czy sensowne jest zastosowanie trybu statycznego czy dynamicznego, zależy od rodzaju obciążenia. W przypadku stale wysokiego obciążenia przekonujący jest tryb statyczny, a w przypadku falowego zapotrzebowania – tryb dynamiczny. W obu przypadkach konieczne jest konsekwentne monitorowanie.

Unikanie przeszkód i ustalanie priorytetów

Nie mylę liczby Odwiedzający z liczbą jednoczesnych skryptów PHP. Wiele wywołań stron trafia do pamięci podręcznej, dostarcza pliki statyczne lub blokuje się poza PHP. Dlatego wymiaruję pm.max_children według zmierzonego czasu PHP, a nie według sesji. Jeśli procesy są ustawione zbyt oszczędnie, widzę oczekujące żądania i rosnącą liczbę błędów. Przy zbyt wysokich wartościach pamięć przechodzi do pamięci wymiany i wszystko zwalnia.

Częsty błąd: więcej procesów oznacza więcej Prędkość. W rzeczywistości liczy się równowaga między procesorem, wejściem/wyjściem i pamięcią RAM. Jeśli procesor osiąga 100 %, a opóźnienie gwałtownie wzrasta, dodatkowe procesy robocze nie są zbyt pomocne. Lepiej jest usunąć rzeczywiste wąskie gardło lub zmniejszyć obciążenie za pomocą pamięci podręcznej. Dlaczego procesy robocze są często wąskim gardłem, wyjaśnia poradnik dotyczący PHP-Worker jako wąskie gardło.

Krótkie podsumowanie

Najpierw ustalam rzeczywistą wartość. RAM-Zużycie na pracownika i na tej podstawie ustawiam pm.max_children z buforem. Następnie wybieram tryb pm odpowiedni do obciążenia i równoważę wartości startowe oraz zapasowe. Za pomocą pm.max_requests utrzymuję procesy w stanie aktualnym, bez zbędnego obciążenia. Logi, statusy i metryki kieruję do przejrzystego systemu monitorowania, aby każda zmiana pozostała mierzalna. W ten sposób osiągam krótkie czasy odpowiedzi, stabilne pule i obciążenie serwera, które ma rezerwy na szczyty.

Artykuły bieżące