O framework de cache do Django

Uma dilema essencial dos sites dinâmicos vem a ser o próprio fato de serem dinâmicos. Cada vez que um usuário requisita uma página, o servidor web faz todo o tipo de cálculos – consultas a bancos de dados, renderização de templates e lógica de negócio – para criar a página que o seu visitante vê. Isso tem um custo de processamento muito maior que apenas a leitura de arquivos estáticos no disco.

Para a maior parte dos aplicativos Web, esse overhead não é um problema. A maior parte das aplicações Web não são o washingtonpost.com oouslashdot.org; são simplesmente sites pequenos a médio com tráfico equivalente. Mas para aplicações de porte mério para grante, é essencial eliminar toda a sobrecarga possível.

É onde entra o cache.

Fazer o cache de algo é gravar o resultado de um cálculo custoso para que você não tenha de executar o cálculo da próxima vez. Aqui está um pseudocódigo explicando como isso funcionaria para uma página Web gerada dinamicamente:

tente encontrar uma página no cache para tal URL
se a página estiver no cache:
    retorne a página do cache
se não:
    gere a página
    guarde a página gerada no cache (para a próxima vez)
    retorne a página gerada

O Django vem com um sistema de cache robusto que permite que você guarde as páginas dinâmicas para que elas não tenham de ser calculadas a cada requisição. Por conveniência, Django oferece diferentes níveis de granularidade de cache: Você pode fazer o cache da saída de views específicas, você pode fazer o cache somente das partes que são difíceis de produzir, ou pode fazer o cache do site inteiro.

