Conteúdo


Aplicando o memcached para aumentar o desempenho do site

Reduza leituras dos bancos de dados e origens de dados

Comments

O memcached é usado para acelerar processos de aplicativos e, aqui, teremos como foco as melhores práticas para sua implementação dentro de seus aplicativos e ambientes. Isto inclui o que deveria e o que não deveria ser armazenado, como tratar a distribuição flexível de dados e como regular o método para atualizar o memcached e versões armazenadas dos dados. Também será coberto o suporte a soluções de alta disponibilidade, incluindo o IBM WebSphere® eXtreme Scale.

Todos os aplicativos, especialmente muitos aplicativos web, precisam otimizar a velocidade com que acessam e retornam informações para o cliente. Frequentemente, no entanto, as mesmas informações são retornadas. Carregar os dados de sua origem de dados (banco de dados ou sistema de arquivos) é ineficiente, especialmente se forem executadas as mesmas consultas toda vez que quiser acessar as informações.

Apesar de vários servidores web poderem ser configurados para usar um cache para retornar informações, isto não funciona com a natureza dinâmica da maioria dos aplicativos. É aqui que o memcached pode ajudar. Ele fornece um armazenamento generalizado em memória que pode conter qualquer coisa, incluindo objetivos nativos de linguagens, permitindo o armazenamento de uma variedade de informações e acessá-las de muitos aplicativos e ambientes.

Os fundamentos

O memcached é um projeto de software livre projetado para fazer uso da RAM sobressalente em muitos servidores para agir como um cache de memória para informações acessadas com frequência. O elemento chave é o uso da palavra cache: o memcached fornece armazenamento temporário, em memória, das informações que podem ser carregadas de outro local.

Por exemplo, considere um aplicativo típico baseado em web. Mesmo um web site servido dinamicamente provavelmente tem alguns componentes de informações constantes durante a vida da página. Dentro de um blog, é improvável que a lista de categorias para os tópicos individuais do blog altere regularmente entre visualizações de páginas. Carregar estas informações toda vez durante uma consulta ao banco de dados é comparativamente caro, especialmente quando os dados não alteraram. É possível ver na Figura 1 alguns dos fragmentos de página dentro de um blog que poderiam ser armazenadas em cache.

Figura 1. Elementos que poderiam ser colocados em cache em uma página de blog típica
Diagram shows cacheable blog elements and their layout: Page Header at top, Current post lists at left, About, Post History, External Links and Category list stacked on the right
Diagram shows cacheable blog elements and their layout: Page Header at top, Current post lists at left, About, Post History, External Links and Category list stacked on the right

Extrapole esta estrutura para outros elementos do blog, informações da pessoa que o publicou, comentários — mesmo os tópicos do blog em si — e é possível identificar 10-20 consultas ao banco de dados e formatações que ocorrem somente para exibir o conteúdo da página principal. Repita isto para centenas, ou mesmo milhares de visualizações de páginas a cada dia, e seus servidores e aplicativos estarão executando muito mais consultas do que o necessário para exibir o conteúdo da página.

Usando o memcached, é possível armazenar as informações formatadas carregadas do banco de dados em um formulário pronto para ser usado diretamente na página web. E como as informações estão sendo carregadas da RAM, e não do disco por meio de um banco de dados e outros processamentos, o acesso às informações é quase instantâneo.

Para reiterar, o memcached é um cache para armazenar informações usadas frequentemente para evitar o carregamento e o processamento de informações de origens mais lentas, como discos ou um banco de dados.

A interface para o memcached é fornecida por meio de uma conexão à rede. Isto significa que é possível compartilhar um único servidor do memcached (ou vários servidores, como será demonstrado posteriormente neste artigo) com vários clientes. A interface de rede é rápida e, para melhorar o desempenho, o servidor deliberadamente não suporta autenticação ou comunicação segura. Mas isto não deveria limitar as opções de implementação. O servidor do memcached deverá existir na parte interna de sua rede. A praticidade da interface de rede e a facilidade com que é possível implementar várias instâncias do memcached permitem o uso de RAM sobressalente em várias máquinas e aumentar o tamanho geral de seu cache.

