Solução de problemas: memória e desempenho
Estas entradas cobrem duas famílias de falhas que você encontra sob carga: o PHP ficando sem memória durante um render, e o throughput que despenca de uma queda brusca quando um processo está quente ou saturado. Cada entrada nomeia um sintoma, a causa mais provável e uma correção que usa superfície real do NextPDF ou controles padrão do PHP-FPM. Para o modelo de streaming subjacente e um tutorial de worker, leia Streaming e memória; esta página é a companheira do lado de incidentes dele.
Meça primeiro. Faça amostragem de memory_get_peak_usage(true) antes e depois de um
render e chame memory_reset_peak_usage() entre iterações, do jeito que o benchmark
do engine isola o custo por render. Ajustar sem uma baseline move a queda em vez de
removê-la.
Entrada: “Allowed memory size exhausted” durante a geração
Seção intitulada “Entrada: “Allowed memory size exhausted” durante a geração”- Sintoma. Um render aborta com um fatal
Allowed memory size of <n> bytes exhausteddo runtime do PHP, frequentemente em um documento grande ou cheio de imagens. - Causa provável. O caminho de escrita padrão compõe o documento inteiro e
depois o serializa, então o pico de memória acompanha o tamanho total da saída. Um
documento grande, imagens incorporadas grandes ou um face de fonte incorporado
grande podem empurrar a requisição além do
memory_limit. - Resolução.
- Limite o cache de imagens.
NextPDF\Core\ConfigexpõeimageCacheBytes(padrão52428800, ou seja, 50 MB). Reduza-o com o wither de instância$config->withImageCacheBytes($bytes)(assinaturawithImageCacheBytes(int $bytes): self) para que um build que incorpora muitas imagens falhe rápido em um teto conhecido em vez de fazer swap. Isso limita o cache de imagens em memória; ele não reamostra nem recodifica as próprias imagens. - Encolha as entradas antes de incorporar. A Core não reduz a escala nem recodifica imagens. Redimensione e recodifique a arte raster grande demais antes de incorporá-la, e incorpore as fontes que você realmente usa para que o subsetting tenha um conjunto pequeno de glifos para manter (veja Reduza o tamanho de arquivo do PDF).
- Mantenha a compressão ligada. Um
Confignovo temcompressdefinido comotrue. Deixe-a ligada para builds normais;withCompress(false)não é uma otimização de tamanho (normalmente aumenta a saída). Recorra a ela para depurar ou perfilar o pipeline — ela desloca o tradeoff de CPU/memória (pulando a etapa de compressão) em vez de reduzir a memória. - Aumente o
memory_limitdeliberadamente, por worker. Esta é uma configuração padrão do PHP, não uma chave do NextPDF. Defina-a no config do pool ou comini_set('memory_limit', '256M')para o processo CLI/fila, e dimensione-a contra um pico perfilado, não um chute.
- Limite o cache de imagens.
- Relacionado. Streaming e memória.
Entrada: a memória cresce com a contagem de páginas em documentos muito grandes
Seção intitulada “Entrada: a memória cresce com a contagem de páginas em documentos muito grandes”- Sintoma. Um documento de muitos milhares de páginas esgota a memória mesmo que cada página seja pequena, e o pico sobe mais ou menos em sincronia com a contagem de páginas.
- Causa provável. O writer em buffer mantém o documento serializado inteiro no heap. Para documentos muito grandes, esse é o custo dominante.
- Resolução.
- Prefira o caminho de escrita por streaming. Use o caminho de escrita por
streaming documentado descrito em Streaming e memória:
ele serializa cada página à medida que é composta e libera o buffer, o que
reduz o crescimento de buffer de página/saída; pequenos metadados por objeto
(offsets, árvore de páginas) ainda podem escalar com a contagem de
páginas/objetos. Siga o ponto de entrada documentado em vez de copiar classes
internas — o engine de streaming subjacente é de nível
experimentale seus símbolos não são a superfície pública estável. - Para o parser
writeHtml()nativo, lembre-se de que a memória do lado da entrada é limitada tanto pela profundidade de aninhamento quanto pelas proteções de contagem de elementos: a ADR-001 limita o aninhamento emMAX_NESTING_DEPTH = 100e rejeita documentos acima deMAX_ELEMENT_COUNT = 50000. Um documento que atinge o limite de elementos é avisado disso explicitamente em vez de esgotar a memória silenciosamente. Esses limites da ADR-001 governam apenas o parser nativo; a ponte do Chrome opcional (writeHtmlChrome()) renderiza fora do processo e tem seus próprios limites separados de memória/entrada, não esses limites.
- Prefira o caminho de escrita por streaming. Use o caminho de escrita por
streaming documentado descrito em Streaming e memória:
ele serializa cada página à medida que é composta e libera o buffer, o que
reduz o crescimento de buffer de página/saída; pequenos metadados por objeto
(offsets, árvore de páginas) ainda podem escalar com a contagem de
páginas/objetos. Siga o ponto de entrada documentado em vez de copiar classes
internas — o engine de streaming subjacente é de nível
- Relacionado. Streaming e memória.
Entrada: um worker de longa duração esgota a memória após muitos jobs
Seção intitulada “Entrada: um worker de longa duração esgota a memória após muitos jobs”- Sintoma. Renders individuais funcionam, mas um worker de fila que renderiza muitos PDFs em sequência esgota a memória após minutos ou horas.
- Causa provável. Um processo PHP de longa duração acumula alocações entre jobs. Um crescimento lento que é invisível em uma requisição se acumula ao longo de milhares.
- Resolução.
- Compartilhe registros, recrie documentos. Construa o
FontRegistrye oImageRegistryuma vez no boot e passe-os a umDocumentFactory; crie umDocumentnovo por job com$factory->create($config). A análise de fontes e imagens então acontece uma vez para o processo, não uma vez por job, e a árvore de documento por job é coletada quando sai de escopo. Sigaexamples/14-worker-factory.php. - Limite o cache de imagens compartilhado com
new ImageRegistry(maxCacheBytes: ...)para que ele não possa crescer sem limite entre jobs. - Recicle o worker — controle de processo, não uma garantia do engine. No
PHP-FPM, defina
pm.max_requestspara que cada filho seja recriado após um número fixo de requisições. Em filas do Laravel usequeue:work --max-jobs/--max-time/--memory; no Symfony Messenger usemessenger:consume --limit/--time-limit/--memory-limit.
- Compartilhe registros, recrie documentos. Construa o
- Relacionado. Streaming e memória.
Entrada: queda de throughput em um processo frio ou pouco aquecido
Seção intitulada “Entrada: queda de throughput em um processo frio ou pouco aquecido”- Sintoma. Os primeiros renders em um processo novo são lentos, ou cada requisição paga um custo de análise que requisições quentes não deveriam pagar.
- Causa provável. Dois custos de cold-start se acumulam. O PHP sem opcache
recompila cada arquivo a cada requisição, e um
FontRegistrynão aquecido analisa cada face de fonte na primeira vez que é usado. - Resolução.
- Habilite o opcache (e o JIT onde ajuda). Defina
opcache.enable=1e umopcache.memory_consumptiongeneroso; em produção definaopcache.validate_timestamps=0para que o cache não seja re-checado por requisição. Essa configuração exige um processo de deploy que reinicie ou recarregue o PHP-FPM (ou de outra forma reset o opcache, por exemploopcache_reset()/cachetool) a cada release — caso contrário, o opcache continua servindo o bytecode antigo e código obsoleto roda após um deploy. Estas são configurações ini padrão do PHP, não chaves do NextPDF. - Aqueça e trave o registro de fontes no boot. Em uma instância de
FontRegistry,$fontRegistry->warmup($fontFiles)analisa os faces uma vez durante o boot, e$fontRegistry->lock()congela o registro para que código de tempo de requisição não possa mutar o estado compartilhado;$fontRegistry->isLocked()reporta o estado. Em um worker ou servidor de aplicação genuinamente de longa duração — um consumidor de fila ou um worker RoadRunner/Swoole/Octane que mantém o mesmo processo PHP vivo entre muitas requisições — um registro aquecido e travado persiste seus faces analisados no estado do objeto, transformando a análise de fontes por requisição em um custo único de boot do processo. Sob o modelo padrão de requisição do PHP-FPM, esse estado de objeto aquecido não sobrevive entre requisições: o opcache faz cache de classes compiladas e bytecode, não de estado de objeto userland aquecido, então umFontRegistryaquecido é reconstruído por requisição (re-executado a cada requisição a partir do bootstrap do filho), não mantido quente entre requisições dentro de um filho. No PHP-FPM puro, o opcache principalmente amortiza o custo de recompilação de bytecode; aceite que a análise de fontes é paga por requisição, não eliminada. A amortização entre requisições — analisar cada face uma vez durante o tempo de vida do processo — só se aplica em um processo genuinamente de longa duração, como um worker RoadRunner/Swoole/Octane ou um consumidor de fila que mantém o mesmo processo PHP vivo entre muitas requisições. - Não reanalise o mesmo template por requisição. Resolva fontes e recursos
reutilizáveis uma vez no boot por meio dos registros compartilhados; apenas o
Documentpor job deve ser criado na requisição.
- Habilite o opcache (e o JIT onde ajuda). Defina
- Relacionado. Streaming e memória.
Entrada: o servidor satura e a latência dispara sob concorrência
Seção intitulada “Entrada: o servidor satura e a latência dispara sob concorrência”- Sintoma. A latência por render está boa isoladamente, mas sob carga a máquina faz swap, a CPU satura, ou as requisições enfileiram e dão timeout.
- Causa provável. Workers PHP-FPM demais para a RAM disponível, então a soma dos picos dos workers excede a memória física e o host faz swap; ou workers de menos, então as requisições serializam atrás de um pool pequeno.
- Resolução.
-
Dimensione
pm.max_childrena partir de um pico perfilado. Use a fórmula padrão:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryMeça o pico real de um worker com um documento representativo (veja a nota de perfilagem em Escopo), reserve folga para o SO e quaisquer serviços colocados, e divida. Deixe uma margem; não dimensione para 100% da RAM.
-
Fixe o custo de compressão no seu orçamento. A compressão Flate pode ser um custo significativo de CPU ao escrever um stream e escala com o volume de bytes de stream comprimíveis, então a contagem de páginas e o volume de fontes incorporadas influenciam a CPU por render; o processamento de imagens, o subsetting de fontes e a análise de entrada também podem dominar. Meça com documentos representativos, e considere o driver real quando você escolher a contagem de workers e a CPU.
-
Defina
pm.max_requestsjunto compm.max_childrenpara que os filhos reciclem e recuperem qualquer crescimento lento, como na entrada de worker acima.
-
- Relacionado. Streaming e memória.
Entrada: entrada não confiável grande é lenta ou cara de analisar
Seção intitulada “Entrada: entrada não confiável grande é lenta ou cara de analisar”- Sintoma. Um render é lento ou pesado em memória em uma entrada grande ou profundamente aninhada, especialmente HTML ou uma fonte que você não produziu.
- Causa provável. O custo de análise escala com o tamanho e a estrutura da entrada. Uma entrada patológica (aninhamento profundo, uma contagem enorme de elementos ou uma fonte malformada) pode dominar o orçamento.
- Resolução.
- Apoie-se nos limites do engine. O parser HTML
writeHtml()nativo impõeMAX_NESTING_DEPTH = 100eMAX_ELEMENT_COUNT = 50000(ADR-001); entradas acima desses limites são rejeitadas em vez de permitidas a esgotar o processo. (A ponte do Chrome opcional,writeHtmlChrome(), está fora de escopo para esses limites da ADR-001 e impõe seus próprios limites separados de memória/entrada.) - Trate fontes fornecidas por quem chama como não confiáveis. Uma fonte malformada
lança
NextPDF\Exception\FontParsingExceptionem vez de corromper a saída, então capture a exceção específica e rejeite a entrada em vez de tentar de novo. - Valide e dimensione as entradas na sua fronteira, e aplique limites em nível de requisição sobre o tamanho do documento para conteúdo influenciado por quem chama.
- Apoie-se nos limites do engine. O parser HTML
- Relacionado. Solução de problemas: fontes e tagging.
Tabela de decisão: sintoma para alavanca
Seção intitulada “Tabela de decisão: sintoma para alavanca”| Sintoma | Alavanca mais provável |
|---|---|
Allowed memory size … exhausted em um único render | Reduza $config->withImageCacheBytes(); encolha imagens antes de incorporar; aumente o memory_limit por worker |
| O pico de memória sobe com a contagem de páginas | Use o caminho de escrita por streaming documentado |
| A memória do worker sobe ao longo de muitos jobs | Compartilhe FontRegistry/ImageRegistry via DocumentFactory; defina pm.max_requests / --max-jobs |
| Primeiras requisições lentas, custo de análise por requisição | Habilite o opcache; $fontRegistry->warmup() e depois ->lock() no boot |
| Host faz swap / latência dispara sob carga | Dimensione pm.max_children = (RAM − overhead) / pico por worker |
| Lento ou pesado em entrada grande/não confiável | Apoie-se nos limites da ADR-001; rejeite fontes malformadas em FontParsingException |
Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”imageCacheBytesé um teto de memória, não um botão de tamanho. Reduzi-lo limita o cache para que um build falhe rápido; ele nunca reamostra nem recodifica as imagens que você incorpora. A Core não tem controle de qualidade de imagem.withCompress(false)torna os arquivos maiores e é um auxílio de depuração/perfilagem. Não é uma otimização de tamanho; ele desloca o tradeoff de CPU/memória (pula a etapa de compressão) em vez de reduzir a memória.- O perfil de memória exato do engine de streaming é uma propriedade de nível
experimentale pode mudar entre releases minor. Trate qualquer medição individual como uma observação, não uma constante portável. memory_limit,opcache.*,pm.max_childrenepm.max_requestssão configurações padrão do PHP / PHP-FPM. O NextPDF não expõe suas próprias chaves para elas; configure-as no seu runtime, não noConfig.
Veja também
Seção intitulada “Veja também”- Streaming e memória — o modelo de streaming, os limites da ADR-001 e o tutorial completo de batch-worker.
- Reduza o tamanho de arquivo do PDF — compressão e subsetting de fontes, os dois controles reais de tamanho.
- Solução de problemas: fontes e tagging — falhas de resolução, análise e subsetting de fontes.
- Índice da base de conhecimento
Glossário: streaming writer · font subsetting