O Django também trabalha com caches do tipo "upstream", como o Squid (http://www.squid-cache.org/) e cache baseado em navegador. Esses são tipos de cache que você não controla diretamente mas para os quais fornece dicas (via cabeçalhos HTTP) sobre quais partes do seu site devem ser mantidas em cache, e como.

Configurando o cache

O sistema de cache requer uma pequena configuração. Você deve informar ao Django onde os seus dados em cache estarão -- se em um banco de dados, no sistema de arquivos ou diretamente na memória. Essa é uma decisão importante que afeta a performance do seu cache; sim, algums tipos de cache são mais rápidos que outros.

Sua preferência de cache vai na configuração CACHE_BACKEND no seu arquivo de configurações. Aqui vai uma explicação de todos os valores disponíveis para CACHE_BACKEND.

Memcached

De longe, o mais rápido e mais eficiente tipo de cache disponível no Django, Memcached é um framework de cache inteiramente baseado em memória originalmente desenvolvido para lidar com as altas cargas no LiveJournal.com e subsequentemente tornada open-sourced pela Danga Interactive. É usado por sites como o Facebook ou Wikipedia para reduzir o acesso a banco de dados e aumentar a performance do site drasticamente.

O Memcached está disponível de graça em http://memcached.org/ . Ele executa como um daemon, para o qual é alocada uma quantidade específica de RAM. Tudo o que ele faz é fornecer uma interface rápida para adição, busca e remoção de dados arbitrários do cache. Todos os dados são gravados diretamente na memória, então não existe sobrecarga de banco de dados ou uso do filesystem.

Após a instalação do Memcached, você precisa instalar o python-memcached, which provides Python bindings to Memcached. This is available at ftp://ftp.tummy.com/pub/python-memcached/

Alterado no Django 1.2: In Django 1.0 and 1.1, you could also use cmemcache as a binding. However, support for this library was deprecated in 1.2 due to a lack of maintenance on the cmemcache library itself. Support for cmemcache will be removed completely in Django 1.4.

Para usar o Memcached com Django, configure o CACHE_BACKEND para memcached://ip:port/, onde ip é o endereço IP dp daemon do Memcached e o port é a porta onde o Memcached está rodando.

Nesse exemplo, o Memcached está rodando em localhost (127.0.0.1) na porta 11211:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

Uma característica excelente do Memcached é sua habilidade de compartilhar o cache em diversos servidores. Isso significa que você pode executar daemons do Memcached em diversas máquinas,e o programa irá tratar o grupo de máquinas como um único cache, sem a necessidade de duplicar os valores do cache em cada máquina.Para aproveitar essa funcionalidade, inclua todos os endereços de servidores em CACHE_BACKEND, separados por ponto e vírgula.

In this example, the cache is shared over Memcached instances running on IP address 172.19.26.240 and 172.19.26.242, both on port 11211:

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'

Nesse exemplo, o cache é compartilhado por instâncias do Memcached rodando nos endereços IP 172.19.26.240 (porta 11211) e 172.19.26.242 (port 11212), e 172.19.26.244 (porta 11213):

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'

O cache baseado em memória tem uma desvantagem: Como os dados em cache estão na memória, serão perdidos se o seu servidor travar. Claramente, a memória não foi feita para armazenamento permanente de dados, então não confie no cache em memória como sua única fonte de armazenamento de dados. Na verdade, nenhum dos backends de cache do Django deve ser usado para armazenamento permanente -- devem ser usados como soluções para cache, e não armazenamento -- mas reafirmamos isso aqui porque o cache em memória é realmente temporário.

Cache em banco de dados

Para usar uma tabela do banco de dados como o seu backend de cache, primeiro crie uma tabela de cache em seu banco de dados com o seguinte comando:

python manage.py createcachetable [cache_table_name]

...onde [cache_table_name] é o nome da tabela no banco de dados a ser criada. (Esse nome pode ser qualquer um, desde que seja um nome válido de tabela e esse nome ainda não esteja sendo usado no seu banco de dados.) Esse comando cria uma única tabela no seu banco de dados, no formato apropriado que o sistema de cache em banco de dados do Django espera encontrar.

Uma vez que você tenha configurado a tabela do banco de dados, configure o seu CACHE_BACKEND para "db://tablename", onde tablename é o nome da tabela. Nesse exemplo, o nome da tabela de cache é my_cache_table:

CACHE_BACKEND = 'db://my_cache_table'

O cache em banco de dados usará o mesmo banco de dados especificado em seu arquivo de configuração. Você não pode usar um banco de dados diferente para sua tabela de cache.

O cache em banco de dados funciona melhor se você tem um servidor de banco de dados rápido e bem indexado.

Database caching and multiple databases

If you use database caching with multiple databases, you'll also need to set up routing instructions for your database cache table. For the purposes of routing, the database cache table appears as a model named CacheEntry, in an application named django_cache. This model won't appear in the models cache, but the model details can be used for routing purposes.

For example, the following router would direct all cache read operations to cache_slave, and all write operations to cache_master. The cache table will only be synchronized onto cache_master:

class CacheRouter(object):
    """A router to control all database cache operations"""

    def db_for_read(self, model, **hints):
        "All cache read operations go to the slave"
        if model._meta.app_label in ('django_cache',):
            return 'cache_slave'
        return None

    def db_for_write(self, model, **hints):
        "All cache write operations go to master"
        if model._meta.app_label in ('django_cache',):
            return 'cache_master'
        return None

    def allow_syncdb(self, db, model):
        "Only synchronize the cache model on master"
        if model._meta.app_label in ('django_cache',):
            return db == 'cache_master'
        return None

If you don't specify routing directions for the database cache model, the cache backend will use the default database.

Of course, if you don't use the database cache backend, you don't need to worry about providing routing instructions for the database cache model.

Cache em sistema de arquivos

Para gravar os items em cache no sistema de arquivos, use o tipo de cache "file://" para CACHE_BACKEND. Por exemplo, para salvar os dados do cache em /var/tmp/django_cache, use essa configuração:

CACHE_BACKEND = 'file:///var/tmp/django_cache'

Perceba que existem três barras próximas ao início do exemplo. As primeiras são para o file://, e a terceira é o primeiro caractere do caminho do diretório, /var/tmp/django_cache. Se você está no Windows, coloque a letra do drive após o file://, dessa forma:

file://c:/foo/bar

O caminho do diretório deve ser absoluto -- isso é, ele deve iniciar na raiz do seu sistema de arquivos. Não faz diferença se você põe a barra no final da configuração.

Assegure-se que o diretório apontado por essa configuração exista e tenha permissões de leitura e escrita pelo usuário do sistema que executa o seu servidor web. Continuando o exemplo acima, se o seu servidor web roda como o usuário apache, tenha certeza que o diretório /var/tmp/django_cache exista e tenha permissão de leitura e escrita pelo usuário apache.

Cada valor de cache será gravado em um arquivo separado cujos conteúdos são os dados servidos pelo cache em um formato serializado ("pickled"), usando o módulo pickle do Python. Cada nome de arquivo é a chave do cache, escapado para uso seguro em sistema de arquivos.

Cache em memória local

Se você quer as vantagens de velocidade de executar um cache em memória mas não pode executar o Memcached, considere o backend de cache em memória local. Esse cache é multi-processo e thread-safe. Para usá-lo, configure o CACHE_BACKEND para "locmem:///". Por exemplo:

CACHE_BACKEND = 'locmem:///'

Note que cada processo irá ter sua própria instância privada de cache, o que significa que nenhum cache entre processos é possível. Isso obviamente significa que o cache em memória local não é muito eficiente em termos de memória, então provavelmente não é uma boa escolha para um ambiente de produção. É bom para desenvolvimento.

Cache falso (para desenvolvimento)

Finalmente, o Django vem com um cache "falso" que não faz cache realmente -- ele apenas implementa a interface de cache sem fazer nada realmente.

Isso é útil se você tem um site em produção que usa cacheamento pesado em vários lugares, mas em desenvolvimento ou no ambiente de testes você não quer usar cache e não quer mudar o código para lidar com o última caso em especial. Para ativar o cache falso, configure o CACHE_BACKEND assim:

CACHE_BACKEND = 'dummy:///'

Usando um backend de cache personalizado

Novo no Django 1.0: Please, see the release notes

Apesar do Django suportar diversos sistemas de cache diferentes, algumas vezes você pode querer usar algum backend de cache personalizado. Para usar um backend externo de cache com o Django, use um caminho de importação de módulos do Python como a parte do esquema (a parte que vem antes do dois pontos inicial) da URI do CACHE_BACKEND, assim:

CACHE_BACKEND = 'path.to.backend://'

Se você está construindo o seu próprio backend, você pode usar os backends padrão de cache como implementações de referência. Você irá encontrar o código no diretório django/core/cache/backends/ dos fontes do Django.

Nota: Você deveria usar os backends de cache incluídos com o Django, anão ser que você tenha uma razão muito boa, como um host que não os suporta. Eles foram bem testados e são fáceis de usar.

Argumentos do CACHE_BACKEND

Cada tipo de cache pode receber argumentos. Eles são informados em um estilo semelhante a query-string na configuração CACHE_BACKEND. Os seguintes argumentos são válidos:

  • timeout: O timeout padrão, em segundos, a ser usado para o cache. O padrão é 300

    segundos (5 minutos).

  • max_entries: Para os backends locmem, filesystem e database,

    o número máximo de entradas permitidas no cache antes dos valores antigos serem removidos. O valor padrão desse argumento é 300.

  • cull_frequency: A fração de entradas que são limpas do cache quando

    max_entries é atingido. A razão real é 1/cull_percentage, então configure o cull_percentage=2 para limpar metadae das entradas quando o valor de max_entries for atingido.

    Um valor de 0 para o cull_frequency significa que o cache todo será limpo quando max_entries for atingido. Isso torna a limpeza muito mais rápida, a custa de mais perdas no cache.

Nesse exemplo, o timeout está configurado para 60:

CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"

Nesse exemplo, o timeout é 30 e max_entries é 400:

CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"

Argumentos inválidos são silenciosamente ignorados, assim como valores inválidos para argumentos conhecidos.

O cache por site

Alterado no Django 1.0: (versões anteriores do Django forneciam apenas um único CacheMiddleware no lugar das duas partes descritas abaixo).

Uma vez que o cache esteja configurado, a forma mais siples de usá-lo é fazer o cache do seu site inteiro. Você precisa adicionar 'django.middleware.cache.UpdateCacheMiddleware' e 'django.middleware.cache.FetchFromCacheMiddleware' as suas configurações de MIDDLEWARE_CLASSES, como nesse exemplo:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

Note

No, isso não é um erro de digitação: o middleware "update" deve aparecer primeiro na list, e o middleware "fetch" por último. Os detalhes são um pouco obscuros, mas veja Ordem das MIDDLEWARE_CLASSES abaixo se você quiser entender os detalhes.

Então, adicione as configurações necessárias ao seu arquivo de configurações do Django:

  • CACHE_MIDDLEWARE_SECONDS -- O número de segundos em que cada página deve permancecer em cache.
  • CACHE_MIDDLEWARE_KEY_PREFIX -- Se o cache é compartilhado entre diversos sites usando a mesma instalação do Django, configure isso para o nome do site, ou alguma outra string única que identifique esse instância do Django, para previnir colisões de cache. Use uma string vazia se você não se importa.

O middleware de cache faz o cache de cada página que não tenha parâmetros de GET ou POST. Opcionalmente, se a configuração CACHE_MIDDLEWARE_ANONYMOUS_ONLY for True, somente requisições anônimas (i.e., aquelas feitas por usuários não autenticados) serão cacheadas. Esse é um modo simples e eficiente para desabilita o cache para quaisquer páginas específicas de usuários (incluindo a interface de administração do Django). Note que se você usou o CACHE_MIDDLEWARE_ANONYMOUS_ONLY, você deve certificar-se de ter ativado o AuthenticationMiddleware.

Adicionalmente, o middleware de cache adiciona alguma cabeçalhos em cada HttpResponse:

  • Configura o cabeçalho Last-Modified para a data/hora atuais quando uma nova versão da página (não cacheada) é resiquitada.
  • Configura o cabeçalho Expires para a hora atual mais o valor definido em CACHE_MIDDLEWARE_SECONDS.
  • Configura o cabeçalho Cache-Control para dar um max age para a página -- novamente, da configuração CACHE_MIDDLEWARE_SECONDS.

Veja Middleware para mais sobre middlewares.

Se uma visão configura o seu próprio tempo de expiração de cache (i.e. tem uma seção max-age no seu cabeçalho Cache-Control) então a página será mantida em cache até o tempo de edxpiração, ao invés de CACHE_MIDDLEWARE_SECONDS. Usando os decoradores em django.views.decorators.cache você pode facilmente configurar o tempo de expiração de uma visão(usando o decorador cache_control) ou desabilitar o cache para uma visão (usando o decorador never_cache). Veja a seção usando outros cabeçalhos para mais informações sobre esses decoradores.

Novo no Django 1.2: Please, see the release notes

If USE_I18N is set to True then the generated cache key will include the name of the active language. This allows you to easily cache multilingual sites without having to create the cache key yourself.

See Deployment of translations for more on how Django discovers the active language.

O cache por visão

Uma forma mais granular de usar o framework de cache é fazer o cache da saída de visões individuais. django.views.decorators.cache define um decorador cache_page que ierá automaticamente fazer o cache da resposta da visão para você. É fácil de usar:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

cache_page recebe um único argumento: o timeout do cache, em segundos. No exemplo acima, o resultado da visão slashdot_this() será mantido em cache por 15 minutos. (Note que escrevemos isso como 60 * 15 visando legibilidade. 60 * 15 será avaliado para 900 -- isso é, 15 minutos multiplicados por 60 segundos por minuto.)

O cache por visão, como o cache por site, é indexado de acordo com a URL. Se múltiplas URLs apontam para a mesma visão, cada URL será cacheada separadamente. Continuando o exemplo my_view, se o seu URLconf for semelhante a isso:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

então requisições para /foo/1/ e /foo/23/ serão cacheadas separadamente, conforme você pode esperar. Mas uma vez que uma URL em particular (ex: /foo/23/) tenha sido requisitada, acessos subsequentes a essa URL usarão o cache.

cache_page can also take an optional keyword argument, key_prefix, which works in the same way as the CACHE_MIDDLEWARE_KEY_PREFIX setting for the middleware. It can be used like this:

@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
    ...

Especificando o cache por visão no URLconf

Os exemplos na seção anterior tem hard-coded o fato que a visão é cacheada, porque cache_page altera a função my_view in place. Esse método acopla suas visões ao sistema de cache, o que não é o ideal por diversas razões. Por exemplo, você pode querer reusar as funções de visão em outro site, sem cache, ou você pode querer distribuir suas visões para pessoas que podem querer usá-las sem cache. A solução para esses problemas é especificar o cache por visão no URLconf ao invés de configurá-lo nas próprias funções.

Fazer isso é fácil: simplemente embrulhe a função de visão com cache_page quando você se referir a ela no URLconf. Aqui está a nossa já conhecida URLconf:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

Aqui está a mesma coisa, com my_view embrulhada em cache_page:

from django.views.decorators.cache import cache_page

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', cache_page(my_view, 60 * 15)),
)