O método de armazenamento com o memcached é um par simples de palavra-chave/valor, similar ao hash ou matriz associativa disponível em várias linguagens. As informações são armazenadas no memcached fornecendo a chave e o valor, e recuperadas solicitando as informações pela chave especificada.

As informações são retidas no cache indefinidamente, a não ser que um dos seguintes ocorra:

  1. A memória alocada para o cache foi esgotada— Neste caso, o memcached usa o método least-recently used (LRU) para remover itens do cache. Itens que não tenham sido usados recentemente são excluídos do cache, primeiro as com acesso mais antigo.
  2. O item é especificamente excluído— É sempre possível excluir um item do cache.
  3. O item expirou — Itens individuais podem ter uma expiração para permitir que sejam retirados do cache quando as informações armazenadas com relação à chave são provavelmente muito antigas.

Essas situações podem ser usadas em combinação com a lógica de seu aplicativo para garantir que as informações no cache estejam atualizadas. Com isso em mente, vamos examinar o quão melhor é usar o memcached em seus aplicativos.

Quando usar o memcached

Há uma série de processos e etapas chave que podem ser modificados ao usar o memcached para melhorar o desempenho do aplicativo.

Ao carregar informações, o cenário típico é o mostrado na Figura 2.

Figura 2. Sequência típica para carregar informações para exibição
Diagram shows the flow from Loading data to Processing/formatting data to Sending data to client

Em termos gerais, as etapas são:

  1. Executar uma ou mais consultas para carregar as informações do banco de dados
  2. Formatar as informações adequadas para exibição (ou processamento adicional)
  3. Usar ou exibir os dados formatados

Ao usar o memcached, a lógica do aplicativo é ligeiramente alterada para acomodar o cache:

  • Tentar carregar as informações do cache
  • Se elas existirem, usar a versão em cache das informações
  • Se não existirem:
    1. Executar uma ou mais consultas para carregar as informações do banco de dados
    2. Formatar as informações adequadas para exibição ou processamento adicional
    3. Armazenar as informações no cache
    4. Usar os dados formatados

Isto é resumido na Figura 3.

Figura 3. Carregando informações para exibição ao usar o memcached
Diagram shows that if requested data resides in the cache it skips all the processing steps, saving time
Diagram shows that if requested data resides in the cache it skips all the processing steps, saving time

O carregamento dos dados, então, se torna um processo de no máximo três estágios, carregando os dados do cache ou do banco de dados e armazenando no cache, se adequado.

Na primeira vez que este processo ocorre, os dados serão carregados do banco de dados ou outra origem, como normalmente, e, a seguir, armazenados no memcached. Da próxima vez que as informações forem armazenadas, elas serão retiradas do memcached, em vez de serem carregadas do banco de dados, economizando tempo e ciclos de CPU.

O outro lado da equação é garantir que, se as informações que possam ser armazenadas dentro do memcached forem alteradas, a versão do memcached é atualizada ao mesmo tempo em que são atualizadas as informações no backend. Isto modifica a sequência típica daquela mostrada na Figura 4 para a ligeira modificação na Figura 5.

Figura 4. Atualizando ou armazenando dados em um aplicativo típico
Diagram shows the flow from Updating data to Processing/formatting data to Sending updated dat to the client

A Figura 5 mostra a sequência modificada usando o memcached.

Figura 5. Atualizando ou armazenando dados ao usar o memcached
Diagram shows expanded flow: Updating data to Processing/formatting data to Storing in memcached to Sending updated data to client

Por exemplo, usando o blog como base, quando o sistema do blog atualiza a lista de categorias no banco de dados, a atualização deverá seguir esta sequência:

  1. Atualizar a lista de categorias no banco de dados
  2. Formatar as informações
  3. Armazenar as informações no memcached
  4. Retornar as informações para o cliente

Operações de armazenamento dentro do memcached são atômicas, portanto as informações serão atualizadas sem que os clientes recebam somente dados parciais; eles receberão a versão antiga ou a versão nova.

