Ga naar inhoud
getnextpdf.com

Problemen oplossen: geheugen en prestaties

Deze items behandelen twee faalfamilies die je onder belasting tegenkomt: PHP dat tijdens een render het geheugen uitput, en doorvoer die van een klif valt zodra een proces warm of verzadigd is. Elk item benoemt een symptoom, de meest waarschijnlijke oorzaak en een oplossing die echt NextPDF-oppervlak of standaard PHP-FPM-besturingselementen gebruikt. Lees voor het onderliggende streamingmodel en een worker-tutorial Streaming en geheugen; deze pagina is het incidentzijde-gezelschap daarvan.

Meet eerst. Bemonster memory_get_peak_usage(true) voor en na een render en roep memory_reset_peak_usage() aan tussen iteraties, op de manier waarop de benchmark van de engine de kosten per render isoleert. Afstemmen zonder baseline verplaatst de klif in plaats van die te verwijderen.

Item: “Allowed memory size exhausted” tijdens generatie

Sectie met titel “Item: “Allowed memory size exhausted” tijdens generatie”
  • Symptoom. Een render breekt af met een fatale Allowed memory size of <n> bytes exhausted uit de PHP-runtime, vaak op een groot of afbeeldingszwaar document.
  • Waarschijnlijke oorzaak. Het standaard schrijfpad stelt het hele document samen, serialiseert het dan, dus het piekgeheugen volgt de totale uitvoergrootte. Een groot document, grote ingesloten afbeeldingen of een grote ingesloten lettertype-face kunnen de request voorbij memory_limit duwen.
  • Oplossing.
    1. Begrens de afbeeldingscache. NextPDF\Core\Config stelt imageCacheBytes beschikbaar (standaard 52428800, dat is 50 MB). Verlaag het met de instance-wither $config->withImageCacheBytes($bytes) (signatuur withImageCacheBytes(int $bytes): self) zodat een build die veel afbeeldingen insluit snel faalt op een bekend plafond in plaats van te swappen. Dit begrenst de in-memory afbeeldingscache; het hersampelt of hercodeert de afbeeldingen zelf niet.
    2. Verklein invoer vóór het insluiten. Core schaalt of hercodeert afbeeldingen niet. Herschaal en hercodeer te groot rasterwerk voordat je het insluit, en sluit lettertypen in die je daadwerkelijk gebruikt zodat subsetting een kleine glyphset overhoudt (zie PDF-bestandsgrootte verkleinen).
    3. Houd compressie aan. Een verse Config heeft compress op true staan. Laat het aan voor normale builds; withCompress(false) is geen grootteoptimalisatie (het vergroot de uitvoer meestal). Grijp ernaar om de pijplijn te debuggen of te profileren — het verschuift de CPU/geheugen-tradeoff (de compressiestap overslaan) in plaats van geheugen te verminderen.
    4. Verhoog memory_limit bewust, per worker. Dit is een standaard PHP-instelling, geen NextPDF-key. Stel het in in de pool-config of met ini_set('memory_limit', '256M') voor het CLI/queue-proces, en stem het af op een geprofileerde piek, niet op een gok.
  • Gerelateerd. Streaming en geheugen.

Item: geheugen groeit met paginaaantal op zeer grote documenten

Sectie met titel “Item: geheugen groeit met paginaaantal op zeer grote documenten”
  • Symptoom. Een document van vele duizenden pagina’s put het geheugen uit ook al is elke pagina klein, en de piek stijgt ruwweg in de pas met het paginaaantal.
  • Waarschijnlijke oorzaak. De gebufferde writer houdt het hele geserialiseerde document in de heap. Voor zeer grote documenten is dat de dominante kost.
  • Oplossing.
    1. Geef de voorkeur aan het streaming-schrijfpad. Gebruik het gedocumenteerde streaming-schrijfpad beschreven in Streaming en geheugen: het serialiseert elke pagina terwijl die wordt samengesteld en laat de buffer vrij, wat de pagina-buffer-/uitvoergroei vermindert; kleine per-object-metadata (offsets, paginaboom) kan nog steeds met pagina-/objectaantal meeschalen. Volg het gedocumenteerde toegangspunt in plaats van interne klassen te kopiëren — de onderliggende streaming-engine is experimental-niveau en zijn symbolen zijn niet het stabiele publieke oppervlak.
    2. Onthoud voor de native writeHtml()-parser dat invoerzijdig geheugen wordt begrensd door zowel de nestingsdiepte- als de element-aantal-guards: ADR-001 begrenst nesting op MAX_NESTING_DEPTH = 100 en weigert documenten boven MAX_ELEMENT_COUNT = 50000. Een document dat de element-cap raakt, krijgt dat expliciet te horen in plaats van stilzwijgend het geheugen uit te putten. Deze ADR-001-caps gelden alleen voor de native parser; de optionele Chrome-brug (writeHtmlChrome()) rendert out of process en heeft zijn eigen aparte geheugen-/invoerlimieten, niet deze caps.
  • Gerelateerd. Streaming en geheugen.

