Ir al contenido
getnextpdf.com

Resolución de problemas: memoria y rendimiento

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 exhausted del 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.
    1. Acota la caché de imágenes. NextPDF\Core\Config expone imageCacheBytes (predeterminado 52428800, es decir, 50 MB). Bájalo con el wither de instancia $config->withImageCacheBytes($bytes) (firma withImageCacheBytes(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.
    2. 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).
    3. Mantén la compresión activada. Un Config nuevo tiene compress establecido en true. 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.
    4. Sube memory_limit deliberadamente, por worker. Este es un ajuste estándar de PHP, no una clave de NextPDF. Establécelo en la configuración del grupo o con ini_set('memory_limit', '256M') para el proceso de CLI/cola, y dimensiónalo frente a un pico perfilado, no a una conjetura.
  • 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.
    1. 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 experimental y sus símbolos no son la superficie pública estable.
    2. 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 en MAX_NESTING_DEPTH = 100 y rechaza documentos por encima de MAX_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.
  • 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.
    1. Comparte los registros, recrea los documentos. Construye el FontRegistry y el ImageRegistry una vez al arrancar y pásalos a un DocumentFactory; crea un Document nuevo 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. Sigue examples/14-worker-factory.php.
    2. 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.
    3. Recicla el worker — control de procesos, no una garantía del motor. En PHP-FPM, establece pm.max_requests para que cada hijo se reinicie tras un número fijo de solicitudes. En las colas de Laravel usa queue:work --max-jobs / --max-time / --memory; en Symfony Messenger usa messenger:consume --limit / --time-limit / --memory-limit.
  • 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 FontRegistry sin calentar analiza cada tipografía de fuente la primera vez que se usa.
  • Resolución.
    1. Habilita opcache (y JIT donde ayude). Establece opcache.enable=1 y un opcache.memory_consumption generoso; en producción establece opcache.validate_timestamps=0 para 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.
    2. 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 un FontRegistry calentado 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.
    3. 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 Document por trabajo debería crearse en la solicitud.
  • 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.
    1. Dimensiona pm.max_children a partir de un pico perfilado. Usa la fórmula estándar:

      pm.max_children = (total RAM - OS/other overhead) / per-worker peak memory

      Mide 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.

    2. 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.

    3. Establece pm.max_requests junto con pm.max_children para 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.
    1. Apóyate en los límites del motor. El analizador de HTML writeHtml() nativo impone MAX_NESTING_DEPTH = 100 y MAX_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).
    2. Trata las fuentes proporcionadas por quien llama como no confiables. Una fuente mal formada lanza NextPDF\Exception\FontParsingException en lugar de corromper la salida, así que captura la excepción específica y rechaza la entrada en lugar de reintentar.
    3. 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.
  • Relacionado. Resolución de problemas: fuentes y etiquetado.
SíntomaPalanca más probable
Allowed memory size … exhausted en una sola representaciónBaja $config->withImageCacheBytes(); reduce las imágenes antes de incrustar; sube memory_limit por worker
La memoria pico sube con el número de páginasUsa la vía de escritura transmitida documentada
La memoria del worker crece a lo largo de muchos trabajosComparte FontRegistry/ImageRegistry mediante DocumentFactory; establece pm.max_requests / --max-jobs
Primeras solicitudes lentas, coste de análisis por solicitudHabilita opcache; $fontRegistry->warmup() y luego ->lock() al arrancar
El host hace swapping / picos de latencia bajo cargaDimensiona pm.max_children = (RAM − overhead) / pico por worker
Lento o pesado con entrada grande/no confiableApóyate en los límites de ADR-001; rechaza fuentes mal formadas en FontParsingException
  • imageCacheBytes es 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 experimental y puede cambiar entre versiones menores. Trata cualquier medición individual como una observación, no como una constante portable.
  • memory_limit, opcache.*, pm.max_children y pm.max_requests son ajustes estándar de PHP / PHP-FPM. NextPDF no expone sus propias claves para ellos; configúralos en tu entorno de ejecución, no en Config.

Glosario: escritor de transmisión · subconjuntado de fuentes