Para a maioria dos aplicativos, estas são as duas únicas operações com as quais você precisa se preocupar. Quando você acessar dados que as pessoas usam, eles são automaticamente adicionados ao cache e alterações àqueles dados são automaticamente atualizadas no cache.

Chaves, espaços de nome e valores

Uma consideração importante com o memcached é como organizar e nomear os dados armazenados dentro do cache. Dos exemplos anteriores do blog, deveria estar claro que é preciso usar uma estrutura de nomeação consistente, para que possa carregar categorias, histórico e outras informações do blog e, a seguir, usar isto ao carregar informações (e atualizar o cache) ou ao atualizar os dados (e, novamente, atualizar o cache).

O sistema exato de nomenclatura que você usar é específico do aplicativo, mas normalmente é possível usar uma estrutura similar a seu aplicativo existente, que provavelmente será baseado em algum tipo de identificador exclusivo. Isto ocorre ao retirar informações do banco de dados ou ao examinar uma coleção de informações.

Usando o exemplo de tópico do blog, é possível armazenar a lista de categorias em uma entrada com a chave category-list. Os valores associados com um único tópico com relação ao ID do tópico, como blogpost-29, poderiam ser usados, enquanto que comentários com relação à entrada poderiam ser armazenados em blogcomments-29, onde 29 é o ID do tópico do blog. Desta forma, é possível armazenar uma grande variedade de informações no cache usando diferentes prefixos para identificar as informações.

A simplicidade do armazenamento de chave/valor do memcached (e a falta de segurança) significa que, se quiser dar suporte a vários aplicativos usando o mesmo servidor do memcached ao mesmo tempo, talvez seja melhor considerar alguma outra forma de quantificador para identificar os dados como pertencentes a um aplicativo específico. Por exemplo, é possível adicionar um prefixo do aplicativo como blogapp:blogpost-29. As chaves têm forma livre, portanto é possível usar qualquer cadeia de caractere que quiser como nome da chave.

Em termos de armazenar valores, é preciso assegurar que as informações armazenadas no cache sejam adequadas para seu aplicativo. Por exemplo, com o sistema do blog, talvez você queira armazenar o objeto usado pelo aplicativo do blog para formatar as informações do tópico, em vez de HTML bruto. Isto pode ser mais prático se a mesma estrutura básica for usada em vários locais dentro de seu aplicativo.

A maioria das interfaces de linguagem, incluindo Java™, Perl, PHP e muitas outras, podem serializar objetos de linguagem para armazenamento com o memcached. Isto permite armazenar e, posteriormente, recuperar objetos inteiros do cache de memória, em vez de reconstruí-los manualmente dentro de seu aplicativo. Muitos objetos, ou as estruturas que eles usam, são baseados em algum tipo de hash ou estrutura de matriz. Para ambientes entre linguagens, como quando você deseja compartilhar as mesmas informações entre seu ambiente JSP e um ambiente JavaScript, é possível usar o formato independente de arquitetura, como o JavaScript Object Notation (JSON) ou mesmo XML.

Preenchendo e usando o memcached

Como um produto de software livre, e um originalmente desenvolvido para trabalhar dentro de um ambiente existente de software livre, o memcached é suportado por uma ampla gama de ambientes e plataformas. As interfaces para comunicação com um servidor do memcached são várias, frequentemente com várias implementações para todas as linguagens. Consulte Recursos para obter bibliotecas e kits de ferramentas comuns.

