...

Korrekt konfiguration af PHP-FPM-processtyring: pm.max_children & Co. forklaret

php-fpm-optimering bestemmer, hvor mange PHP-FPM-processer der må køre samtidigt, hvor hurtigt nye processer starter, og hvor længe de behandler forespørgsler. Jeg viser dig, hvordan du pm.max_børn, pm, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers og pm.max_requests, så din applikation reagerer hurtigt under belastning, og serveren ikke går i swapping.

Centrale punkter

  • pm-tilstand: Vælg mellem static, dynamic eller ondemand, så processerne passer til din trafik.
  • pm.max_børn: Tilpas antallet af samtidige PHP-processer til RAM og det reelle procesforbrug.
  • Start-/spareværdier: pm.start_servers, pm.min_spare_servers, pm.max_spare_servers på en fornuftig måde.
  • genanvendelse: Brug pm.max_requests til at afbøde hukommelsestab uden at skabe unødvendig overhead.
  • Overvågning: Hold øje med logfiler, status og RAM, og juster derefter trin for trin.

Hvorfor processtyring er vigtig

Jeg styrer med PHP-FPM udførelsen af hvert PHP-script som en separat proces, og hver parallel forespørgsel kræver sin egen worker. Uden passende begrænsninger blokerer forespørgsler i køer, hvilket fører til Timeouts og fejl. Hvis jeg sætter lofterne for højt, spiser procespuljen al hukommelsen, og kernen begynder at swappen. Denne balance er ikke et gætteri: Jeg baserer mig på reelle måleværdier og holder en sikkerhedsmargen. På den måde forbliver latenstiden lav og gennemstrømningen stabil, selv når belastningen svinger.

Det er vigtigt for mig, at der er en klar målværdi: Hvor mange samtidige PHP-udførelser vil jeg tillade uden at udtømme RAM? Samtidig kontrollerer jeg, om flaskehalse snarere opstår i Database, i eksterne API'er eller på webserveren. Kun når jeg kender flaskehalsen, vælger jeg de rigtige værdier for pm, pm.max_children og lignende. Jeg starter konservativt, måler og øger derefter gradvist. På den måde undgår jeg hårde genstarter og uventede nedbrud.

De tre pm-tilstande: static, dynamic, ondemand

Funktionen statisk holder altid nøjagtigt pm.max_children processer klar. Det giver meget forudsigelige ventetider, fordi der ikke er behov for nogen opstartsproces. Jeg bruger static, når belastningen er meget jævn, og der er tilstrækkelig RAM til rådighed. Ved svingende efterspørgsel spilder jeg dog let i static. Hukommelse. Derfor bruger jeg static målrettet dér, hvor jeg har brug for konstant udførelse.

Med dynamisk starter jeg en startmængde og lader poolstørrelsen ligge mellem min_spare og max_spare. Denne tilstand er velegnet til trafik med bølger, fordi arbejdere opstår og ophører efter behov. Jeg holder altid nok inaktive processer til rådighed til at håndtere spidsbelastninger uden ventetid. For mange inaktive arbejdere binder dog unødigt RAM, hvorfor jeg holder sparebeløbet lavt. På den måde forbliver puljen fleksibel uden at svulme op.

I tilstanden ondemand Der findes i første omgang ingen arbejdere, PHP-FPM starter dem først ved forespørgsler. Det sparer hukommelse i hvilefaser, men det første hit koster lidt latenstid. Jeg vælger ondemand til sjældent anvendte puljer, admin-værktøjer eller cron-endepunkter. For meget besøgte hjemmesider giver ondemand som regel dårligere responstider. Der foretrækker jeg klart dynamic med korrekt indstillede spareværdier.

Dimensioner pm.max_children korrekt

Det regner jeg med pm.max_børn fra den tilgængelige RAM til PHP og den gennemsnitlige hukommelse pr. worker. Til dette formål reserverer jeg først hukommelse til systemet, webserveren, databasen og caches, så systemet ikke går i Outsourcing kører. Jeg deler den resterende RAM med det reelt målte procesforbrug. Fra teorien trækker jeg 20-30 % sikkerhedsmargen for at udligne afvigelser og belastningsspidser. Jeg bruger resultatet som startværdi og observerer derefter effekten.

