“PHP é lento” costuma ser diagnóstico de quem não olhou o caminho da request. A linguagem interpreta um script e devolve HTML ou JSON. O que decide se isso leva 40 ms ou 4 s é, com frequência, o processo que executa, o tamanho do pool, o que o script espera e o que o Nginx faz antes de chegar no PHP.
Entender o ciclo não é saudosismo de sysadmin. É o chão de qualquer conversa séria sobre disponibilidade em PHP. Sem esse mapa, a gente otimiza o framework e ignora a fila de processos.
O que o navegador realmente aciona
O cliente fala HTTP com o Nginx (ou outro servidor web). Arquivo estático — CSS, imagem, PDF — o Nginx serve sozinho. Script PHP não. O Nginx encaminha para o PHP-FPM por FastCGI, em geral via socket Unix ou TCP local.
O FPM é um gerenciador de processos. Há um mestre que não executa sua aplicação. Ele mantém um ou mais pools: conjuntos de workers. Cada worker é um processo PHP pronto (ou quase pronto) para receber uma request, rodar o script e devolver a resposta. Uma request, na configuração clássica, ocupa um worker até o fim.
Isso importa. Se você tem 10 workers e 40 requests simultâneas que demoram 2 s cada, 30 esperam. O usuário vê timeout. O monitor vê CPU baixa. A conclusão errada é “PHP não escala”. A conclusão certa é “o pool está saturado de trabalho lento”.
flowchart TD
A[Cliente HTTP] --> B[Nginx]
B -->|estático| C[Arquivo]
B -->|PHP| D[PHP-FPM mestre]
D --> E[Worker do pool]
E --> F[Script + app]
F --> G[Resposta]
G --> B
E --> H[Worker livre de novo]
classDef ouro fill:#FBF5E4,stroke:#C9A227,color:#0B1F18,stroke-width:1.4px
classDef verde fill:#F3FAF7,stroke:#1E7A57,color:#0B1F18,stroke-width:1.4px
classDef fim fill:#14513B,stroke:#0F3328,color:#FBFAF6,stroke-width:1.4px
class A,B ouro
class D,E,F verde
class C,G,H fim
Três jeitos de criar processos
O pool tem política de criação. Static já sobe pm.max_children workers e os mantém. Não há custo de fork no pico. Há custo fixo de memória. Se cada processo come 80 MB e você põe 40 filhos, reserve uns 3 GB só para o FPM — e ainda deixe folga para o sistema e o banco, se estiver na mesma caixa.
On-demand nasce processo quando chega request e morre depois de ocioso. Economiza memória em site quieto. No pico, paga o preço de criar processo exatamente quando você menos quer.
Dynamic tenta o meio: um mínimo vivo, um máximo, e um alvo de ociosos. É o padrão de muita instalação. Também é o padrão que esconde surpresa: o máximo é pequeno, o tráfego sobe, o Nginx espera o FPM, o timeout estoura.
Não existe número universal. O cálculo começa pela memória: quanto o processo realmente usa depois de aquecido, não no php -v. Depois pela concorrência que você precisa. Depois pelo tempo de cada request. Pool dimensionado no chute é a origem da lenda da lentidão.
| Política | Serve quando | Cuidado |
|---|---|---|
| Static | Carga previsível, memória conhecida | Superdimensionar come RAM à toa |
| Dynamic | Tráfego oscila ao longo do dia | max_children baixo vira fila invisível |
| On-demand | Quase idle, picos raros | Fork no pico aumenta latência |
O que o FPM ainda decide por você
Além de quantos filhos, o FPM decide o que fazer com processo doente. pm.max_requests recicla o worker depois de N requests — útil quando uma extensão vaza memória. request_terminate_timeout mata script que não termina. No mestre, emergency_restart_threshold e emergency_restart_interval reiniciam o FPM se muitos filhos morrem num intervalo: sinal de que o problema não é um request, é o pool inteiro.
Há também fpm_get_status() e a página de status do FPM. Idle, active, listen queue: esses números explicam mais incidentes do que o flamegraph prematuro. Fila de listen crescendo com CPU baixa é pool pequeno ou request longa. CPU no teto com idle zero é outra conversa — aí sim, código ou falta de máquina.
Opcache entra neste ciclo. Sem ele, cada request recompila. Com ele, o worker reutiliza bytecode. “PHP é lento” em produção sem opcache é, muitas vezes, configuração, não linguagem. JIT ajuda em laços pesados; na request web típica, opcache e I/O bem resolvidos pesam mais.
Por que a request segura o worker
PHP-FPM clássico não é event loop. Enquanto o script espera SMTP, HTTP de terceiro ou query, o processo está ocupado. Ele não atende a próxima pessoa. Por isso e-mail síncrono e relatório na request derrubam concorrência mesmo com CPU ociosa.
A saída não é “trocar de linguagem”. É encurtar o que o worker precisa terminar: índice, cache, fila, timeout agressivo em cliente HTTP, menos trabalho no bootstrap. Runtimes persistentes (Swoole, FrankenPHP, Octane) mudam o modelo — processo longo, menos bootstrap — e trazem outro conjunto de cuidados: memória, estado vazando entre requests, extensão não thread-safe. Útil, não mágico. O mapa mental continua: quem segura a concorrência?
Quem está aprendendo a linguagem pelo PHP One encontra o ciclo request–resposta cedo, e é o lugar certo: framework nenhum apaga o FPM.
Um roteiro curto quando “está lento”
- O Nginx está servindo estático ou tudo passa no PHP?
- Quantos workers, quantos ativos, qual a listen queue?
- Quanto RAM cada filho usa de verdade?
- Qual rota segura o worker por mais tempo?
- Esse tempo é CPU ou espera?
Se a fila cresce e a CPU não, não comece por instância maior. Comece por request longa e por max_children. Se a CPU sobe com workers saturados e o código já está enxuto, aí o eixo vira escala — vertical na caixa ou mais instâncias atrás do balanceador.
Disponibilidade em PHP é, em grande parte, gestão de processo. A linguagem executa o script. O FPM decide quantos scripts ao mesmo tempo. O Nginx decide quem espera. Quando os três estão visíveis, a frase “PHP é lento” perde o emprego.
Leia também: Escalabilidade vertical e horizontal e Load balancer, Nginx e vários servidores.