Se você usar esse método, não se esqueça de importar cache_page dentro de seu URLconf.

Cache de fragmento de template

Novo no Django 1.0: Please, see the release notes

Se você quer ainda mais controle, você pode também fazer cache de fragmentos de template usando a tag de template cache. Para permitir que seu template use essa tag, coloque {% load cache %} perto do topo do seu template.

A tag de template {% cache %} faz o cache do conteúdo do bloco por um certo período de tempo. Recebe ao menos dois argumentos: o timeout do cache, em segundos, e o nome que será dado ao cache de fragmento. Por exemplo:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

Algumas vezes você pode querer adicionar ao cache diversas cópias de um fragmento dependendo de algum dado dinâmico que apareça dentro do fragmento. Por exemplo, você pode querer uma cópia separada da barra lateral usada no exemplo anterior para cada usuário do seu site. Faça isso passando argumentos adicionais a template tag {% cache %} que unicamente identifiquem esse cache de fragmento:

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

É perfeitamente válido especificar mais de um argumento para identificar o fragmento. Simplesmente passe tantos argumentos quanto você precisar para {% cache %}.

If USE_I18N is set to True the per-site middleware cache will respect the active language. For the cache template tag you could use one of the translation-specific variables available in templates to archieve the same result:

{% load i18n %}
{% load cache %}

