Zum Inhalt springen
getnextpdf.com

Fehlerbehebung: Speicher und Performance

Diese Einträge behandeln zwei Fehlerfamilien, auf die Sie unter Last stoßen: PHP, das während eines Renders den Speicher erschöpft, und ein Durchsatz, der von einer Klippe fällt, sobald ein Prozess warm oder gesättigt ist. Jeder Eintrag benennt ein Symptom, die wahrscheinlichste Ursache und eine Behebung, die echte NextPDF-Oberfläche oder Standard-PHP-FPM-Kontrollen verwendet. Für das zugrunde liegende Streaming-Modell und ein Worker-Tutorial lesen Sie Streaming und Speicher; diese Seite ist der vorfallseitige Begleiter dazu.

Messen Sie zuerst. Erfassen Sie memory_get_peak_usage(true) vor und nach einem Render und rufen Sie memory_reset_peak_usage() zwischen den Iterationen auf – so wie der Benchmark der Engine die Kosten pro Render isoliert. Ein Tuning ohne Baseline verschiebt die Klippe, statt sie zu entfernen.

Eintrag: „Allowed memory size exhausted“ während der Generierung

Abschnitt betitelt „Eintrag: „Allowed memory size exhausted“ während der Generierung“
  • Symptom. Ein Render bricht mit einem fatalen Allowed memory size of <n> bytes exhausted aus der PHP-Laufzeit ab, oft bei einem großen oder bildlastigen Dokument.
  • Wahrscheinliche Ursache. Der Standard-Schreibpfad setzt das gesamte Dokument zusammen und serialisiert es dann, sodass die Speicherspitze der gesamten Ausgabegröße folgt. Ein großes Dokument, große eingebettete Bilder oder ein großer eingebetteter Schriftschnitt können die Anfrage über memory_limit hinaustreiben.
  • Lösung.
    1. Begrenzen Sie den Bild-Cache. NextPDF\Core\Config stellt imageCacheBytes bereit (Standard 52428800, das sind 50 MB). Senken Sie ihn mit dem Instanz-Wither $config->withImageCacheBytes($bytes) (Signatur withImageCacheBytes(int $bytes): self), sodass ein Build, der viele Bilder einbettet, an einer bekannten Obergrenze schnell fehlschlägt, statt zu swappen. Dies deckelt den In-Memory-Bild-Cache; es resampelt oder re-encodiert die Bilder selbst nicht.
    2. Verkleinern Sie Eingaben vor dem Einbetten. Core skaliert oder re-encodiert Bilder nicht herunter. Skalieren und re-encodieren Sie überdimensionierte Rastergrafiken, bevor Sie sie einbetten, und betten Sie Schriften ein, die Sie tatsächlich verwenden, damit das Subsetting eine kleine Glyphenmenge zu behalten hat (siehe PDF-Dateigröße reduzieren).
    3. Lassen Sie die Komprimierung an. Eine frische Config hat compress auf true gesetzt. Lassen Sie es für normale Builds an; withCompress(false) ist keine Größenoptimierung (es vergrößert die Ausgabe meist). Greifen Sie dazu, um die Pipeline zu debuggen oder zu profilieren – es verschiebt den CPU/Speicher-Tradeoff (das Überspringen des Komprimierungsschritts), statt Speicher zu reduzieren.
    4. Erhöhen Sie memory_limit bewusst, pro Worker. Dies ist eine Standard-PHP-Einstellung, kein NextPDF-Schlüssel. Setzen Sie sie in der Pool-Konfiguration oder mit ini_set('memory_limit', '256M') für den CLI-/Queue-Prozess und dimensionieren Sie sie gegen eine profilierte Spitze, nicht gegen eine Schätzung.
  • Verwandt. Streaming und Speicher.

Eintrag: Speicher wächst mit der Seitenanzahl bei sehr großen Dokumenten