Jeg beregner det gennemsnitlige procesforbrug med værktøjer som ps, top eller htop og kigger på RSS/RES. Vigtigt: Jeg måler under typisk belastning, ikke i tomgang. Når jeg indlæser mange plugins, frameworks eller store biblioteker, stiger forbruget pr. worker mærkbart. Desuden begrænser CPU'en kurven: Flere processer hjælper ikke, hvis en Enkelt tråd-CPU-ydeevne begrænset pr. forespørgsel. Hvis du ønsker at dykke dybere ned i CPU-karakteristikken, finder du baggrundsinformation om Single-thread-ydeevne.

Jeg holder mine antagelser transparente: Hvor meget RAM har PHP reelt til rådighed? Hvor stor er en worker ved typiske anmodninger? Hvilke spidsbelastninger opstår der? Hvis svarene stemmer, indstiller jeg pm.max_children, foretager en blød genindlæsning og kontrollerer RAM, svartider og fejlrater. Først derefter går jeg videre i små trin op eller ned.

Retningslinjer efter serverstørrelse

Følgende tabel giver mig Startværdier til rådighed. Den erstatter ikke målinger, men giver en solid vejledning til de første indstillinger. Jeg tilpasser værdierne til hver enkelt anvendelse og kontrollerer dem med overvågning. Hvis der er uudnyttede reserver, øger jeg forsigtigt. Når serveren når RAM-grænsen, trækker jeg værdierne tilbage.

Server-RAM RAM til PHP Ø MB/arbejder pm.max_børn (Start) Brug
1–2 GB ~1 GB 50–60 15–20 Små websteder, blogs
4–8 GB ~4–6 GB 60–80 30–80 Forretning, små butikker
16+ GB ~10–12 GB 70–90 100–160 Høj belastning, API, butikker

Jeg læser tabellen fra højre mod venstre: Passer det Brug til projektet, kontrollerer jeg, om RAM er realistisk reserveret til PHP. Derefter vælger jeg en worker-størrelse, der passer til kodebasen og udvidelserne. Derefter indstiller jeg pm.max_children og observerer effekten i live-drift. Træffesprocenten og stabiliteten stiger, når jeg dokumenterer disse trin ordentligt.

Indstil start-, reserve- og anmodningsværdier

Med pm.start_servers Jeg fastlægger, hvor mange processer der skal være klar med det samme. For lavt giver koldstart under belastning, for højt binder unødvendigt RAM. Jeg går ofte efter 15–30 % fra pm.max_children og afrunder, hvis belastningen starter roligt. Ved trafikspidser vælger jeg en lidt højere startmængde, så forespørgsler ikke ruller ind, før der er nok arbejdere, der venter. Denne finjustering reducerer den første svartid betydeligt.

Værdierne pm.min_spare_servers og pm.max_spare_servers definerer inaktivitetsintervallet. Jeg holder så mange ledige arbejdere klar, at nye forespørgsler kan få direkte adgang, men ikke så mange, at inaktive processer spilder hukommelse. I butikker foretrækker jeg at indstille et snævrere vindue for at udjævne spidsbelastninger. Med pm.max_anmodninger Jeg genbruger processer efter nogle hundrede anmodninger for at begrænse hukommelsesafvigelser. For ubetydelige applikationer vælger jeg 500–800, og hvis jeg har mistanke om lækager, vælger jeg bevidst et lavere tal.

Overvågning og fejlfinding

Jeg tjekker regelmæssigt Logfiler, status sider og RAM. Advarsler om nåede pm.max_children-grænser er for mig et klart signal om at hæve den øvre grænse eller optimere kode/DB. Hvis 502/504-fejl hober sig op, kigger jeg i webserverloggene og i køerne. Markante udsving i latenstiden tyder på for få processer, blokerende I/O eller for høje procesomkostninger. Jeg ser først på de hårde fakta og reagerer derefter med små skridt, aldrig med XXL-spring.