Item: een langlevende worker put geheugen uit na veel jobs

Sectie met titel “Item: een langlevende worker put geheugen uit na veel jobs”
  • Symptoom. Enkele renders slagen, maar een queue worker die veel PDF’s back-to-back rendert, put geheugen uit na minuten of uren.
  • Waarschijnlijke oorzaak. Een langlevend PHP-proces verzamelt allocaties over jobs heen. Een trage groei die in één request onzichtbaar is, stapelt op over duizenden.
  • Oplossing.
    1. Deel registers, maak documenten opnieuw. Bouw het FontRegistry en ImageRegistry eenmaal tijdens het opstarten en geef ze door aan een DocumentFactory; maak een vers Document per job met $factory->create($config). Het parsen van lettertypen en afbeeldingen gebeurt dan eenmaal voor het proces, niet eenmaal per job, en de documentboom per job wordt opgeruimd wanneer die buiten scope raakt. Volg examples/14-worker-factory.php.
    2. Begrens de gedeelde afbeeldingscache met new ImageRegistry(maxCacheBytes: ...) zodat die niet onbegrensd kan groeien over jobs heen.
    3. Recycle de worker — processturing, geen engine-garantie. Stel in PHP-FPM pm.max_requests in zodat elk kind respawnt na een vast aantal requests. Gebruik in Laravel-queues queue:work --max-jobs / --max-time / --memory; gebruik in Symfony Messenger messenger:consume --limit / --time-limit / --memory-limit.
  • Gerelateerd. Streaming en geheugen.

Item: doorvoerklif op een koud of onvoldoende opgewarmd proces

Sectie met titel “Item: doorvoerklif op een koud of onvoldoende opgewarmd proces”
  • Symptoom. De eerste renders in een vers proces zijn traag, of elke request betaalt een parse-kost die warme requests niet zouden moeten betalen.
  • Waarschijnlijke oorzaak. Twee koudestartkosten stapelen op. PHP zonder opcache hercompileert elk bestand bij elke request, en een onverwarmd FontRegistry parset elke lettertype-face de eerste keer dat die wordt gebruikt.
  • Oplossing.
    1. Schakel opcache in (en JIT waar het helpt). Stel opcache.enable=1 en een royale opcache.memory_consumption in; stel in productie opcache.validate_timestamps=0 in zodat de cache niet per request wordt hercontroleerd. Die instelling vereist een deploy-proces dat PHP-FPM herstart of herlaadt (of opcache anderszins reset, bijv. opcache_reset() / cachetool) bij elke release — anders blijft opcache de oude bytecode bedienen en draait er verouderde code na een deploy. Dit zijn standaard PHP-ini-instellingen, geen NextPDF-keys.
    2. Warm en lock het lettertyperegister tijdens het opstarten. Op een FontRegistry-instance parset $fontRegistry->warmup($fontFiles) faces eenmaal tijdens het opstarten, en $fontRegistry->lock() bevriest het register zodat request-time-code de gedeelde staat niet kan muteren; $fontRegistry->isLocked() rapporteert de staat. In een werkelijk langlevende worker of applicatieserver — een queue consumer of een RoadRunner/Swoole/Octane-worker die hetzelfde PHP-proces over veel requests levend houdt — behoudt een opgewarmd, gelockt register zijn geparste faces in objectstaat, wat per-request-lettertypeparsing omzet in een eenmalige proces-opstartkost. Onder het standaard PHP-FPM-requestmodel overleeft die opgewarmde objectstaat niet over requests heen: opcache cachet gecompileerde klassen en bytecode, geen opgewarmde userland-objectstaat, dus een opgewarmd FontRegistry wordt per request herbouwd (per request opnieuw uitgevoerd vanuit de bootstrap van het kind), niet warm gehouden over requests binnen een kind. Op gewone PHP-FPM amortiseert opcache vooral de bytecode-hercompileerkost; accepteer dat lettertypeparsing per request wordt betaald, niet geëlimineerd. Cross-request-amortisatie — elke face eenmaal parsen voor de levensduur van het proces — geldt alleen in een werkelijk langlevend proces zoals een RoadRunner/Swoole/Octane-worker of een queue consumer die hetzelfde PHP-proces over veel requests levend houdt.
    3. Parse niet hetzelfde template per request opnieuw. Los lettertypen en herbruikbare resources eenmaal op tijdens het opstarten via de gedeelde registers; alleen het Document per job zou in de request moeten worden gemaakt.
  • Gerelateerd. Streaming en geheugen.

Item: server verzadigt en latency piekt onder concurrency