Seria impossível listar todas as interfaces suportadas e ambientes oferecidos, mas todos suportam a API básica fornecida pelo protocolo do memcached. Essas descrições foram simplificadas e deverão ser interpretadas dentro do contexto de diferentes linguagens, onde os erros podem ser indicados usando diferentes valores. As funções principais são:

  • get(key)— Obtém as informações do memcached armazenadas com a chave especificada. Retorna um erro se a chave não existir.
  • set(key, value [, expiry])— Armazena o valor especificado usando a chave do Identificador no cache. Se a chave já existir, ela será atualizada. O tempo de expiração é em segundos e é considerado como um tempo relativo se o valor for menor do que 30 dias (30*24*60*60) ou um tempo absoluto (época) se for maior do que este valor.
  • add(key, value [, expiry])— Adiciona a chave ao cache se ela não existir ou retorna um erro se a chave já existir. Isto pode ser útil se quiser adicionar explicitamente uma nova chave sem atualizá-la, se já existir.
  • replace(key, value [, expiry])— Atualiza o valor da chave especificada, retornando um erro se a chave não existir.
  • delete(key [, time])— Exclui o par chave/valor do cache. Se for fornecido um tempo, adicionar um novo valor com esta chave será bloqueado pelo período especificado. O tempo limite permite garantir que o valor seja sempre lido novamente de sua origem de dados.
  • incr(key [, value]) — Incrementa a chave especificada com um ou com o valor especificado. Só funciona com valores numéricos.
  • decr(key [, value])— Decrementa a chave especificada com um ou com o valor especificado. Só funciona com valores numéricos.
  • flush_all— Invalida (ou expira) todos os itens atuais no cache.

Por exemplo, dentro do Perl, uma operação básica de conjunto seria tratada como mostra a Listagem 1.

Listagem 1. Operação básica de conjunto dentro do Perl
use Cache::Memcached;

my $cache = new Cache::Memcached {
    'servers' => [
                   'localhost:11211',
                   ],
    };

$cache->set('mykey', 'myvalue');

A mesma operação básica no Ruby é mostrada na Listagem 2.

Listagem 2. Operação básica de conjunto dentro do Ruby
require 'memcache'
memc = MemCache::new '192.168.0.100:11211'

memc["mykey"] = "myvalue"

É possível ver em ambos os exemplos a mesma estrutura básica: definir o servidor do memcached e, a seguir, atribuir ou definir o valor. Estão disponíveis outras interfaces, incluindo aquelas que funcionam dentro da tecnologia Java, permitindo usar o memcached dentro de seus aplicativos WebSphere. A classe da interface do memcached permite serializar objetos Java diretamente dentro do memcached para poder armazenar e carregar estruturas complexas. Ao implementar dentro de um ambiente como o WebSphere, duas coisas são realmente importantes: a resiliência do serviço (e o que fazer se o memcached não estiver disponível) e como aumentar seu armazenamento em cache para melhorar seu desempenho ao usar vários servidores de aplicativo, ou ambientes como o WebSphere eXtreme Scale. A seguir, veremos estas duas questões.

Resiliência e disponibilidade

Uma das perguntas mais comuns sobre o memcached é "O que acontece quando o cache está indisponível?" Como deveria estar claro das seções anteriores, as informações no cache nunca deverão ser a única origem das informações. É preciso ser capaz de carregar os dados armazenados no cache a partir de algum outro local.

Apesar de a praticidade ser de que não ser capaz de acessar informações do cache reduzirá o desempenho de seu aplicativo, ela não deverá impedir que o aplicativo continue funcionando. Existem alguns cenários que poderão ocorrer:

  1. Se o serviço do memcached sair do ar, seu aplicativo deverá voltar a carregar informações da origem de dados original e formatá-la conforme necessário para exibição. O aplicativo também deverá continuar a tentar carregar e armazenar as informações no memcached.
  2. Quando o servidor do memcached estiver disponível novamente, seu aplicativo deverá automaticamente tentar armazenar os dados. Não há necessidade de recarregar forçosamente os dados armazenados em cache, é possível usar o acesso padrão para carregar e popular o cache com informações. Eventualmente, o cache será preenchido novamente com os dados usados mais comumente.

Para reiterar, o memcached é um cache de informações, e não a única origem. Perder seu servidor do memcached não deverá ser o fim de seu aplicativo, apesar de que poderá implicar em uma redução no desempenho até que o servidor do memcached esteja disponível novamente. Na prática, o servidor do memcached é relativamente simples e, apesar de não estar livre de problemas, a simplicidade leva a menos erros.

Distribuindo seu cache

O servidor do memcached é somente um cache armazenando valores com relação a chaves em uma rede. Se houver várias máquinas, a tentação é definir uma instância do memcached em todas as suas máquinas sobressalentes para fornecer uma grande quantidade de armazenamento de cache em RAM na rede.