Jeg opdager flaskehalse hurtigere, når jeg Ventetider måle langs hele kæden: webserver, PHP-FPM, database, eksterne tjenester. Hvis backend-tiden kun stiger for bestemte ruter, isolerer jeg årsagerne ved hjælp af profilering. Hvis der opstår ventetider overalt, ser jeg på server- og poolstørrelsen. Det er også nyttigt at se på arbejdskøer og processer i D-status. Først når jeg forstår situationen, ændrer jeg grænserne – og dokumenterer hver ændring nøje.

Webserver og PHP-FPM i samspil

Jeg sørger for, at Webserver-Limits og PHP-FPM harmonerer. For mange samtidige forbindelser på webserveren med for få arbejdere forårsager køer og timeouts. Hvis arbejdstagerne er sat højt, men webserveren begrænser accepten, forbliver ydeevnen lav. Parametre som worker_connections, event-Loop og Keep-Alive har direkte indflydelse på PHP-belastningen. En praktisk introduktion til finjustering findes i vejledningen til Trådpuljer på webserveren.

Jeg beholder Keep-Alive-tidsvindue i kikkerten, så tomgangforbindelser ikke blokerer arbejdere unødigt. For statiske aktiver sætter jeg aggressiv caching foran PHP for at holde arbejdsbyrden væk fra puljen. Reverse-proxy-caches hjælper desuden, når identiske svar ofte hentes. Så kan jeg holde pm.max_children lavere og alligevel levere hurtigere. Mindre arbejde pr. forespørgsel er ofte den mest effektive justeringsskrue.

Fine justeringsskruer i php-fpm.conf

Jeg går ud over de grundlæggende værdier og justerer Pool-parametre fint. Med pm.max_spawn_rate begrænser jeg, hvor hurtigt nye arbejdere må oprettes, så serveren ikke starter processer for aggressivt ved belastningsspidser og glider ind i CPU-thrashing. For ondemand angiver jeg med pm.process_idle_timeout fast, hvor hurtigt ubrugte arbejdere forsvinder igen – for kort skaber startomkostninger, for lang binder RAM. Ved lytte-Socket vælger jeg mellem Unix-Socket og TCP. En Unix-Socket sparer overhead og giver en klar tildeling af rettigheder via listen.ejer, listen.gruppe og listen.mode. For begge varianter sætter jeg listen.backlog tilstrækkelig høj, så indkommende bursts ender i kernelbufferen i stedet for at blive afvist med det samme. Med rlimit_files hvis nødvendigt øger jeg antallet af åbne filer pr. worker, hvilket giver stabilitet ved mange samtidige uploads og downloads. Og hvis der er behov for prioriteter, bruger jeg procesprioritet, for at behandle mindre kritiske puljer lidt sekundært på CPU-siden.

Slowlog og beskyttelse mod hængninger

For at gøre langsomme anmodninger synlige aktiverer jeg Slowlog. Med request_slowlog_timeout definerer jeg den tærskel (f.eks. 2–3 sekunder), hvorfra en stacktrace sendes til slowlog skrives. Så finder jeg blokerende I/O, dyre sløjfer eller uventede låse. Mod ægte hængere bruger jeg request_terminate_timeout, der afbryder hårdt, hvis en anmodning kører for længe. Jeg holder disse tidsvinduer konsistente med max_udførelsestid fra PHP og webserverens timeouts, så det ene lag ikke afbrydes før det andet. I praksis starter jeg konservativt, analyserer slowlogs under belastning og justerer tærsklerne gradvist, indtil signalerne er meningsfulde uden at oversvømme loggen.

Opcache, memory_limit og deres indflydelse på størrelsen af arbejdere

Jeg henviser til Opcache i min RAM-planlægning. Dets delte hukommelsesområde tæller ikke pr. arbejdstager, men deles af alle processer. Størrelse og fragmentering (opcache.memory_consumption, interned_strings_buffer) har en betydelig indflydelse på opvarmningstiden og træfsikkerheden. En vel dimensioneret Opcache reducerer CPU- og RAM-belastningen pr. forespørgsel, fordi der skal kompileres mindre kode. Samtidig bemærker jeg, at memory_limit: En høj værdi beskytter mod hukommelsesmangel i enkelte tilfælde, men øger det teoretiske worst case-budget pr. worker. Derfor planlægger jeg med et målt gennemsnit plus buffer, ikke med det rene memory_limit. Funktioner som preloading eller JIT øger hukommelsesbehovet – jeg tester dem målrettet og indregner det ekstra forbrug i pm.max_children-beregningen.

