Introdução ao memcache

O problema

Imagine um portal como o globo.com, com suas inúmeras chamadas para matérias que constam já na página principal.

Apelação milenar

Apelação milenar

Cada imagem, título e descrição de reportagem ou comercial que aparecem na página são informações dinâmicas dela. A cada vez que acessamos a página, podemos obter notícias mais recentes, resultando em uma página diferente. Assim, imagina-se que os dados apresentados na página estejam armazenados em um banco de dados e que, a cada acesso de usuário, temos várias consultas sendo realizadas pelo servidor ao banco de dados. Isso seria o mais natural, considerando-se a alta taxa de criação de novas notícias no site. Porém, você já deve saber que o disco é o grande gargalo dos computadores modernos e que acessos a disco são o grande vilão da performance da maioria dos programas de computador. Então, como fazer um sistema web com foco no conteúdo dinâmico sem realizar frequentes acessos ao BD (e consequentemente, ao disco)?

Uma possível solução

Uma solução pode ser fazer o caching de alguns dados na memória. Em um portal de notícias, os usuários que acessarem a página mais ou menos no mesmo horário vão receber o mesmo conteúdo, que são as últimas notícias postadas. Então por que repetir as consultas ao banco de dados para todo e qualquer acesso de usuário ao portal? Seria mais inteligente se o nosso sistema consultasse o banco de dados somente uma vez, armazenasse os resultados da consulta em memória (na nossa cache) e então passasse a responder às requisições subsequentes usando o conteúdo armazenado em memória sem precisar buscar nada no disco até que o conteúdo mude novamente. Assim, as requisições podem ser respondidas com o conteúdo da memória. Quando o conteúdo do BD sofrer alguma alteração, a cache pode ser invalidada e atualizada com o novo conteúdo.

Um exemplo

Vamos seguir com o foco em um portal de notícias, já que existem muitos casos de uso similares. Como já vimos em outro post, o Google App Engine suporta Python; e o mecanismo de datastore que ele oferece é legal pra caramba. Suponhamos que nesse portal existe um modelo de dados que contenha uma entidade Noticia, conforme representado abaixo:

class Noticia(db.Model):
    titulo = db.StringProperty(required=True)
    conteudo = db.TextProperty(required=True)
    url = db.LinkProperty(required=True)
    autor = db.UserProperty(required=True)
    thumbnail = db.LinkProperty()
    data_publicacao = db.DateTimeProperty()

Como todo bom portal de notícias, esse também vai ter aquela página inicial carregadíssima, cheia de notícias e outros elementos. Haja barramento pra aguentar tantos acessos ao disco!

O código a seguir poderia ser a view que gera a sua página principal:

    class PostHandler(webapp2.RequestHandler):

        def get(self):
            ultimas = get_noticias(100, '-data_publicacao')
            categorias = get_categorias()
            comerciais = get_comerciais_ativos()
            for n in ultimas:
                # lista e formata as noticias, categorias, comerciais
                ...

    def get_noticias(limit, order_by):
        return Noticia.all().order(order_by).fetch(limit)

    def get_categorias():
        return Categoria.all()

    def get_comerciais_ativos():
        return Comercial.all().filter('ativa = ', True)

Beleza, funciona. O problema começa quando o site começa a ficar popular e os usuários começam a reclamar de lentidão e de indisponibilidade constante. O que fazer? Bom, a resposta certa é fazer profiling da aplicação, isto é, medir o tempo que estão levando as operações necessárias para carregar a página — tanto no lado cliente quanto no lado servidor — e então você poderá decidir melhor como resolver o problema. Mas quando um site está realizando várias consultas ao banco para cada acesso do usuário, frequentemente uma solução é evitar a realização dessas consultas usando uma cache.

Let’s cache them all!

mecanismo de cache que vou apresentar aqui é o memcache específico para o Google AppEngine, embora existam várias implementações do memcache para as mais variadas plataformas e linguagens. O memcache é um mecanismo que permite o armazenamento de pares chave-valor (assim como um dicionário, ou uma tabela hash) em memória, de forma que o acesso aos valores ocorre de forma bem eficiente.

Agora, vamos modificar o método get_noticias() para fazer caching do conteúdo da página principal. O princípio de funcionamento é bem simples: antes de qualquer consulta ao banco, verifique se os dados que queremos estão presentes na cache. Veja o código:

from google.appengine.api import memcache

def get_noticias(limit, order_by):
    # busca na cache por um valor de chave 'noticias'
    ultimas = memcache.get('noticias')
    if ultimas is None: # valor ainda não está em cache
        # busca no BD
        ultimas = Noticia.all().order(order_by).fetch(limit)
        # e inclui o resultado na cache para os futuros acessos
        if not memcache.add('noticias', ultimas):
            logging.error('Erro ao setar memcache.')

O que foi feito acima para os dados das notícias pode ser feito também para os comerciais e para as categorias. Assim, o primeiro de todos os acessos ao site pode demorar um pouquinho mais, mas os acessos seguintes de todos os usuários vão ser muito rápidos.

Cuidado com a Sincronização

Uma vez que começamos a usar a cache, nós passamos a ter informações redundantes (BD e cache). Dessa forma, basta que alguém insira uma notícia nova no BD para que tenhamos os dados fora de sincronia. Uma alternativa para solucionar esse problema pode ser: logo após salvar uma nova notícia no BD, atualizar os valores da cache. Se isso for algo demorado, poderíamos iniciar uma tarefa em background para fazer isso.

Quando usar cache?

Como já comentei anteriormente, a cache não é a solução para todo e qualquer problema de um sistema web. O problema pode estar no plano de hospedagem, no excesso de arquivos estáticos, em lógica duplicada resultante de um mau hábito de copiar-e-colar, em algoritmos pouco otimizados, etc.

A cache é um mecanismo bem simples de ser implementado, em alguns casos, mas isso não quer dizer que você já deve sair de cara utilizando cache em todo e qualquer projeto a partir de agora. Afinal, premature optimization is the root of all evil. 😛

Para finalizar…

No curso Web Development, ministrado pelo grande Steve Huffman no Udacity.com, aprendi o seguinte:

Um simples acesso de usuário a uma página jamais deveria gerar um acesso ao banco de dados.

Ou seja, se um usuário qualquer quiser apenas visualizar sua página, blog, ou portal, esse acesso não deveria exigir do sistema um acesso ao banco de dados. É um princípio bem simples e que pode evitar que o seu site caia quando se tornar mega-popular. 🙂

Grandes sites como Twitter, Facebook e Reddit usam largamente os mecanismos de cache, de forma que seja possível responder aos tsunamis de requisições que eles recebem a cada segundo.

Leia mais sobre o memcache na Wikipedia.

 

Obrigado ao Elias pela revisão!

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s