O armazenamento em cache é essencial para aprimorar o desempenho e a escalabilidade dos aplicativos da internet, e o armazenamento em cache no Ruby on Rails não é exceção. Ao armazenar e reutilizar os resultados de cálculos ou consultas complexas ao bancos de dados, o armazenamento em cache reduz significativamente o tempo e os recursos necessários para atender às solicitações dos usuários.

Aqui, analisamos como implementar diferentes tipos de armazenamento em cache no Rails, como o armazenamento em cache de fragmentos e o armazenamento em cache de Russian doll. Também mostraremos como gerenciar dependências de cache e escolher armazenamentos de cache, além de delinear as práticas recomendadas para usar o cache de forma eficaz em um aplicativo Rails.

Este artigo pressupõe que você esteja familiarizado com o Ruby on Rails, use a versão 6 ou superior do Rails e se sente à vontade para usar as visualizações do Rails. Os exemplos de código demonstram como utilizar o cache dentro de novos ou existentes templates de visualização.

Tipos de cache do Ruby on Rails

Vários tipos de armazenamento em cache estão disponíveis nos aplicativos Ruby on Rails, dependendo do nível e da detalhamento do conteúdo a ser armazenado em cache. Os principais tipos usados em aplicativos modernos do Rails são:

  • Cache de fragmentos: Armazena em cache partes de uma página da internet que não mudam com frequência, como cabeçalhos, rodapés, barras laterais ou conteúdo estático. O cache de fragmentos reduz o número de parciais ou componentes renderizados em cada solicitação.
  • Cache de russian doll: Armazena em cache fragmentos aninhados de uma página da internet que dependem uns dos outros, como coleções e associações. O cache de russian doll evita consultas desnecessárias ao banco de dados e facilita a reutilização de fragmentos inalterados armazenados em cache.

Dois outros tipos de armazenamento em cache faziam parte do Ruby on Rails, mas agora estão disponíveis como bibliotecas separadas:

  • Cache de página: Armazena em cache páginas da internet inteiras como arquivos estáticos no servidor, ignorando todo o ciclo de vida de renderização da página
  • Cache de ação: Armazena em cache a saída de ações inteiras do controlador. É semelhante ao cache de página, mas permite que você aplique filtros como autenticação.

O armazenamento em cache de páginas e ações não é usado com frequência e não é mais recomendado para a maioria dos casos de uso em aplicativos Rails modernos.

Cache de fragmentos no Ruby on Rails

O cache de fragmentos permite que você armazene em cache partes de uma página alteradas raramente. Por exemplo, uma página que exibe uma lista de produtos com seus preços e classificações associados pode armazenar em cache detalhes que provavelmente não serão alterados.

Enquanto isso, você pode permitir que o Rails renderize novamente as partes dinâmicas da página – como comentários ou avaliações – a cada carregamento da página. O cache de fragmentos é menos útil quando os dados subjacentes de uma exibição mudam com frequência, devido à sobrecarga de atualização frequente do cache.

Como o tipo mais simples de armazenamento em cache incluído no Rails, o armazenamento em cache de fragmentos deve ser sua primeira opção ao adicionar cache ao seu aplicativo para melhorar o desempenho.

Para usar o cache de fragmentos no Rails, use o método auxiliar cache em suas exibições. Por exemplo, escreva o seguinte código para armazenar em cache um produto parcial em sua exibição:

<% @products.each do |product| %>
  <% cache product do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

O auxiliar cache gera uma chave de cache com base no nome da classe de cada elemento, id, e no registro de data e hora updated_at (por exemplo, products/1-20230501000000). Na próxima vez que um usuário solicitar o mesmo produto, o auxiliar cache buscará o fragmento armazenado em cache no armazenamento de cache e o exibirá sem ler o produto no banco de dados.

Você também pode personalizar a chave de cache passando opções para o auxiliar cache. Por exemplo, para incluir um número de versão ou um carimbo de data/hora em sua chave de cache, escreva algo assim:

<% @products.each do |product| %>
  <% cache [product, "v1"] do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

Como alternativa, você pode definir um tempo de expiração:

<% @products.each do |product| %>
  <% cache product, expires_in: 1.hour do %>
    <%= render partial: "product", locals: { product: product } %>
  <% end %>
<% end %>

O primeiro exemplo anexará v1 à chave do cache (por exemplo, products/1-v1). Isso serve para invalidar o cache quando você altera o modelo parcial ou o layout. O segundo exemplo define um tempo de expiração para a entrada do cache (1 hora), o que ajuda a expirar dados obsoletos.

Cache em estilo “Russian Doll” no Ruby on Rails

O cache de russian doll é uma poderosa estratégia de cache no Ruby on Rails que otimiza o desempenho do seu aplicativo aninhando caches uns dentro dos outros. Ele usa o cache de fragmentos do Rails e as dependências de cache para minimizar o trabalho redundante e melhorar os tempos de carregamento.