{% get_current_language as LANGUAGE_CODE %}

{% cache 600 welcome LANGUAGE_CODE %}
    {% trans "Welcome to example.com" %}
{% endcache %}

O timeout de cache pode ser uma variável de template, desde que a variável de template seja um valor inteiro. Por exemplo, se a variável de template my_timeout está configurada para o valor 600, então os dois exemplos a seguir são equivalentes:

.. code-block:: html+django
{% cache 600 sidebar %} ... {% endcache %} {% cache my_timeout sidebar %} ... {% endcache %}

Essa característica é útil para evitar repetição em seus templates. Você pode configurar o timeout em uma variável, em um lugar, e apenas reutilizar esse valor.

A API de baixo nível do cache

Sometimes, caching an entire rendered page doesn't gain you very much and is, in fact, inconvenient overkill.

Talvez, por exemplo, o seu site inclua uma visão cujos resultados dependam de algumas consultas intensivas ao banco de dados, cujos resultados mudem em diferentes intervalos. Nesse caso, não seria ideal usar o cache completo de página que as estratégias de cache por site ou por visão oferecem, porque você não quer fazer o cache do resultado completo (já que alguns dados mudam frequentemente), mas você ainda gostaria de manter em cache os resultados que mudam com menos frequência.

Para casos como esse, o Django expõe uma API simples de baixo nível de cache. Você pode usar essa API para guardar objetos no cache com quaisquer níveis de granularidade que você quiser. Você pode cacher quaisquer objetos Python que podem ser serializados com segurança: strings, dicionários, listas ou objetos do modelo, e por aí vai. (A maioria dos objetos comum do Python podem ser serializados através de pickle; veja a documentação do Python para mais informações sobre o pickling.)

