← Voltar ao blog

Ajuste do PHP-FPM no Magento 2: workers, OPcache, php.ini

Rodrigo Biazzi

O PHP-FPM é onde o Magento 2 passa a maior parte do tempo em um request sem cache. Se você erra o tamanho do pool, o servidor ou fica ocioso enquanto os visitantes esperam em fila, ou fica sem memória e começa a matar processos. Se erra o OPcache, todo request compila milhares de arquivos PHP do zero. Este guia cobre as configurações de pool do PHP-FPM, o OPcache e os valores de php.ini que importam para o Magento Open Source e o Adobe Commerce 2.4.8 no PHP 8.3 e 8.4.

Como um request usa o PHP-FPM

O nginx passa cada request PHP para um pool do PHP-FPM por um socket. O pool tem um conjunto de processos worker. Cada worker atende um request por vez. Se todos os workers estão ocupados, os novos requests esperam na listen queue até um ficar livre, e se a fila enche, o nginx devolve 502 ou 504.

Isso dá o problema básico de dimensionamento. Workers de menos e os requests ficam na fila mesmo com CPU livre. Workers demais e eles disputam CPU, cada request fica mais lento e a memória acaba. O número certo depende dos cores de CPU, da RAM disponível e de quanta memória um request do Magento usa.

Escolhendo o process manager

O PHP-FPM tem três modos para o pm:

  • static: um número fixo de workers, sempre rodando. Uso de memória previsível e nenhum tempo gasto com fork sob carga. Bom para servidores web dedicados.
  • dynamic: um mínimo e um máximo, com workers sobressalentes sempre prontos. Bom quando o servidor também roda outros serviços e você quer que o PHP devolva memória quando o tráfego está baixo.
  • ondemand: os workers só sobem quando chegam requests. Serve para sites de pouco tráfego e staging, mas o custo do fork aparece como latência em uma loja.

Para um nó web de produção do Magento eu costumo preferir static ou dynamic com mínimo alto. Workers do Magento são pesados para subir, porque o primeiro request em um processo novo precisa carregar muito código na memória, mesmo com OPcache.

Calculando o pm.max_children

Comece medindo o uso real de memória de um worker sob tráfego normal:

ps -ylC php-fpm8.3 --sort:rss | awk 'NR>1 {sum+=$8; n++} END {print n, "workers,", sum/n/1024, "MB average RSS"}'

O RSS conta a memória compartilhada, incluindo o OPcache, em cada processo, então esse número superestima um pouco o custo real. Para dimensionar tudo bem, porque erra para o lado seguro.

A fórmula aproximada é:

# RAM you give to PHP divided by average worker size
# Example: 12 GB for PHP, 120 MB per worker
# 12288 / 120 = about 100 workers

Depois, compare esse número com a CPU. Um request do Magento é na maior parte limitado por CPU quando banco e cache estão rápidos. Se o servidor tem 8 cores, 100 workers todos ocupados ao mesmo tempo significam que cada um ganha uma fatia pequena de CPU e toda resposta fica mais lenta. Uma faixa prática comum é de dois a quatro workers por core, depois ajuste pelo monitoramento. Fique com o menor entre o número baseado em memória e o baseado em CPU.

Uma configuração de pool baseada nisso pode ficar assim:

[magento]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm-magento.sock
listen.owner = www-data
listen.group = www-data
listen.backlog = 511

pm = dynamic
pm.max_children = 32
pm.start_servers = 16
pm.min_spare_servers = 16
pm.max_spare_servers = 24
pm.max_requests = 1000

pm.status_path = /fpm-status
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-magento-slow.log
request_terminate_timeout = 300s

pm.max_requests

Cada worker reinicia depois desse número de requests. Protege contra vazamentos lentos de memória em extensões. Não coloque muito baixo. Cada restart joga fora o processo já aquecido. Algo entre 500 e alguns milhares é típico.

Slow log

O request_slowlog_timeout grava um stack trace do PHP de qualquer request que rode mais que o limite. É uma das ferramentas de debug mais baratas que você tem. Quando uma página leva dez segundos aleatoriamente, o slow log muitas vezes mostra exatamente qual função estava esperando, por exemplo uma chamada curl a uma API de terceiros dentro de um block.

Status page

Com o pm.status_path definido e exposto no nginx só para localhost ou para o seu host de monitoramento, você vê workers ativos e ociosos, a listen queue e o max children reached. Se o max children reached não para de subir e a listen queue não está vazia, o pool está pequeno demais ou os requests estão lentos demais.

Pools separados para frontend e admin

Requests de admin e de API se comportam diferente dos do storefront. Salvar um produto no admin ou uma exportação grande pode usar muito mais memória e tempo que uma página de categoria. Alguns setups rodam pools de FPM separados e roteiam por path no nginx, com um pool pequeno e um limite de memória maior para o admin. Assim, uma ação pesada no admin não consome os workers de que o storefront precisa. Isso adiciona configuração, então vale a pena em lojas movimentadas e não nas pequenas.

Configurações de OPcache para o Magento