Em um aplicativo típico do Rails, você geralmente renderiza uma coleção de itens, cada um com vários componentes secundários. Ao atualizar um único item, evite renderizar novamente a coleção inteira ou qualquer item não afetado. Use o cache “Russian Doll” ao lidar com estruturas de dados hierárquicas ou aninhadas, especialmente quando os componentes aninhados têm seus próprios dados associados que podem ser alterados independentemente.

A desvantagem do cache “Russian Doll” é que ele aumenta a complexidade. Você deve entender as relações entre os níveis aninhados de itens que estão sendo armazenados em cache para garantir que os itens certos sejam armazenados em cache. Em alguns casos, você precisará adicionar associações aos seus templates Active Record para que o Rails possa inferir os relacionamentos entre os itens de dados armazenados em cache.

Assim como o cache de fragmento regular, o cache de russian doll usa o método auxiliar cache. Por exemplo, para armazenar em cache uma categoria com suas subcategorias e produtos em sua exibição, escreva algo assim:

<% @categories.each do |category| %>
  <% cache category do %>
    <h2><%= category.name %></h2>
    <% category.subcategories.each do |subcategory| %>
    <% cache subcategory do %>
    <h3><%= subcategory.name %></h3>
    <% subcategory.products.each do |product| %>
    <% cache product do %>
        <%= render partial: "product", locals: { product: product } %>
        <% end %>
    <% end %>
    <% end %>
    <% end %>
  <% end %>
<% end %>

O auxiliar cache armazenará cada nível aninhado separadamente no armazenamento de cache. Na próxima vez que a mesma categoria for solicitada, ele buscará o fragmento armazenado em cache no armazenamento de cache e o exibirá sem renderizá-lo novamente.

No entanto, se os detalhes de qualquer subcategoria ou produto forem alterados, como o nome ou a descrição, isso invalidará o fragmento armazenado em cache, que será renderizado novamente com os dados atualizados. O cache em estilo “russian doll” assegura que você não precise invalidar uma categoria inteira caso apenas uma subcategoria ou produto seja alterado.

Gerenciamento de dependência de cache em Ruby on Rails

As dependências de cache são relações entre os dados em cache e suas fontes subjacentes, e gerenciá-las pode ser complicado. Caso os dados de origem sejam alterados, todos os dados em cache associados deverão expirar.

O Rails pode usar carimbos de data/hora para gerenciar automaticamente a maioria das dependências de cache. Todo modelo Active Record tem atributos created_at e updated_at que indicam quando o cache criou ou atualizou o registro pela última vez. Para garantir que o Rails possa gerenciar automaticamente o cache, defina os relacionamentos dos seus modelos Active Record da seguinte forma:

class Product < ApplicationRecord
  belongs_to :category
end
class Category < ApplicationRecord
  has_many :products
end

Neste exemplo:

  • Se você atualizar um registro de produto (por exemplo, alterando seu preço), o carimbo de data/hora updated_at será alterado automaticamente.
  • Se você usar esse carimbo de data/hora como parte da chave de cache (como products/1-20230504000000), ele também invalidará automaticamente o fragmento em cache.
  • Para invalidar o fragmento em cache da sua categoria quando você atualizar um registro de produto – talvez porque ele mostre alguns dados agregados como o preço médio – use o método touch no seu controlador (@product.category.touch) ou adicione uma opção touch nos seus membros de modelo (belongs_to :category touch: true).

Outro mecanismo para gerenciar dependências de cache é usar métodos de cache de baixo nível, como fetch e write, diretamente em seus templates ou controladores. Esses métodos permitem que você armazene dados ou conteúdo arbitrário no armazenamento de cache com chaves e opções personalizadas. Por exemplo:

class Product < ApplicationRecord
  def self.average_price
    Rails.cache.fetch("products/average_price", expires_in: 1.hour) do
    average(:price)
    end
  end
end

Este exemplo demonstra como armazenar em cache dados calculados – como o preço médio de todos os produtos – por uma hora usando o método fetch com uma chave personalizada (products/average_price) e uma opção de expiração (expires_in: 1.hour).

O método fetch tentará primeiro ler os dados do armazenamento de cache. Caso não consiga encontrar os dados ou se os dados tiverem expirado, ele executará o bloco e armazenará o resultado no armazenamento de cache.

Para invalidar manualmente uma entrada de cache antes do seu tempo de expiração, use o método write com a opção force:

Rails.cache.write("products/average_price", Product.average(:price), force: true))

Armazenamento e backends de cache no Ruby on Rails