O módulo de cache, django.core.cache tem um objeto cache``que é criado automaticamente a partir da configuração ``CACHE_BACKEND:

>>> from django.core.cache import cache

A interface básica é set(key, value, timeout_seconds) e get(key):

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

O argumento timeout_seconds é opcional e o seu padrão é o mesmo do argumento timeout na configuração CACHE_BACKEND (explicada acima).

Se o objeto não existe no cache, cache.get() retorna None:

# Espere 30 segundos para 'my_key' expirar...

>>> cache.get('my_key')
None

We advise against storing the literal value None in the cache, because you won't be able to distinguish between your stored None value and a cache miss signified by a return value of None.

cache.get() pode ter um argumento padrão. Isso especifica qual valor retornar se o objeto não existe no cache:

>>> cache.get('my_key', 'has expired')
'has expired'

Para adicionar uma cache somente se ela ainda não existir, use o método add(). Ele recebe os mesmos parâmetros que set(), mas não irá tentar atualizar o cache se a chave especificada já estiver presente:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

Se você precisa saber se add() salvou um valor no cache, você pode verificar o valor retornado. O retorno será True se o valor foi gravado, e False se nada for gravado.

Existe também uma interface chamada get_many() que acessa o cache apenas uma vez. get_many() retorna um dicionário com todas as chaves que você pediu que estavam no cache e não estavem expiradas):

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Novo no Django 1.2: Please, see the release notes

To set multiple values more efficiently, use set_many() to pass a dictionary of key-value pairs:

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Like cache.set(), set_many() takes an optional timeout parameter.

Você pode remover chaves diferentes com delete(). Essa é uma forma fácil de limpar o cache para um objeto em particular:

>>> cache.delete('a')
Novo no Django 1.2: Please, see the release notes

If you want to clear a bunch of keys at once, delete_many() can take a list of keys to be cleared:

>>> cache.delete_many(['a', 'b', 'c'])
Novo no Django 1.2: Please, see the release notes

Finally, if you want to delete all the keys in the cache, use cache.clear(). Be careful with this; clear() will remove everything from the cache, not just the keys set by your application.

>>> cache.clear()