O OPcache guarda PHP compilado em memória compartilhada. O Magento tem um codebase muito grande, e os tamanhos padrão do OPcache são pequenos demais para ele. Quando o OPcache enche, o PHP continua compilando arquivos a cada request e a performance cai sem erro nenhum.

opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 512
opcache.interned_strings_buffer = 32
opcache.max_accelerated_files = 130000
opcache.validate_timestamps = 0
opcache.save_comments = 1
opcache.consistency_checks = 0

realpath_cache_size = 10M
realpath_cache_ttl = 7200

save_comments precisa ficar ligado

O Magento lê comentários PHPDoc para alguns recursos, por exemplo a Web API lê anotações de métodos para montar os serviços REST e SOAP. Com opcache.save_comments = 0 eles quebram. Deixe em 1.

validate_timestamps e deploys

Com opcache.validate_timestamps = 0, o PHP nunca verifica se os arquivos mudaram no disco. Isso poupa uma chamada stat por arquivo por request. O custo é que, depois de um deploy, você precisa dar reload no PHP-FPM, ou o código antigo continua rodando. Coloque o systemctl reload php8.3-fpm no script de deploy. Se você faz deploy em uma pasta de release nova e troca um symlink, o reload também garante que o PHP pare de usar paths em cache do release antigo.

Confira se é grande o bastante

A verificação mais fácil é chamar opcache_get_status(false) de um script pequeno servido pelo mesmo pool, não pelo CLI, já que o CLI tem o próprio OPcache. Olhe a memória livre, o número de scripts em cache comparado com o max_accelerated_files e se o oom_restarts está acima de zero. Se a memória está quase cheia depois de um dia de tráfego, aumente o memory_consumption.

JIT

O PHP 8 tem um compilador JIT no OPcache. Para um request típico do Magento o ganho é pequeno, porque o tempo vai em I/O, chamadas ao banco e manipulação de arrays, não em loops numéricos intensos. Ele também pode dificultar o debug. Eu deixo desligado, a menos que um benchmark na loja real mostre um ganho claro.

Valores de php.ini que o Magento precisa

Algumas configurações dos requisitos de sistema do Magento e dos pontos de falha mais comuns:

  • memory_limit: a config de nginx do sample passa 756M ao FPM. Para comandos de CLI como setup:di:compile e o deploy de conteúdo estático, use pelo menos 2G.
  • max_execution_time: mantenha razoável para requests web. Operações longas no admin devem ser cron jobs ou consumers de queue, não requests HTTP de 15 minutos.
  • max_input_vars: o padrão 1000 é baixo para alguns formulários do admin com muitas opções, como produtos configuráveis grandes ou permissões de role. Aumente se os saves do admin perdem dados em silêncio.
  • session.auto_start: precisa estar desligado.
  • zlib.output_compression: deixe desligado e deixe o nginx comprimir as respostas.

Garanta que o Xdebug não está carregado em produção. Mesmo sem depurar ativamente, a extensão adiciona overhead a todo request. O guia do Xdebug cobre como mantê-lo só em ambientes locais.

CLI e cron

Cron jobs e consumers de queue rodam pelo PHP CLI, que usa o próprio arquivo ini. Eles não contam no pool do FPM, mas competem pela mesma CPU e RAM. Um grupo de cron pesado, como indexação ou uma importação de produtos, pode esgotar o pool web em um servidor pequeno. Conte-os ao dimensionar o pm.max_children e considere rodá-los em um nó separado quando a loja crescer.

Sinais de que você errou

  • Erros 502 ou 504 nos picos de tráfego: workers esgotados, confira o max children reached e o slow log.
  • Servidor fazendo swap ou mensagens do OOM killer: workers demais para a RAM, ou um memory leak. Reduza o pool e olhe o RSS por worker.
  • CPU alta, throughput baixo: workers demais para os cores, ou OPcache cheio e recompilando.
  • Código antigo depois do deploy: validate_timestamps desligado e sem reload do FPM.

O ajuste do PHP-FPM fica ao lado do resto do stack. Veja o guia de configuração do nginx para saber como os requests chegam ao pool e o guia de setup do Redis para os backends de cache e sessão.

FAQ

Quantos workers de PHP-FPM o Magento 2 precisa?

Meça a memória média do worker, divida a RAM que você dá ao PHP por ela e compare com dois a quatro workers por core de CPU. Use o menor número e ajuste pela página de status do FPM.

O opcache.validate_timestamps deve ficar desligado em produção?

Sim, ele poupa verificações de arquivo a cada request. Você então precisa dar reload no PHP-FPM em todo deploy, ou o código antigo fica na memória.

Posso definir opcache.save_comments como 0 no Magento?

Não. O Magento lê anotações PHPDoc, por exemplo para montar serviços da Web API, e esses recursos quebram quando os comentários são removidos.

O PHP JIT deixa o Magento mais rápido?

Geralmente não muito, porque o tempo do Magento vai em I/O, banco e trabalho com arrays. Faça um benchmark na sua própria loja antes de ligar.

Atendo como freelancer em Magento e Adobe Commerce, incluindo ajuste de servidor. Se precisar de ajuda com isso, fale comigo.

Artigos relacionados