Posts de November, 2010

[Rafael Carício] Deployment de Django usando NGINX, Memcached e um pouco de código em C

Friday, November 5th, 2010
Depois do post anterior eu começei a pensar em como eu poderia melhorar ainda mais a performance da nossa aplicação Django. Então tive a ideia de fazer cache das páginas estáticas e servi-las direto do memcached para o usuário. Isso funcionou muito bem, mas existe um problema quando essa mesma ideia é aplicada a uma aplicação onde os usuários realizam login e precisam acessar páginas personalizadas com coisas do tipo “Olá {{ nome_do_usuario }}, bem-vindo!”. E pensei bastante sobre isso e deixei pra resolver este problema depois. Resolvi atacar inicialmente os visitantes externos, que não são usuários do sistema consequentemente que não estão autenticados no sistema. Neste grupo de visitantes se encontram também os engines de pesquisa como Google, Yahoo!, Bing e etc. E também os navegantes de primeira viagem que chegaram no site. Assim, quanto melhor a experiência que estes visitantes tiverem no primeiro acesso ao site maior as chances deles voltares ou se cadastrarem para usar o sistema. A velocidade de carregamento das páginas do site é um dos criterios de qualidade que estes visitantes analisam para decidirem se voltam ou não. Inclusive o Google recentemente anunciou que vai usar/usa o tempo de carregamento como critério no seu algoritmo de PageRank, ou seja, mais um motivo para motivar a desbravar esta ideia.

Para deixar o acesso dos visitantes não autenticados mais rápido não precisamos pensar muito, se você leu o post anterior e fez um teste simples deve ter percebido os ganhos em velocidade que foram extremamente absurdos. Assim, precisamos enviar esses visitantes não autenticados para o memcached diretamente sem que a requisição passe pela stack do Python / Django. Dessa forma desafogamos um pouco (ou muito) nossa aplicação pois ela só vai processar requisições que são realmente interessantes e que precisam de um processamento mais dinâmico. Se pararmos pra pensar, percebemos que os visitantes não precisam acessar as páginas com dados atualizando em tempo real (depende muito da aplicação, mas de um modo geral isso é verdade). Por exemplo, se nossa aplicação tem um forúm os visitantes do site não precisam ver o forúm se atualizando a cada acesso ou a cada segundo que ele visita o site. Já os usuários autenticados, precisam e devem ver o forúm atualizado o mais rápido possível para que eles possam interagir com os outros usuários do site. E essa ideia é aplicada a todas as páginas externas do site pois nem os engines de pesquisa ficam acessando o site a cada milisegundo pra ver o que mudou. Resumindo, os visitantes do site não precisam ver as coisas atualizadas eles provávelmente estão entrando no site pela primeira vez e tudo pra eles é novidade.

Agora que decidimos que precisamos fazer cache de tudo que é acessado por usuários não autenticados ficou fácil, não é? Não, como vamos saber quando o usuário está autenticado ou não? No código Python / Django podemos descobrir isso fácil mas como descobrir isso antes da requisição chegar ao Python? Precisamos validar se o usuário está autenticado ou não o mais rápido possível e enviar a resposta pra ele. O ideal é descobrir isso diretamente no NGINX, mas não tem uma forma de fazer isso. Inicialmente eu pensei em validar a existência do cookie do Django nas requisições e se ele existisse o usuário estaria autenticado. Porém isso não funciona pois o Django cria um cookie de sessão mesmo se o usuário não for autenticado. Sendo assim, o que fazer? Essa foi minha dúvida e decidi estudar o código do Django nessa parte de autenticação e gerenciamento de sessão para saber o que eu preciso fazer pra descobrir se o usuário está autenticado ou não. Depois de fazer isso, resumindo, cheguei a conclusão que não dava pra deserializar o objeto pickler que o Django salva no BD pra descobrir se o usuário está autenticado ou não. Então, analisando o comportamento do cookie do Django eu percebi que dava pra fazer uma validação simples, que não dá 100% de acerto sobre a questão do usuário estar autenticado ou não, mas já é uma boa (do meu ponto de vista). Eu descobri que posso validar o tamanho dos dados da sessão, não dá pra deserializar o objeto da sessão, mas dá pra validar o tamanho da string salva no BD. E é assim que eu faço a validação pra saber se o usuário está autenticado. Se os dados da sessão forem maior que um limite mínimo, quer dizer que o usuário está autenticado. No meu caso isso funciona pra mim porquê eu não salvo nada na sessão quando o usuário não está autenticado (é importante essa informação). Se você salva algo na sessão do usuário em páginas públicas da sua aplicação esse esquema, do jeito que eu faço agora, pode não funcionar pra você.

