- Castro/
- blog/
- Too many open files, upstream timed out e CPU alta: analisando uma degradação sob concorrência/
Too many open files, upstream timed out e CPU alta: analisando uma degradação sob concorrência
Caso real de degradação em Nginx e PHP-FPM com Too many open files, upstream timed out, 5xx e CPU/load altos — sem transformar o primeiro erro encontrado em causa raiz.
Tem incidente em que o log praticamente grita uma resposta.
Nesse caso, o Nginx estava registrando ao mesmo tempo:
| |
Seria muito fácil parar na primeira linha e fechar a análise com:
“faltou file descriptor.”
Só que o cenário tinha mais coisa acontecendo.
A monitoria também mostrava CPU/load elevados, o volume de requisições na origem havia crescido bastante e o impacto estava concentrado em rotas dinâmicas atendidas por FastCGI/PHP-FPM.
Então o trabalho virou separar três perguntas diferentes:
- como a degradação se manifestou?
- quais recursos estavam sob pressão?
- o que as evidências realmente permitem chamar de causa?
O sintoma visto pelo usuário #
Durante a janela crítica, a aplicação apresentou erro ou lentidão principalmente em fluxos dinâmicos, como home, cadastro, login e autenticação.
A origem registrou respostas:
- HTTP 500;
- HTTP 502;
- HTTP 504.
No backend, o Nginx encaminhava as requisições para FastCGI/PHP-FPM em loopback.
Ou seja, o caminho simplificado era:
| |
Quando a origem começou a degradar, o Nginx passou a falhar tanto ao aceitar novas conexões quanto ao conversar com o upstream.
Primeiro fato forte: file descriptors estavam entrando no modo de falha #
O error log continha centenas de ocorrências relacionadas a descritores e conexão com upstream.
Amostras sanitizadas:
| |
Isso prova que, durante a janela, o Nginx encontrou erro de limite de arquivos/descritores ao tentar aceitar ou criar sockets.
Mas vale a distinção:
provar que houve Too many open files não prova, sozinho, que esse foi o único mecanismo responsável pela indisponibilidade.
O mesmo período também tinha sinais claros de pressão de CPU e concorrência.
Segundo fato: a origem estava muito mais carregada #
O access log disponível era global da origem e não registrava o campo Host.
Isso muda como os números precisam ser interpretados.
Não dá para chamar uma contagem daquele arquivo de “visitantes do site”. O que ele mede é requisição HTTP recebida pela origem Nginx.
Na comparação feita durante a análise, a janela crítica apresentou volume de requisições várias vezes maior do que uma janela comparável anterior.
Esse dado não serve para medir audiência.
Serve para sustentar outra afirmação: a origem estava processando uma carga muito acima do padrão comparado naquele intervalo.
A correlação específica com a aplicação foi feita pelo error log, porque ali os campos de host e server estavam presentes.
Essa é uma boa lição de troubleshooting: antes de usar uma métrica, confirme exatamente o que ela mede.
Terceiro fato: CPU e load também subiram #
A monitoria registrou elevação significativa de CPU/load no período.
Em uma coleta posterior do mesmo tipo de ambiente, era possível observar muitos processos PHP-FPM simultâneos, load alto e CPU quase totalmente ocupada.
Isso reforça que o evento não deveria ser tratado apenas como “um número baixo de nofile”.
A capacidade de atender requisições dinâmicas dependia de várias camadas ao mesmo tempo:
| |
Quando há concorrência alta, uma limitação pode amplificar a outra.
O erro que eu não queria cometer: culpar PHP-FPM sem evidência #
Como os timeouts aconteciam entre Nginx e FastCGI, PHP-FPM era um suspeito natural.
Então procuramos um sinal bem específico:
| |
Ele não apareceu na busca realizada.
Isso não prova que PHP-FPM estava folgado.
Prova apenas que não havia evidência direta, naquela coleta, de esgotamento explícito de workers pelo mecanismo registrado como pm.max_children.
Os erros de upstream mostram que a comunicação Nginx → PHP-FPM sofreu durante a degradação. Não permitem transformar “PHP-FPM” em causa isolada só porque ele está no caminho.
E o banco? O Slow Query Log estava desligado #
Outra hipótese óbvia era banco lento.
Ao verificar a configuração, o Slow Query Log estava desabilitado.
Em termos práticos:
| |
Isso cria uma limitação forense importante.
Depois do evento, sem histórico de slow query para aquela janela, não havia evidência suficiente para confirmar ou descartar queries lentas como fator contribuinte.
A conclusão correta não era:
O banco não teve slow query.
Era:
Não havia histórico adequado para responder essa pergunta com segurança.
Incidente também ensina pela evidência que faltou.
O que o conjunto de evidências sustentava #
A leitura consolidada ficou mais forte quando deixamos de procurar “o erro vencedor”.
O que estava comprovado:
- houve crescimento forte de carga HTTP na origem na janela analisada;
- as rotas dinâmicas concentraram grande parte do impacto;
- apareceram respostas 500, 502 e 504;
- o Nginx registrou
Too many open filese falha ao criar sockets; - houve
upstream timed oute outras falhas na comunicação com FastCGI/PHP-FPM; - a monitoria registrou pressão de CPU/load;
- não foi encontrado
server reached pm.max_childrenna busca realizada; - o Slow Query Log não estava habilitado para permitir análise histórica de queries lentas.
O comportamento era compatível com degradação da origem sob alta concorrência, envolvendo pressão de processamento e dificuldade de manter/encaminhar conexões para o backend.
Isso é mais preciso do que escolher uma linha do log e transformá-la em RCA inteira.
Como eu investigaria esse tipo de incidente #
A sequência que mais ajuda é começar pelo estado e ir reduzindo o problema por camada.
1. Confirmar pressão de CPU, memória e load #
| |
Se houver histórico de monitoramento, correlacionar exatamente com a janela do erro.
2. Procurar os modos de falha no Nginx #
| |
O objetivo não é só contar mensagens. É ver quando começam, quais rotas aparecem e para qual upstream a requisição estava indo.
3. Ver limites de descritores #
| |
E comparar com a quantidade de FDs realmente abertos pelo processo quando possível.
4. Conferir PHP-FPM #
| |
Se status do pool estiver habilitado, melhor ainda: ele dá uma visão mais útil do que simplesmente contar processos no ps.
5. Verificar evidência de banco sem inventar histórico #
No MySQL/MariaDB:
| |
Se o Slow Query Log estava desligado durante a falha, registrar essa limitação explicitamente.
Correção e prevenção são outra etapa #
Este artigo não pretende transformar o caso em receita universal de tuning.
A correção correta depende do que cada ambiente mostrar: limites de nofile, configuração do serviço, capacidade de CPU, comportamento do pool PHP-FPM, custo das rotas dinâmicas, banco e estratégia de cache.
A parte reproduzível é o método:
| |
O aprendizado mais útil #
Too many open files era uma evidência real e importante.
upstream timed out também.
CPU/load altos também.
O erro seria escolher qualquer um deles isoladamente e fingir que o restante não existia.
Em incidentes de concorrência, o comportamento costuma emergir da interação entre limites e recursos.
Por isso a conclusão técnica ficou menos dramática e mais útil:
a origem degradou sob carga concentrada, com pressão de processamento e falhas operacionais na manutenção e encaminhamento de conexões para o backend.
E duas coisas ficaram deliberadamente sem virar culpadas: esgotamento explícito de workers PHP-FPM e slow queries, porque a evidência disponível não sustentava essas afirmações.
Sem floreio. E, principalmente, sem fechar a causa que o log não fechou.
Me chama.
Manda o contexto, os sintomas e o que já foi testado. Bora organizar as evidências antes de sair mexendo.