Apache, processos, threads e django

Muitas vezes apenas adotamos os padrões e não nos questionamos porque eles foram escolhidos, então resolvi fazer uma breve pesquisa sobre threads e processos no Apache

Antes de mais nada, é importante falar um pouco sobre processos e threads. Os processos são independentes e têm seus próprios endereços de memória, os threads pertencem a um processo e compartilham o mesmo endereço de memória e recursos.

Tudo começa no webserver, vou falar do Apache. Existem dois sabores que se diferenciam em como será feito o processamento de múltiplas requisições: prefork e worker.

O prefork tem um processo de controle para outros sub-processos que irão receber e responder às requisições. Aqui não são utilizados threads, uma requisição é repassada para o primeiro sub-processo livre e este, por sua vez, só atenderá a uma requisição por vez.

O worker também tem um processo de controle para outros sub-processos, a diferença é que cada sub-processo cria um thread de escuta que fará o repasse de requisições para outros threads. Isto faz com que o uso de memória normalmente seja menor, pois os threads compartilham os mesmos recursos.

Em ambos os modelos é possível regular o mínimo ou máximo de processos e threads, o Apache se encarrega de reciclar os processos dentro destes limites. O ideal é que o servidor esteja configurado para aguentar o número esperado de requisições mas evitando consumir toda a RAM disponível, o que resultaria em uso da SWAP em disco e uma cascata de problemas.

Segundo a documentação, o prefork é indicado para evitar threading em aplicações que utilizam bibliotecas non-thread-safe, ou seja, evitar que ações em threads diferentes de um mesmo processo possam influenciar outro thread. Como o worker mantém um conjunto de threads em um mesmo processo, compartilhando recursos, bibliotecas mal preparadas podem causar resultados inesperados.

Em geral, o Django é tido como thread-safe, houve uma discussão sobre isso mas acredito que muitos dos problemas da época já tenham sido resolvidos. Ainda não fiz a experiência, mas este post do Armin Ronacher compara o sistema de templates do Django com o Jinja e comenta que algumas templatetags como a cycle não são thread-safe.

Por muito tempo o Django teve o modpython com Apache prefork como deploy recomendado, agora a recomendação oficial é para se utilizar o modwsgi* mas não cobre qual modelo de webserver é mais adequado.

* Curiosidade: conversando com o Jacob na Python Brasil aprendi que se pronuncia “mód uísgui”, mais fácil não? 🙂

Aqui vão mais alguns links interessantes:

Anúncios

Consumo de memória em processos longos no Django

Há pouco tempo criei um script para importação offline de XML no Django.

O tamanho dos arquivos era próximo a 100Mb, pelas minhas contas são mais de 60 mil registros, argh.

Durante os testes, reparei que o processo crescia absurdamente e achei bom dar uma revisada antes de colocar em produção.

Tentei me entender com o módulo gc (Garbage Collector) mas não deu muito certo, encerrou o expediente e fui pra casa sem resolver o problema.

Mas nada como o ócio criativo para nos trazer idéias espontâneas. Pensei:

O que estou fazendo que pode estar estourando a memória?
Para cada item, verifico se existe, se existir atualizo, senão eu crio, são pelo menos duas consultas pra isso, mais as chaves estrangeiras, …..
Ei, isso tudo vai para o log de consultas do Django!! Diacho.

Pronto, achei o culpado! Em modo de debug, o Django mantém um histórico de todas as consultas feitas ao banco, então os 100Mb do XML vão facilmente virando 200Mb, 300Mb, o meu limite de paciência foi quando o processo chegou aos 400Mb.

Estes logs são mantidos no django.db.connection.queries, aqui vai um exemplo para testar:

from django.db import connection
print connection.queries

Para provar o meu ponto, fiz o teste mais tosco, a cada item processado limpava a lista de consultas. O processo crescia, mas muito pouco, nada que fosse crítico.

from django.db import connection
connection.queries = []

Por fim, fiz outro teste, com o debug desligado, e o processo também se manteve enxuto, sem limpar o log de consultas.

Probleminha chato, que demorou pra ser encontrado. Quando eu ia pensar em desligar o debug pra testar alguma coisa? Geralmente é o contrário: teste + debug / produção – debug.

Então fica a dica, quando estiver trabalhando com processos pesados ou de longa duração, é aconselhável desligar o modo de debug, principalmente se é algo que roda offline, agendado no cron ou longe dos “olhos dos usuários”.