Open In App

Ruby on Rails - Caching

Last Updated : 08 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Ruby on Rails provides a set of powerful caching mechanisms that can significantly help optimize the performance of web applications, which rely heavily on database operations and rendering. Caching in Rails means storing the result of expensive operations such as database lookups or complex view rendering in some temporary storage so that they can be retrieved fast on successive events without recomputation. Rails has four different ways of implementing caching: page caching, action caching, fragment caching, and low-level caching. Rails integrates by default with in-memory stores, Memcached, or Redis, but It can also work with file- or database-backed caching.

What is Caching in Ruby on Rails?

Caching in Rails is a basic performance optimization feature that accelerates applications by reducing the amount of work a server has to do. Instead of generating the content from scratch for every request, caching enables Rails to store the results of expensive operations like database queries, complex view rendering, or API calls and return them when needed. This makes page requests much faster since the number of hits to the server is lessened, and, in turn, it improves the user experience. The major types of caching available through Rails are underlined below.

Types of Caching

1. Page Caching

Page caching is the most straightforward and fastest of all caching techniques: The whole page output is stored as an HTML file, and for subsequent requests for that page, the entire Rails stack is bypassed. If a page exists in the cache, a request won't need to enter the Rails stack at all; the web server serves the cached file (by Nginx or Apache, for example) straight; no database call, controller processing, or view rendering is invoked.

  1. It is extremely fast since it skips Rails entirely
  2. It should not be used on pages with dynamic content or those containing user-specific data since it will serve up the same cached result for all users.

Note:

Page Caching no longer ships with Rails by default but can be added back using the actionpack-page_caching gem.

2. Action Caching

Action caching is very similar to page caching, but a bit more flexible due to the fact that it'll store the output of whole controller actions, yet still allow some code execution before serving up the cached content. For example, authentication filters can run before serving up a cached version of a page.

  1. It caches responses and does whatever amount of logic needs to be done on each request, such as user authentication or authorization.
  2. It is less effective than page caching since it still involves a part of the Rails stack.
  3. Action caching was removed from Rails core for fragment caching, but can be added back using the actionpack-action_caching gem.

3. Fragment Caching

Fragment caching allows you to cache specific parts of a view, henceforth referred to as "fragments." This is particularly useful when parts of a page are expensive to generate and don't change very often. Other than caching an entire page, you are able to cache reusable elements such as partials or HTML blocks.

  1. Fine-grained control over what gets cached, and hence useful for dynamic pages with static elements.
  2. More complicated to implement than full-page or action caching. Cache management may also become intricate.

For example, a product listing page could store the product details and the sidebar in different caches: the former would vary less often than the actual products.

Example:

Ruby
<% cache @product do %>
  <%= render @product %>
<% end %>

Here @product is cached, and subsequent requests to this view will reuse the cached fragment until the cache is expired or invalidated.

4. Low-level Caching

Low-level caching is quite a bit more manual: You're caching arbitrary data, such as database query results or computed values. This form of caching isn't directly connected to views or controllers; it just provides the ability for developers to manually store anything into and retrieve from the cache store.

  1. Full control on what is being cached and for how much time.
  2. It requires manual cache management and proper invalidation strategies.

Rails provides low-level caching with a set of convenience methods, such as Rails.cache.fetch. Here's an example:

Ruby
Rails.cache.fetch("user_#{user.id}_details") do
  user.details
end

In this case, Rails will store the result of user.details and reuse it for subsequent requests without recomputing it every time.

5. Russian Doll Caching

A specialized form of fragment caching where nested fragments are cached within each other. When an inner fragment changes, the outer cache is automatically updated.

  1. Ideal for pages with nested, reusable elements that need separate caching logic.
  2. Efficient, as only the parts that change get invalidated and updated.
  3. Adds complexity in managing the nesting of caches.

6. Memory Store / Cache Store

A system where cache data is stored in-memory (e.g., Redis, Memcached) or file-based storage.

  1. Suitable for speeding up access to frequently requested data.
  2. Extremely fast retrieval of cached data.
  3. Can consume a large amount of memory; managing cache expiration is critical.

7. SQL Caching

Caches SQL query results, reducing the number of times the database is hit for the same data.

  1. Useful when querying expensive database operations multiple times within a request cycle.
  2. Reduces database load significantly.
  3. May become stale if underlying data changes without cache invalidation.

Page Caching

Full Page caching is the most straightforward, quickest sort of caching in Ruby on Rails. It caches the whole HTML output of a page and retrieves it straightaway for further requests without even going through the Rails stack at all-no controller, no database calls, etc. The web server stores a cached version of the response as a static file, and future requests for the same page return this file instantly, for example, from the public/ directory.

How It Works?

The first request for a page will render the complete page as usual by Rails and save its output to a static file, like public/products.html. The Web server would be configured, say Nginx or Apache, to serve the static file directly in subsequent requests to /products bypassing Rails entirely.

Best Use Cases:

Pages that have static content and don't change very often. These would include landing pages, product catalogs, and other publicly viewable pages.

Can be used when there is no variation in content by either user or session.