Resolvido esses problemas, eu tentei resolver o problema de acessar o banco de dados para pegar os dados que estão armazenados na sessão do usuário e validar se ele está autenticado antes da requisição ir para o Django. Essa validação tinha que ser feita no próprio NGINX ou em alguma coisa bastante rápida pra conseguir obter essa resposta. Então procurei várias alternativas. Primeiro eu pensei em usar node.js, mas cai no problema que eu ainda não conheço a tecnologia direito e então isso poderia ser um problema. Tentei procurar outra coisa, um modulo para o NGINX, talvez. Até achei um módulo que dá pra usar código em Lua no NGINX, mas eu também tinha que aprender Lua e não me pareceu necessário nesse momento também. Tentei achar uma outra solução e foi ai que achei um post em um blog mostrando um exemplo de consulta ao memcached usando código em C. Entao me ocorreu que eu poderia modificar esse código para atender as minhas necessidades. E foi isso que eu fiz. Código em C é o mais rápido que eu poderia conseguir (tá, eu sei… mas eu não sei Assembly tão bem assim, ainda).

Para explicar a ideia que eu tive, eu criei alguns diagramas de sequencia pra ficar mais fácil de entender. Abaixo eu mostro a primeira situação. Nesse caso eu mostro o que acontece quando o usuário acessa a aplicação e não está autenticado. Eu chamo o meu código em C de UserRouter, ele valida se o usuário está autenticado e se não estiver ele consulta no memcached a página que o usuário está tentando acessar e envia de volta para o NGINX no caso da página existir no memcached. Caso a página não exista ele envia a requisição para o Django normalmente.

arquitetura

No segundo cenário eu quero mostrar o que acontece quando um usuário autenticado acessa a página ou quando não existe uma página pública ainda em cache.

sem dados

A parte que coloca a página no memcached é opcional, pois se for uma página interna da aplicação ou uma página que apenas usuários autenticados acessam, ele nunca vai pra o memcached.
Então, para suportar esse funcionamento eu reusei o middleware do post anterior.

Adicionei algumas configurações ao settings.py:

E o código em C que faz toda essa mágica acontecer.

PS.: Bem, eu só vou avisando logo que esse código em C tem uma falha de SQL Injection que eu ainda vou corrigir (e atualizo aqui pra vocês).
Pra compilar esse código vocês teem que instalar algumas bibliotecas, mas essa parte é fácil se você usa o ubuntu.

rafaelcaricio@ubuntu:~/development$ sudo apt-get install libmysqlclient-dev libevent-dev libmemcached-dev

E pra compilar o código. A linha de comando é:

rafaelcaricio@ubuntu:~/development$ gcc -o v_auth_and_cache -levent -I/usr/include/mysql  -DBIG_JOINS=1  -fno-strict-aliasing   -DUNIV_LINUX -DUNIV_LINUX verify_auth_and_cache.c -Wl,-Bsymbolic-functions -rdynamic -L/usr/lib/mysql -lmysqlclient -lmemcached

Depois é só executar o servidor UserRouter colocando na porta 8000. Usando essa linha de comando:
rafaelcaricio@ubuntu:~/development$ ./v_auth_and_cache localhost 8000

E fazer algumas alterações na configuração no NGINX. Assim a requisição sempre vai primeiro pra o UserRouter e depois, se for o caso, passa para o Django.


No final eu realizei bastante testes e verifiquei que tudo isso realmente funciona e deixa o acesso a páginas externas extremamente rápido. Certo que eu ainda vou testar muito isso e melhorar esse funcionamento para poder usar no AtéPassar. Mas tenho certeza que isso vai melhorar bastante a velocidade de indexação pelos engines de busca e melhorar também a experiência dos usuários que estão acessando o site pela primeira vez. Eles vão ver tudo funcionando incrivelmente rápido. Espero ter ajudado ou aberto a mente de alguns de vocês para novas possibilidades. Em breve estarei escrevendo mais um post sobre outra ideia ou sobre alguma outra coisa que eu já fiz nas minhas horas vagas. Abraços e até logo.

[Rafael Carício] Profiling de aplicações Django

Friday, November 5th, 2010

Hoje tive que identificar um problema complicado no AtéPassar, uma rede social de estudantes de concursos públicos e OAB onde eu trabalho atualmente como líder técnico da equipe de desenvolvimento. A aplicação estava ficando lenta quando tinha muitos acessos simutâneos e não havia motivos aparentes para isso acontecer. Devido a esse problema tive que procurar uma forma de descobrir o que estava acontendo internamente no código da aplicação. Assim, lembrei que existe uma técnica de analise de programas que ecaixa totalmente com esta situação que é o profiling de software.

Pesquisei bastante na internet sobre como fazer isso em aplicações Django. Python tem um módulo especifico para isso, que é o hotshot. Assim acabei encontrando um projeto bem interessante que já existe uma integração com o django para rodar sobre o hotshot. Então ficou mais fácil de realizar essa tarefa de analisar o funcionamento do projeto. E para a visualização das chamadas eu utilizei o KCachegrind, um projeto OpenSource que permite visualizar os dados gerados por softwares de profiling.