No Rails, você tem a opção de escolher entre diversos armazenamentos ou backends de cache para guardar seus dados e conteúdos em cache. A loja de cache do Rails atua como uma camada de abstração, fornecendo uma interface comum para interagir com diferentes sistemas de armazenamento. Um backend de cache implementa a interface da loja de cache para um sistema de armazenamento específico.

Aqui estão algumas opções:

Armazenamento de memória

O armazenamento de memória usa um hash na memória como armazenamento em cache. Ele é rápido e simples, mas tem capacidade e persistência limitadas. Esse armazenamento de cache é adequado para ambientes de desenvolvimento e teste ou aplicativos pequenos e simples.

Armazenamento em disco

Esse armazenamento utiliza arquivos no disco como local de armazenamento. Embora seja a opção de cache mais lenta no Rails, possui uma capacidade ampla e é persistente. Essa abordagem é indicada para aplicativos que precisam armazenar dados em excesso e não exigem desempenho máximo.

Redis

O armazenamento Redis usa uma instância do Redis para armazenamento em cache. O Redis é um armazenamento de dados na memória que oferece suporte a vários tipos de dados. Embora seja rápido e flexível, requer um servidor separado e configuração específica. Essa opção é indicada para aplicativos que precisam armazenar em cache dados complexos ou dinâmicos que sofrem alterações frequentes. O Redis é uma escolha ideal ao executar aplicativos Rails na nuvem, pois alguns provedores de hospedagem, como a Kinsta, oferecem o Redis como um cache de objeto persistente.

Memcached

O armazenamento do Memcached usa uma instância Memcached para armazenamento em cache. O Memcached é um armazenamento em memória do tipo chave-valor que suporta tipos de dados simples e oferece várias funcionalidades. É rápido e possui boa escalabilidade, mas, assim como o Redis, requer um servidor separado e configuração. Essa opção é adequada para aplicativos que necessitam armazenar em cache, dados simples ou estáticos que sofrem atualizações frequentes.

Você pode configurar o armazenamento de cache nos arquivos de ambiente do Rails (por exemplo, config/environments/development.rb) usando a opção config.cache_store. A seguir, aprenda como usar cada um dos métodos de cache incorporados do Rails:

# Use memory store
config.cache_store = :memory_store
# Use disk store
config.cache_store = :file_store, "tmp/cache"
# Use Redis
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
# Use Memcached
config.cache_store = :mem_cache_store, "localhost"

É recomendado ter apenas uma chamada config.cache_store por arquivo de ambiente. Caso exista mais de uma, o armazenamento em cache usará apenas a última.

Cada tipo de armazenamento em cache apresenta vantagens e desvantagens específicas, que variam de acordo com as necessidades e preferências do seu aplicativo. Escolha aquele que melhor se adapte ao seu caso de uso e ao seu nível de experiência.

Melhores práticas para o armazenamento em cache do Ruby on Rails

Usar o armazenamento em cache no seu aplicativo Rails pode aumentar significativamente o desempenho e a escalabilidade, especialmente quando você implementa as melhores práticas a seguir:

  • Armazenamento seletivo: Armazena em cache somente os dados acessados com frequência, que geram custos ou atualizados raramente. Evite o excesso de armazenamento em cache para evitar o uso excessivo de memória, riscos de dados obsoletos e degradação do desempenho.
  • Expiração de entradas de cache: Evite dados obsoletos ao expirar entradas inválidas ou irrelevantes. Use carimbos de data e hora, opções de expiração ou invalidação manual.
  • Otimize o desempenho do cache: Escolha o armazenamento de cache que atenda às necessidades do seu aplicativo e ajuste seus parâmetros, como tamanho, compressão ou serialização, para obter o desempenho ideal.
  • Monitore e teste o impacto do cache: Observe o comportamento do cache, como taxa de acerto, taxa de erro e latência, e avalie seus respectivos impactos no desempenho (tempo de resposta, taxa de transferência, uso de recursos). Use ferramentas como New Relic, registros do Rails, notificações do ActiveSupport ou o mini profiler do Rack.

Resumo

O armazenamento em cache do Ruby on Rails melhora o desempenho e a escalabilidade do aplicativo, armazenando e reutilizando com eficiência dados ou conteúdo acessados com frequência. Com uma compreensão mais detalhada das técnicas de cache, você estará preparado para fornecer aplicativos Rails mais rápidos aos seus usuários.

Ao implementar seu aplicativo Rails otimizado, você pode recorrer à plataforma de hospedagem de aplicativos da Kinsta. Comece gratuitamente com uma conta Hobby Tier e explore a plataforma com este exemplo de início rápido do Ruby on Rails.

Steve Bonisteel Kinsta

Steve Bonisteel is a Technical Editor at Kinsta who began his writing career as a print journalist, chasing ambulances and fire trucks. He has been covering Internet-related technology since the late 1990s.