Pros

  1. Speed: Since the Rails stack is bypassed entirely, serving up a cached page is super fast.
  2. Efficiency: It's really light on the server load since it does not have to hit the database repeatedly, instantiate and execute controllers, and render views.

Cons

  • Lack of Flexibility: Page caching is not designed for pages with dynamic content or user-specific data. Examples include a dashboard or anything that shows personalized content. It serves the same static content to all users.
  • Removed from Rails Core: As of Rails 4, page caching is no longer part of the default Rails stack. To get this functionality back, you will have to include the actionpack-page_caching gem in your Gemfile.

Example

If your site has a static page for "Products" (/products), page caching will store the full HTML of this page in public/products.html. Any subsequent request to /products will directly serve the cached file without hitting Rails.

Ruby
gem 'actionpack-page_caching'

Action Caching

Whereas page caching operates on the entire output of an action, Action Caching is much more flexible in running some before-filters before serving the result from cache. It caches the entire output of controller action, but opposed to page caching, it ensures that some code, like for example access control, is executed before the cache is served.

How It Works?

Where a request is sent to a controller action, such as ProductsController#index, Rails will immediately check whether an action response has been cached. If so, Rails will serve the cached content, running any before filters like before_action :authenticate_user beforehand. If not, it will normally process the request and then cache the output.

Use Cases

  1. Pages that require the same content for all users but must run some code logic before serving the content.
  2. Pages mostly static in content and yet having some dynamic checks, for example permission checks.

Advantages

  1. Flexibility: Since Action caching allows for partial execution of the Rails stack, hence it is more flexible compared to page caching.
  2. Efficiency: Speeds up requests by caching the output of the controller but keeps running user specific logics such as authentication.

Disadvantages:

  1. Less Efficient: Since it has to run part of the rails stack, it is slower as compared to page caching.
  2. Deprecated: Action caching was removed from Rails core after Rails 4. You can bring it back with the actionpack-action_caching gem.

Example

If your "Products" page requires authentication, action caching will run the authentication logic for every request, while serving a cached version of the page content.

Ruby
class ProductsController < ApplicationController
  before_action :authenticate_user!
  caches_action :index
  
  def index
    @products = Product.all
  end
end


To bring back action caching install the gem:

Ruby
gem 'actionpack-action_caching'

Fragment Caching

Fragment Caching is one of the most powerful caching mechanisms in Rails. Instead of the whole page or action, fragment caching enables you to cache parts of a view, hence the name fragments. That is useful on pages that have mixed contents, some of which is dynamic and some of which is static. Where parts of a page are really costly to render or query, but those parts do not change much.

How It Works?

Wrapping caching tags around fragments of a view-such as a side bar, a list of products, or a specific partial-developers can tell Rails to serve the fragment directly from its cache after the initial request. This completely bypasses re-rendering of the relevant view code.

Use Cases

  1. Dynamic pages with some static components, like product listings, navigation menus, or ad blocks.
  2. Complex pages, some of whose content is generated based on database queries, although those pieces of content don't change very often, such as blog post details or sidebar recommendations.

Pros

  1. Cache Only Certain Elements: It allows you to cache just certain elements of a page, hence giving you fine-grained control over what gets cached. This works well for pages combining static and dynamic content where the dynamic parts get re-rendered while the static ones are cached.
  2. Caching Efficiency: Reduces overhead since, for instance, it doesn't have to render each time very complex partials or fetch data from the database for frequently viewed content.

Cons

  1. Complexity: The caching of fragments can be complicated in managing, because you need to make sure proper cache invalidation is performed.
  2. Stale Content: If in the case that the underlying data changes, the fragment should be invalidated; otherwise, you risk serving stale content.

Example

Suppose you have a blog page which renders the content of a blog post, and displays a list of comments and a sidebar containing links to recent posts. You might want to cache the rendering of the sidebar and the content of the blog post, but not the list of comments. Tell me.erb

<% cache @blog_post do %> <%= render @blog_post %> <% end %> <% cache 'sidebar' do %> <%= render 'shared/sidebar' %> <% end %>

Here we cache both the @blog_post and the contents of the sidebar. The comments are not cached and will be dynamically rendered on each request.

Russian Doll Caching

Fragment caching can also be nested inside other fragments, a technique called Russian Doll Caching. This allows for more granular cache invalidation. If an inner fragment changes, the outer fragment is automatically updated too.

<% cache @product do %> <%= render @product %> <% cache @product.reviews do %> <%= render @product.reviews %> <% end %> <% end %>

Above, there is a separate cache for the product and its reviews. If the reviews changes, the cache of product is invalidated and then regenerates it.

Conclusion

Caching in Ruby on Rails is one of its most useful features, really improving the performance of any application by reducing redundant computation and database queries. Because of the various ways of caching, like static content page caching, controller output action caching, and dynamic page section fragment caching, Rails offers flexibility regarding optimization for several aspects of web applications. Such a caching of the expensive operations allows developers to reuse the results stored, reducing response times and relieving some of the unnecessary load on servers, therefore allowing for better scalability. However, the invalidation of the cache and choosing an appropriate caching strategy is extremely important to ensure fresh data with accurate performance for the applications.


Next Article
Article Tags :

Similar Reads