Você pode também incrementar ou decrementar uma chave que já existe usando os métodos incr() ou decr(), respectivamente. Por padrão, o valor existente em cache será incrementado ou decrementado em 1. Outros valores para incremento/decremento podem ser especificados fornecendo um argumento para a chamada increment/decrement. Um ValueError será lançado se você tentar incrementar ou decrementar uma chave de cache não existente.:

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

Note

incr()/decr() não tem garantia de serem atômicos. Nos backends que suportam incremento/decremento atômico (mais notavelmente, o backend memcached), operações de incremento e decremento serão atômicas. Porém, se o backend não provê nativamente uma operação de incremento/decremento, ela será implementada usando uma operação obter/atualizar de dois passos.

Cache key prefixing

Novo no Django 1.3: Please, see the release notes

If you are sharing a cache instance between servers, or between your production and development environments, it's possible for data cached by one server to be used by another server. If the format of cached data is different between servers, this can lead to some very hard to diagnose problems.

To prevent this, Django provides the ability to prefix all cache keys used by a server. When a particular cache key is saved or retrieved, Django will automatically prefix the cache key with the value of the KEY_PREFIX cache setting.

By ensuring each Django instance has a different KEY_PREFIX, you can ensure that there will be no collisions in cache values.

Cache versioning

Novo no Django 1.3: Please, see the release notes

When you change running code that uses cached values, you may need to purge any existing cached values. The easiest way to do this is to flush the entire cache, but this can lead to the loss of cache values that are still valid and useful.

Django provides a better way to target individual cache values. Django's cache framework has a system-wide version identifier, specified using the VERSION cache setting. The value of this setting is automatically combined with the cache prefix and the user-provided cache key to obtain the final cache key.

By default, any key request will automatically include the site default cache key version. However, the primitive cache functions all include a version argument, so you can specify a particular cache key version to set or get. For example:

# Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
# Get the default version (assuming version=1)
>>> cache.get('my_key')
None
# Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'

The version of a specific key can be incremented and decremented using the incr_version() and decr_version() methods. This enables specific keys to be bumped to a new version, leaving other keys unaffected. Continuing our previous example:

# Increment the version of 'my_key'
>>> cache.incr_version('my_key')
# The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
# But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

Cache key transformation

Novo no Django 1.3: Please, see the release notes

As described in the previous two sections, the cache key provided by a user is not used verbatim -- it is combined with the cache prefix and key version to provide a final cache key. By default, the three parts are joined using colons to produce a final string:

def make_key(key, key_prefix, version):
    return ':'.join([key_prefix, str(version), smart_str(key)])

If you want to combine the parts in different ways, or apply other processing to the final key (e.g., taking a hash digest of the key parts), you can provide a custom key function.

The KEY_FUNCTION cache setting specifies a dotted-path to a function matching the prototype of make_key() above. If provided, this custom key function will be used instead of the default key combining function.

Cache key warnings

Novo no Django 1.3: Please, see the release notes

Memcached, the most commonly-used production cache backend, does not allow cache keys longer than 250 characters or containing whitespace or control characters, and using such keys will cause an exception. To encourage cache-portable code and minimize unpleasant surprises, the other built-in cache backends issue a warning (django.core.cache.backends.base.CacheKeyWarning) if a key is used that would cause an error on memcached.

If you are using a production backend that can accept a wider range of keys (a custom backend, or one of the non-memcached built-in backends), and want to use this wider range without warnings, you can silence CacheKeyWarning with this code in the management module of one of your INSTALLED_APPS:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

If you want to instead provide custom key validation logic for one of the built-in backends, you can subclass it, override just the validate_key method, and follow the instructions for usando um backend de cache personalizado. For instance, to do this for the locmem backend, put this code in a module:

from django.core.cache.backends.locmem import CacheClass as LocMemCacheClass

class CacheClass(LocMemCacheClass):
    def validate_key(self, key):
        """Custom validation, raising exceptions or warnings as needed."""
        # ...

...and use the dotted Python path to this module as the scheme portion of your CACHE_BACKEND.

Caches Upstream

Até agora, esse documento concentrou-se no cache de seus próprios dados. Mas outro tipo de cache é relevante para o desenvolvimento Web: o cache executado por cache "upstream". Esses são sistemas que fazem o cache de páginas para usuários antes mesmo da requisição chegar ao seu Web site.

