Salta ai contenuti
getnextpdf.com

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 exhausted dal 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.
    1. Delimitare la cache delle immagini. NextPDF\Core\Config espone imageCacheBytes (predefinito 52428800, ovvero 50 MB). Abbassarlo con il wither di istanza $config->withImageCacheBytes($bytes) (firma withImageCacheBytes(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.
    2. 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).
    3. Mantenere attiva la compressione. Un Config nuovo ha compress impostato a true. 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.
    4. Aumentare memory_limit in modo deliberato, per worker. Questa è un’impostazione PHP standard, non una chiave di NextPDF. Impostarla nella configurazione del pool o con ini_set('memory_limit', '256M') per il processo CLI/coda, e dimensionarla su un picco profilato, non su una congettura.
  • 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.
    1. 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 experimental e i suoi simboli non sono la superficie pubblica stabile.
    2. 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 a MAX_NESTING_DEPTH = 100 e rifiuta i documenti oltre MAX_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.
  • 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.
    1. Condividere i registri, ricreare i documenti. Costruire il FontRegistry e l’ImageRegistry una volta all’avvio e passarli a un DocumentFactory; creare un Document nuovo 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. Seguire examples/14-worker-factory.php.
    2. Delimitare la cache delle immagini condivisa con new ImageRegistry(maxCacheBytes: ...) in modo che non possa crescere senza limite tra i job.
    3. Riciclare il worker — controllo di processo, non una garanzia del motore. In PHP-FPM, impostare pm.max_requests in modo che ogni figlio si ricrei dopo un numero fisso di richieste. Nelle code Laravel usare queue:work --max-jobs / --max-time / --memory; in Symfony Messenger usare messenger:consume --limit / --time-limit / --memory-limit.
  • 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 FontRegistry non scaldato analizza ciascun carattere di font la prima volta che viene usato.
  • Risoluzione.
    1. Abilitare opcache (e JIT dove aiuta). Impostare opcache.enable=1 e un opcache.memory_consumption generoso; in produzione impostare opcache.validate_timestamps=0 in 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 esempio opcache_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.
    2. 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ò un FontRegistry scaldato 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.
    3. 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 Document per job dovrebbe essere creato nella richiesta.
  • 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.
    1. Dimensionare pm.max_children da un picco profilato. Usare la formula standard:

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

      Misurare 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.

    2. 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.

    3. Impostare pm.max_requests insieme a pm.max_children in 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.
    1. Appoggiarsi ai limiti del motore. Il parser HTML writeHtml() nativo impone MAX_NESTING_DEPTH = 100 e MAX_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.)
    2. Trattare i font forniti dal chiamante come non attendibili. Un font malformato solleva NextPDF\Exception\FontParsingException anziché corrompere l’output, perciò catturare l’eccezione specifica e rifiutare l’input invece di riprovare.
    3. 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.
  • Correlato. Risoluzione dei problemi: font e tagging.
SintomoLeva più probabile
Allowed memory size … exhausted su un singolo renderingAbbassare $config->withImageCacheBytes(); ridurre le immagini prima dell’incorporamento; aumentare memory_limit per worker
Il picco di memoria cresce con il numero di pagineUsare il percorso di scrittura in streaming documentato
La memoria del worker sale su molti jobCondividere FontRegistry/ImageRegistry tramite DocumentFactory; impostare pm.max_requests / --max-jobs
Prime richieste lente, costo di analisi per richiestaAbilitare opcache; $fontRegistry->warmup() poi ->lock() all’avvio
L’host ricorre allo swap / la latenza schizza sotto caricoDimensionare pm.max_children = (RAM − overhead) / picco per worker
Lento o pesante su input grande/non attendibileAffidarsi ai limiti di ADR-001; rifiutare i font malformati su FontParsingException
  • 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 experimental e può cambiare tra release minor. Trattare qualsiasi singola misurazione come un’osservazione, non come una costante portabile.
  • memory_limit, opcache.*, pm.max_children e pm.max_requests sono impostazioni standard di PHP / PHP-FPM. NextPDF non espone proprie chiavi per esse; configurarle nel proprio runtime, non in Config.

Glossario: streaming writer · font subsetting