Resolución de problemas: memoria y rendimiento
Alcance
Sección titulada «Alcance»Estas entradas cubren dos familias de fallos que aparecen bajo carga: PHP quedándose sin memoria durante una representación, y el rendimiento que cae en picado una vez que un proceso está caliente o saturado. Cada entrada nombra un síntoma, la causa más probable y una solución que usa superficie real de NextPDF o controles estándar de PHP-FPM. Para el modelo de transmisión subyacente y un tutorial de workers, lee Transmisión y memoria; esta página es el complemento, en el lado de los incidentes, de aquella.
Mide primero. Muestrea memory_get_peak_usage(true) antes y después de una
representación y llama a memory_reset_peak_usage() entre iteraciones, igual que
el banco de pruebas del motor aísla el coste por representación. Ajustar sin una
referencia mueve la caída en lugar de eliminarla.
Entrada: «Allowed memory size exhausted» durante la generación
Sección titulada «Entrada: «Allowed memory size exhausted» durante la generación»- Síntoma. Una representación se aborta con un error fatal
Allowed memory size of <n> bytes exhausteddel entorno de ejecución de PHP, a menudo en un documento grande o con muchas imágenes. - Causa probable. La vía de escritura predeterminada compone todo el
documento y luego lo serializa, de modo que la memoria pico sigue al tamaño
total de salida. Un documento grande, imágenes incrustadas grandes o una
tipografía de fuente incrustada grande pueden empujar la solicitud más allá de
memory_limit. - Resolución.
- Acota la caché de imágenes.
NextPDF\Core\ConfigexponeimageCacheBytes(predeterminado52428800, es decir, 50 MB). Bájalo con el wither de instancia$config->withImageCacheBytes($bytes)(firmawithImageCacheBytes(int $bytes): self) para que una compilación que incrusta muchas imágenes falle rápido en un límite conocido en lugar de hacer swapping. Esto limita la caché de imágenes en memoria; no remuestrea ni recodifica las propias imágenes. - Reduce las entradas antes de incrustar. El Core no reduce ni recodifica imágenes. Redimensiona y recodifica el material gráfico rasterizado sobredimensionado antes de incrustarlo, e incrusta las fuentes que realmente usas para que el subconjuntado tenga un conjunto de glifos pequeño que conservar (consulta Reducir el tamaño de archivo de un PDF).
- Mantén la compresión activada. Un
Confignuevo tienecompressestablecido entrue. Déjalo activado para compilaciones normales;withCompress(false)no es una optimización de tamaño (normalmente aumenta la salida). Recurre a él para depurar o perfilar la canalización: desplaza el compromiso CPU/memoria (omitiendo el paso de compresión) en lugar de reducir la memoria. - Sube
memory_limitdeliberadamente, por worker. Este es un ajuste estándar de PHP, no una clave de NextPDF. Establécelo en la configuración del grupo o conini_set('memory_limit', '256M')para el proceso de CLI/cola, y dimensiónalo frente a un pico perfilado, no a una conjetura.
- Acota la caché de imágenes.
- Relacionado. Transmisión y memoria.
Entrada: la memoria crece con el número de páginas en documentos muy grandes
Sección titulada «Entrada: la memoria crece con el número de páginas en documentos muy grandes»- Síntoma. Un documento de varios miles de páginas agota la memoria aunque cada página sea pequeña, y el pico sube más o menos al ritmo del número de páginas.
- Causa probable. El escritor con búfer mantiene todo el documento serializado en el heap. Para documentos muy grandes ese es el coste dominante.
- Resolución.
- Prefiere la vía de escritura transmitida. Usa la vía de escritura transmitida
documentada que se describe en Transmisión y memoria:
serializa cada página a medida que se compone y libera el búfer, lo que
reduce el crecimiento del búfer de página/de salida; los metadatos pequeños
por objeto (desplazamientos, árbol de páginas) aún pueden escalar con el
número de páginas/objetos. Sigue el punto de entrada documentado en lugar de
copiar clases internas: el motor de transmisión subyacente es de nivel
experimentaly sus símbolos no son la superficie pública estable. - Para el analizador
writeHtml()nativo, recuerda que la memoria del lado de la entrada está acotada tanto por las salvaguardas de profundidad de anidación como de número de elementos: ADR-001 limita el anidamiento enMAX_NESTING_DEPTH = 100y rechaza documentos por encima deMAX_ELEMENT_COUNT = 50000. A un documento que alcanza el límite de elementos se le indica explícitamente en lugar de agotar silenciosamente la memoria. Estos límites de ADR-001 rigen únicamente para el analizador nativo; el puente Chrome opcional (writeHtmlChrome()) representa fuera de proceso y tiene sus propios límites separados de memoria/entrada, no estos límites.
- Prefiere la vía de escritura transmitida. Usa la vía de escritura transmitida
documentada que se describe en Transmisión y memoria:
serializa cada página a medida que se compone y libera el búfer, lo que
reduce el crecimiento del búfer de página/de salida; los metadatos pequeños
por objeto (desplazamientos, árbol de páginas) aún pueden escalar con el
número de páginas/objetos. Sigue el punto de entrada documentado en lugar de
copiar clases internas: el motor de transmisión subyacente es de nivel
- Relacionado. Transmisión y memoria.
Entrada: un worker de larga duración agota la memoria tras muchos trabajos
Sección titulada «Entrada: un worker de larga duración agota la memoria tras muchos trabajos»- Síntoma. Las representaciones individuales tienen éxito, pero un worker de cola que representa muchos PDF seguidos agota la memoria tras minutos u horas.
- Causa probable. Un proceso de PHP de larga duración acumula asignaciones a lo largo de los trabajos. Un crecimiento lento que es invisible en una solicitud se agrava a lo largo de miles.
- Resolución.
- Comparte los registros, recrea los documentos. Construye el
FontRegistryy elImageRegistryuna vez al arrancar y pásalos a unDocumentFactory; crea unDocumentnuevo por trabajo con$factory->create($config). El análisis de fuentes e imágenes ocurre entonces una vez por proceso, no una vez por trabajo, y el árbol de documento por trabajo se recolecta cuando sale del alcance. Sigueexamples/14-worker-factory.php. - Acota la caché de imágenes compartida con
new ImageRegistry(maxCacheBytes: ...)para que no pueda crecer sin límite a lo largo de los trabajos. - Recicla el worker — control de procesos, no una garantía del motor. En
PHP-FPM, establece
pm.max_requestspara que cada hijo se reinicie tras un número fijo de solicitudes. En las colas de Laravel usaqueue:work --max-jobs/--max-time/--memory; en Symfony Messenger usamessenger:consume --limit/--time-limit/--memory-limit.
- Comparte los registros, recrea los documentos. Construye el
- Relacionado. Transmisión y memoria.
Entrada: caída de rendimiento en un proceso frío o insuficientemente calentado
Sección titulada «Entrada: caída de rendimiento en un proceso frío o insuficientemente calentado»- Síntoma. Las primeras representaciones en un proceso nuevo son lentas, o cada solicitud paga un coste de análisis que las solicitudes calientes no deberían pagar.
- Causa probable. Se acumulan dos costes de arranque en frío. PHP sin opcache
recompila cada archivo en cada solicitud, y un
FontRegistrysin calentar analiza cada tipografía de fuente la primera vez que se usa. - Resolución.
- Habilita opcache (y JIT donde ayude). Establece
opcache.enable=1y unopcache.memory_consumptiongeneroso; en producción estableceopcache.validate_timestamps=0para que la caché no se vuelva a comprobar por solicitud. Ese ajuste requiere un proceso de despliegue que reinicie o recargue PHP-FPM (o que de otro modo restablezca opcache, p. ej.opcache_reset()/cachetool) en cada versión; de lo contrario opcache sigue sirviendo el bytecode antiguo y se ejecuta código obsoleto tras un despliegue. Estos son ajustes ini estándar de PHP, no claves de NextPDF. - Calienta y bloquea el registro de fuentes al arrancar. En una instancia
de
FontRegistry,$fontRegistry->warmup($fontFiles)analiza las tipografías una vez durante el arranque, y$fontRegistry->lock()congela el registro para que el código en tiempo de solicitud no pueda mutar el estado compartido;$fontRegistry->isLocked()informa del estado. En un worker o servidor de aplicaciones genuinamente de larga duración — un consumidor de cola o un worker RoadRunner/Swoole/Octane que mantiene vivo el mismo proceso de PHP a lo largo de muchas solicitudes —, un registro calentado y bloqueado conserva sus tipografías analizadas en el estado de objeto, convirtiendo el análisis de fuentes por solicitud en un coste único de arranque del proceso. Bajo el modelo estándar de solicitud de PHP-FPM, ese estado de objeto calentado no sobrevive entre solicitudes: opcache almacena en caché las clases compiladas y el bytecode, no el estado de objeto de espacio de usuario calentado, de modo que unFontRegistrycalentado se reconstruye por solicitud (se vuelve a ejecutar en cada solicitud desde el arranque del hijo), no se mantiene caliente entre solicitudes dentro de un hijo. En PHP-FPM simple, opcache amortiza principalmente el coste de recompilación del bytecode; acepta que el análisis de fuentes se paga por solicitud, no se elimina. La amortización entre solicitudes — analizar cada tipografía una vez durante la vida del proceso — solo se aplica en un proceso genuinamente de larga duración, como un worker RoadRunner/Swoole/Octane o un consumidor de cola que mantiene vivo el mismo proceso de PHP a lo largo de muchas solicitudes. - No vuelvas a analizar la misma plantilla por solicitud. Resuelve las
fuentes y los recursos reutilizables una vez al arrancar a través de los
registros compartidos; solo el
Documentpor trabajo debería crearse en la solicitud.
- Habilita opcache (y JIT donde ayude). Establece
- Relacionado. Transmisión y memoria.
Entrada: el servidor se satura y la latencia se dispara bajo concurrencia
Sección titulada «Entrada: el servidor se satura y la latencia se dispara bajo concurrencia»- Síntoma. La latencia por representación está bien de forma aislada, pero bajo carga la máquina hace swapping, la CPU se satura, o las solicitudes se encolan y agotan el tiempo de espera.
- Causa probable. Demasiados workers de PHP-FPM para la RAM disponible, de modo que la suma de los picos de los workers supera la memoria física y el host hace swapping; o demasiado pocos workers, de modo que las solicitudes se serializan tras un grupo pequeño.
- Resolución.
-
Dimensiona
pm.max_childrena partir de un pico perfilado. Usa la fórmula estándar:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryMide el pico real de un worker con un documento representativo (consulta la nota de perfilado en Alcance), reserva margen para el sistema operativo y cualquier servicio colocado, y divide. Deja un margen; no dimensiones al 100 % de la RAM.
-
Fija el coste de compresión en tu presupuesto. La compresión Flate puede ser un coste de CPU significativo de escribir un flujo y escala con el volumen de bytes de flujo comprimibles, de modo que el número de páginas y el volumen de fuentes incrustadas influyen en la CPU por representación; el procesamiento de imágenes, el subconjuntado de fuentes y el análisis de entradas también pueden dominar. Mide con documentos representativos, y ten en cuenta el verdadero factor determinante al elegir el número de workers y la CPU.
-
Establece
pm.max_requestsjunto conpm.max_childrenpara que los hijos se reciclen y recuperen cualquier crecimiento lento, como en la entrada del worker de arriba.
-
- Relacionado. Transmisión y memoria.
Entrada: una entrada grande no confiable es lenta o costosa de analizar
Sección titulada «Entrada: una entrada grande no confiable es lenta o costosa de analizar»- Síntoma. Una representación es lenta o consume mucha memoria con una entrada grande o profundamente anidada, especialmente HTML o una fuente que no produjiste.
- Causa probable. El coste de análisis escala con el tamaño y la estructura de la entrada. Una entrada patológica (anidamiento profundo, un número enorme de elementos o una fuente mal formada) puede dominar el presupuesto.
- Resolución.
- Apóyate en los límites del motor. El analizador de HTML
writeHtml()nativo imponeMAX_NESTING_DEPTH = 100yMAX_ELEMENT_COUNT = 50000(ADR-001); las entradas por encima de esos límites se rechazan en lugar de permitirles agotar el proceso. (El puente Chrome opcional,writeHtmlChrome(), queda fuera del alcance de estos límites de ADR-001 e impone sus propios límites separados de memoria/entrada). - Trata las fuentes proporcionadas por quien llama como no confiables. Una
fuente mal formada lanza
NextPDF\Exception\FontParsingExceptionen lugar de corromper la salida, así que captura la excepción específica y rechaza la entrada en lugar de reintentar. - Valida y dimensiona las entradas en tu frontera, y aplica límites a nivel de solicitud sobre el tamaño del documento para contenido influido por quien llama.
- Apóyate en los límites del motor. El analizador de HTML
- Relacionado. Resolución de problemas: fuentes y etiquetado.
Tabla de decisión: síntoma a palanca
Sección titulada «Tabla de decisión: síntoma a palanca»| Síntoma | Palanca más probable |
|---|---|
Allowed memory size … exhausted en una sola representación | Baja $config->withImageCacheBytes(); reduce las imágenes antes de incrustar; sube memory_limit por worker |
| La memoria pico sube con el número de páginas | Usa la vía de escritura transmitida documentada |
| La memoria del worker crece a lo largo de muchos trabajos | Comparte FontRegistry/ImageRegistry mediante DocumentFactory; establece pm.max_requests / --max-jobs |
| Primeras solicitudes lentas, coste de análisis por solicitud | Habilita opcache; $fontRegistry->warmup() y luego ->lock() al arrancar |
| El host hace swapping / picos de latencia bajo carga | Dimensiona pm.max_children = (RAM − overhead) / pico por worker |
| Lento o pesado con entrada grande/no confiable | Apóyate en los límites de ADR-001; rechaza fuentes mal formadas en FontParsingException |
Casos límite y trampas
Sección titulada «Casos límite y trampas»imageCacheByteses un techo de memoria, no una perilla de tamaño. Bajarlo limita la caché para que una compilación falle rápido; nunca remuestrea ni recodifica las imágenes que incrustas. El Core no tiene control de calidad de imagen.withCompress(false)hace que los archivos sean más grandes y es una ayuda de depuración/perfilado. No es una optimización de tamaño; desplaza el compromiso CPU/memoria (omite el paso de compresión) en lugar de reducir la memoria.- El perfil de memoria exacto del motor de transmisión es una propiedad de nivel
experimentaly puede cambiar entre versiones menores. Trata cualquier medición individual como una observación, no como una constante portable. memory_limit,opcache.*,pm.max_childrenypm.max_requestsson ajustes estándar de PHP / PHP-FPM. NextPDF no expone sus propias claves para ellos; configúralos en tu entorno de ejecución, no enConfig.
Véase también
Sección titulada «Véase también»- Transmisión y memoria — el modelo de transmisión, los límites de ADR-001 y el tutorial completo del worker por lotes.
- Reducir el tamaño de archivo de un PDF — la compresión y el subconjuntado de fuentes, los dos controles de tamaño reales.
- Resolución de problemas: fuentes y etiquetado — fallos de resolución, análisis y subconjuntado de fuentes.
- Índice de la base de conocimientos
Glosario: escritor de transmisión · subconjuntado de fuentes