Adskil og prioriter puljer

Jeg deler applikationer op flere pools når belastningsprofilerne er meget forskellige. En pool til frontend-trafik, en til admin/backend, en tredje til cron/uploads: Sådan isolerer jeg spidsbelastninger og tildeler differentierede grænser. For sjældent besøgte slutpunkter indstiller jeg ondemand med kort idle-timeout for frontend dynamisk med snæver spare-margin. Om bruger/gruppe og eventuelt. chroot Jeg sørger for ren isolering, mens socket-rettigheder regulerer, hvilken webserverproces der har adgang. Hvor der er behov for prioriteter, får frontend mere pm.max_børn og eventuelt en neutral procesprioritet, mens Cron/Reports kører med et mindre budget og lavere prioritet. På den måde forbliver brugergrænsefladen responsiv, selv når der kører tunge opgaver i baggrunden.

Brug statusendpunkter korrekt

For at diagnosticere løbetiden aktiverer jeg pm.status_path og valgfrit ping.path pr. pool. I status ser jeg Active/Idle-Worker, der Lister Kø, gennemløbsrelaterede tællere og slow request-metrikker. En konstant voksende listekø eller konstant 0 idle workers er for mig alarmsignaler. Jeg beskytter disse slutpunkter bag Auth og et internt netværk, så ingen driftsdetaljer kommer ud. Derudover aktiverer jeg catch_workers_output, hvis jeg hurtigt vil indsamle stdout/stderr fra workerne – f.eks. ved fejl, der er svære at reproducere. Jeg kombinerer disse signaler med systemmetrikker (RAM, CPU, I/O) for at beslutte, om jeg skal øge pm.max_children, justere reserveværdier eller ændre applikationen.

Særlige forhold i containere og VM'er

Containere og små VM'er tager jeg højde for cgroup-begrænsninger og risikoen for OOM-killeren. Jeg indstiller pm.max_children strengt efter Container-hukommelsesgrænse og tester belastningsspidser, så ingen arbejdere bliver lukket ned. Uden swap i containere er sikkerhedsmargenen særlig vigtig. Ved CPU-kvoter skalerer jeg antallet af arbejdere til det tilgængelige antal vCPU'er: hvis applikationen er CPU-bundet, medfører mere parallelitet snarere køer end gennemstrømning. IO-bundne arbejdsbelastninger tåler flere processer, så længe RAM-budgettet holder. Derudover indstiller jeg emergency_restart_threshold og emergency_restart_interval til master-processen for at afværge en nedbrudsspiral, hvis en sjælden fejl rammer flere børn på kort tid. På den måde forbliver tjenesten tilgængelig, mens jeg analyserer årsagen.

Problemfri implementeringer og genindlæsninger uden nedbrud

Jeg planlægger Genindlæsninger således, at igangværende anmodninger afsluttes korrekt. En elegant genindlæsning (f.eks. via systemd reload) overtager nye konfigurationer uden at afbryde åbne forbindelser. Jeg holder socket-stien stabil, så webserveren ikke oplever forbindelsesafbrydelser. Ved versionsændringer, der ugyldiggør meget Opcache, forvarmer jeg cachen (preloading/warmup-requests) for at begrænse latenstoppe umiddelbart efter implementeringen. Større ændringer tester jeg først på en mindre pool eller i en Canary-instans med identisk konfiguration, før jeg ruller værdierne ud over hele linjen. Hver tilpasning ender med tidsstempel og metrik-skærmbilleder i min ændringslog – det forkorter fejlfinding, hvis der er uventede bivirkninger.

Burst-adfærd og ventekøer

Jeg udligner belastningsspidser med et afstemt Kødesign af. Jeg sætter listen.backlog så høj, at kernen kortvarigt kan buffe flere forbindelsesforsøg. På webserver-siden begrænser jeg det maksimale antal samtidige FastCGI-forbindelser pr. pool, så de til pm.max_børn passer. Således ophobes bursts hellere kortvarigt i webserveren (billigt) end dybt i PHP (dyrt). Jeg måler Lister Kø i FPM-status: Hvis den stiger regelmæssigt, øger jeg enten antallet af arbejdere, optimerer cache-hitfrekvensen eller sænker aggressive keep-alive-værdier. Målet er at undgå Tid til første byte at holde stabiliteten i stedet for at lade anmodninger forsvinde i endeløse køer.

