Fix too many open files issue#1443
Conversation
What This PR DoesThis PR implements graceful degradation for the profiling system to prevent monitoring failures from causing application outages. Specifically, it addresses an incident where Key behavior change: From "fail closed" (profiling errors crash the request) to "fail open" (profiling errors are silently handled, memory metrics marked unavailable). Business Impact
Core ChangesThe PR introduces four new helper functions that safely wrap memory metric collection: def _get_memory_usage_mb(process=None):
"""Returns RSS in MB or None if psutil fails"""
try:
if process is None:
process = psutil.Process()
return process.memory_info().rss / 1024 / 1024
except (OSError, psutil.Error) as exc:
profiling_logger.debug("Skipping profiling memory sample: %s", exc)
return None
def _get_memory_delta_mb(process, start_memory):
"""Safely calculates memory delta, returns None if either value unavailable"""
if start_memory is None:
return None
end_memory = _get_memory_usage_mb(process)
if end_memory is None:
return None
return end_memory - start_memory
def _format_memory_delta(memory_used):
"""Formats memory delta or returns 'unavailable' string"""
if memory_used is None:
return "unavailable"
return f"+{memory_used:.1f}MB"
def _is_high_memory(memory_used):
"""Safely checks if memory exceeded threshold"""
return memory_used is not None and memory_used > PROFILING_LOG_HIGH_MEMORYWhy this matters:
Other ChangesAll profiling decorators and middleware are updated to use these helpers:
Consistency: All 7 locations that read memory metrics now follow the same safe pattern. Merge Readiness and Risk Assessment✅ Strengths
|
O que esse PR faz? Ajusta o script de inicializacao do Django em producao para elevar o limite de arquivos abertos antes de iniciar o Gunicorn, parametrizar os principais argumentos por variaveis de ambiente e habilitar reciclagem de workers com max-requests. Isso reduz a chance de erro Too many open files em pods Kubernetes executando gunicorn/gevent sob carga. Onde a revisao poderia comecar? compose/production/django/start Como este poderia ser testado manualmente? 1. Gerar e publicar uma imagem com este script. 2. Implantar o pod Django no Kubernetes com GUNICORN_NOFILE=65536. 3. Validar no pod: cat /proc/<worker-pid>/limits | grep 'open files'. 4. Confirmar nos logs de startup a linha Open files limit: 65536. 5. Executar carga ou processamento que antes aproximava o limite e monitorar: ls -1 /proc/<worker-pid>/fd | wc -l. Algum cenario de contexto que queira dar? Os pods observados tinham hard limit alto, mas soft limit de apenas 1024 descritores. Ao mesmo tempo, gunicorn/gevent estava configurado com muitos worker-connections, deixando pouca margem para sockets, banco, logs, arquivos temporarios e leituras de /proc feitas por instrumentacao. Screenshots Nao aplicavel; mudanca operacional em script de inicializacao. Quais sao tickets relevantes? Nao informado. Referencias Diagnostico via /proc/<pid>/limits e /proc/<pid>/fd em Linux; configuracao de Gunicorn para worker-connections, max-requests e max-requests-jitter.
Análise: limite de file descriptors vs. configuração do GunicornCom
Então, mesmo sem vazamento, um worker sob carga pode chegar perto do limite. Se houver qualquer vazamento pequeno, ele estoura rápido.
TimeoutOutro ponto: Se precisar manter esse timeout, eu colocaria também reciclagem de workers: Avaliação curtaA configuração atual explica bem o erro. O patch no profiling impede o 500 causado pelo ObservaçãoAo abrir o chamado será necessário pedir para adicionar as seguintes variáveis: env:
- name: GUNICORN_NOFILE
value: "65536"
- name: WEB_CONCURRENCY
value: "3"
- name: GUNICORN_WORKER_CONNECTIONS
value: "1000"
- name: GUNICORN_MAX_REQUESTS
value: "1000"
- name: GUNICORN_MAX_REQUESTS_JITTER
value: "100" |
robertatakenaka
left a comment
There was a problem hiding this comment.
Resumindo: O PR resolve de forma segura o sintoma imediato (HTTP 500 quando o psutil falha por falta de File Descriptors) centralizando a lógica em helpers com tratamento de erro e logs em debug. O código está aprovado para merge.
No entanto, a causa raiz (esgotamento de FDs por alta concorrência e requests longas) permanece latente e a aprovação fica condicionada a ações de infraestrutura e monitoramento pós-deploy.
⚠️ Riscos Importantes
- Regressão de capacidade: o script
startalterou o default deGUNICORN_WORKER_CONNECTIONSde 1000 para 100. Sem a env var explícita no deploy, a capacidade cairá 10x. - Falha silenciosa: se o hard limit do cgroup (
ulimit -Hn) for menor que 65536, o script emitirá apenas um warning nostderr, que pode passar despercebido se a observabilidade não o capturar — o novoulimit -n 65536só entrega o headroom de FDs necessário para sustentarworker-connections=1000se for de fato aplicado. - Débito técnico: o PR não inclui testes unitários para os novos helpers nem guard-rails (ex.: verificação estática/lint que bloqueie chamadas diretas a
psutil.Process().memory_info()fora dos helpers, evitando reintrodução do bug no futuro).
📋 Próximos Passos
🚀 Infra & Deploy (Imediato)
- Merge do PR e aplicação obrigatória das seguintes env vars no deploy para evitar perda de capacidade:
GUNICORN_NOFILE: "65536"
WEB_CONCURRENCY: "3"
GUNICORN_WORKER_CONNECTIONS: "1000"
GUNICORN_MAX_REQUESTS: "1000"
GUNICORN_MAX_REQUESTS_JITTER: "100"- Validar em Produção: confirmar se o
ulimit -Hn(hard limit do cgroup) realmente permite 65536 FDs, se os logs destderr(warning de falha doulimit) estão sendo coletados pela observabilidade, e acompanhar a frequência dememory: unavailablenos logs como indicador direto de quão presente ainda está a falha dopsutilmesmo após o ajuste.
📈 Melhorias & Investigação (Médio Prazo)
- Testes: criar testes unitários para os helpers (falha do
psutil, cálculo de delta com valorNone, ausência do headerX-Memory-Used) e adicionar trava/comentário contra o uso direto dopsutilfora dos helpers. - Causa Raiz: investigar se o endpoint
/api/v2/pid/pid_provider/tem vazamento de conexões/arquivos e planejar a redução do--timeout 1000, migrando processamentos longos para Celery. - Monitorar: acompanhar métricas de FDs abertos por worker e a taxa de logs
memory: unavailableapós a virada, para avaliar se a mitigação foi suficiente ou seworker-connections/ulimitprecisam de novo ajuste.
Concluindo: o merge do código pode seguir sem bloqueios, mas o incidente só será considerado de fato encerrado quando as ações de infraestrutura acima forem aplicadas e validadas em produção — o patch de profiling trata o sintoma, não a causa raiz do esgotamento de file descriptors.
O que esse PR faz?
Este PR corrige um problema em que falhas na coleta de métricas de memória pelo sistema de profiling resultavam em erro HTTP 500 para o usuário final.
A leitura de memória realizada por meio da biblioteca
psutilfoi centralizada em funções auxiliares seguras emcore/utils/profiling_tools.py. Quando ocorre uma exceção como:durante a leitura de informações do processo (
/proc/<pid>/statm), o sistema passa a tratar a métrica de memória como indisponível (unavailable) e permite que a requisição continue normalmente.Além disso:
X-Memory-Usedpassa a ser enviado apenas quando a informação de memória foi obtida com sucesso;compose/production/django/start) passa a ajustar o limite de arquivos abertos (ulimit -n, configurável viaGUNICORN_NOFILE) e a reiniciar workers periodicamente (--max-requests/--max-requests-jitter), como mitigação adicional para o esgotamento de descritores de arquivo.O objetivo é evitar que uma falha de observabilidade/profiling impacte a disponibilidade da aplicação.
Onde a revisão poderia começar?
Recomenda-se iniciar a revisão pelo arquivo:
core/utils/profiling_tools.pyPrincipais alterações:
_get_memory_usage_mb,_get_memory_delta_mb,_format_memory_delta,_is_high_memory) para obtenção e formatação de métricas de memória;OSError/psutil.Errornos pontos que chamamprocess.memory_info();profile_endpoint,profile_classmethod,profile_method,profile_function,profile_staticmethod) e doLightweightProfilingMiddlewarepara usar os helpers em vez de acessarpsutildiretamente.Em seguida, revisar:
compose/production/django/startulimit -nantes de subir o Gunicorn;GUNICORN_TIMEOUT,WEB_CONCURRENCY,GUNICORN_WORKER_CONNECTIONS,GUNICORN_WORKER_CLASS,GUNICORN_MAX_REQUESTS,GUNICORN_MAX_REQUESTS_JITTER).Como este poderia ser testado manualmente?
Cenário normal
X-Memory-Usedcontinua sendo enviado quando a coleta de memória é bem-sucedida (em ambiente comDEBUG=True).Cenário de falha na coleta de memória
psutil.Process(...).memory_info()(por exemplo, via mock lançandoOSError);X-Memory-Usednão é enviado quando a métrica não está disponível;memory: unavailable).Cenário de limite de arquivos abertos
start;Open files limit: <valor>, confirmando que oulimitfoi aplicado;GUNICORN_NOFILEe confirmar que o valor configurado é refletido no log.Algum cenário de contexto que queira dar?
Foi identificado um incidente em produção onde a aplicação retornava HTTP 500 devido a uma exceção originada no mecanismo de profiling (
OSError: [Errno 24] Too many open files: '/proc/16/statm'), ocorrida durante a leitura de métricas de memória viapsutil. Um componente de observabilidade acabava interrompendo o processamento normal da requisição.Este PR atua como mecanismo de proteção (graceful degradation), impedindo que falhas de monitoramento provoquem indisponibilidade da aplicação, e complementa essa proteção com um ajuste operacional no limite de arquivos abertos e no ciclo de vida dos workers do Gunicorn.
Importante destacar que a causa raiz do erro (Too many open files) não é totalmente eliminada por este PR e deve continuar sendo monitorada. Possíveis causas remanescentes incluem:
ulimitinsuficientes em outros ambientes;Obs.: o comportamento mudou de "fail closed" para "fail open": se o profiling falhar, a aplicação continua atendendo a requisição. Isso deixa explícita a decisão arquitetural tomada e facilita a aprovação pelos revisores.
Screenshots
Não se aplica.
Quais são os tickets relevantes?
Não há ticket associado no momento.
Referências
psutilsobreProcess.memory_info();--max-requestse--max-requests-jitter(mitigação de vazamento de memória/descritores por worker);OSError: [Errno 24] Too many open files.Segurança da informação (NSI.04)
Este PR manipula dados sensíveis ou pessoais (LGPD)?
Este PR altera autenticação, autorização, controle de acesso ou gerenciamento de sessão?
Este PR introduz, atualiza ou remove dependências de terceiros?
psutiljá era utilizado anteriormente; nenhuma dependência nova foi adicionada, atualizada ou removida.Este PR foi validado pelo pipeline de segurança (SonarQube / Trivy)?
Este PR concatena, monta ou executa comandos SQL, HTML ou JavaScript a partir de entrada externa?
Este PR expõe novos endpoints, telas ou serviços?
Algum segredo, senha, chave ou token está sendo adicionado ao código-fonte?