Устранение неполадок: память и производительность
Область
Заголовок раздела «Область»Эти записи охватывают два семейства сбоев, в которые вы упираетесь под нагрузкой: PHP исчерпывает память во время отрисовки и пропускная способность падает с обрыва, как только процесс прогрет или насыщен. Каждая запись называет симптом, наиболее вероятную причину и исправление, которое использует реальную поверхность NextPDF или стандартные элементы управления PHP-FPM. О лежащей в основе модели потоковой обработки и руководстве по воркеру читайте Потоковая обработка и память; эта страница — её компаньон со стороны инцидентов.
Сначала измеряйте. Снимайте memory_get_peak_usage(true) до и после отрисовки и
вызывайте memory_reset_peak_usage() между итерациями — так, как бенчмарк движка
изолирует стоимость одной отрисовки. Настройка без базовой линии перемещает обрыв,
а не убирает его.
Запись: «Allowed memory size exhausted» во время генерации
Заголовок раздела «Запись: «Allowed memory size exhausted» во время генерации»- Симптом. Отрисовка прерывается фатальной ошибкой
Allowed memory size of <n> bytes exhaustedиз среды выполнения PHP, часто на большом или насыщенном изображениями документе. - Вероятная причина. Путь записи по умолчанию компонует весь документ, затем
сериализует его, поэтому пиковая память отслеживает общий размер вывода. Большой
документ, крупные встроенные изображения или большое встроенное начертание
шрифта могут вытолкнуть запрос за
memory_limit. - Решение.
- Ограничьте кэш изображений.
NextPDF\Core\ConfigпредоставляетimageCacheBytes(по умолчанию52428800, то есть 50 МБ). Понизьте его инстанс-витером$config->withImageCacheBytes($bytes)(сигнатураwithImageCacheBytes(int $bytes): self), чтобы сборка, встраивающая много изображений, давала сбой быстро на известном потолке, а не уходила в своп. Это ограничивает кэш изображений в памяти; оно не пересэмплирует и не перекодирует сами изображения. - Уменьшайте входные данные перед встраиванием. Core не масштабирует и не перекодирует изображения. Измените размер и перекодируйте завышенную растровую графику перед её встраиванием и встраивайте шрифты, которые вы действительно используете, чтобы сабсеттингу нужно было сохранить небольшой набор глифов (см. Уменьшение размера файла PDF).
- Держите сжатие включённым. У свежего
Configcompressустановлен вtrue. Оставляйте его включённым для обычных сборок;withCompress(false)— это не оптимизация размера (обычно она увеличивает вывод). Тянитесь к нему, чтобы отлаживать или профилировать конвейер — она сдвигает компромисс CPU/память (пропуская шаг сжатия), а не уменьшает память. - Повышайте
memory_limitнамеренно, на воркер. Это стандартная настройка PHP, а не ключ NextPDF. Задайте её в конфигурации пула или черезini_set('memory_limit', '256M')для процесса CLI/очереди и определите её размер по профилированному пику, а не по догадке.
- Ограничьте кэш изображений.
- Связанное. Потоковая обработка и память.
Запись: память растёт с числом страниц на очень больших документах
Заголовок раздела «Запись: память растёт с числом страниц на очень больших документах»- Симптом. Документ в несколько тысяч страниц исчерпывает память, даже хотя каждая страница мала, и пик растёт примерно в ногу с числом страниц.
- Вероятная причина. Буферизованный писатель держит весь сериализованный документ в куче. Для очень больших документов это доминирующая стоимость.
- Решение.
- Предпочитайте потоковый путь записи. Используйте документированный потоковый
путь записи, описанный в
Потоковой обработке и памяти:
он сериализует каждую страницу по мере её компоновки и освобождает буфер, что
уменьшает рост буфера страниц/вывода; небольшие метаданные на объект (смещения,
дерево страниц) всё ещё могут масштабироваться с числом страниц/объектов.
Следуйте документированной точке входа, а не копируйте внутренние классы —
лежащий в основе потоковый движок имеет уровень
experimental, и его символы не являются стабильной публичной поверхностью. - Для нативного парсера
writeHtml()помните, что память со стороны ввода ограничена защитами глубины вложенности и числа элементов: ADR-001 ограничивает вложенность наMAX_NESTING_DEPTH = 100и отклоняет документы свышеMAX_ELEMENT_COUNT = 50000. Документу, который упирается в предел элементов, об этом сообщается явно, а не он молча исчерпывает память. Эти пределы ADR-001 управляют только нативным парсером; необязательный мост Chrome (writeHtmlChrome()) отрисовывает вне процесса и имеет собственные отдельные пределы памяти/ввода, а не эти пределы.
- Предпочитайте потоковый путь записи. Используйте документированный потоковый
путь записи, описанный в
Потоковой обработке и памяти:
он сериализует каждую страницу по мере её компоновки и освобождает буфер, что
уменьшает рост буфера страниц/вывода; небольшие метаданные на объект (смещения,
дерево страниц) всё ещё могут масштабироваться с числом страниц/объектов.
Следуйте документированной точке входа, а не копируйте внутренние классы —
лежащий в основе потоковый движок имеет уровень
- Связанное. Потоковая обработка и память.
Запись: долгоживущий воркер исчерпывает память после многих задач
Заголовок раздела «Запись: долгоживущий воркер исчерпывает память после многих задач»- Симптом. Одиночные отрисовки удаются, но воркер очереди, отрисовывающий много PDF подряд, исчерпывает память через минуты или часы.
- Вероятная причина. Долгоживущий процесс PHP накапливает выделения между задачами. Медленный рост, невидимый в одном запросе, накапливается через тысячи.
- Решение.
- Разделяйте реестры, пересоздавайте документы. Постройте
FontRegistryиImageRegistryодин раз при загрузке и передайте их вDocumentFactory; создавайте свежийDocumentна задачу с$factory->create($config). Разбор шрифтов и изображений тогда происходит один раз на процесс, а не один раз на задачу, и дерево документа на задачу собирается, когда выходит из области видимости. Следуйтеexamples/14-worker-factory.php. - Ограничьте общий кэш изображений через
new ImageRegistry(maxCacheBytes: ...), чтобы он не мог расти без предела между задачами. - Перерабатывайте воркер — управление процессами, а не гарантия движка. В
PHP-FPM задайте
pm.max_requests, чтобы каждый потомок перерождался после фиксированного числа запросов. В очередях Laravel используйтеqueue:work --max-jobs/--max-time/--memory; в Symfony Messenger используйтеmessenger:consume --limit/--time-limit/--memory-limit.
- Разделяйте реестры, пересоздавайте документы. Постройте
- Связанное. Потоковая обработка и память.
Запись: провал пропускной способности на холодном или недопрогретом процессе
Заголовок раздела «Запись: провал пропускной способности на холодном или недопрогретом процессе»- Симптом. Первые отрисовки в свежем процессе медленны, или каждый запрос платит стоимость разбора, которую прогретые запросы платить не должны.
- Вероятная причина. Складываются две стоимости холодного старта. PHP без
opcache перекомпилирует каждый файл на каждом запросе, а непрогретый
FontRegistryразбирает каждое начертание шрифта при первом его использовании. - Решение.
- Включите opcache (и JIT, где он помогает). Задайте
opcache.enable=1и щедрыйopcache.memory_consumption; в продакшене задайтеopcache.validate_timestamps=0, чтобы кэш не перепроверялся на каждом запросе. Эта настройка требует процесса развёртывания, который перезапускает или перезагружает PHP-FPM (либо иначе сбрасывает opcache, напримерopcache_reset()/cachetool) при каждом релизе — иначе opcache продолжает отдавать старый байт-код и устаревший код выполняется после развёртывания. Это стандартные ini-настройки PHP, а не ключи NextPDF. - Прогрейте и заблокируйте реестр шрифтов при загрузке. На экземпляре
FontRegistry$fontRegistry->warmup($fontFiles)разбирает начертания один раз во время загрузки, а$fontRegistry->lock()замораживает реестр, чтобы код времени запроса не мог мутировать общее состояние;$fontRegistry->isLocked()сообщает состояние. В по-настоящему долгоживущем воркере или сервере приложений — потребителе очереди или воркере RoadRunner/Swoole/Octane, который держит один процесс PHP живым через многие запросы, — прогретый, заблокированный реестр сохраняет свои разобранные начертания в состоянии объекта, превращая разбор шрифтов на каждый запрос в одноразовую стоимость загрузки процесса. При стандартной модели запросов PHP-FPM это прогретое состояние объекта не переживает запросы: opcache кэширует скомпилированные классы и байт-код, а не прогретое состояние объектов userland, поэтому прогретыйFontRegistryперестраивается на каждый запрос (перезапускается каждым запросом из загрузчика потомка), а не держится прогретым через запросы внутри потомка. На простом PHP-FPM opcache в основном амортизирует стоимость перекомпиляции байт-кода; примите, что разбор шрифтов оплачивается на каждый запрос, а не устраняется. Межзапросовая амортизация — разбор каждого начертания один раз на время жизни процесса — применяется только в по-настоящему долгоживущем процессе, таком как воркер RoadRunner/Swoole/Octane или потребитель очереди, который держит один процесс PHP живым через многие запросы. - Не перепарсивайте один и тот же шаблон на каждый запрос. Разрешайте шрифты
и переиспользуемые ресурсы один раз при загрузке через общие реестры; только
Documentна задачу должен создаваться в запросе.
- Включите opcache (и JIT, где он помогает). Задайте
- Связанное. Потоковая обработка и память.
Запись: сервер насыщается и латентность подскакивает при конкуренции
Заголовок раздела «Запись: сервер насыщается и латентность подскакивает при конкуренции»- Симптом. Латентность на одну отрисовку в изоляции нормальна, но под нагрузкой машина уходит в своп, CPU насыщается, либо запросы выстраиваются в очередь и выходят за тайм-аут.
- Вероятная причина. Слишком много воркеров PHP-FPM для доступной RAM, поэтому сумма пиков воркеров превышает физическую память и хост уходит в своп; либо слишком мало воркеров, поэтому запросы сериализуются за небольшим пулом.
- Решение.
-
Определите
pm.max_childrenпо профилированному пику. Используйте стандартную формулу:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryИзмерьте реальный пик воркера на репрезентативном документе (см. примечание о профилировании в разделе «Область»), зарезервируйте запас для ОС и любых совмещённых служб и поделите. Оставьте маржу; не определяйте размер до 100% RAM.
-
Закрепите стоимость сжатия в своём бюджете. Сжатие Flate может быть значительной стоимостью CPU при записи потока и масштабируется с объёмом сжимаемых байтов потока, поэтому число страниц и объём встроенных шрифтов влияют на CPU на одну отрисовку; обработка изображений, сабсеттинг шрифтов и разбор ввода тоже могут доминировать. Измеряйте на репрезентативных документах и учитывайте реальный драйвер, когда выбираете число воркеров и CPU.
-
Задайте
pm.max_requestsрядом сpm.max_children, чтобы потомки перерабатывались и возвращали любой медленный рост, как в записи о воркере выше.
-
- Связанное. Потоковая обработка и память.
Запись: большой недоверенный ввод медленно или дорого разбирать
Заголовок раздела «Запись: большой недоверенный ввод медленно или дорого разбирать»- Симптом. Отрисовка медленна или тяжела по памяти на большом или глубоко вложенном вводе, особенно HTML или шрифте, который вы не производили.
- Вероятная причина. Стоимость разбора масштабируется с размером и структурой ввода. Патологический ввод (глубокая вложенность, огромное число элементов или некорректный шрифт) может доминировать в бюджете.
- Решение.
- Опирайтесь на пределы движка. Нативный HTML-парсер
writeHtml()обеспечиваетMAX_NESTING_DEPTH = 100иMAX_ELEMENT_COUNT = 50000(ADR-001); входы свыше этих пределов отклоняются, а не допускаются к исчерпанию процесса. (Необязательный мост Chrome,writeHtmlChrome(), вне области этих пределов ADR-001 и обеспечивает собственные отдельные пределы памяти/ввода.) - Относитесь к шрифтам, поданным вызывающим кодом, как к недоверенным.
Некорректный шрифт выбрасывает
NextPDF\Exception\FontParsingException, а не портит вывод, поэтому перехватывайте конкретное исключение и отклоняйте ввод, а не повторяйте попытку. - Валидируйте и ограничивайте размеры входов на своей границе и применяйте пределы уровня запроса на размер документа для содержимого, на которое влияет вызывающий код.
- Опирайтесь на пределы движка. Нативный HTML-парсер
- Связанное. Устранение неполадок: шрифты и тегирование.
Таблица решений: от симптома к рычагу
Заголовок раздела «Таблица решений: от симптома к рычагу»| Симптом | Наиболее вероятный рычаг |
|---|---|
Allowed memory size … exhausted на одной отрисовке | Понизьте $config->withImageCacheBytes(); уменьшите изображения до встраивания; повысьте memory_limit на воркер |
| Пиковая память растёт с числом страниц | Используйте документированный потоковый путь записи |
| Память воркера растёт через многие задачи | Разделяйте FontRegistry/ImageRegistry через DocumentFactory; задайте pm.max_requests / --max-jobs |
| Первые запросы медленны, стоимость разбора на запрос | Включите opcache; $fontRegistry->warmup(), затем ->lock() при загрузке |
| Хост уходит в своп / подскоки латентности под нагрузкой | Определите pm.max_children = (RAM − накладные расходы) / пик на воркер |
| Медленно или тяжело на большом/недоверенном вводе | Опирайтесь на пределы ADR-001; отклоняйте некорректные шрифты на FontParsingException |
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»imageCacheBytes— это потолок памяти, а не регулятор размера. Его понижение ограничивает кэш, чтобы сборка дала сбой быстро; оно никогда не пересэмплирует и не перекодирует встраиваемые вами изображения. У Core нет управления качеством изображений.withCompress(false)делает файлы больше и является вспомогательным средством для отладки/профилирования. Это не оптимизация размера; оно сдвигает компромисс CPU/память (пропуская шаг сжатия), а не уменьшает память.- Точный профиль памяти потокового движка — свойство уровня
experimentalи может сдвигаться между минорными релизами. Относитесь к любому одиночному измерению как к наблюдению, а не как к переносимой константе. memory_limit,opcache.*,pm.max_childrenиpm.max_requests— это стандартные настройки PHP / PHP-FPM. NextPDF не предоставляет собственных ключей для них; настраивайте их в своей среде выполнения, а не вConfig.
См. также
Заголовок раздела «См. также»- Потоковая обработка и память — модель потоковой обработки, пределы ADR-001 и полное руководство по пакетному воркеру.
- Уменьшение размера файла PDF — сжатие и сабсеттинг шрифтов, два реальных средства управления размером.
- Устранение неполадок: шрифты и тегирование — сбои разрешения, разбора и сабсеттинга шрифтов.
- Индекс базы знаний
Глоссарий: потоковый писатель · сабсеттинг шрифтов