Sectie met titel “Item: server verzadigt en latency piekt onder concurrency”
  • Symptoom. Per-render-latency is prima in isolatie, maar onder belasting swapt de doos, verzadigt de CPU, of stapelen requests op en time-outen.
  • Waarschijnlijke oorzaak. Te veel PHP-FPM-workers voor het beschikbare RAM, zodat de som van de workerpieken het fysieke geheugen overschrijdt en de host swapt; of te weinig workers, zodat requests serialiseren achter een kleine pool.
  • Oplossing.
    1. Dimensioneer pm.max_children vanuit een geprofileerde piek. Gebruik de standaardformule:

      pm.max_children = (total RAM - OS/other overhead) / per-worker peak memory

      Meet de echte piek van een worker met een representatief document (zie de profileringsnotitie in Reikwijdte), reserveer speling voor het OS en eventuele gecolokeerde services, en deel. Laat een marge; dimensioneer niet op 100% van RAM.

    2. Pin compressiekost in je budget. Flate-compressie kan een significante CPU-kost van het schrijven van een stream zijn en schaalt met het volume aan comprimeerbare stream-bytes, dus paginaaantal en ingesloten-lettertype-volume beïnvloeden CPU per render; afbeeldingsverwerking, lettertype-subsetting en invoerparsing kunnen ook domineren. Meet met representatieve documenten, en houd rekening met de echte driver wanneer je workeraantal en CPU kiest.

    3. Stel pm.max_requests naast pm.max_children in zodat kinderen recyclen en eventuele trage groei terugwinnen, zoals in het worker-item hierboven.

  • Gerelateerd. Streaming en geheugen.

Item: grote niet-vertrouwde invoer is traag of duur om te parsen

Sectie met titel “Item: grote niet-vertrouwde invoer is traag of duur om te parsen”
  • Symptoom. Een render is traag of geheugenzwaar op een grote of diep geneste invoer, vooral HTML of een lettertype dat je niet hebt geproduceerd.
  • Waarschijnlijke oorzaak. Parse-kost schaalt met invoergrootte en -structuur. Een pathologische invoer (diepe nesting, een enorm element-aantal of een misvormd lettertype) kan het budget domineren.
  • Oplossing.
    1. Leun op de grenzen van de engine. De native writeHtml()-HTML-parser dwingt MAX_NESTING_DEPTH = 100 en MAX_ELEMENT_COUNT = 50000 af (ADR-001); invoer boven die caps wordt geweigerd in plaats van toegestaan om het proces uit te putten. (De optionele Chrome-brug, writeHtmlChrome(), valt buiten de scope van deze ADR-001-caps en dwingt zijn eigen aparte geheugen-/invoerlimieten af.)
    2. Behandel door de aanroeper geleverde lettertypen als niet-vertrouwd. Een misvormd lettertype werpt NextPDF\Exception\FontParsingException op in plaats van de uitvoer te beschadigen, dus vang de specifieke exceptie en weiger de invoer in plaats van opnieuw te proberen.
    3. Valideer en dimensioneer invoer aan je grens, en pas request-niveau-limieten op documentgrootte toe voor door de aanroeper beïnvloede inhoud.
  • Gerelateerd. Troubleshooting: lettertypen en tagging.
SymptoomMeest waarschijnlijke hefboom
Allowed memory size … exhausted op één renderVerlaag $config->withImageCacheBytes(); verklein afbeeldingen vóór insluiten; verhoog memory_limit per worker
Piekgeheugen stijgt met paginaaantalGebruik het gedocumenteerde streaming-schrijfpad
Workergeheugen klimt over veel jobsDeel FontRegistry/ImageRegistry via DocumentFactory; stel pm.max_requests / --max-jobs in
Eerste requests traag, parse-kost per requestSchakel opcache in; $fontRegistry->warmup() gevolgd door ->lock() tijdens opstarten
Host swapt / latency piekt onder belastingDimensioneer pm.max_children = (RAM − overhead) / piek per worker
Traag of zwaar op grote/niet-vertrouwde invoerLeun op ADR-001-caps; weiger misvormde lettertypen bij FontParsingException
  • imageCacheBytes is een geheugenplafond, geen grootteknop. Het verlagen begrenst de cache zodat een build snel faalt; het hersampelt of hercodeert nooit de afbeeldingen die je insluit. Core heeft geen afbeeldingskwaliteitsbesturing.
  • withCompress(false) maakt bestanden groter en is een debug-/profileerhulp. Het is geen grootteoptimalisatie; het verschuift de CPU/geheugen-tradeoff (het slaat de compressiestap over) in plaats van geheugen te verminderen.
  • Het exacte geheugenprofiel van de streaming-engine is een experimental-niveau-eigenschap en kan verschuiven tussen minor-releases. Behandel elke enkele meting als een observatie, geen overdraagbare constante.
  • memory_limit, opcache.*, pm.max_children en pm.max_requests zijn standaard PHP- / PHP-FPM-instellingen. NextPDF stelt er geen eigen keys voor beschikbaar; configureer ze in je runtime, niet in Config.

Glossary: streaming writer · font subsetting