Geheugenfragmentatie In webhosting vertraagt PHP-FPM en MySQL, hoewel er ogenschijnlijk voldoende RAM beschikbaar is, omdat het geheugen in veel kleine blokken uiteenvalt en grotere toewijzingen mislukken. Ik laat in de praktijk zien hoe fragmentatie verzoeken duurder maakt, swap activeert en waarom gerichte tuning bij PHP en MySQL de laadtijden, betrouwbaarheid en schaalbaarheid zichtbaar verbetert.
Centrale punten
- PHP-FPM recyclen: processen regelmatig opnieuw starten via pm.max_requests
- Buffer doseren: MySQL-Per-Connection-Buffer conservatief houden
- Wissel Vermijden: Swappiness verlagen, NUMA in acht nemen
- Tabellen onderhouden: Data_free controleren, gericht optimaliseren
- Controle Gebruikmaken van: trends vroegtijdig signaleren en handelen
Wat betekent geheugenfragmentatie in het dagelijkse hostingwerk?
In hosting ontmoet Versnippering op langlopende processen die voortdurend geheugen aanvragen en vrijgeven, waardoor er gaten in de adresruimte ontstaan. Hoewel de totale hoeveelheid vrij RAM groot lijkt, ontbreken aaneengesloten blokken voor grotere toewijzingen, wat toewijzingspogingen vertraagt. Ik zie dit in PHP-FPM-workers en in mysqld, die na uren steeds „opgeblazen“ lijken. Het effect maakt elk verzoek minimaal duurder en verhoogt de responstijden onder belasting merkbaar. Hierdoor worden pieken zoals verkoopacties of back-ups een remblok, hoewel de CPU en het netwerk onopvallend blijven.
Waarom PHP-FPM fragmentatie veroorzaakt
Elke PHP-FPM-worker laadt code, plug-ins en gegevens in een eigen Adresruimte, verwerkt allerlei verschillende verzoeken en laat bij het vrijgeven verspreide gaten achter. Na verloop van tijd groeien processen en maken ze intern geheugen vrij, maar niet noodzakelijkerwijs aan het besturingssysteem, waardoor de fragmentatie toeneemt. Verschillende scripts, importtaken en beeldverwerking versterken deze mix en leiden tot wisselende toewijzingspatronen. Ik zie dit als een sluipende toename van het RAM-geheugen, hoewel de belasting en het verkeer constant lijken. Zonder recycling vertraagt deze interne fragmentatie de toewijzing en bemoeilijkt het de planning bij hoge bezoekersaantallen.
Merkbare gevolgen voor laadtijden en betrouwbaarheid
Gefragmenteerde processen genereren meer Overhead in het geheugenbeheer, wat zich uit in tragere admin-backends en aarzelende checkouts. Vooral WordPress-winkels of grote CMS-instanties reageren traag wanneer veel gelijktijdige verzoeken op gefragmenteerde workers terechtkomen. Dit resulteert in time-outs, 502/504-fouten en meer herhalingspogingen aan de kant van NGINX of Apache. Ik lees dergelijke situaties af aan statistieken zoals pieken in de responstijd, een stijgende RAM-basislijn en een plotseling toenemend swapgebruik. Wie dit negeert, verspeelt prestaties, verslechtert de gebruikerservaring en verhoogt het afbreekpercentage in kritieke funnels.
PHP-FPM correct instellen: limieten, pools, recycling
Ik zet in op realistische Grenzen, aparte pools en consequent recyclen om fragmentatie tegen te gaan. Ik beëindig pm.max_requests zodat workers regelmatig opnieuw starten zonder lopende bezoekers te storen. Voor verkeersprofielen met piekbelastingen is pm = dynamic vaak beter geschikt, terwijl pm = ondemand RAM bespaart bij rustige sites. Ik houd de memory_limit per site bewust gematigd en pas deze aan met het oog op echte scripts; een inleiding hierover vindt u onder het onderwerp PHP-geheugenlimiet. Daarnaast scheid ik projecten met een hoge belasting in aparte pools, zodat een geheugenvreter niet alle sites beïnvloedt.
OPcache, preloading en PHP-allocator in beeld
Om fragmentatie in het PHP-proces te beperken, zet ik in op een goed gedimensioneerde OPcache. Een royale, maar niet overdreven opcache.memory_consumption en voldoende geïnternaliseerde strings verminderen herhaalde toewijzingen per verzoek. Ik houd de hitrate, verspilling en resterende capaciteit in de gaten; als de verspilling in de loop van de tijd toeneemt, is een geplande herlaadbeurt beter dan het ongecontroleerd laten groeien van workers. Preloading kan hot-code stabiel in het geheugen houden en zo allocatiepatronen afvlakken, mits de codebasis daarop is voorbereid. Daarnaast let ik op de Allocator-keuze: Afhankelijk van de distributie werken PHP‑FPM en uitbreidingen met verschillende Malloc‑implementaties. Alternatieve allocators zoals jemalloc verminderen in sommige opstellingen de fragmentatie merkbaar. Ik implementeer dergelijke wijzigingen echter alleen na ze te hebben getest, omdat debugging, DTrace/eBPF‑profiling en geheugendumps verschillend reageren, afhankelijk van de allocator.
Voor taken die veel geheugen vereisen, zoals beeldverwerking of export, geef ik de voorkeur aan aparte pools met strengere limieten. Zo groeit de hoofdpool niet ongecontroleerd en blijft fragmentatie geïsoleerd. Bovendien beperk ik geheugenintensieve uitbreidingen (bijvoorbeeld via omgevingsvariabelen) en maak ik gebruik van backpressure: verzoeken die grote buffers vereisen, worden afgeremd of naar asynchrone wachtrijen verplaatst, in plaats van alle workers tegelijkertijd op te blazen.
MySQL-geheugen begrijpen: buffers, verbindingen, tabellen
Bij MySQL maak ik onderscheid tussen globale Buffer zoals de InnoDB-bufferpool, buffers per verbinding en tijdelijke structuren, die per bewerking kunnen groeien. Te hoge waarden leiden bij een hoge verbindingsbelasting tot een explosieve toename van het RAM-gebruik en meer fragmentatie op OS-niveau. Bovendien raken tabellen door updates/verwijderingen versnipperd en laten ze Data_free-delen achter, waardoor de bufferpool minder goed kan worden benut. Daarom controleer ik regelmatig de grootte, hitratio's en het aantal tijdelijke schijftabellen. Het volgende overzicht helpt me om typische symptomen nauwkeurig te identificeren en af te wegen tegen maatregelen.
| Symptoom | Vermoedelijke oorzaak | Maatregel |
|---|---|---|
| RAM stijgt gestaag, swap begint | Te grote bufferpool of te veel buffers per verbinding | Beperk de pool tot de juiste grootte, verlaag via de verbindingsbuffer |
| Veel trage sorts/joins | Ontbrekende indexen, overschreden sort/join-buffer | Indexen controleren, sort/join conservatief houden |
| Grote hoeveelheid vrije gegevens in tabellen | Grote updates/verwijderingen, pagina's versnipperd | Gerichte OPTIMIZE, archivering, schema stroomlijnen |
| Pieken bij tijdelijke schijftabellen | Te kleine tmp_table_size of ongeschikte queries | Waarden gematigd verhogen, queries aanpassen |
MySQL Memory Tuning: groottes kiezen in plaats van overdrijven
Ik kies de InnoDB-bufferpool zodanig dat de Besturingssysteem voldoende ruimte overhoudt voor bestandssysteemcache en diensten, vooral bij combiservers met web en DB. Per-connection-buffer zoals sort_buffer_size, join_buffer_size en read-buffer schaal ik conservatief, zodat veel gelijktijdige verbindingen niet leiden tot RAM-stormen. tmp_table_size en max_heap_table_size stel ik zo in dat onbelangrijke bewerkingen geen enorme in-memory-tabellen vereisen. Voor verdere instellingen vind ik onder MySQL-prestaties nuttige denkanstoten. Het belangrijkste blijft: ik stel liever iets krapper in en meet, dan blindelings op te voeren en fragmentatie plus swap te riskeren.
InnoDB in detail: herbouwstrategieën en poolinstanties
Om MySQL intern „compacter“ te houden, ben ik van plan om regelmatig Herbouw voor tabellen met een groot aandeel schrijfbewerkingen. Een gerichte OPTIMIZE TABLE (of een online herbouw via ALTER) voegt gegevens en indexen samen en vermindert Data_free. Ik kies daarbij tijdvensters met een lage belasting, omdat rebuilds I/O-intensief zijn. De optie innodb_file_per_table Ik houd dit actief omdat het per tabel gecontroleerde rebuilds toestaat en het risico vermindert dat individuele „probleemkinderen“ het hele tablespace-bestand fragmenteren.
Ik gebruik meerdere Bufferpool-instanties (innodb_buffer_pool_instances) in relatie tot de poolgrootte en CPU-kernen om interne latches te ontlasten en toegangen te verdelen. Dit verbetert niet alleen de parallelliteit, maar egaliseert ook de toewijzingspatronen in de pool. Daarnaast controleer ik de grootte van de redo-logs en de activiteit van de purge-threads, omdat opgebouwde geschiedenis geheugen en I/O kan binden, wat fragmentatie op OS-niveau versterkt. Het blijft belangrijk om instellingen stapsgewijs te wijzigen, te meten en alleen te behouden als de latentie en foutpercentages daadwerkelijk dalen.
Swap vermijden: kernel-instellingen en NUMA
Zodra Linux actief swapt, nemen de responstijden toe met grootteordes, omdat RAM-toegang tot trage I/O leidt. Ik verlaag vm.swappiness aanzienlijk, zodat de kernel langer gebruikmaakt van fysiek RAM. Op multi-CPU-hosts controleer ik de NUMA-topologie en activeer ik indien nodig interleaving om ongelijkmatige geheugengebruik te verminderen. Voor achtergrondinformatie en invloed van hardware helpt mij het perspectief op NUMA-architectuur. Daarnaast plan ik veiligheidsreserves in voor paginacache, want een uitgehongerde cache versnelt de fragmentatie van de hele machine.
Transparante enorme pagina's, overcommit en allocatorkeuze
Transparante enorme pagina's (THP) kunnen bij databases tot latentiepieken leiden, omdat het samenvoegen/splitsen van grote pagina's op een ongelegen moment plaatsvindt. Ik stel THP in op „madvise“ of schakel het uit als MySQL onder belasting te traag reageert. Tegelijkertijd let ik op Overcommit: Met een te royale vm.overcommit_memory-configuratie loopt u het risico op OOM-kills, juist wanneer fragmentatie grote aaneengesloten blokken schaars maakt. Ik geef de voorkeur aan conservatieve overcommit-instellingen en controleer regelmatig kernel-logs op tekenen van geheugendruk.
De Allocator-keuze op systeemniveau is het de moeite waard om eens te bekijken. glibc‑malloc, jemalloc of tcmalloc gedragen zich verschillend wat betreft fragmentatie. Ik test alternatieven altijd afzonderlijk, meet RSS‑geschiedenis en latenties en implementeer alleen wijzigingen als de statistieken stabiel blijven bij echt verkeer. Het nut varieert sterk, afhankelijk van de werklast, extensiemix en OS-versie.
Fragmentatie herkennen: statistieken en aanwijzingen
Ik let op langzaam stijgende basislijnen bij RAM, meer 5xx-antwoorden onder belasting en vertragingen bij beheerdersacties. Een blik op de PM-statistieken van PHP-FPM laat zien of children tegen limieten aanlopen of te lang blijven bestaan. In MySQL controleer ik hitratio's, tijdelijke tabellen op schijf en Data_free per tabel. Tegelijkertijd helpen OS-metrics zoals Page Faults, Swap-In/Out en Memory-Fragmentation-indicatoren, afhankelijk van de kernelversie. Wie deze signalen samenbrengt, herkent patronen vroegtijdig en kan geplande maatregelen nemen.
Monitoring verdiepen: hoe ik signalen samenbreng
Ik correleer Toepassingsgegevens (p95/p99-latenties, foutpercentages) met processtatistieken (RSS per FPM-worker, mysqld-geheugen) en OS-waarden (Pagecache, Slab, Major Faults). In PHP‑FPM gebruik ik de statusinterface voor wachtrijlengtes, actieve/gespawnde kinderen en de levensduur van de workers. In MySQL bekijk ik Created_tmp_disk_tables, Handler_write/Handler_tmp_write en Buffer‑Pool‑Misses. Daarnaast kijk ik naar proceskaarten (pmap/smaps) om te zien of er veel kleine arena's zijn ontstaan. Belangrijk voor mij is de trendgerichtheid: niet de afzonderlijke piek, maar de geleidelijke verschuiving gedurende uren/dagen bepaalt of fragmentatie een reëel gevaar vormt.
Praktische routine: onderhoud en gegevensbeheer
Ik ruim regelmatig op. Gegevens op: verlopen sessies, oude logs, onnodige revisies en verweesde caches. Voor sterk veranderlijke tabellen plan ik gerichte OPTIMIZE-vensters om gefragmenteerde pagina's samen te voegen. Grote importtaken of cron-golven verdeel ik in de tijd, zodat niet alle processen tegelijkertijd maximale buffers aanvragen. Bij groeiende projecten scheid ik web en DB vroegtijdig om geheugenintensieve patronen te isoleren. Deze discipline houdt het werkgeheugen samenhangender en vermindert het risico op burst-latenties.
Groottes correct berekenen: limieten en pools dimensioneren
Ik bepaal pm.max_children op basis van het daadwerkelijk beschikbare RAM voor PHP. Hiervoor meet ik de gemiddelde RSS van een worker onder werkelijke belasting (inclusief uitbreidingen en OPcache) en voeg daar veiligheidsmarges voor pieken aan toe. Voorbeeld: op een host van 16 GB reserveer ik 4-6 GB voor het besturingssysteem, paginacache en MySQL. Dan blijft er 10 GB over voor PHP; bij 150 MB per worker levert dat theoretisch 66 children op. In de praktijk stel ik pm.max_children in op ~80-90% van deze waarde om ruimte te laten voor pieken, dus ongeveer 52-58. pm.max_aanvragen Ik kies ervoor om workers te recyclen voordat er merkbare fragmentatie optreedt (vaak tussen 500 en 2000, afhankelijk van de codemix).
Voor MySQL bereken ik de Bufferpool op basis van de actieve gegevensgrootte, niet op basis van de totale DB-grootte. Belangrijke tabellen en indexen moeten erin passen, maar de OS-cache heeft ruimte nodig voor binlogs, sockets en statische assets. Per verbindingsbuffer reken ik met de maximale realistische parallelliteit. Als 200 verbindingen mogelijk zijn, dimensioner ik niet zodanig dat er in totaal meerdere gigabytes per verbinding exploderen, maar stel ik harde grenzen die ook tijdens pieken geen swap-gevaar opleveren.
Wachtrijen, beeldverwerking en nevenactiviteiten ontkoppelen
Veel fragmentatieproblemen ontstaan wanneer bijbanen dezelfde pools als frontend-verzoeken gebruiken. Voor exporten, crawls, beeldconversies of zoekindex-updates gebruik ik aparte FPM-pools of CLI-taken met duidelijke ulimit-grenzen. Ik beperk bewerkingen van afbeeldingen met GD/Imagick bovendien door middel van passende resource-limieten, zodat afzonderlijke enorme conversies niet de volledige adresruimte „versnipperen“. Ik plan taken met een tijdsverschil en geef ze hun eigen concurrency-limieten, zodat ze het frontend-pad niet verstoren.
Containers en virtualisatie: Cgroups, OOM en balloneffecten
In containers observeer ik Geheugenlimieten en de OOM-killer bijzonder nauwkeurig. Fragmentatie zorgt ervoor dat processen eerder tegen cgroup-limieten aanlopen, ook al is er nog RAM vrij op de host. Ik stem pm.max_children strikt af op de containerlimiet en houd voldoende reserve aan om pieken op te vangen. Ik vermijd swapping binnen containers (of op de host), omdat de extra indirectheid latentiepieken versterkt. In VM's controleer ik ballooning en KSM/UKSM; agressieve deduplicatie bespaart weliswaar RAM, maar kan extra latentie veroorzaken en het fragmentatiebeeld vertekenen.
Korte checklist zonder opsommingstekens
Ik stel eerst een realistisch geheugenlimiet per site en bekijk hoe het piekgebruik zich gedurende meerdere dagen ontwikkelt. Vervolgens stem ik PHP-FPM af met passende pm-waarden en een zinvolle pm.max_requests, zodat gefragmenteerde workers volgens plan werken. Voor MySQL richt ik me op een passende bufferpoolgrootte en conservatieve per-connection-buffers in plaats van algemene vergrotingen. Aan de kernelzijde verlaag ik swappiness, controleer ik NUMA-instellingen en houd ik reserves vrij voor de pagecache. Ten slotte evalueer ik tabellen met Data_free-afwijkingen en plan ik optimalisaties buiten de dagelijkse werkzaamheden om.
Kort samengevat: wat telt in het bedrijf
Het grootste effect tegen geheugenfragmentatie bereik ik door consequent recycling de PHP‑FPM‑Worker, gematigde limieten en schone pools. MySQL profiteert van redelijke groottes voor bufferpool en per‑connectie‑buffer, evenals van opgeruimde tabellen. Ik vermijd proactief swappen door rekening te houden met swappiness en NUMA en vrij RAM te reserveren voor de bestandscache. Monitoring brengt sluipende patronen aan het licht voordat gebruikers het merken en maakt rustige, geplande ingrepen mogelijk. Wie deze hefbomen gedisciplineerd gebruikt, houdt PHP en MySQL sneller, betrouwbaarder en kostenefficiënter zonder onmiddellijke hardware-upgrades.