Praksis-workflow for tilpasninger

Jeg starter med en Revision: RAM-budget, processtørrelse, I/O-profiler. Derefter indstiller jeg konservative startværdier for pm.max_children og pm-tilstand. Derefter kører jeg belastningstests eller observerer reelle spidsbelastningstider. Jeg logger alle ændringer sammen med målinger og tidsvinduer. Efter hver justering kontrollerer jeg RAM, latenstid P50/P95 og fejlrater – først derefter går jeg videre til næste trin.

Når jeg gentagne gange når grænsen, eskalerer jeg ikke straks situationen. Arbejder-tal. Først optimerer jeg forespørgsler, cache-hitrater og dyre funktioner. Jeg flytter IO-tunge opgaver til køer og forkorter svarveje. Først når applikationen fungerer effektivt, øger jeg poolstørrelsen. Denne fremgangsmåde sparer ressourcer og undgår følgeskader andre steder.

Typiske scenarier: Eksempelværdier

På en 2 GB vServer reserverer jeg ca. 1 GB for PHP-FPM og indstiller en worker-forbrug på ca. 50-60 MB. Dermed starter jeg med pm.max_children på 15-20 og bruger dynamic med en lille startmængde. min_spare holder jeg på 2-3, max_spare på 5-6. pm.max_requests indstiller jeg på 500, så processer udskiftes regelmæssigt. Disse indstillinger giver små projekter stabile reaktionstider.

Med 8 GB RAM planlægger jeg normalt 4–6 GB til PHP og indstiller arbejdstagerstørrelser på 60–80 MB. Dette resulterer i 30–80 underprocesser som startområde. pm.start_servers ligger på 15–20, min_spare på 10–15, max_spare på 25–30. pm.max_requests vælger jeg mellem 500 og 800. Under belastning kontrollerer jeg, om RAM-spidsbelastningen giver plads, og øger derefter forsigtigt.

I højbelastningsopsætninger med 16+ GB RAM reserverer jeg 10–12 GB til FPM. Med 70–90 MB pr. medarbejder ender jeg hurtigt med 100–160 processer. Om statisk eller dynamisk er mest hensigtsmæssigt, afhænger af belastningsformen. Ved konstant høj belastning er statisk det bedste valg, ved bølgende efterspørgsel er dynamisk det bedste. I begge tilfælde er konsekvent overvågning et must.

Undgå forhindringer og sæt prioriteter

Jeg forveksler ikke antallet af Besøgende med antallet af samtidige PHP-scripts. Mange sidevisninger rammer caches, leverer statiske filer eller blokerer uden for PHP. Derfor dimensionerer jeg pm.max_children efter målt PHP-tid, ikke efter sessioner. Hvis processerne er sat for sparsomt, ser jeg ventende anmodninger og stigende fejlrater. Ved for høje værdier tipper hukommelsen i swap, og alt bliver langsommere.

En almindelig misforståelse: Flere processer er lig med mere Hastighed. I virkeligheden er det balancen mellem CPU, IO og RAM, der tæller. Hvis CPU'en går op på 100 % og latenstiden stiger kraftigt, hjælper flere arbejdere næppe. Det er bedre at fjerne den reelle flaskehals eller reducere belastningen via cachen. Hvorfor arbejdere ofte er flaskehalsen, forklares i vejledningen til PHP-Worker som flaskehals.

Kort opsummeret

Jeg undersøger først den reelle RAM-forbruget pr. worker og indstiller pm.max_children med buffer. Derefter vælger jeg pm-tilstanden, der passer til belastningsformen, og afbalancerer start- og reserveværdier. Med pm.max_requests holder jeg processerne friske uden unødvendig overhead. Logs, status og metrikker leder jeg ind i en ren overvågning, så hver ændring forbliver målbar. På den måde opnår jeg korte svartider, stabile puljer og en serverbelastning, der har reserver til spidsbelastninger.

Aktuelle artikler