Abschnitt betitelt „Eintrag: Speicher wächst mit der Seitenanzahl bei sehr großen Dokumenten“
  • Symptom. Ein mehrtausendseitiges Dokument erschöpft den Speicher, obwohl jede Seite klein ist, und die Spitze steigt etwa im Gleichschritt mit der Seitenanzahl.
  • Wahrscheinliche Ursache. Der gepufferte Writer hält das gesamte serialisierte Dokument im Heap. Für sehr große Dokumente ist das die dominante Kostenursache.
  • Lösung.
    1. Bevorzugen Sie den Streaming-Schreibpfad. Verwenden Sie den dokumentierten Streaming-Schreibpfad, beschrieben in Streaming und Speicher: Er serialisiert jede Seite, sobald sie zusammengesetzt ist, und gibt den Puffer frei, was das Wachstum von Seitenpuffer/Ausgabe reduziert; kleine Pro-Objekt-Metadaten (Offsets, Seitenbaum) können dennoch mit der Seiten-/Objektanzahl skalieren. Folgen Sie dem dokumentierten Einstiegspunkt, statt interne Klassen zu kopieren – die zugrunde liegende Streaming-Engine ist experimental-eingestuft, und ihre Symbole sind nicht die stabile öffentliche Oberfläche.
    2. Für den nativen writeHtml()-Parser bedenken Sie, dass der eingabeseitige Speicher sowohl durch die Verschachtelungstiefe- als auch durch die Elementanzahl-Guards begrenzt ist: ADR-001 deckelt die Verschachtelung bei MAX_NESTING_DEPTH = 100 und lehnt Dokumente über MAX_ELEMENT_COUNT = 50000 ab. Einem Dokument, das die Elementobergrenze erreicht, wird dies explizit mitgeteilt, statt den Speicher stillschweigend zu erschöpfen. Diese ADR-001-Obergrenzen regeln nur den nativen Parser; die optionale Chrome-Bridge (writeHtmlChrome()) rendert prozessextern und hat ihre eigenen, separaten Speicher-/Eingabegrenzen, nicht diese Obergrenzen.
  • Verwandt. Streaming und Speicher.

Eintrag: Ein langlebiger Worker erschöpft den Speicher nach vielen Jobs

Abschnitt betitelt „Eintrag: Ein langlebiger Worker erschöpft den Speicher nach vielen Jobs“
  • Symptom. Einzelne Renders gelingen, aber ein Queue-Worker, der viele PDFs hintereinander rendert, erschöpft den Speicher nach Minuten oder Stunden.
  • Wahrscheinliche Ursache. Ein langlebiger PHP-Prozess akkumuliert Allokationen über Jobs hinweg. Ein langsames Wachstum, das in einer Anfrage unsichtbar ist, summiert sich über Tausende.
  • Lösung.
    1. Teilen Sie Registrys, erstellen Sie Dokumente neu. Bauen Sie die FontRegistry und ImageRegistry einmal beim Boot und übergeben Sie sie an eine DocumentFactory; erstellen Sie ein frisches Document pro Job mit $factory->create($config). Das Parsen von Schriften und Bildern geschieht dann einmal für den Prozess, nicht einmal pro Job, und der Dokumentbaum pro Job wird eingesammelt, sobald er den Gültigkeitsbereich verlässt. Folgen Sie examples/14-worker-factory.php.
    2. Begrenzen Sie den gemeinsam genutzten Bild-Cache mit new ImageRegistry(maxCacheBytes: ...), sodass er nicht über Jobs hinweg unbegrenzt wachsen kann.
    3. Recyceln Sie den Worker – Prozesssteuerung, keine Engine-Garantie. Setzen Sie in PHP-FPM pm.max_requests, sodass jedes Kind nach einer festen Anzahl von Anfragen neu erzeugt wird. Verwenden Sie in Laravel-Queues queue:work --max-jobs / --max-time / --memory; in Symfony Messenger messenger:consume --limit / --time-limit / --memory-limit.
  • Verwandt. Streaming und Speicher.

Eintrag: Durchsatz-Einbruch bei einem kalten oder unzureichend aufgewärmten Prozess