Ao encarar esta ideia, a tentação é presumir que é necessário algum tipo de mecanismo de distribuição ou replicação que copiará os pares chave/valor entre as máquinas. O problema com esta abordagem é que você está, na verdade, diminuindo seu cache disponível em RAM, e não aumentando. Se observar a Figura 6, você verá que existem três servidores de aplicativo, cada um com acesso a uma instância do memcached.

Figura 6. uso incorreto de várias instâncias do memcached
Diagram shows three isolated 1-GB     instances of memcached supporting three application servers yields 1 GB of cache space each
Diagram shows three isolated 1-GB instances of memcached supporting three application servers yields 1 GB of cache space each

Apesar de cada instância do memcached ter 1 GB de tamanho (dando 3 GB de cache de RAM), se cada servidor de aplicativo tiver somente seu próprio cache (ou houve uma replicação de dados entre as instâncias do memcached), toda sua instalação ainda teria somente 1 GB de cache duplicado em cada instância.

Como o memcached fornece suas informações sobre uma interface de rede, um único cliente pode acessar os dados de qualquer instância do memcached ao qual tenha acesso. Se os dados não forem copiados ou duplicados em cada instância, o que você terá é 3 GB de cache de RAM disponível para cada servidor de aplicativos, como mostra a Figura 7.

Figura 7. Uso correto de várias instâncias do memcached
Diagram shows three interactive 1-GB     instances of memcached supporting three application servers resulting in 3 GB of shared cache space overall
Diagram shows three interactive 1-GB instances of memcached supporting three application servers resulting in 3 GB of shared cache space overall

O problema com esta abordagem é escolher qual servidor armazenará seu par chave/valor e como determinar qual servidor do memcached acessar quando quiser recuperar um valor. A solução é ignorar complexidades como tabelas de consulta ou esperar que o servidor do memcached trate o processo para você. Em vez disso, os clientes do memcached deverão pensar de forma simples.

Em vez de ter que determinar estas informações, os clientes do memcached usam um algoritmo de hashing simples na chave que for especificada ao armazenar a informação. Ao armazenar ou obter informações de uma lista de servidores do memcached, o cliente do memcached deriva um valor numérico da chave usando um algoritmo de hashing consistente. Para dar um exemplo, a chave mykey é convertida para o valor 23875. Não importa se você está armazenando ou obtendo informações, sempre usará a mesma chave como identificador exclusivo para carregar do servidor do memcached, portanto "mykey" sempre obterá o hash com um valor de 23875, pelo menos neste exemplo.

Se houver dois servidores, o cliente do memcached obtém este valor numérico e realiza um cálculo simples nele (por exemplo, módulo) para determinar se deveria armazenar o valor na primeira ou na segunda instância configurada do memcached.

Ao armazenar um valor, o cliente determina o valor do hash a partir da chave e o servidor usado para armazená-lo. Ao obter um valor, o cliente determina o mesmo valor do hash a partir da chave e escolhe o mesmo servidor para obter as informações.

Se você usar a mesma lista de servidores (e na mesma ordem) em cada servidor de aplicativos, cada servidor de aplicativos escolherá o mesmo servidor quando solicitado a armazenar ou recuperar a mesma chave. Agora você está compartilhando 3 GB de espaço de memcached, em vez de duplicar o mesmo 1 GB de espaço, dando mais cache para usar e, provavelmente, melhorando o desempenho de seu aplicativo para ainda mais usuários.

Existem complexidades neste processo (como o que acontece quando um servidor está indisponível), mas a documentação oferece mais informações (consulte Recursos).

Como não usar o memcached

Apesar da simplicidade do memcached, a tentação é usar a instância do memcached em algumas formas inesperadas e, algumas vezes, infelizes.

O memcached não é um banco de dados

