Przejdź do głównej zawartości

Generowanie dokumentów na dużą skalę

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

Wygenerowanie jednego pliku PDF to jedno wywołanie funkcji. Wygenerowanie stu tysięcy zgodnie z harmonogramem jest już problemem systemowym: pamięć musi pozostać ograniczona, praca musi przebiegać równolegle, a liczby muszą coś znaczyć. Ta strona prowadzi przez scenariusz generowania wsadowego — od pytania o przepustowość po wdrożenie, które sprawdza się w praktyce. Mówi wprost, że uczciwa odpowiedź brzmi „zmierz to na własnych dokumentach”, a nie „efektowna liczba w nagłówku”.

Generowanie wsadowe zawodzi zwykle na dwa charakterystyczne sposoby. Pierwszy to stopniowe narastanie zużycia pamięci. Długo działający proces roboczy gromadzi stan utrzymywany między dokumentami, aż zostaje przerwany w połowie partii, a przebieg nie jest ani ukończony, ani jednoznacznie zakończony niepowodzeniem. Drugi to pozornie pewna, lecz bezwartościowa liczba: wynik testu na trywialnym dokumencie służy do oszacowania floty renderującej złożone dokumenty, a błąd wychodzi na jaw dopiero pod obciążeniem produkcyjnym.

Obu problemów można uniknąć, ale tylko wtedy, gdy strukturę pamięci i metodę pomiaru zaprojektuje się od początku, zamiast dokładać je po pierwszym incydencie.

  • Jednostką pracy jest dokument jednorazowy, a nie współdzielony. Stan o czasie życia procesu (czcionki, pamięć podręczna obrazów) przechowuj we współdzielonych rejestrach; dokument twórz i odrzucaj przy każdym renderowaniu.
  • Pamięć ma dwie części, a dla długo działającego procesu roboczego liczy się tylko jedna. Przejściowy szczyt podczas renderowania jest oczekiwany; utrzymywana pamięć, która nie jest zwalniana, to wyciek kończący partię.
  • Przepustowość wynika ze zrównoleglenia i ograniczonego kosztu pojedynczego renderowania. Architekturą, która sprawdza się w praktyce, jest kolejka zasilająca bezstanowe procesy robocze, z których każdy renderuje i zwalnia zasoby.
  • Liczba bez metody nie jest liczbą. NextPDF raportuje pomiary pojedynczego renderowania jako dane, które zbierasz, i odmawia formułowania nieuzasadnionych deklaracji o szybkości. Najważniejsza liczba to ta, którą zmierzysz na własnych szablonach (ISO 24495-1 §5.x11 — umieść istotną informację tam, gdzie czytelnik ją znajdzie).

Architektura opiera się na jednej decyzji: stan żyjący przez czas trwania procesu jest współdzielony i niezmienny; stan żyjący przez czas renderowania jest świeży i odrzucany. Czcionki są danymi strukturalnymi: analizuje się je raz, a następnie blokuje, dzięki czemu żadne renderowanie nie może ich zmienić ani zanieczyścić kolejnego. Pamięć podręczna obrazów to ograniczony magazyn typu least-recently-used, którego nigdy się nie blokuje, dzięki czemu pamięć pozostaje ograniczona i nie wycieka między żądaniami. Fabryka dokumentów to bezstanowy singleton; każdy tworzony przez nią dokument jest jednorazowy.

To właśnie ten podział sprawia, że proces roboczy można bezpiecznie uruchamiać przez wiele godzin pod Octane, RoadRunner lub Swoole. Eliminuje tryb awarii, w którym „żądanie N uszkadza żądanie N+1”, już na etapie konstrukcji, zamiast liczyć na to, że dokument sam się zresetuje.

Scenariusz składa się z czterech etapów.

  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.