Screenshot do KCachegrind

O gráfico gerado pelo KCachegrind mostra as funções do sistema e os caminhos de chamadas com o custo de cada chamada. Assim podemos visualizar e identificar qual o ponto especifico que está deixando o sistema lento.

Depois de utilizar essas ferramentas consegui identificar o problema que estava deixando o AtéPassar lento e agora estamos trabalhando bastante para resolver este gargalo. Acredito que a solução para este problema também irá gera um post interessate para este blog.

[Rafael Carício] Melhoria de performance projetos Django usando Gunicorn, Nginx e Memcached

Friday, November 5th, 2010

Estou estudando formas para a melhoria de performance de aplicações na web. Atualmente eu trabalho com Python/Django no desenvolvimento de aplicações web. Pesquisando na internet sobre como melhorar o tempo de respostas das requisições ao máximo encontrei vários artigos explicando diversas formas de fazer isso. Muitos desses artigos me chamaram a atenção, pois faz exatamente o que eu imaginava. O próprio Nginx se comunica com o memcached e verifica se existe, aquela determinada página que o usuário está requisitando, no cache e apenas se não tiver, é que a requisição é repassada para o stack do python/django.

 

Depois de visualizar esse funcionamento eu resolvi implementar alguns testes em uma simples aplicação rodando localmente para verificar este funcionamento e os ganhos em performance que isso pode proporcionar. Fiz uma compilação das ideias apresentadas pelos artigos que eu li sobre o assunto.

Instalando o software necessário

Começei instalando o memcached e colocando ele para rodar com 512mb de mémoria.

Pronto, depois disso podemos verificar se o memcached está rodando corretamente.

Agora o memcached está instalado e funcionando. Vamos instalar o nginx, é bem simples.

E também temos que instalar o suporte ao memcached no Python e instalar também o Gunicorn para rodar nossa aplicação.

Hora de verificar se o nginx está rodando.

Podemos ver que está acessando a url http://localhost/ e vendo a mensagem “Wellcome to nginx!”.

Configurando para a aplicação rodar usando o gunicorn

Agora vamos configurar o nginx para acessar nossa aplicação que vamos colocar rodando sobre o gunicorn. Primeiro vamos configurar o nginx para enviar repassar as requisições.

No arquivo de configuração, eu coloquei:

 

Temos que deletar/desabilitar as configurações padrão do nginx, pois nem vamos usar para este caso. Eu preferi deletar o arquivo.

 

rafaelcaricio@ubuntu:~$ sudo rm /etc/nginx/sites-enabled/default 

 

 

Agora criamos um link para as configurações no diretório de sites-enabled do gunicorn.

rafaelcaricio@ubuntu:~$ sudo ln /etc/nginx/sites-available/easyproject.conf /etc/nginx/sites-enabled/easyproject.conf 

E reiniciamos o nginx para pegar as novas configurações.

 

rafaelcaricio@ubuntu:~$ sudo /etc/init.d/nginx reload

Reloading nginx configuration: the configuration file /etc/nginx/nginx.conf syntax is ok

configuration file /etc/nginx/nginx.conf test is successful

nginx.

Pronto, agora se acessarmos http://localhost vamos ver um erro 502 do nginx, isso acontece porque nossa aplicação não está rodando ainda. Vamos colocar nossa aplicação django para rodar agora. Para isso, precisamos criar a configuração do gunicorn pra rodar o projeto. Assim eu crio o arquivo gunicorn.conf.py dentro do meu projeto django.

Essas são as configurações básicas que fazem meu projeto executar corretamente. Agora podemos mandar executar o gunicorn com as configurações escolhidas.

rafaelcaricio@ubuntu:~/development/easyproject$ gunicorn_django -c gunicorn.conf.py settings.py

Pronto! Agora já podemos acessar nossa aplicação através do nginx que repassa as requisições para o gunicorn. Isso já deixa a nossa aplicação bem rápida. Porém esse é o básico que podemos fazer pra deixar tudo rodando. A minha ideia aqui é ir um pouco mais além e deixar as coisas funcionando mais rápido ainda. E para isso vou fazer um esquema de cache na aplicação django criando um middleware para salvar os resultados das requisições no memcached. E deixar o nginx perguntar ao memcached sobre a existência do resultado da requisição antes de repassar a requisição para o django.

Para isso acontecer eu modifiquei as configurações no nginx.

 

 

E também criei um novo middleware que foi adicionado ao meu projeto no django. E adicionei algumas opções ao meu settings.py. 

 

 

No settings.py eu adicionei as seguintes configurações:

 

 

