Varnish é um web cache open source. Ele fica em uma camada acima do Apache (ou nginx, gunicorn, etc) e faz o cache de páginas inteiras por URL, melhorando vastamente a velocidade de qualquer website dinâmico. Em vez de gerar repetidamente uma página que faz várias consultas pesadas de SQL ou executar outra tarefa computacional pesada, a página resultante e seus headers ficam como cache na memória ou em um arquivo no disco. Pedidos subsequentes para a URL são buscados diretamente do cache. Isso fornece um aumento significativo de velocidade para qualquer projeto Django não trivial.
Claro, você pode usar o framework de chache do Django com ou sem o Varnish. O Django é ótimo para fazer o cache de partes de páginas geradas, mas na minha opinião é melhor usar um acelerador HTTP dedicado se você quer executar o cache em páginas inteiras.
Configurando o Varnish para uso com Django
O Varnish usa arquivos VCL para configuração. Isso garante um controle preciso sobre qualquer aspecto da pipeline da requisição; o VLC é essencialmente uma mini-linguagem para especificar como lidar com um pedido que está chegando.
Eu uso o seguinte VCL para a produção de sites Django, a maior parte dele peguei emprestado do Chase Seibert:
sub vcl_recv {
# unless sessionid/csrftoken is in the request, don't pass ANY cookies (referral_source, utm, etc)
if (req.request == "GET" && (req.url ~ "^/static" || (req.http.cookie !~ "sessionid" && req.http.cookie !~ "csrftoken"))) {
remove req.http.Cookie;
}
# normalize accept-encoding to account for different browsers
# see: https://www.varnish-cache.org/trac/wiki/VCLExampleNormalizeAcceptEncoding
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm
remove req.http.Accept-Encoding;
}
}
}
sub vcl_fetch {
# static files always cached
if (req.url ~ "^/static") {
unset beresp.http.set-cookie;
return (deliver);
}
# pass through for anything with a session/csrftoken set
if (beresp.http.set-cookie ~ "sessionid" || beresp.http.set-cookie ~ "csrftoken") {
return (pass);
} else {
return (deliver);
}
}
Ele faz os seguintes ajustes no comportamento do Varnish:
- Todos os cookies, exceto csrftoken e sessionid são ignorados. O Varnish irá parar de servir páginas em cache quando os cookies são determinados. Isso inclui cookies determinados pelo Google Analytics ou outro código JavaScript client-side. Ao ignorar esses cookies, o Varnish começará a servir conteúdo em cache para usuários não autenticados. Usuários autenticados nunca verão uma página em cache.
- Accept-Encoding é normalizado para garantir que o Varnish não faça o cache de várias cópias da mesma página. Os navegadores tendem a levemente diferenciar como eles especificam o Accept-Encoding, e o Varnish o usa como um diferenciador ao calcular a chave hash para um determinado pedido.
- URLs que se iniciam com /static sempre ficam em cache. Conteúdo estático é mais provável de se manter o mesmo, o usuário estando logado ou não, então removemos os cookies desses pedidos para garantir que eles fiquem em cache.
Ressalvas
Mesmo feito todas essas correções, ainda existe algumas grandes ressalvas:
- Se um objeto muda depois de estar em cache, haverá um atraso não especificado antes de essas mudanças se tornarem visíveis para usuários não autenticados.
- Uma vez que o usuário faz o login ou acessa um formulário com uma proteção CSRF, ele nunca verá uma página em cache novamente, a não ser que ele delete os cookies csrftoken e/ou sessionid, mesmo depois de fazer o logout. O Django não deleta o cookie sessionid no logout; em vez disso, a sessão é regenerada e um novo sessionid é determinado.
python-varnish e django-varnish
Para resolver a ressalva #1, devemos remover a URL de um objeto do cache do Varnish depois que ela for modificada. O django-varnish faz exatamente isso: ele monitora certos modelos e remove o get_absolute_url do objeto quando ele é atualizado. Eu uso um fork que também é capaz de limpar todo o cache quando certos modelos são atualizados.
O django-varnish depende do python-varnish, que fornece uma simples API do Python para gerenciamento de servidores Varnish através de suas portas de gerenciamento.
Instalação
Infelizmente, o código original de manutenção não funciona mais na versão atual do Vernish, mas forks alternativos estão disponíveis. Estes podem ser instalados via pip:
pip install -e git://github.com/kennu/python-varnish#egg=varnish
pip install -e git://github.com/kennu/django-varnish#egg=django-varnish
Configuração
Depois de instalado, apenas adicione django.contrib.humanize e varnishapp a INSTALLED_APPS, adicione (r’^admin/varnish/’, include(‘varnishapp.urls’)) à sua URLconf, e algumas configurações:
- VARNISH_WATCHED_MODELS é uma lista de modelos instalados, cujas get_absolute_urls você quer remover do cache do Varnish ao salvar. Exemplo: (‘auth.user’,’profiles.profile’)
- VARNISH_GLOBAL_WATCHED_MODELS é uma lista de modelos instalados, que irá apagar todo o cache do Varnish ao salvar.
- VARNISH_MANAGEMENT_ADDRS é uma lista dos endereços de cache do Varnish com suas postas de gerenciamento. Exemplo: (‘server1:6082′,’server2:6082’)
- VARNISH_SECRET é o segredo compartilhado usado para ser autenticado com o servidor Varsnish. Ele pode ser encontrado em /etc/varnish/secret ou em uma instalação Ubuntu/Debian do Varnish.
Uma vez que você tenha feito tudo isso, as mudanças no objeto irão resultar na URL absoluta do objeto removida do seu chache do Varnish.
Delete os cookies da sessão no logout
Para resolver a ressalva #2, é necessária uma mudança comportamental no Django: precisamos deletar os cookies csrftoken e sessionid depois que a view django.contrib.auth.views.logout é chamada Podíamos tentar realizar um monkey patch nessa view, ou podíamos escrever um pequeno pedaço de middleware para fazer o trabalho. O monkey patch é um crime terrível, então eu escolhi a opção do middleware. (há links aqui)
Crie um arquivo nomeado middleware.py no diretótrio raíz do seu projeto:
from django.conf import settings
"""Delete sessionid and csrftoken cookies on logout, for better compatibility with upstream caches."""
class DeleteSessionOnLogoutMiddleware(object):
def process_response(self, request, response):
if getattr(request, '_delete_session', False):
response.delete_cookie(settings.CSRF_COOKIE_NAME, domain=settings.CSRF_COOKIE_DOMAIN)
response.delete_cookie(settings.SESSION_COOKIE_NAME, settings.SESSION_COOKIE_PATH, settings.SESSION_COOKIE_DOMAIN)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
try:
view_name = '.'.join((view_func.__module__, view_func.__name__))
# flag for deletion if this is a logout view
request._delete_session = view_name in ('django.contrib.admin.sites.logout', 'django.contrib.auth.views.logout')
except AttributeError:
pass # if view_func doesn't have __module__ or __name__ attrs
Então adicione esse novo middleware no início de MIDDLEWARE_CLASSES no arquivo de configurações do seu projeto.
Resultados
Eu rodei o ab contra a homepage de um projeto em que venho trabalhando recentemente, testando contra o Varnish e o Apache. No momento do teste, cada pedido direto para a página resultou em um total de 50 consultas ao banco de dados. Eu testei da máquina onde o site está hospedado.
Eu usei os seguintes argumentos: -n 1000 -c 50 -k. Isso instrui para executar 1000 pedidos com um nível de concorrência de 50, com conexões persistentes HTTP habilitadas.
Os resultados (abaixo) destacam por que é absolutamente necessário empregar um cache apropriado para a produção dos seus deploys Django.
Usando Varnish ou outro acelerador HTTP, ou até o cache de página inteira intrínseco ao Django, as implicações são claras: se você permitir que os usuários acionem o Apache em todo carregamento de página, o seu site não ficará online por muito tempo.
requests/sec | 50% served within | 95% served within | |
---|---|---|---|
Apache 2.2.21 | 4.10 | 2441ms | 3096ms |
Varnish 3.0.2 | 20204.88 | 1ms | 5ms |
(Atualização: Tive a sugestão de rodar ab com o argumento -k para habilitar conexões persisistentes HTTP, então eu repeti o teste e anexei os resultados acima.)
?
Texto original disponível em http://ghughes.com/blog/2011/11/11/using-varnish-with-django-for-high-performance-caching/