Kompleksowe omówienie scenariusza na dużą skalę: współdzielony niezmienny stan jest rozgrzewany raz; każde zadanie renderuje na dokumencie jednorazowym i zwalnia zasoby; przepustowość skaluje się przez dodawanie procesów roboczych, a nie przez powiększanie jednego.

Mostki integracyjne dla frameworków sprawiają, że ta architektura jest domyślna, a nie składana samodzielnie. Dostawca usług Laravel rejestruje rejestr czcionek jako rozgrzany, zablokowany singleton, a dokument wiąże jako świeżą instancję przy każdym rozwiązaniu zależności. Dostarcza zadanie kolejkowane z ograniczoną liczbą prób, limitem czasu i wykładniczym odstępem między ponowieniami. Zadanie weryfikuje swoją ścieżkę wyjściową po stronie procesu roboczego, ponieważ zserializowany ładunek z kolejki może zostać zmanipulowany w trakcie przesyłania. Integracje z Symfony i CodeIgniter stosują tę samą dyscyplinę dokumentu jednorazowego i współdzielonego rejestru.

Model pamięci jest poparty kodem. Evidence: Code-backed Laravelowy NextPdfServiceProvider rejestruje FontRegistry jako singleton, który jest rozgrzewany, a następnie blokowany metodą lock(), ImageRegistry jako singleton typu bounded-LRU, który celowo nie jest blokowany, oraz Document jako powiązanie tworzone przez bezstanową fabrykę przy każdym rozwiązaniu zależności. Model dokumentu jednorazowego wynika z okablowania, a nie tylko z opisu. GeneratePdfJob zawiera parametry tries, timeout i backoff oraz ponownie weryfikuje swoją ścieżkę wyjściową wewnątrz handle().

Warstwa pomiarowa jest poparta testami porównawczymi. Evidence: Benchmark-backed Dla każdego generowania silnik emituje niezmienny raport RenderReport, który zawiera czas renderowania w milisekundach, szczytowe zużycie pamięci w bajtach, liczbę stron, liczbę ostrzeżeń oraz użycie rozwiązań awaryjnych — czyli dokładnie te dane wejściowe, których potrzebujesz do wymiarowania floty. Osobny analizator fragmentacji pamięci odróżnia pamięć szczytową (przejściową) od utrzymywanej. To rozróżnienie pokazuje, czy długo działający proces roboczy jest sprawny, czy powoli przecieka. Sam zestaw testów porównawczych jest skonfigurowany do powtarzanych przebiegów z rozgrzewką, ponieważ pojedynczy pomiar czasu to szum.

Ta dyscyplina jest zasadą projektową: Evidence: Design principle NextPDF raportuje wydajność razem z metodą pomiaru i odmawia formułowania nieuzasadnionych deklaracji o szybkości. To spójne ze sposobem, w jaki napisano tę dokumentację — Spec: ISO 24495-1:2023, §5 umieszcza istotną informację tam, gdzie czytelnik ją znajdzie. Istotną informacją jest tu „zmierz własne obciążenie”.

Poniższy kod pokazuje pętlę dokumentu jednorazowego z pomiarem. Silnik wytwarza RenderReport; kolejka należy do twojej infrastruktury.

<?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),
]);
}
}

Wywołanie unset() nie jest kosmetyczne. Stan pojedynczego renderowania ma być zwalniany w każdej iteracji, aby utrzymywana pamięć wracała do poziomu bazowego. Proces roboczy, którego poziom bazowy rośnie w kolejnych iteracjach, to awaria, której ta pętla ma zapobiegać.

Najczęstsze nieporozumienie to pytanie „ile plików PDF na sekundę potrafi NextPDF?”, tak jakby istniała na nie jedna odpowiedź. Nie istnieje, a podawanie jej prowadzi do błędnego wymiarowania floty. Koszt renderowania zależy przede wszystkim od dokumentu, więc jedyną liczbą, na której warto się opierać, jest ta zmierzona na własnych szablonach z użyciem raportu pojedynczego renderowania z silnika. Liczba bez stojącego za nią dokumentu, sprzętu i metody jest ozdobą, a nie danymi.

