Массовая генерация документов
Spec: ISO 24495-1:2023, §5 ISO 24495-1:2023 §5 Spec: ISO 9241-112:2025, §6.1.2.3 ISO 9241-112:2025 §6.1.2.3 Evidence: Benchmark-backed
Сгенерировать один PDF — это вызов функции. Сгенерировать сто тысяч по расписанию — уже системная задача: память должна оставаться в заданных пределах, работа — выполняться параллельно, а числа — что-то значить. Эта страница проводит через сценарий пакетной генерации: от вопроса о пропускной способности до развёртывания, которое выдерживает нагрузку. Здесь прямо сказано: честный ответ — “измерьте на своих документах”, а не громкая цифра.
Почему это важно
Заголовок раздела «Почему это важно»Пакетная генерация обычно ломается двумя способами. Первый — постепенный рост потребления памяти. Долгоживущий worker документ за документом накапливает удерживаемое состояние, пока его принудительно не завершают посреди пакета, и прогон оказывается ни завершённым, ни корректно остановленным. Второй — уверенное, но бессмысленное число: результат теста на тривиальном документе используют для расчёта парка машин, которые отрисовывают сложные документы, и ошибка проявляется только под продакшн-нагрузкой.
Избежать можно и того, и другого, но только если с самого начала спроектировать модель памяти и методику измерений, а не добавлять их после первого инцидента.
Если коротко
Заголовок раздела «Если коротко»- Единица работы — одноразовый документ, а не общий. Храните данные времени жизни процесса (шрифты, кэш изображений) в общих реестрах; документ создавайте и уничтожайте для каждой отрисовки.
- У памяти две части, и для долгоживущего worker важна только одна. Кратковременный пик во время отрисовки ожидаем; удерживаемая память, которая не возвращается, — это утечка, которая обрывает пакет.
- Пропускная способность — это параллелизм плюс ограниченная стоимость на одну отрисовку. Схема, которая выдерживает нагрузку, — это очередь, питающая stateless-worker’ы, каждый из которых отрисовывает и освобождает память.
- Число без метода его получения — это не число. NextPDF сообщает измерения по каждой отрисовке как данные, которые вы собираете, и отказывается от ничем не оговорённых заявлений о скорости. Самая важная цифра — та, которую вы измеряете на собственных шаблонах (ISO 24495-1 §5.x11 — размещайте значимое сообщение там, где его найдёт читатель).
Как к этому подходит NextPDF
Заголовок раздела «Как к этому подходит NextPDF»Архитектура строится вокруг одного решения: состояние, живущее в рамках процесса, является общим и неизменяемым; состояние, живущее в рамках отрисовки, создаётся заново и выбрасывается. Шрифты — это структурные данные: их разбирают один раз и затем блокируют, поэтому ни одна отрисовка не может изменить их и испортить следующую. Кэш изображений — это ограниченное хранилище по принципу least-recently-used, которое никогда не блокируется, поэтому память остаётся в пределах лимита и не утекает между запросами. Фабрика документов — stateless-синглтон; каждый создаваемый ею документ является одноразовым.
Именно это разделение позволяет безопасно держать worker запущенным часами под Octane, RoadRunner или Swoole. Оно устраняет режим сбоя, при котором “запрос N портит запрос N+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.
- Enqueue the work A queue holds the render jobs. The queue is the throughput dial — workers scale horizontally behind it.
- Render on a disposable document Each worker creates a fresh document from the factory, renders, emits the bytes, and lets the document go.
- Measure, then size Collect per-render time and peak memory. Size the fleet from measurements on your own templates, not a generic figure.
Мосты к фреймворкам делают эту схему вариантом по умолчанию, а не тем, что приходится собирать вручную. Сервис-провайдер Laravel регистрирует реестр шрифтов как заранее прогретый и заблокированный синглтон, а документ связывает как новый экземпляр при каждом разрешении зависимости. Он поставляется с заданием очереди с ограниченным числом попыток, таймаутом и экспоненциальной задержкой между повторами. Это задание проверяет путь вывода на стороне worker’а, потому что сериализованная полезная нагрузка очереди может быть подменена при передаче. Интеграции для Symfony и CodeIgniter следуют той же дисциплине одноразового документа и общего реестра.
Что говорят факты
Заголовок раздела «Что говорят факты»Модель памяти подтверждена кодом. Evidence: Code-backed В Laravel NextPdfServiceProvider регистрирует FontRegistry как синглтон, который сначала прогревается, а затем блокируется через lock(), ImageRegistry — как синглтон с ограниченным LRU, который намеренно не блокируется, а Document — как привязку, создаваемую при каждом разрешении зависимости через stateless-фабрику. Модель одноразового документа заложена в проводке зависимостей, а не только описана в тексте. GeneratePdfJob задаёт tries, timeout и backoff и повторно проверяет путь вывода внутри handle().
Поверхность измерений подтверждена тестами производительности.
Evidence: Benchmark-backed Движок выдаёт неизменяемый
RenderReport на каждую генерацию, содержащий время отрисовки в миллисекундах, пиковое
потребление памяти в байтах, число страниц, число предупреждений и случаи срабатывания резервных вариантов — те
самые исходные данные, которые нужны для расчёта парка машин. Отдельный анализатор фрагментации
памяти различает пиковую (кратковременную) и удерживаемую память. Это
различие показывает, остаётся ли долгоживущий worker в норме или он медленно
даёт утечку. Сама инфраструктура тестов производительности настроена на повторные
прогоны с прогревом, потому что одно измерение времени — это шум.
Эта дисциплина является принципом проектирования: Evidence: Design principle NextPDF сообщает производительность вместе с методом её получения и отказывается от ничем не оговорённых заявлений о скорости. Это согласуется с тем, как написана эта документация: Spec: ISO 24495-1:2023, §5 ISO 24495-1:2023 §5 важное сообщение размещается там, где его найдёт читатель. Сообщение, важное здесь, — “измерьте собственную нагрузку”.
Практический пример
Заголовок раздела «Практический пример»Код ниже — пример цикла с одноразовым документом и измерением. Движок выдаёт RenderReport; очередь — это ваша инфраструктура.
<?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), ]); }}Вызов unset() здесь не формальность. Состояние уровня отрисовки должно освобождаться на каждой итерации, чтобы удерживаемая память возвращалась к базовому уровню. Worker, у которого базовый уровень растёт от итерации к итерации, — это именно тот сбой, который призван предотвратить этот цикл.
Частое заблуждение
Заголовок раздела «Частое заблуждение»Главное заблуждение — “сколько PDF в секунду может NextPDF?” — будто у этого вопроса есть один ответ. Его нет, и именно из-за подобных цифр парки машин рассчитывают неверно. Стоимость отрисовки определяется главным образом самим документом, поэтому единственное число, на которое стоит опираться, — это число, измеренное на собственных шаблонах с помощью встроенного отчёта движка по каждой отрисовке. Цифра, за которой не указаны документ, оборудование и метод, — это украшение, а не данные.
Второе заблуждение — что следить нужно за пиковой памятью. Пик кратковременен и ожидаем — он возвращается. Пакет обрывает удерживаемая память, которая не возвращается. Именно поэтому движок разделяет эти две величины.
Ограничения и границы
Заголовок раздела «Ограничения и границы»- Универсального значения пропускной способности не существует, и эта страница намеренно его не приводит. Стоимость отрисовки зависит от ваших документов; измеряйте её с помощью отчёта по каждой отрисовке.
- Ограниченность памяти зависит от того, используется ли модель одноразового документа. Удержание документа на протяжении многих отрисовок или совместное использование изменяемого состояния уровня отрисовки отменяет эту гарантию. Мосты к фреймворкам по умолчанию используют безопасную схему. Самостоятельная проводка зависимостей должна воспроизводить её.
- Кэш изображений ограничен, а не безграничен. При тяжёлых нагрузках с уникальными изображениями LRU вытесняет элементы. Это проектное решение, а не регрессия.
- Размер пула worker’ов, выбор очереди и автомасштабирование — это решения по развёртыванию вне движка. NextPDF предоставляет измерения и примитив с заданными границами. Он не управляет вашей очередью.
RenderReport— это данные, а не вердикт. Он сообщает, что произошло при отрисовке. Превращение этого в план мощностей — уже ваш анализ.- Эта страница подтверждена тестами производительности в части поверхности измерений и подтверждена кодом в части модели памяти. Она не утверждает никакой конкретной скорости.
| Edition | Availability |
|---|---|
| Core | Модель одноразового документа, общие неизменяемые реестры,
|
| Pro | Те же примитивы; коммерческие возможности (подписание, PDF/A) добавляют стоимость на отрисовку, которую следует измерять, а не предполагать. |
| Enterprise | Те же примитивы; работа со структурированными счетами и валидацией добавляет дополнительную стоимость на отрисовку, растущую с объёмом полезной нагрузки и размером набора правил. |
Связанные документы
Заголовок раздела «Связанные документы»- Память и потоковая обработка — как движок удерживает память в заданных пределах на больших документах и где использует потоковую обработку.
- Честное измерение производительности — чего стоит тестовая цифра без метода её получения и как NextPDF сообщает производительность.
- Эксплуатация NextPDF в продакшене — превращение отчётов по каждой отрисовке в сигналы состояния, когда пакет действительно запускается.
Глоссарий
Заголовок раздела «Глоссарий»- Одноразовый документ — экземпляр документа, создаваемый для одной отрисовки и затем уничтожаемый, чтобы никакое состояние не утекало в следующую отрисовку.
- Общий реестр — состояние времени жизни процесса, неизменяемое после прогрева (шрифты, кэш изображений), повторно используемое между отрисовками без затрат на каждую отрисовку.
- Пиковая память — кратковременный максимум во время отрисовки; он ожидаем и возвращается к базовому уровню.
- Удерживаемая память — память, всё ещё занятая после завершения отрисовки; рост удерживаемого базового уровня от отрисовки к отрисовке — это утечка.
- Worker — долгоживущий процесс, который забирает задания отрисовки из очереди; чтобы пережить пакет, он должен оставаться в пределах лимита памяти.
- RenderReport — неизменяемый снимок метрик движка по каждой отрисовке (время, пиковая память, число страниц, предупреждения), используемый для планирования мощности по реальным данным.