Abschnitt betitelt „Eintrag: Durchsatz-Einbruch bei einem kalten oder unzureichend aufgewärmten Prozess“
  • Symptom. Die ersten Renders in einem frischen Prozess sind langsam, oder jede Anfrage zahlt Parse-Kosten, die warme Anfragen nicht zahlen sollten.
  • Wahrscheinliche Ursache. Zwei Kaltstartkosten stapeln sich. PHP ohne Opcache kompiliert jede Datei bei jeder Anfrage neu, und eine nicht aufgewärmte FontRegistry parst jeden Schriftschnitt beim ersten Verwenden.
  • Lösung.
    1. Aktivieren Sie Opcache (und JIT, wo es hilft). Setzen Sie opcache.enable=1 und ein großzügiges opcache.memory_consumption; setzen Sie in der Produktion opcache.validate_timestamps=0, sodass der Cache nicht pro Anfrage erneut geprüft wird. Diese Einstellung erfordert einen Deploy-Prozess, der PHP-FPM bei jedem Release neu startet oder neu lädt (oder Opcache anderweitig zurücksetzt, z. B. opcache_reset() / cachetool) – andernfalls liefert Opcache weiter den alten Bytecode, und veralteter Code läuft nach einem Deploy. Dies sind Standard-PHP-Ini-Einstellungen, keine NextPDF-Schlüssel.
    2. Wärmen und sperren Sie die Font-Registry beim Boot. Auf einer FontRegistry-Instanz parst $fontRegistry->warmup($fontFiles) Schnitte einmal während des Boots, und $fontRegistry->lock() friert die Registry ein, sodass Code zur Anfragezeit gemeinsam genutzten Zustand nicht mutieren kann; $fontRegistry->isLocked() meldet den Zustand. In einem echt langlebigen Worker oder Anwendungsserver – einem Queue-Consumer oder einem RoadRunner/Swoole/Octane-Worker, der denselben PHP-Prozess über viele Anfragen am Leben hält – behält eine aufgewärmte, gesperrte Registry ihre geparsten Schnitte im Objektzustand und verwandelt das Parsen von Schriften pro Anfrage in eine einmalige Prozess-Boot-Kostenstelle. Unter dem Standard-PHP-FPM-Anfragemodell überlebt dieser aufgewärmte Objektzustand nicht über Anfragen hinweg: Opcache cacht kompilierte Klassen und Bytecode, nicht aufgewärmten Userland-Objektzustand, sodass eine aufgewärmte FontRegistry pro Anfrage neu aufgebaut wird (bei jeder Anfrage aus dem Bootstrap des Kindes erneut ausgeführt), nicht über Anfragen innerhalb eines Kindes warm gehalten. Auf schlichtem PHP-FPM amortisiert Opcache hauptsächlich die Kosten der Bytecode-Neukompilierung; akzeptieren Sie, dass das Parsen von Schriften pro Anfrage bezahlt wird, nicht eliminiert. Die anfrageübergreifende Amortisierung – jeden Schnitt einmal für die Lebensdauer des Prozesses zu parsen – gilt nur in einem echt langlebigen Prozess wie einem RoadRunner/Swoole/Octane-Worker oder einem Queue-Consumer, der denselben PHP-Prozess über viele Anfragen am Leben hält.
    3. Parsen Sie nicht dieselbe Vorlage pro Anfrage erneut. Lösen Sie Schriften und wiederverwendbare Ressourcen einmal beim Boot über die gemeinsam genutzten Registrys auf; nur das Document pro Job sollte in der Anfrage erstellt werden.
  • Verwandt. Streaming und Speicher.

Eintrag: Server sättigt und Latenz spitzt unter Nebenläufigkeit

Abschnitt betitelt „Eintrag: Server sättigt und Latenz spitzt unter Nebenläufigkeit“
  • Symptom. Die Render-Latenz ist isoliert in Ordnung, aber unter Last swappt die Box, die CPU sättigt, oder Anfragen stauen sich und laufen in einen Timeout.
  • Wahrscheinliche Ursache. Zu viele PHP-FPM-Worker für den verfügbaren RAM, sodass die Summe der Worker-Spitzen den physischen Speicher übersteigt und der Host swappt; oder zu wenige Worker, sodass Anfragen sich hinter einem kleinen Pool serialisieren.
  • Lösung.
    1. Dimensionieren Sie pm.max_children aus einer profilierten Spitze. Verwenden Sie die Standardformel:

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

      Messen Sie die reale Spitze eines Workers mit einem repräsentativen Dokument (siehe den Profiling-Hinweis im Geltungsbereich), reservieren Sie Spielraum für das OS und alle kolokierten Dienste und dividieren Sie. Lassen Sie eine Marge; dimensionieren Sie nicht auf 100 % des RAM.

    2. Verankern Sie die Komprimierungskosten in Ihrem Budget. Die Flate-Komprimierung kann ein erheblicher CPU-Kostenfaktor beim Schreiben eines Streams sein und skaliert mit dem Volumen der komprimierbaren Stream-Bytes, sodass Seitenanzahl und eingebettetes Schriftvolumen die CPU pro Render beeinflussen; Bildverarbeitung, Schrift-Subsetting und Eingabe-Parsing können ebenfalls dominieren. Messen Sie mit repräsentativen Dokumenten und berücksichtigen Sie den realen Treiber, wenn Sie Worker-Anzahl und CPU wählen.

    3. Setzen Sie pm.max_requests neben pm.max_children, sodass Kinder recyceln und jegliches langsame Wachstum zurückgewinnen, wie im Worker-Eintrag oben.

  • Verwandt. Streaming und Speicher.