Drugie nieporozumienie polega na tym, że to pamięć szczytową należy obserwować. Szczyt jest przejściowy i oczekiwany — wraca do normy. Liczbą, która kończy partię, jest utrzymywana pamięć, która nie wraca do normy. Właśnie dlatego silnik rozdziela te dwie wartości.

  • Nie istnieje uniwersalna liczba przepustowości i ta strona celowo żadnej nie podaje. Koszt renderowania zależy od twoich dokumentów; zmierz go raportem pojedynczego renderowania.
  • Ograniczone zużycie pamięci zależy od stosowania modelu dokumentu jednorazowego. Przetrzymywanie dokumentu przez wiele renderowań lub współdzielenie zmiennego stanu pojedynczego renderowania unieważnia tę gwarancję. Mostki integracyjne dla frameworków domyślnie przyjmują bezpieczną architekturę. Ręcznie wykonane okablowanie musi ją odtworzyć.
  • Pamięć podręczna obrazów ma limit, nie jest nieograniczona. Przy dużym obciążeniu unikalnymi obrazami mechanizm LRU usuwa wpisy. Taki jest zamysł projektowy, a nie regres.
  • Wymiarowanie puli procesów roboczych, wybór kolejki i automatyczne skalowanie to decyzje wdrożeniowe poza silnikiem. NextPDF dostarcza pomiary oraz ograniczone prymitywy. Nie zarządza twoją kolejką.
  • RenderReport to dane, a nie werdykt. Mówi, co się wydarzyło podczas renderowania. Przełożenie tego na plan wydajnościowy to już twoja analiza.
  • Ta strona jest poparta testami porównawczymi w zakresie warstwy pomiarowej oraz poparta kodem w zakresie modelu pamięci. Nie deklaruje żadnej konkretnej wartości.
Queued high-volume generation primitives — edition availability
Edition Availability
Core

Model dokumentu jednorazowego, współdzielone niezmienne rejestry, RenderReport dla pojedynczego renderowania oraz analizator fragmentacji pamięci należą do edycji Core. Zwykłe generowanie plików PDF na dużą skalę nie wymaga edycji komercyjnej.

Pro

Te same prymitywy; funkcje komercyjne (podpisywanie, PDF/A) dodają koszt pojedynczego renderowania, który należy zmierzyć, a nie zakładać.

Enterprise

Te same prymitywy; obsługa faktur ustrukturyzowanych i walidacji dodaje dalszy koszt pojedynczego renderowania, który skaluje się wraz z rozmiarem ładunku i zestawu reguł.

  • Dokument jednorazowy — instancja dokumentu tworzona na potrzeby pojedynczego renderowania i odrzucana po nim, dzięki czemu żaden stan nie przecieka do kolejnego renderowania.
  • Współdzielony rejestr — stan o czasie życia procesu, niezmienny po rozgrzaniu (czcionki, pamięć podręczna obrazów), używany ponownie w kolejnych renderowaniach bez ponoszenia kosztu przy każdym renderowaniu.
  • Pamięć szczytowa — przejściowy najwyższy poziom zużycia podczas renderowania; oczekiwany i wracający do poziomu bazowego.
  • Pamięć utrzymywana — pamięć nadal zajęta po zakończeniu renderowania; rosnący poziom bazowy utrzymywanej pamięci w kolejnych renderowaniach to wyciek.
  • Proces roboczy — długo działający proces pobierający zadania renderowania z kolejki; musi utrzymywać ograniczone zużycie pamięci, aby przetrwać partię.
  • RenderReport — niezmienna migawka metryk pojedynczego renderowania tworzona przez silnik (czas, pamięć szczytowa, liczba stron, ostrzeżenia), używana do wymiarowania wydajności na podstawie rzeczywistych danych.