Provavelmente o uso incorreto mais comum do memcached é o uso deste para armazenamento de dados, e não como um cache. A finalidade principal do memcached é melhorar os tempos de resposta para dados que, de outra forma, podem ser construídos ou recuperados de alguma outra origem. Um exemplo é a recuperação de informações de um banco de dados, particularmente se houver alguma formatação ou manipulação das informações antes que sejam exibidas ao usuário. O memcached foi projetado para armazenar estas informações em memória para evitar a realização repetida daquela tarefa cada vez que os dados forem recuperados.

O memcached nunca deverá ser usado como a única origem de dados de que necessita para executar seu aplicativo; os dados sempre deverão ser deriváveis de alguma outra origem. Além disso, tenha em mente que o memcached é somente um armazenamento de chave/valor. Não é possível realizar uma consulta sobre os dados ou realizar uma iteração no conteúdo para extrair informações. Ele deverá ser usado para armazenar blocos ou objetos de dados que você precisa usar em estilo de atacado.

Não armazene em cache linhas de banco de dados ou arquivos

Apesar de ser possível usar o memcached para armazenar as linhas de dados como carregadas de um banco de dados, isto é na verdade armazenamento em cache de consultas, e a maioria dos bancos de dados fornecem seus próprios mecanismos de armazenamento de consultas em cache. O mesmo pode ser dito para outros objetos, como imagens ou arquivos do sistema de arquivos. Muitos aplicativos e servidores web já têm soluções bem otimizadas para este tipo de trabalho.

Você ganhará mais melhorias de utilidade e desempenho do memcached se usá-lo para armazenar blocos inteiros de informações depois do carregamento e da formatação. Usando nosso exemplo do blog, o melhor ponto para armazenar informações é depois que tiver formatado as categorias do blog como um objeto, ou mesmo depois de formatar em HTML. A construção da página do blog pode, então, ser alcançada carregando os componentes (tópicos do blog, lista de categorias, histórico de tópicos etc.) a partir do memcached e gravando o HTML completo de volta para o cliente.

O memcached não é seguro

Para garantir o desempenho máximo, o memcached não fornece nenhum tipo de segurança, seja autenticação ou criptografia. Isto significa que o acesso a seus servidores do memcached deverão ser tratados colocando-os na mesma zona privada que seu ambiente de implementação do aplicativo ou, se a segurança for fundamental, use os soquetes do UNIX® e permita que somente aplicativos no mesmo host atual acessem o servidor do memcached.

Isto remove parte da flexibilidade e resiliência e sua capacidade de compartilhar um cache de RAM entre várias máquinas pela rede, mas é a única solução para implementar segurança em seus dados do memcached nesta situação.

Não se limite

Apesar das coisas para as quais você não deveria usar suas instâncias do memcached, a flexibilidade do memcached não deve ser ignorada. Como o memcached está no mesmo nível de arquitetura que seu aplicativo, ele é fácil de integrar e conectar. E alterar seu aplicativo para tirar vantagem do memcached não é complicado. Além disso, como o memcached é somente um cache, ele não deverá interromper a execução de seu aplicativo no caso de um problema. O que ele faz, se usado corretamente, é diminuir a carga no resto da infraestrutura do servidor (reduz leituras de bancos de dados e origens de dados), e isto significa suportar mais clientes sem a necessidade de mais hardware.

Mas lembre-se, é só cache!

Resumo

Neste artigo, abordamos o memcached e como usá-lo da melhor forma. Em particular, vimos como as informações são armazenadas, como escolher chaves razoáveis e como escolher as informações a serem armazenadas. Também vimos alguns problemas chave da implementação pelos quais todos os usuários do memcached passam, incluindo o uso de vários servidores, o que fazer quando uma instância do memcached fica indisponível e, talvez o aspecto mais importante, como não usar o memcached.

Como um aplicativo de software livre, e um que tem um objetivo bem simples e direto, o poder e a utilidade do memcached advêm dessa simplicidade. Ao fornecer um grande armazenamento em RAM para informações, tornando-as disponíveis na rede e, a seguir, acessíveis por meio de tal imensa gama de diferentes interfaces e linguagens, é possível integrar o memcached em uma enorme variedade de instalações.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=512596
ArticleTitle=Aplicando o memcached para aumentar o desempenho do site
publish-date=08202010