Como dimensionar um servidor para uma aplicação Python

CONTEÚDO TBF HOST

Como dimensionar um servidor para uma aplicação Python

Dimensionar um servidor para uma aplicação Python tem uma particularidade que confunde quem vem do mundo dos sites: o número de acessos simultâneos que a aplicação suporta é definido por uma configuração, e não pela capacidade do servidor.

Em um site em PHP, o servidor web administra os processos conforme a demanda. Em uma aplicação Python, você define quantos trabalhadores vão existir — e esse número é o teto rígido de requisições que podem ser atendidas ao mesmo tempo.

Isso significa que uma aplicação pode estar recusando requisições com o servidor pela metade da capacidade, simplesmente porque foi configurada com poucos trabalhadores. E pode derrubar o servidor com muitos, por consumo de memória.

Este guia mostra como chegar a esse número com base em medição. Ele pressupõe conhecida a arquitetura de produção descrita no artigo sobre hospedar aplicações Python em produção.

O que determina a capacidade

Três números, e a relação entre eles:

FatorO que define
TrabalhadoresQuantas requisições são atendidas simultaneamente
Memória por trabalhadorQuantos cabem no servidor
Tempo por requisiçãoQuantas requisições cada trabalhador atende por minuto

A conta que decorre: trabalhadores multiplicados pela memória de cada um precisa caber na memória do servidor, com folga para o sistema e para o banco de dados, se ele estiver no mesmo lugar.

E a capacidade real de atendimento é o número de trabalhadores dividido pelo tempo médio de cada requisição. Uma aplicação com quatro trabalhadores e requisições de meio segundo atende muito mais que uma com oito trabalhadores e requisições de cinco segundos.

Por isso reduzir o tempo das requisições costuma render mais que aumentar trabalhadores — e é bem mais barato.

O que medir

Seis números, obtidos com a aplicação sob carga real ou simulada:

  1. Memória por trabalhador em regime, depois de horas atendendo requisições — não logo após iniciar.
  2. Tempo médio de resposta por rota, com atenção às mais lentas.
  3. Requisições simultâneas no pico, não o total diário.
  4. Proporção de tempo esperando — banco de dados, APIs externas, disco — versus processando.
  5. Conexões abertas com o banco, somadas entre todos os trabalhadores.
  6. Memória total do processo ao longo do tempo, para detectar crescimento contínuo.

O item 4 é o que orienta a configuração, e merece explicação.

Uma aplicação que passa a maior parte do tempo esperando — consultando banco, chamando serviços externos — deixa o processador ocioso durante essa espera. Existem modelos de trabalhador que aproveitam isso, atendendo mais requisições simultâneas por processo.

Uma aplicação que passa a maior parte do tempo processando não se beneficia disso: o trabalho é real e ocupa o processador de fato.

A medição do item 6 é o alarme mais importante: se a memória cresce continuamente sem estabilizar, existe um vazamento — e dimensionar apenas adia o problema. Uma aplicação saudável estabiliza em um patamar e oscila em torno dele.

Quantos trabalhadores

A pergunta prática.

Existem fórmulas de ponto de partida baseadas no número de núcleos do servidor, e elas servem como ponto de partida — não como resposta. O que as torna insuficientes é que elas ignoram a memória, que costuma ser o limite real.

Um método que funciona melhor:

  1. Meça a memória de um trabalhador em regime.
  2. Calcule quantos cabem na memória do servidor, reservando espaço para o sistema, o servidor web e o banco de dados, se local.
  3. Compare com o ponto de partida baseado em núcleos. Use o menor dos dois números.
  4. Ajuste conforme o perfil. Aplicações que esperam muito comportam configurações que atendem mais simultâneos por processo.
  5. Teste com carga e observe se as requisições entram em fila.
  6. Deixe margem. Configurar no limite exato significa que qualquer variação derruba o servidor.

O passo 3 é o que evita o erro mais comum: seguir uma fórmula de núcleos em um servidor com pouca memória. Quando ela esgota, o sistema operacional encerra processos, e o resultado são falhas intermitentes que parecem aleatórias.

