Mastering Caching in Ruby on Rails: A Comprehensive Guide — Ruby Deep Dive[15]

Bhavyansh @ DiversePixel
4 min readAug 1, 2024

--

Caching is a crucial technique for improving the performance of Ruby on Rails applications. It reduces database queries, minimizes server processing time, and speeds up response times. In this article, we’ll explore different caching strategies in Rails, their implementations, and best practices.

Page Caching

Page caching is the simplest form of caching, where entire HTML pages are saved as static files.

Implementation:

class HomeController < ApplicationController
caches_page :index
def index
# Action content
end
end

Pros:

  • Extremely fast, as it bypasses Rails completely
  • Ideal for pages that rarely change

Cons:

  • Not suitable for pages with dynamic content
  • Requires web server configuration for proper handling

Best practices:

  • Use for truly static pages only
  • Implement proper cache expiration strategies

Action Caching

Action caching is similar to page caching but runs through the Rails stack, allowing before filters to be executed.

Implementation:

class ProductsController < ApplicationController
before_action :authenticate_user!
caches_action :index, :show
def index
@products = Product.all
end
def show
@product = Product.find(params[:id])
end
end

Pros:

  • Allows for authentication and other before filters
  • Caches the entire action output

Cons:

  • Still bypasses the action for cached pages, which might not be desirable for all scenarios

Best practices:

  • Use for pages that require authentication but have mostly static content
  • Implement cache expiration based on related model updates

Fragment Caching

Fragment caching allows caching of a particular piece of a view rather than the entire page.

Implementation:

<% cache [@product, 'sidebar'] do %>
<div class="sidebar">
<!-- Sidebar content -->
</div>
<% end %>

Pros:

  • More granular control over what gets cached
  • Allows for dynamic and static content on the same page

Cons:

  • Requires careful consideration of cache keys to avoid stale data

Best practices:

  • Use Russian Doll caching for nested fragments
  • Utilize touch: true in model associations for automatic cache expiration

Low-Level Caching

Low-level caching gives you fine-grained control over caching, allowing you to cache arbitrary data.

Implementation:

def expensive_operation
Rails.cache.fetch('expensive_operation', expires_in: 12.hours) do
# Perform expensive operation
end
end

Pros:

  • Highly flexible, can cache any Ruby object
  • Useful for caching API responses, computation results, etc.

Cons:

  • Requires manual management of cache keys and expiration

Best practices:

  • Use meaningful, namespaced cache keys
  • Set appropriate expiration times
  • Consider using cache versioning for easier cache invalidation

SQL Caching

Rails automatically caches SQL queries within a single request.

Implementation: Automatic in Rails, but can be cleared manually:

ActiveRecord::Base.connection.clear_query_cache

Pros:

  • Improves performance for repeated queries in a single request
  • No setup required

Cons:

  • Only lasts for the duration of a single request

Best practices:

  • Be aware of its existence when debugging performance issues
  • Clear the cache manually if necessary within a single request

HTTP Caching

HTTP caching involves setting appropriate headers to allow client-side caching.

Implementation:

class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.updated_at, etag: @product
end
end

Pros:

  • Reduces server load by allowing clients to cache responses
  • Works well with CDNs

Cons:

  • Requires careful consideration of cache invalidation strategies

Best practices:

  • Use etags and last_modified headers
  • Implement conditional GET requests
  • Consider using stale? for more complex caching scenarios

Russian Doll Caching

Russian Doll caching is a technique where nested cache fragments automatically expire when a related object is updated.

Implementation:

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

Pros:

  • Efficient caching for complex view hierarchies
  • Automatic cache expiration when associated records change

Cons:

  • Can be complex to set up for deeply nested structures

Best practices:

  • Use touch: true on model associations
  • Combine with key-based cache expiration for more control

Memoization

While not strictly a caching technique, memoization can improve performance by caching the result of expensive computations within a single request.

Implementation:

def expensive_method
@expensive_method ||= begin
# Expensive computation
end
end

Pros:

  • Simple to implement
  • Useful for expensive computations within a single request

Cons:

  • Only lasts for the duration of a single request

Best practices:

  • Use for expensive computations that are called multiple times in a request
  • Be cautious with instance variables in controllers, as they can persist across redirects

Redis Caching

Rails can use Redis as a cache store, which is particularly useful for distributed caching in a multi-server environment.

Implementation: In config/environments/production.rb:

config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }

Pros:

  • Fast, in-memory caching
  • Supports distributed caching across multiple servers

Cons:

  • Requires setting up and maintaining a Redis server

Best practices:

  • Use for distributed environments
  • Consider using Redis for both caching and as a session store

Cache Versioning

Cache versioning allows you to expire all caches at once by changing the version.

Implementation:

config.cache_version = 'v1'
# In your code
Rails.cache.fetch("data_key", version: 'v2') do
# Expensive operation
end

Pros:

  • Allows for easy cache invalidation of all caches at once
  • Useful for major application updates

Cons:

  • Blanket cache invalidation can lead to temporary performance degradation

Best practices:

  • Use for major application updates or data migrations
  • Combine with more granular caching strategies for day-to-day operations

Conclusion

Caching in Rails is a powerful tool for improving application performance. By understanding and correctly implementing these various caching strategies, you can significantly reduce database load, minimize server processing time, and improve response times for your users. Remember that effective caching requires a good understanding of your application’s data flow and user patterns. Always monitor your caching strategy’s effectiveness and be prepared to adjust as your application grows and evolves.

--

--

Bhavyansh @ DiversePixel
Bhavyansh @ DiversePixel

Written by Bhavyansh @ DiversePixel

Hey I write about Tech. Join me as I share my tech learnings and insights. 🚀

No responses yet