Risoluzione dei problemi: memoria e prestazioni
Queste voci coprono due famiglie di guasti che si incontrano sotto carico: PHP che esaurisce la memoria durante un rendering, e un throughput che precipita una volta che un processo è caldo o saturo. Ogni voce nomina un sintomo, la causa più probabile e una correzione che usa vera superficie di NextPDF o controlli standard di PHP-FPM. Per il modello di streaming sottostante e un tutorial sui worker, leggere Streaming e memoria; questa pagina ne è la compagna sul lato incidente.
Misurare per prima cosa. Campionare memory_get_peak_usage(true) prima e dopo un
rendering e chiamare memory_reset_peak_usage() tra le iterazioni, nel modo in cui
il benchmark del motore isola il costo per rendering. Regolare senza una baseline
sposta il punto critico anziché eliminarlo.
Voce: “Allowed memory size exhausted” durante la generazione
Sezione intitolata “Voce: “Allowed memory size exhausted” durante la generazione”- Sintomo. Un rendering si interrompe con un fatal
Allowed memory size of <n> bytes exhausteddal runtime PHP, spesso su un documento grande o ricco di immagini. - Causa probabile. Il percorso di scrittura predefinito compone l’intero
documento, poi lo serializza, perciò il picco di memoria segue la dimensione
totale dell’output. Un documento grande, immagini incorporate di grandi dimensioni
o un carattere di font incorporato di grandi dimensioni possono spingere la
richiesta oltre
memory_limit. - Risoluzione.
- Delimitare la cache delle immagini.
NextPDF\Core\ConfigesponeimageCacheBytes(predefinito52428800, ovvero 50 MB). Abbassarlo con il wither di istanza$config->withImageCacheBytes($bytes)(firmawithImageCacheBytes(int $bytes): self) in modo che una build che incorpora molte immagini fallisca rapidamente su un tetto noto anziché ricorrere allo swap. Questo limita la cache delle immagini in memoria; non ricampiona né ricodifica le immagini stesse. - Ridurre gli input prima di incorporarli. Core non riduce la scala né ricodifica le immagini. Ridimensionare e ricodificare l’artwork raster sovradimensionato prima di incorporarlo, e incorporare i font effettivamente usati in modo che la creazione del subset abbia un piccolo insieme di glifi da mantenere (si veda Ridurre la dimensione del file PDF).
- Mantenere attiva la compressione. Un
Confignuovo hacompressimpostato atrue. Lasciarlo attivo per le build normali;withCompress(false)non è un’ottimizzazione della dimensione (di solito la aumenta). Ricorrervi per fare il debug o il profiling della pipeline — sposta il compromesso CPU/memoria (saltando il passaggio di compressione) anziché ridurre la memoria. - Aumentare
memory_limitin modo deliberato, per worker. Questa è un’impostazione PHP standard, non una chiave di NextPDF. Impostarla nella configurazione del pool o conini_set('memory_limit', '256M')per il processo CLI/coda, e dimensionarla su un picco profilato, non su una congettura.
- Delimitare la cache delle immagini.
- Correlato. Streaming e memoria.
Voce: la memoria cresce con il numero di pagine su documenti molto grandi
Sezione intitolata “Voce: la memoria cresce con il numero di pagine su documenti molto grandi”- Sintomo. Un documento di molte migliaia di pagine esaurisce la memoria anche se ogni pagina è piccola, e il picco cresce all’incirca di pari passo con il numero di pagine.
- Causa probabile. Il writer bufferizzato mantiene nell’heap l’intero documento serializzato. Per documenti molto grandi quello è il costo dominante.
- Risoluzione.
- Preferire il percorso di scrittura in streaming. Usare il percorso di scrittura
in streaming documentato descritto in
Streaming e memoria: serializza
ogni pagina man mano che viene composta e rilascia il buffer, il che riduce la
crescita del buffer di pagina/output; i piccoli metadati per oggetto (offset,
albero delle pagine) possono comunque scalare con il numero di pagine/oggetti.
Seguire il punto di ingresso documentato anziché copiare classi interne — il
motore di streaming sottostante è di livello
experimentale i suoi simboli non sono la superficie pubblica stabile. - Per il parser
writeHtml()nativo, ricordare che la memoria sul lato input è delimitata sia dalla protezione sulla profondità di annidamento sia da quella sul numero di elementi: ADR-001 limita l’annidamento aMAX_NESTING_DEPTH = 100e rifiuta i documenti oltreMAX_ELEMENT_COUNT = 50000. A un documento che raggiunge il limite di elementi viene comunicato esplicitamente, anziché esaurire silenziosamente la memoria. Questi limiti di ADR-001 governano soltanto il parser nativo; il bridge Chrome facoltativo (writeHtmlChrome()) rende fuori processo e ha i propri limiti separati di memoria/input, non questi.
- Preferire il percorso di scrittura in streaming. Usare il percorso di scrittura
in streaming documentato descritto in
Streaming e memoria: serializza
ogni pagina man mano che viene composta e rilascia il buffer, il che riduce la
crescita del buffer di pagina/output; i piccoli metadati per oggetto (offset,
albero delle pagine) possono comunque scalare con il numero di pagine/oggetti.
Seguire il punto di ingresso documentato anziché copiare classi interne — il
motore di streaming sottostante è di livello
- Correlato. Streaming e memoria.
Voce: un worker a lunga durata esaurisce la memoria dopo molti job
Sezione intitolata “Voce: un worker a lunga durata esaurisce la memoria dopo molti job”- Sintomo. I rendering singoli riescono, ma un worker di coda che rende molti PDF di seguito esaurisce la memoria dopo minuti o ore.
- Causa probabile. Un processo PHP a lunga durata accumula allocazioni tra i job. Una crescita lenta invisibile in una singola richiesta si somma su migliaia.
- Risoluzione.
- Condividere i registri, ricreare i documenti. Costruire il
FontRegistrye l’ImageRegistryuna volta all’avvio e passarli a unDocumentFactory; creare unDocumentnuovo per ogni job con$factory->create($config). L’analisi di font e immagini avviene quindi una volta per il processo, non una volta per job, e l’albero del documento per job viene raccolto quando esce dallo scope. Seguireexamples/14-worker-factory.php. - Delimitare la cache delle immagini condivisa con
new ImageRegistry(maxCacheBytes: ...)in modo che non possa crescere senza limite tra i job. - Riciclare il worker — controllo di processo, non una garanzia del motore. In
PHP-FPM, impostare
pm.max_requestsin modo che ogni figlio si ricrei dopo un numero fisso di richieste. Nelle code Laravel usarequeue:work --max-jobs/--max-time/--memory; in Symfony Messenger usaremessenger:consume --limit/--time-limit/--memory-limit.
- Condividere i registri, ricreare i documenti. Costruire il
- Correlato. Streaming e memoria.
Voce: calo di throughput su un processo freddo o poco scaldato
Sezione intitolata “Voce: calo di throughput su un processo freddo o poco scaldato”- Sintomo. I primi rendering in un processo nuovo sono lenti, oppure ogni richiesta paga un costo di analisi che le richieste calde non dovrebbero pagare.
- Causa probabile. Si sommano due costi di avvio a freddo. PHP senza opcache
ricompila ogni file a ogni richiesta, e un
FontRegistrynon scaldato analizza ciascun carattere di font la prima volta che viene usato. - Risoluzione.
- Abilitare opcache (e JIT dove aiuta). Impostare
opcache.enable=1e unopcache.memory_consumptiongeneroso; in produzione impostareopcache.validate_timestamps=0in modo che la cache non venga ricontrollata a ogni richiesta. Quell’impostazione richiede un processo di deploy che riavvii o ricarichi PHP-FPM (o reimposti comunque opcache, ad esempioopcache_reset()/cachetool) a ogni rilascio — altrimenti opcache continua a servire il vecchio bytecode e codice obsoleto viene eseguito dopo un deploy. Queste sono impostazioni ini standard di PHP, non chiavi di NextPDF. - Scaldare e bloccare il registro dei font all’avvio. Su un’istanza di
FontRegistry,$fontRegistry->warmup($fontFiles)analizza i caratteri una volta durante l’avvio, e$fontRegistry->lock()congela il registro così che il codice a tempo di richiesta non possa mutare lo stato condiviso;$fontRegistry->isLocked()riferisce lo stato. In un worker o application server genuinamente a lunga durata — un consumatore di coda o un worker RoadRunner/Swoole/Octane che mantiene vivo lo stesso processo PHP attraverso molte richieste — un registro scaldato e bloccato persiste i propri caratteri analizzati nello stato dell’oggetto, trasformando l’analisi dei font per richiesta in un costo una tantum all’avvio del processo. Sotto il modello di richiesta standard di PHP-FPM quello stato dell’oggetto scaldato non sopravvive tra le richieste: opcache memorizza in cache classi e bytecode compilati, non lo stato scaldato degli oggetti userland, perciò unFontRegistryscaldato viene ricostruito a ogni richiesta (rieseguito a ogni richiesta dal bootstrap del figlio), non mantenuto caldo tra le richieste all’interno di un figlio. Su PHP-FPM puro, opcache ammortizza principalmente il costo di ricompilazione del bytecode; accettare che l’analisi dei font sia pagata a ogni richiesta, non eliminata. L’ammortizzazione tra richieste — analizzare ciascun carattere una volta per la durata del processo — si applica soltanto in un processo genuinamente a lunga durata come un worker RoadRunner/Swoole/Octane o un consumatore di coda che mantiene vivo lo stesso processo PHP attraverso molte richieste. - Non rianalizzare lo stesso template a ogni richiesta. Risolvere i font e le
risorse riutilizzabili una volta all’avvio attraverso i registri condivisi; solo
il
Documentper job dovrebbe essere creato nella richiesta.
- Abilitare opcache (e JIT dove aiuta). Impostare
- Correlato. Streaming e memoria.
Voce: il server si satura e la latenza schizza sotto concorrenza
Sezione intitolata “Voce: il server si satura e la latenza schizza sotto concorrenza”- Sintomo. La latenza per rendering è buona in isolamento, ma sotto carico la macchina ricorre allo swap, la CPU si satura, oppure le richieste si accodano e vanno in timeout.
- Causa probabile. Troppi worker PHP-FPM per la RAM disponibile, perciò la somma dei picchi dei worker supera la memoria fisica e l’host ricorre allo swap; oppure troppo pochi worker, perciò le richieste si serializzano dietro un piccolo pool.
- Risoluzione.
-
Dimensionare
pm.max_childrenda un picco profilato. Usare la formula standard:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryMisurare il picco reale di un worker con un documento rappresentativo (si veda la nota sul profiling in Ambito), riservare margine per il sistema operativo e per qualsiasi servizio colocato, e dividere. Lasciare un margine; non dimensionare al 100% della RAM.
-
Fissare il costo della compressione nel proprio budget. La compressione Flate può essere un costo di CPU significativo della scrittura di uno stream e scala con il volume di byte di stream comprimibili, perciò il numero di pagine e il volume di font incorporati influenzano la CPU per rendering; anche l’elaborazione delle immagini, la creazione del subset dei font e l’analisi dell’input possono dominare. Misurare con documenti rappresentativi, e tenere conto del vero fattore trainante quando si sceglie il numero di worker e la CPU.
-
Impostare
pm.max_requestsinsieme apm.max_childrenin modo che i figli si riciclino e recuperino qualsiasi crescita lenta, come nella voce sui worker qui sopra.
-
- Correlato. Streaming e memoria.
Voce: input grande e non attendibile lento o costoso da analizzare
Sezione intitolata “Voce: input grande e non attendibile lento o costoso da analizzare”- Sintomo. Un rendering è lento o pesante in memoria su un input grande o profondamente annidato, specialmente HTML o un font non prodotto da chi opera.
- Causa probabile. Il costo di analisi scala con la dimensione e la struttura dell’input. Un input patologico (annidamento profondo, un numero enorme di elementi o un font malformato) può dominare il budget.
- Risoluzione.
- Appoggiarsi ai limiti del motore. Il parser HTML
writeHtml()nativo imponeMAX_NESTING_DEPTH = 100eMAX_ELEMENT_COUNT = 50000(ADR-001); gli input oltre quei limiti vengono rifiutati anziché lasciati esaurire il processo. (Il bridge Chrome facoltativo,writeHtmlChrome(), esula dall’ambito di questi limiti di ADR-001 e impone i propri limiti separati di memoria/input.) - Trattare i font forniti dal chiamante come non attendibili. Un font malformato
solleva
NextPDF\Exception\FontParsingExceptionanziché corrompere l’output, perciò catturare l’eccezione specifica e rifiutare l’input invece di riprovare. - Convalidare e dimensionare gli input al proprio confine, e applicare limiti a livello di richiesta sulla dimensione del documento per i contenuti influenzati dal chiamante.
- Appoggiarsi ai limiti del motore. Il parser HTML
- Correlato. Risoluzione dei problemi: font e tagging.
Tabella decisionale: dal sintomo alla leva
Sezione intitolata “Tabella decisionale: dal sintomo alla leva”| Sintomo | Leva più probabile |
|---|---|
Allowed memory size … exhausted su un singolo rendering | Abbassare $config->withImageCacheBytes(); ridurre le immagini prima dell’incorporamento; aumentare memory_limit per worker |
| Il picco di memoria cresce con il numero di pagine | Usare il percorso di scrittura in streaming documentato |
| La memoria del worker sale su molti job | Condividere FontRegistry/ImageRegistry tramite DocumentFactory; impostare pm.max_requests / --max-jobs |
| Prime richieste lente, costo di analisi per richiesta | Abilitare opcache; $fontRegistry->warmup() poi ->lock() all’avvio |
| L’host ricorre allo swap / la latenza schizza sotto carico | Dimensionare pm.max_children = (RAM − overhead) / picco per worker |
| Lento o pesante su input grande/non attendibile | Affidarsi ai limiti di ADR-001; rifiutare i font malformati su FontParsingException |
Casi limite e insidie
Sezione intitolata “Casi limite e insidie”imageCacheBytesè un tetto di memoria, non una manopola di dimensione. Abbassarlo limita la cache così che una build fallisca rapidamente; non ricampiona né ricodifica mai le immagini che si incorporano. Core non ha controllo sulla qualità delle immagini.withCompress(false)rende i file più grandi ed è un ausilio di debug/profiling. Non è un’ottimizzazione della dimensione; sposta il compromesso CPU/memoria (salta il passaggio di compressione) anziché ridurre la memoria.- Il profilo di memoria esatto del motore di streaming è una proprietà di livello
experimentale può cambiare tra release minor. Trattare qualsiasi singola misurazione come un’osservazione, non come una costante portabile. memory_limit,opcache.*,pm.max_childrenepm.max_requestssono impostazioni standard di PHP / PHP-FPM. NextPDF non espone proprie chiavi per esse; configurarle nel proprio runtime, non inConfig.
Vedere anche
Sezione intitolata “Vedere anche”- Streaming e memoria — il modello di streaming, i limiti di ADR-001 e il tutorial completo sui worker batch.
- Ridurre la dimensione del file PDF — la compressione e la creazione del subset dei font, i due veri controlli di dimensione.
- Risoluzione dei problemi: font e tagging — i guasti di risoluzione, analisi e creazione del subset dei font.
- Indice della knowledge base
Glossario: streaming writer · font subsetting