Ga naar inhoud

Documentgeneratie op grote schaal

Spec: ISO 24495-1:2023, §5 Spec: ISO 9241-112:2025, §6.1.2.3 Evidence: Benchmark-backed

Eén PDF genereren is een functieaanroep. Honderdduizend PDF’s volgens een schema genereren is een systeemvraagstuk: geheugen moet begrensd blijven, werk moet parallel verlopen en cijfers moeten iets betekenen. Deze pagina loopt het scenario voor batchgeneratie door, van de doorvoervraag tot een uitrol die standhoudt. Ze maakt duidelijk dat het eerlijke antwoord „meet het op je eigen documenten” is, niet één kerncijfer.

Batchgeneratie mislukt op twee kenmerkende manieren. De eerste is geleidelijk oplopend geheugengebruik. Een langlevende worker stapelt document na document vastgehouden status op totdat hij halverwege de batch wordt afgebroken; de run is dan niet voltooid en ook niet netjes mislukt. De tweede is een zelfverzekerd maar betekenisloos cijfer: een benchmark van een triviaal document wordt gebruikt om een vloot te dimensioneren die complexe documenten weergeeft, en pas onder productiebelasting blijkt dat cijfer onjuist.

Je kunt beide vermijden, maar alleen als je de geheugenvorm en de meetmethode vanaf het begin ontwerpt, in plaats van ze pas na het eerste incident toe te voegen.

  • De werkeenheid is een wegwerpdocument, geen gedeeld document. Bewaar gegevens met proceslevensduur (lettertypen, afbeeldingscache) in gedeelde registries; maak het document per weergave aan en gooi het daarna weg.
  • Geheugen bestaat uit twee delen, en slechts één daarvan is van belang voor een langlevende worker. Een kortstondige piek tijdens een weergave is te verwachten; vastgehouden geheugen dat niet terugkeert is het lek dat een batch beëindigt.
  • Doorvoer is parallellisme plus begrensde kosten per weergave. De vorm die standhoudt is een wachtrij die stateless workers voedt, die elk weergeven en daarna weer vrijgeven.
  • Een cijfer zonder zijn methode is geen cijfer. NextPDF rapporteert metingen per weergave als gegevens die je zelf verzamelt, en weigert ongekwalificeerde snelheidsclaims. Het belangrijkste cijfer is het cijfer dat je meet op je eigen sjablonen (ISO 24495-1 §5.x11 — plaats de boodschap die ertoe doet daar waar de lezer haar vindt).

De architectuur is opgebouwd rond één beslissing: status met proceslevensduur is gedeeld en onveranderlijk; status voor één weergave wordt nieuw aangemaakt en weggegooid. Lettertypen zijn structurele gegevens die één keer worden ontleed en daarna vergrendeld, zodat geen enkele weergave ze kan wijzigen en de volgende kan vervuilen. De afbeeldingscache is een begrensde least-recently-used-opslag die nooit wordt vergrendeld, zodat het geheugen begrensd blijft zonder tussen verzoeken te lekken. De documentfactory is een stateless singleton; elk document dat hij aanmaakt is een wegwerpdocument.

Dankzij die scheiding is het veilig om een worker urenlang onder Octane, RoadRunner of Swoole te laten draaien. De opzet sluit de faalwijze waarbij „verzoek N verzoek N+1 beschadigt” uit, in plaats van te hopen dat het document zichzelf terugzet.

Het scenario kent vier fasen.

  1. Warm the shared state once On worker boot, parse and lock the font registry and size the image cache. This cost is paid once, not per document.
  2. Enqueue the work A queue holds the render jobs. The queue is the throughput dial — workers scale horizontally behind it.
  3. Render on a disposable document Each worker creates a fresh document from the factory, renders, emits the bytes, and lets the document go.
  4. Measure, then size Collect per-render time and peak memory. Size the fleet from measurements on your own templates, not a generic figure.
Het scenario voor grote schaal van begin tot eind: gedeelde onveranderlijke status wordt één keer opgewarmd; elke taak geeft weer op een wegwerpdocument en geeft dat daarna vrij; de doorvoer schaalt door workers toe te voegen, niet door er één te vergroten.

