Left arrow
Practices / Tech

Unleashing Performance Potential: Caching Strategies in Ruby on Rails 7

Clock icon 8 min read
Top right decor Bottom left decor

Caching in Ruby on Rails 7 offers a host of strategies to turbocharge application performance. By implementing these techniques, developers can strike a balance between speed, efficiency, and dynamic content delivery, ensuring stellar user experience. 

In this guide, we’ll review strategies engineered to elevate the performance of your Rails applications and uncover the secrets behind building efficient and responsive web experiences through the art of caching.

1. Page Caching

Page caching in Rails allows the web server (such as Apache or NGINX) to serve pre-generated pages without routing them through the entire Rails stack. While this method offers impressive speed, it’s not suitable for all scenarios, especially for pages that require authentication. Moreover, because the web server serves files directly from the filesystem, it requires careful implementation of cache expiration strategies. 
To implement page caching in your Rails application, ensure that page caching is enabled by setting config.action_controller.perform_caching = true:

Rails.application.configure do
  # Enable page caching
  config.action_controller.perform_caching = true
end

Next, utilize the caches_page method to specify which action to cache:

class ItemsController < ApplicationController
  # Cache the index page
  caches_page :index
  
  def show
    @item = Item.find_by(id: params[:id])
  end
end

With these configurations, the specified action, such as the index page in this example, will be cached and served directly to the web server, enhancing the overall performance of your Rails application.

2. Action Caching

While Page Caching provides inadequate actions involving before filters, like those requiring authentication, Action Caching provides an effective solution. Functioning akin to Page Caching, Action Caching diverges by first engaging with the Rails stack upon incoming web requests. This sequence allows “before” filters, including authentication mechanisms, to execute before delivering the cached content. 
To implement Action Caching, simply utilize the caches_action method to designate which action to cache:

class ItemsController < ApplicationController
  # Cache the index action for 2 hours
  caches_action :index, expires_in: 2.hours
  
  def index
    @items = Item.all
  end
end

In this example, the show action is cached for a duration of one hour. By employing Action Caching, you can ensure that authentication measures and other constraints are upheld while benefiting from the performance enhancements derived from cached content.

3. Fragment Caching

In dynamic web applications, pages often consist of various components with different caching needs. Fragment caching becomes essential when specific parts of a page need to be cached and expired separately. 

Fragment Caching enables caching specific portions of view logic by enclosing them within a cache block. These fragments can then be retrieved from the cache store when requested.

For example, suppose you want to cache each product displayed ona page. You can achieve this with the following code snippet:

<% @items.each do |item| %>
  <% cache item do %>
    <%= render item %>
  <% end %>
<% end %>

You can also cache a fragment using cache_if or cache_unless:

<% cache_if manager?, item do %>
  <%= render item %>
<% end %>

Additionally, the render helper can efficiently cache individual templates rendered for a collection. By including cached: true when rendering the collection, all cached templates from previous renders will be fetched at once, significantly improving performance:

<%= render partial: 'items/item', collection: @items, cached: true %>

This approach ensures that both cached and uncached templates are handled swiftly, optimizing the overall performance of your application.

4. Russian Doll Caching

Russian Doll caching, a technique of nesting cached fragments within other cached fragments, offers the advantage of reusing all inner fragments when regenerating the outer fragment, should a single product undergo an update. 

However, it’s important to note that while a cached file expires only if there’s a change in the updated_at value of the associated record, this doesn’t automatically expire any cache containing the nested fragment.

For example, consider the following view:

<% cache item do %>
  <%= render item.variants %>
<% end %>

Which in turn renders this view:

<% cache variant do %>
  <%= render variant %>
<% end %>

If any attribute of the variant is modified, the updated_at value will be updated to the current time, consequently expiring the cache. However, since the updated_at attribute of the product object remains unchanged, its cache won’t expire, potentially resulting in the serving of outdated data by your application. 
To mitigate this issue, we connect the models using the touch method:

class Item < ApplicationRecord
  has_many :variants
end

class Variant < ApplicationRecord
  belongs_to :item, touch: true
end

With touch: true, any action which changes updated_at for a variant record will also change it for the associated product, thereby expiring the cache.

5. Low-Level Caching

In certain situations, it’s necessary to cache specific values or query results rather than entire view fragments. Rails’ caching mechanism is well-suited for storing any serializable data.

The most effective method for implementing low-level caching is by utilizing the Rails.cache.fetch method. This method handles both reading from and writing to the cache. When provided with a single argument, it retrieves the value associated with the key from the cache. If a block is supplied, it executes that block in the event of a cache miss. The result of the block execution is stored in the cache under the specified key, and subsequently returned. Upon a cache hit, the cached value is returned without executing the block.

Consider the following scenario: an application includes a Product model with an instance method tasked with fetching the product’s price from a competing website. The data returned by this method would be ideally suited for low-level caching:

class Item < ApplicationRecord
  def compare_price
    Rails.cache.fetch("#{cache_key}/compare_price", expires_in: 24.hours) do
      External::API.call(id)
    end
  end
end

6. SQL Caching

Query caching in Rails is a powerful feature designed to cache the result set returned by each query. When Rails encounters the same query within a single request, it bypasses executing the query against the database and retrieves the cached result set instead. 

Consider the following example:

class ItemsController < ApplicationController
  def index
    # Run a find query
    @items = Item.all

    # ...

    # Run the same query again
    @items = Item.all
  end
end

Upon the second execution of a query against the database, no actual database hit occurs. Instead, the result retrieved from the initial query is stored in the query cache, typically residing in memory. Subsequently, when the same query is issued again, the result is fetched directly from memory.

However, it’s crucial to note that query caches are scoped to the lifespan of an action. They are created at the beginning of an action and discarded at its conclusion, lasting only for the duration of that action. Should you require a more enduring storage solution for query results, employing low-level caching is the recommended approach.

Unveiling the Power of Caching in Ruby on Rails 7

Caching in Ruby on Rails 7 holds the key to unlocking the true potential of your web applications. By leveraging a variety of caching strategies, developers can significantly enhance application performance while delivering dynamic content with efficiency.

In this guide, we’ve explored a wide range of caching techniques engineered to elevate the performance of Rails applications. From Page Caching to SQL Caching, each strategy offers unique advantages tailored to different scenarios.

By implementing these caching strategies, developers can strike a balance between speed, efficiency, and dynamic content delivery, ensuring stellar user experiences. Through the art of caching, you can transform your Rails applications into lightning-fast, responsive platforms, poised to meet the demands of today’s web landscape.

Next Articles

Load more

Have a task with an important mission? Let’s discuss it!

    Thank you for getting in touch!

    We appreciate you contacting us. One of our colleagues will get back in touch with you soon!

    Have a great day!

    Thank you for getting in touch!

    Your form data is saved and will be sent to the site administrator as soon as your network is stable.