Um detalhe que economiza memória: alguns modos de inicialização permitem que os trabalhadores compartilhem parte da memória carregada antes da bifurcação dos processos. O ganho é real em aplicações que carregam bibliotecas grandes, e vale verificar na documentação da ferramenta em uso.

Conexões de banco: o limite escondido

Um teto que trava aplicações aparentemente bem dimensionadas.

Cada trabalhador mantém as próprias conexões com o banco de dados. O total é o número de trabalhadores multiplicado pelas conexões que cada um abre — e esse total precisa caber no limite configurado no banco.

O sintoma é característico: a aplicação funciona bem até certo volume e então passa a falhar com erros de conexão, mesmo com processador e memória confortáveis.

Duas soluções que se complementam:

  • Agrupador de conexões, que mantém um conjunto controlado e reutilizado em vez de abrir uma nova a cada necessidade.
  • Conexões persistentes com limite, quando a ferramenta permite, reduzindo o custo de abrir e fechar a cada requisição.

Em aplicações com muitos trabalhadores, isso deixa de ser otimização e vira requisito.

Trabalhadores de fila contam também

Um erro de dimensionamento frequente e fácil de evitar.

Se a aplicação usa uma fila de tarefas — e a maior parte das aplicações Python de porte usa —, os processos que executam essas tarefas são processos adicionais, com consumo próprio de memória e de conexões de banco.

Isso significa que a conta precisa incluir:

  • Trabalhadores da aplicação, que atendem requisições.
  • Trabalhadores da fila, que executam tarefas em segundo plano.
  • O agendador de tarefas, se houver.
  • As conexões de banco de todos eles, somadas.

E há uma diferença importante: trabalhadores de fila frequentemente consomem mais memória que os da aplicação, porque executam operações pesadas — processar arquivos, gerar relatórios, manipular imagens. Dimensioná-los como se fossem iguais subestima o consumo.

O conceito e os requisitos de fila estão no artigo sobre o que é uma fila de tarefas.

O que fazer antes de aumentar recursos

Como sempre, as hipóteses baratas primeiro:

  1. Reduza o tempo das requisições lentas. Cada segundo economizado multiplica a capacidade de cada trabalhador.
  2. Verifique as consultas ao banco. Consultas sem índice adequado ou que buscam dados em excesso são a causa mais comum de requisições lentas.
  3. Mova trabalho pesado para a fila, liberando trabalhadores para atender usuários.
  4. Sirva arquivos estáticos pelo servidor web, e não pela aplicação.
  5. Adicione cache onde fizer sentido — resultados de consultas repetidas, respostas de APIs externas.
  6. Investigue o consumo de memória antes de assumir que a aplicação precisa de mais.
  7. Confirme que não há código bloqueante em rotas que deveriam ser rápidas.

O item 2 rende mais que todos os outros na maioria dos casos. Uma consulta lenta não melhora com mais trabalhadores — ela apenas passa a ser executada em paralelo, aumentando a carga do banco e podendo piorar a situação.

Quando a restrição é real

Se depois dos ajustes a aplicação continua saturando, a ordem de prioridade:

  • Memória, que é o limite mais frequente e o que define quantos trabalhadores cabem.
  • Processamento, relevante em aplicações que fazem trabalho intensivo.
  • Banco em servidor próprio, quando as consultas são o gargalo.
  • Distribuição entre servidores, quando um único já não comporta.

Sobre o último item: distribuir exige que a aplicação não guarde estado local entre requisições, que arquivos enviados fiquem em armazenamento compartilhado e que as sessões sejam centralizadas. São decisões de projeto, e a ressalva é a mesma feita no comparativo entre VPS e Cloud Server — redundância não vem junto com o tipo de hospedagem.

Sinais de que está mal dimensionado