Assim, todas as páginas que o django serve que sejam GET serão adicionadas ao memcached e o nginx vai pegar de lá o seu conteúdo. Assim as respostas ficarão bem mais rápidas.

Conclusão

Esta configuração é incrivelmente mais rápida, pois as requisições não vão direto para o django. O django só vai processar requisições POST, telas de erro e no caso de uma nova página ser acessada pela primeira vez. A ideia agora é pensar mais a frente, em como invalidar esse cache para que as informações mostradas aos usuários estejam sempre atualizadas. Essa tarefa não é muito complexa, apenas será uma coisa a mais que vai ter que ser feita quando houver modificações no banco de dados. Porém merece uma atenção e cuidado maior, para não ter grande impacto no processo de desenvolvimento. Vou analisar várias implementações e técnicas de invalidação de cache. Assim, no próximo post eu falarei mais sobre isso e demostrarei qual foi a solução que eu encontrei para isso.

 

Referências

http://kovyrin.net/2007/08/05/using-nginx-ssi-and-memcache-to-make-your-web-applications-faster/

http://amix.dk/blog/post/19414

http://jimmyg.org/blog/2009/ssi-memcached-nginx.html

http://www.willmcgugan.com/blog/tech/2009/3/1/fast-caching-with-django-and-nginx/

http://tabbedthinking.posterous.com/nginx-memcached-uwsgi-django

http://soyrex.com/articles/django-nginx-memcached.html

 

[Rafael Carício] Acrescentando métodos ou atributos ao objeto User do Django

Friday, November 5th, 2010

O Django é um framework para desenvolvimento web em python muito bem conceituado. Facilita o desenvolvimento web entre outras coisas que podem ser encontradas no site oficial do framework.
Estou usando ele para desenvolver um projeto relativamente grande, com muitas funcionalidades (atualmente em fase de desenvolvimento). Um dia desses tive que modificar o modelo de dados do sistema para adicionar mais um tipo de usuário. No inicio o sistema tinha apenas um tipo de perfil que era o de estudante, porém tive que adicionar o profile do professor no sistema. Quando um novo usuário vai entrar no sistema ele escolhe se é estudante ou professor, dependendo da escolha o sistema cria um tipo de profile diferente. Ai foi onde tive problema, pois o Django já é preparado para trabalhar com sistemas que utilizam profile, porém sistemas com apenas um profile (um tipo de usuário). Procurei bastante na internet sobre isso e acabei achando uma solução interessante, porém não me lembro mais qual foi o blog gringo que achei sobre isso (vou pesquisar e achando eu atualizo aqui). Adicionei este código no arquivo urls.py :

from django.contrib.auth.models import User

class UserGetProfileExtension:
    def _get_profile(self):
        if not hasattr(self, ‘_UserGetProfileExtension__cached_profile’):
            try:
                if hasattr(self, ’student’):
                    self.__cached_profile = self.student
                elif hasattr(self, ‘professor’):
                    self.__cached_profile = self.professor
            except:
                self.__cached_profile = None
        else:
            self.__cached_profile = self._UserGetProfileExtension__cached_profile
        return self.__cached_profile
    profile = property(_get_profile)
User.__bases__ += ( UserGetProfileExtension, )

Agora para pegar o profile do usuário basta continuar usando:

user.profile

Espero que isso possa ajudar muita gente. Como as referências estão em inglês, este conteúdo em português é interessante. Até logo e depois vou colocando mais soluções interessantes.

[Tiago Motta] Gem brazilian-rails em versões de rails antigas

Wednesday, November 3rd, 2010

Se você estiver usando uma versão antiga do brazilian-rails é bem provável que ao tentar instalar esta gem ela comece a instalar também versões superiores do rails. Isso porque esta gem possui um conjunto de dependências definidas somente com o operador “>=”.

Ou seja, se você tentar instalar a versão 2.1.13 por exemplo, que funciona com o rails 2.3.2, ele tentará baixar a dependência da gem brdinheiro “>= 2.1.13″, acabando por baixar a versão 3.0.0, que depende do activerecord 3.0.0, do rails 3. Nem preciso dizer que isso acabará dando merda.

A solução é baixar todos os componentes independentemente sem suas dependências da seguinte forma:

sudo gem install brnumeros --version=2.1.13 --ignore-dependenciessudo gem install brdinheiro --version=2.1.13 --ignore-dependenciessudo gem install brcep --version=2.1.13 --ignore-dependenciessudo gem install brdata --version=2.1.13 --ignore-dependenciessudo gem install brhelper --version=2.1.13 --ignore-dependenciessudo gem install brstring --version=2.1.13 --ignore-dependenciessudo gem install brcpfcnpj --version=2.1.13 --ignore-dependenciessudo gem install brI18n --version=2.1.13 --ignore-dependenciessudo gem install brazilian-rails --version=2.1.13 --ignore-dependencies