Eintrag: Große nicht vertrauenswürdige Eingabe ist langsam oder teuer zu parsen

Abschnitt betitelt „Eintrag: Große nicht vertrauenswürdige Eingabe ist langsam oder teuer zu parsen“
  • Symptom. Ein Render ist langsam oder speicherlastig bei einer großen oder tief verschachtelten Eingabe, besonders HTML oder einer Schrift, die Sie nicht erzeugt haben.
  • Wahrscheinliche Ursache. Die Parse-Kosten skalieren mit Eingabegröße und -struktur. Eine pathologische Eingabe (tiefe Verschachtelung, eine enorme Elementanzahl oder eine fehlerhafte Schrift) kann das Budget dominieren.
  • Lösung.
    1. Stützen Sie sich auf die Grenzen der Engine. Der native writeHtml()-HTML- Parser erzwingt MAX_NESTING_DEPTH = 100 und MAX_ELEMENT_COUNT = 50000 (ADR-001); Eingaben über diesen Obergrenzen werden abgelehnt, statt zugelassen zu werden, den Prozess zu erschöpfen. (Die optionale Chrome-Bridge, writeHtmlChrome(), liegt außerhalb des Geltungsbereichs dieser ADR-001-Obergrenzen und erzwingt ihre eigenen, separaten Speicher-/Eingabegrenzen.)
    2. Behandeln Sie vom Aufrufer gelieferte Schriften als nicht vertrauenswürdig. Eine fehlerhafte Schrift löst NextPDF\Exception\FontParsingException aus, statt die Ausgabe zu beschädigen, also fangen Sie die spezifische Exception ab und lehnen die Eingabe ab, statt es erneut zu versuchen.
    3. Validieren und dimensionieren Sie Eingaben an Ihrer Grenze und wenden Sie anfragebezogene Grenzen für die Dokumentgröße bei vom Aufrufer beeinflusstem Inhalt an.
  • Verwandt. Fehlerbehebung: Schriften und Tagging.
SymptomWahrscheinlichster Hebel
Allowed memory size … exhausted bei einem einzelnen Render$config->withImageCacheBytes() senken; Bilder vor dem Einbetten verkleinern; memory_limit pro Worker erhöhen
Speicherspitze steigt mit der SeitenanzahlDen dokumentierten Streaming-Schreibpfad verwenden
Worker-Speicher steigt über viele JobsFontRegistry/ImageRegistry über DocumentFactory teilen; pm.max_requests / --max-jobs setzen
Erste Anfragen langsam, Parse-Kosten pro AnfrageOpcache aktivieren; $fontRegistry->warmup(), dann ->lock() beim Boot
Host swappt / Latenzspitzen unter Lastpm.max_children = (RAM − Overhead) / Worker-Spitze dimensionieren
Langsam oder schwer bei großer/nicht vertrauenswürdiger EingabeAuf ADR-001-Obergrenzen verlassen; fehlerhafte Schriften bei FontParsingException ablehnen
  • imageCacheBytes ist eine Speicherobergrenze, kein Größenregler. Das Senken deckelt den Cache, sodass ein Build schnell fehlschlägt; es resampelt oder re-encodiert die eingebetteten Bilder niemals. Core hat keine Bildqualitätssteuerung.
  • withCompress(false) macht Dateien größer und ist eine Debugging-/ Profiling-Hilfe. Es ist keine Größenoptimierung; es verschiebt den CPU/Speicher-Tradeoff (es überspringt den Komprimierungsschritt), statt Speicher zu reduzieren.
  • Das genaue Speicherprofil der Streaming-Engine ist eine experimental-eingestufte Eigenschaft und kann sich zwischen Minor-Releases verschieben. Behandeln Sie jede einzelne Messung als Beobachtung, nicht als portable Konstante.
  • memory_limit, opcache.*, pm.max_children und pm.max_requests sind Standard-PHP-/PHP-FPM-Einstellungen. NextPDF stellt keine eigenen Schlüssel dafür bereit; konfigurieren Sie sie in Ihrer Laufzeit, nicht in Config.

Glossar: Streaming-Writer · Schrift-Subsetting