SintomaCausa provável
Requisições em fila com recursos livresTrabalhadores insuficientes
Falhas intermitentes sem padrãoMemória esgotando e processos encerrados
Erro de conexão em volume médioLimite de conexões do banco atingido
Lentidão progressiva ao longo dos diasVazamento de memória
Uma rota lenta e o resto normalConsulta ou operação específica
Tarefas de fila atrasandoTrabalhadores de fila insuficientes

A segunda linha é a mais difícil de diagnosticar e a mais comum em aplicações recém-publicadas: configurar trabalhadores demais para a memória disponível produz falhas que parecem aleatórias, porque dependem de quais processos estavam ativos no momento.

Conclusão

Dimensionar uma aplicação Python é definir quantos trabalhadores existem — e esse número é limitado pela memória de cada um, não por uma fórmula de núcleos. Configurar acima do que a memória comporta produz falhas intermitentes; configurar abaixo recusa requisições com o servidor ocioso.

Duas medições resolvem a maior parte: memória por trabalhador em regime e tempo médio de resposta. E dois limites escondidos derrubam aplicações bem dimensionadas: as conexões de banco somadas entre todos os processos, e os trabalhadores de fila, que consomem mais do que se imagina e costumam ficar fora da conta.

Antes de contratar mais recursos, a verificação que rende mais: reduzir o tempo das requisições lentas, que quase sempre são consultas ao banco. Conheça o Cloud Server para Python da TBF Host e avalie a configuração a partir dos números que você mediu.

Perguntas frequentes

Quantos trabalhadores devo configurar em uma aplicação Python?

O número que couber na memória do servidor, comparado com um ponto de partida baseado nos núcleos disponíveis — use o menor dos dois. Meça a memória de um trabalhador em regime, reserve espaço para o sistema, o servidor web e o banco se for local, e deixe margem para variações.

Por que minha aplicação Python recusa requisições com o servidor ocioso?

Porque o número de trabalhadores é um teto rígido de requisições simultâneas, definido por configuração e não pela capacidade do servidor. Com poucos trabalhadores, as requisições entram em fila mesmo havendo memória e processador disponíveis.

Quanta memória uma aplicação Python precisa?

Depende do consumo de cada trabalhador multiplicado pela quantidade deles, mais o sistema, o servidor web e o banco se estiver no mesmo servidor. O valor a medir é o consumo em regime, depois de horas atendendo requisições — e não logo após iniciar, que é sempre menor.

Por que minha aplicação tem falhas intermitentes sem padrão?

Uma causa comum é configurar mais trabalhadores do que a memória comporta. Quando ela esgota, o sistema operacional encerra processos, e o resultado são falhas que parecem aleatórias porque dependem de quais processos estavam ativos naquele momento.

Por que a aplicação falha com erro de conexão em volume médio?

Provavelmente o limite de conexões do banco de dados foi atingido. Cada trabalhador mantém as próprias conexões, e o total é o número de trabalhadores multiplicado pelas conexões de cada um. Um agrupador de conexões resolve, e em aplicações com muitos trabalhadores ele é requisito.

Trabalhadores de fila entram no dimensionamento?

Sim, e são frequentemente esquecidos. Eles são processos adicionais, com consumo próprio de memória e de conexões de banco — e costumam consumir mais que os trabalhadores da aplicação, porque executam operações pesadas como processar arquivos e gerar relatórios.

O que fazer antes de aumentar os recursos do servidor?

Reduzir o tempo das requisições lentas, que quase sempre são consultas ao banco sem índice adequado ou que buscam dados em excesso. Cada segundo economizado multiplica a capacidade de cada trabalhador — e uma consulta lenta não melhora com mais trabalhadores, apenas passa a rodar em paralelo.

Minha aplicação fica progressivamente mais lenta ao longo dos dias. O que é?

Provavelmente um vazamento de memória: o consumo cresce continuamente sem estabilizar. Nesse caso, dimensionar apenas adia o problema, aumentando o intervalo entre reinícios. Uma aplicação saudável estabiliza em um patamar de memória e oscila em torno dele.

Facebook
X
LinkedIn