De framework-bruggen maken deze vorm de standaard, in plaats van iets dat je zelf in elkaar zet. De Laravel-serviceprovider registreert de lettertyperegistry als een opgewarmde, vergrendelde singleton en bindt het document als een nieuwe instantie per resolve. Ze levert een in de wachtrij geplaatste taak met een begrensd aantal pogingen, een time-out en exponentiële backoff. Die taak valideert haar uitvoerpad aan workerzijde, omdat een geserialiseerde wachtrij-payload onderweg kan zijn gemanipuleerd. De Symfony- en CodeIgniter-integraties volgen dezelfde discipline van wegwerpdocumenten en gedeelde registries.

Het geheugenmodel is door code onderbouwd. Evidence: Code-backed De Laravel-NextPdfServiceProvider registreert de FontRegistry als een singleton die wordt opgewarmd en daarna lock()-d, de ImageRegistry als een begrensde LRU-singleton die bewust niet wordt vergrendeld, en het Document als een binding per resolve via een stateless factory. Het model van het wegwerpdocument is vastgelegd in de bedrading, niet alleen in proza. De GeneratePdfJob draagt tries, timeout en backoff met zich mee en valideert haar uitvoerpad opnieuw binnen handle().

Het meetoppervlak is door benchmarks onderbouwd. Evidence: Benchmark-backed De engine produceert een onveranderlijk RenderReport per generatie, die de weergavetijd in milliseconden, het piek- geheugen in bytes, het aantal pagina’s, het aantal waarschuwingen en het aantal fallbacks bevat — precies de invoer die je nodig hebt om een vloot te dimensioneren. Een afzonderlijke analysefunctie voor geheugenfragmentatie onderscheidt piekgeheugen (kortstondig) van vastgehouden geheugen. Dat onderscheid laat zien of een langlevende worker gezond is of langzaam lekt. De benchmark-harness zelf is geconfigureerd voor herhaalde metingen met opwarming, omdat één enkele meting ruis is.

De discipline is een ontwerpprincipe: Evidence: Design principle NextPDF rapporteert prestaties samen met de methode en weigert ongekwalificeerde snelheidsclaims. Dat is in lijn met de manier waarop deze documentatie is geschreven: de boodschap die ertoe doet staat daar waar de lezer haar zal vinden — Spec: ISO 24495-1:2023, §5 . De boodschap die hier telt, is „meet je eigen werklast”.

De onderstaande code is de lus met wegwerpdocumenten, inclusief meting. De engine produceert een RenderReport; de wachtrij is je infrastructuur.

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Observability\RenderReport;
use Psr\Log\LoggerInterface;
/**
* One batch worker iteration: render, emit, release, measure.
*
* The factory and its registries are process-lifetime singletons; the
* document is disposable. Retained memory must return to baseline between
* iterations or the worker is leaking.
*
* @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs
*/
function runBatch(
DocumentFactoryInterface $factory,
LoggerInterface $logger,
iterable $jobs,
): void {
foreach ($jobs as $jobId => $build) {
$startedAt = hrtime(true);
// Fresh, disposable document — shares the warmed registries.
$doc = $factory->create();
$doc = $build($doc);
$bytes = $doc->getPdfData();
// Hand the bytes off to your sink (object store, response, etc.).
unset($doc, $bytes); // let the per-render state go
$elapsedMs = (hrtime(true) - $startedAt) / 1_000_000;
$logger->info('pdf.render.complete', [
'job_id' => $jobId,
'render_time_ms' => round($elapsedMs, 2),
'peak_memory_mb' => round(memory_get_peak_usage(true) / 1_048_576, 2),
]);
}
}

De unset() is niet cosmetisch. De status per weergave moet bij elke iteratie worden vrijgegeven, zodat het vastgehouden geheugen terugkeert naar het basisniveau. Een worker waarvan het basisniveau over iteraties heen oploopt, is precies de fout die deze lus beoogt te vermijden.