Aqui estão algumns exemplos de cache upstream:

  • Seu ISP pode fazer o cache de certas páginas, então se você requisitou uma página de http://example.com/, seu ISP poderia enviar a página sem ter de acessar o example.com diretamente. Os mantenedores de example.com não tem conhecimento desse cache; O ISP fica entre example.com e o seu navegador Web, lidando com todo o cache de forma transparente.
  • Seu Web site Django pode ficar atrás de um proxy cache como o proxy Squid (http://www.squid-cache.org/), que faz cache de páginas para performance. Nesse caso, cada requisição seria tratada primeiramente pelo proxy e seria passada para a sua aplicação somente se necessário.
  • Seu navegador web também faz cache. Se uma página envia os cabeçalhos apropriados, seu navegador irá usar a cópia local (em cache) para requisições subsequentes para essa página, sem nem mesmo contatar a página Web novamente paraver se o conteúdo mudou.

Caches upstream são um potencializador de eficiência interessante, mas há um perigo nisso: muitos conteúdos de páginas web diferem baseados na autenticação e em diversas outras variáveis, e sistemas de cache que servem páginas baseadas em URL cegamente poderiam expor dados incorretos ou sensíveis para visitantes subsequentes a essas páginas.

Por exemplo, digamos que você opere um serviço de webmail, e o conteúdo da página "inbox" obviamente depende do usuário que está logado. Se um ISP cegamente cacheia seu site, então o primeiro usuário que logou através desse ISP poderia ter seu conteúdo da caixa de entrada exposto aos próximos visitantes do site. Isso não é legal.

Felizmente, o HTTP fornece uma solução para esse problema: um número de cabeçalhos HTTP existem para instruir os cache upstream a diferir seus conteúdos de cache dependendo de certas variáveis definidas, e como dizer aos mecanismos de cache como não cachear páginas particulares. Veremos alguns desses cabeçalhos na seção a seguir.

Usando cabeçalhos Vary

O cabeçalho Vary define quais cabeçalhos de requisição um mecanismo de cache deve levar em conta ao construir sua chave de cache. Por exemplo, se o conteúdo do site de uma págfina web depende da preferência de idioma do usuário, dizemos que a página "varia no idioma."

Alterado no Django 1.3: In Django 1.3 the full request path -- including the query -- is used to create the cache keys, instead of only the path component in Django 1.2.

Por padrão, o sistema de cache do DJango cria suas chaves de acesso usando o caminho da requisição (ex: "/stories/2005/jun/23/bank_robbed/"). Isso significa que cada requisição a essa URL irá usar a mesma versão do cache, não importando as diferentes de agente de usuário, como cookies ou preferência de idioma. Porém, se essa página produz conteúdos diferentes baseados em alguma diferença de cabeçalhos de requisição -- como um cookie, ou um idioma, ou um agente de usuário -- você irá precisar usar o cabeçalho Vary para dizer aos mecanismos de cache que a saída da página depende dessas coisas.

Para fazer isso no Django, use o decorador de visão de conveniência vary_on_headers, assim:

from django.views.decorators.vary import vary_on_headers

@vary_on_headers('User-Agent')
def my_view(request):
    # ...

Nesse caso, um mecanismo de cache (como o próprio middleware de cache do Django) irá cachear uma versão diferente da página para cada agente de usuário único.

A vantagem de usar o decorador vary_on_headers ao invés de configurar o cabeçalho Vary manualmente (usando algo como response['Vary'] = 'user-agent') é que o decorador adiciona algo ao cabeçalho Vary (que pode já existir), ao invés de sobrescrevê-lo e potencialmente apagar algo que já estava lá.

Você pode passar diversos cabeçalhos para vary_on_headers():

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    # ...

Isso diz aos cache upstream para variar em ambos os cabeçalhos, o que significa que cada combinação de agente de usuário e cookie irá ter seu próprio valor de cache. Por exemplo, uma requisição com o agente de usuário Mozilla e o valor de cookie foo=bar será considerado diferente de uma requisição com um agente de usuário Mozilla e o valor de cookie foo=ham.

Como a variação baseada em cookie é um caso comum, existe um decorador vary_on_cookie. Essas duas views são equivalentes:

@vary_on_cookie
def my_view(request):
    # ...

@vary_on_headers('Cookie')
def my_view(request):
    # ...

Os cabeçalhos que você passa para vary_on_headers não são sensíveis a caso. "User-Agent" é a mesma coisa que "user-agent".

Você pode também usar uma função auxiliar, django.utils.cache.patch_vary_headers, diretamente. Essa função configura ou adiciona ao cabeçalho Vary. Por exemplo:

from django.utils.cache import patch_vary_headers

def my_view(request):
    # ...
    response = render_to_response('template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers recebe uma instância de HttpResponse como seu primeiro argumento e uma lista/tupla de nomes de cabeçalho não sensíveis a caso como seu segundo argumento.

Para mais sobre os cabeçalhos Vary, veja a especificação oficial do Vary.

Controlando o cache: Usando outros cabeçalhos

Outros problemas com o cache são a privacidade de dados e a questão de onde os dados devem ser armazenados em uma cascada de caches.

Um usuário normalmente encontra dois tipos de cache: o do seu próprio navegador (um cache privado) e o do seu provedor (um cache público). Um cache público é usado por muitos usuários e controlado por mais alguém. Isso coloca problemas com dados sensíveis--você não quer, por exemplo, que o número da sua conta bancária seja gravada em um cache público. Assim, aplicações web precisam de uma forma de dizer aos caches quais dados são privados e quais dados são públicos.

A solução é indicar que o cache de página seja "particular". Para fazer isso no Django, use o decorador de visão cache_control. Exemplo:

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    # ...

Esse decorador cuida de enviar o cabeçalho HTTP apropriado nos bastidores.

Existem algumas outras formas de controlar os parâmetros de cache. Por exemplo, o HTTP permite que aplicações façam o seguinte:

  • Define o tempo máximo que uma página deva permanecer em cache.
  • Especifica se um cache deve sempre verificar por novas versões da página, exibindo o conteúdo em cache apenas quando não houve mudanças. (Alguns caches podem entregar conteúdo em cache mesmo se a página no servidor mudou -- simplesmente porque a cópia do cache ainda não expirou.)

No Django, use o decorador de visão cache_control para especificar esses parâmetros de cache. Nesse exemplo, cache_control diz ao cache para revalidar o cache em cada acesso e para guardar versões em cache em no máximo por 3600 segundos:

from django.views.decorators.cache import cache_control

@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
    # ...

Qualquer diretiva HTTP Cache-Control é válida em cache_control(). Aqui está uma lista completa:

  • public=True
  • private=True
  • no_cache=True
  • no_transform=True
  • must_revalidate=True
  • proxy_revalidate=True
  • max_age=num_seconds
  • s_maxage=num_seconds

Para explicação das diretivas HTTP de Cache-Control, veja a Especificação de Cache-Control.

(Note que o middleware de cache já configura o tempo máximo (max-age) do cache com o valor da configuração CACHE_MIDDLEWARE_SETTINGS. Se você usa um max_age personalizado no decorador cache_control, o decorador terá precedência sobre a configuração, e os valores do cabeçalho serão mesclados corretamente.)

Se você quer usar cabeçalhos para desabilitar todo o cache, django.views.decorators.cache.never_cache é um decorador de visão que adiciona cabeçalhos que garantem que a resposta não será posta em cache por navegadores ou outros caches. Exemplo:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    # ...

Outras otimizações

O Django vem com alguns outros middlewares que podem ajudar a otimizar a performance do seu aplicativo:

  • django.middleware.http.ConditionalGetMiddleware adiciona suporte para GET condicional em navegadores modernos, baseado nos cabeçalhos ETag e Last-Modified.
  • django.middleware.gzip.GZipMiddleware comprime conteúdo para todos os navegadores modernos reduzindo o uso de banda e o tempo de transferência.

Ordem das MIDDLEWARE_CLASSES

Se você está usando o middleware de cache, é importador colocar cada parte dentro do lugar correto nas configurações de MIDDLEWARE_CLASSES. Isso porque o cabeçalho de cache precisa saber por quais cabeçalhos ele irá variar o armazenamento de cache. Middleware sempre adiciona algo ao cabeçalho Vary quando ele pode.

UpdateCacheMiddleware executa durante a fase de resposta, onde os middlewares são executados em ordem reversa, então um item no topo da lista sempre executa por último durante a fase de resposta. Assim, você precisa garantir que UpdateCacheMiddleware apareça antes de quaisquer outros middlewares que possam adicionar algo ao cabeçalho Vary. Os seguintes módulos de middleware fazem isso so:

  • SessionMiddleware adiciona Cookie
  • GZipMiddleware adiciona Accept-Encoding
  • LocaleMiddleware adiciona Accept-Language

FetchFromCacheMiddleware, por outro lado, executa durante a fase de requisição, onde os middlewares são aplicados do primeiro ao último, então um item no topo da lista execute primeiro durante a fase de requisição. O FetchFromCacheMiddleware também precisa edxecutar depois de alguns outros middlewares atualizarem o cabeçalho Vary, assim FetchFromCacheMiddleware deve ser configurado após qualquer item que faça isso.