Het meest in het oog springende misverstand is “hoeveel PDF’s per seconde kan NextPDF maken?” alsof daarop één antwoord bestaat. Dat is niet zo, en het noemen van zo’n getal is precies hoe vloten verkeerd worden gedimensioneerd. De weergavekosten worden bepaald door het document, dus het enige cijfer dat het waard is om op te sturen, is het cijfer dat je op je eigen sjablonen meet met het rapport per weergave van de engine zelf. Een cijfer zonder het document, de hardware en de methode erachter is decoratie, geen gegevens.

Het tweede misverstand is dat piekgeheugen het cijfer is om in de gaten te houden. De piek is kortstondig en te verwachten — hij keert terug. Het cijfer dat een batch beëindigt, is vastgehouden geheugen dat niet terugkeert. Precies daarom scheidt de engine de twee.

  • Er bestaat geen universeel doorvoercijfer, en deze pagina noemt er bewust geen. De weergavekosten zijn afhankelijk van je documenten; meet ze met het rapport per weergave.
  • Begrensd geheugen hangt ervan af dat het model van het wegwerpdocument wordt gebruikt. Een document over veel weergaven heen vasthouden, of veranderlijke status per weergave delen, maakt de garantie ongedaan. De framework-bruggen kiezen standaard de veilige vorm. Zelfgebouwde bedrading moet die vorm repliceren.
  • De afbeeldingscache is begrensd, niet onbegrensd. Bij zware werklasten met veel unieke afbeeldingen verwijdert de LRU items. Dat is het ontwerp, geen regressie.
  • Het dimensioneren van de workerpool, de keuze van de wachtrij en autoscaling zijn uitrolbeslissingen buiten de engine. NextPDF levert de metingen en de begrensde primitieve. Ze beheert je wachtrij niet.
  • RenderReport is data, geen oordeel. Het vertelt je wat er bij een weergave is gebeurd. Het is aan jou om daar een capaciteitsplan van te maken.
  • Deze pagina is door benchmarks onderbouwd voor het meetoppervlak en door code onderbouwd voor het geheugenmodel. Ze doet geen uitspraak over een specifiek tempo.
Queued high-volume generation primitives — edition availability
Edition Availability
Core

Het model van het wegwerpdocument, gedeelde onveranderlijke registries, het RenderReport per weergave en de analysefunctie voor geheugenfragmentatie horen tot Core. Gewone PDF-generatie op grote schaal vereist geen commerciële laag.

Pro

Dezelfde primitieven; commerciële functies (ondertekenen, PDF/A) voegen kosten per weergave toe die je moet meten, niet aannemen.

Enterprise

Dezelfde primitieven; werk rond gestructureerde facturen en validatie voegt verdere kosten per weergave toe die schalen met de payload en de omvang van de regelset.

  • Geheugen en streaming — hoe de engine het geheugen begrensd houdt bij grote documenten en waar de engine streamt.
  • Eerlijk benchmarken — wat een benchmarkcijfer waard is zonder de bijbehorende methode, en hoe NextPDF prestaties rapporteert.
  • NextPDF in productie gebruiken — rapporten per weergave omzetten in gezondheidssignalen zodra de batch echt draait.
  • Wegwerpdocument — een documentinstantie die voor één enkele weergave wordt aangemaakt en daarna wordt weggegooid, zodat er geen status naar de volgende weergave lekt.
  • Gedeelde registry — status met proceslevensduur die na opwarming onveranderlijk is (lettertypen, afbeeldingscache) en zonder kosten per weergave tussen weergaven wordt hergebruikt.
  • Piekgeheugen — de kortstondige hoogste waarde tijdens een weergave; die wordt verwacht en keert terug naar het basisniveau.
  • Vastgehouden geheugen — geheugen dat nog steeds in gebruik is nadat een weergave is voltooid; een stijgend basisniveau van vastgehouden geheugen over weergaven heen is een lek.
  • Worker — een langlevend proces dat weergavetaken uit een wachtrij haalt; moet geheugenbegrensd blijven om een batch te overleven.
  • RenderReport — de onveranderlijke momentopname van de meetgegevens per weergave van de engine (tijd, piekgeheugen, aantal pagina’s, waarschuwingen), gebruikt om capaciteit op basis van echte gegevens te dimensioneren.