Back to Scaling Ruby Applications guides

Getting Started with Solid Cache in Ruby on Rails

Ayooluwa Isaiah
Updated on April 14, 2025

Caching is a fundamental optimization technique in web development that can significantly improve application performance.

While traditionally developers have favored memory-based caching solutions, Rails 7.1 introduced an interesting alternative with Solid Cache.

This database-backed caching approach challenges conventional wisdom about caching strategies and offers compelling advantages for many applications.

In this article, we'll examine how Solid Cache works to help you understand if it's the right choice for your projects.

The fundamentals of caching in web applications

Diagram of cache-aside pattern

At its core, caching is about storing computed results so they can be quickly retrieved later, avoiding the need to recalculate the same information repeatedly.

In web applications, caching is particularly important because it helps reduce database queries, API calls, and complex calculations that can slow down response times.

The primary benefit of caching comes from the fact that retrieving data from a cache is typically much faster than generating that data from scratch.

For example, if your application needs to display a list of popular products that doesn't change frequently, rather than querying the database every time a user visits the page, you can store the results in a cache and refresh it periodically.

This simple principle—trading storage for computation—forms the foundation of various caching strategies used in web development.

How caching works in Rails

Ruby on Rails provides several built-in caching mechanisms to help developers optimize their applications. These include:

  1. Page caching: Storing entire HTML pages.
  2. Action caching: Storing the output of controller actions.
  3. Fragment caching: Storing portions of views.
  4. Low-level caching: A flexible approach for caching arbitrary data.
  5. SQL caching: Storing the results of database queries.

Among these, low-level caching offers the most flexibility and is compatible with Rails' configurable cache store system. The cache store is configured in the environment-specific files located in the config/environments/ directory.

Here's how you might use low-level caching to store the results of an expensive database query:

 
Rails.cache.fetch("all_courses", expires_in: 12.hours) do
  Course.includes(:instructor, :category).all
end

This code first checks if there's a valid cached value for the key "all_courses". If found, Rails returns that value immediately. If not (a "cache miss"), it executes the code block, saves the result in the cache with a 12-hour expiration time, and then returns the value.

You can also use more dynamic cache keys based on record IDs or timestamps:

 
def show
  @product = Product.find(params[:id])

  @related_products = Rails.cache.fetch("product/#{@product.id}/related", expires_in: 3.hours) do
    @product.find_related_products
  end
end

By incorporating the product ID into the cache key, each product gets its own cached set of related items, and changes to one product don't invalidate caches for other products.

Traditional caching solutions: Redis and Memcached

ChatGPT Image Apr 14, 2025, 09_16_47 AM.png

Traditionally, Rails applications use memory-based caching systems like Redis or Memcached. These systems store cached data in memory, which provides extremely fast read access times.

To configure Redis as your cache store in a Rails application, you would typically add the following to your production environment configuration:

config/environments/production.rb
config.cache_store = :redis_cache_store, {
  url: ENV['REDIS_URL'],
  expires_in: 1.day
}

Memory-based caches are popular because they offer substantial performance improvements. When an application needs to retrieve cached data, it can do so almost instantly since memory access is orders of magnitude faster than disk access or database queries.

However, memory-based caching comes with limitations. Memory is significantly more expensive than disk storage, which means you're often working with limited cache space. This constraint forces developers to make careful decisions about what to cache, for how long, and when to invalidate cached items.

Understanding Solid Cache

Solid cache Github

Rails 7.1 introduced Solid Cache, a new caching solution that takes a different approach. Rather than using memory to store cached data, Solid Cache uses your application's database.

At first glance, this might seem counterproductive. After all, isn't the whole point of caching to avoid database queries? However, there's an important distinction to make: while Solid Cache does use a database for storage, it's not executing your original queries again. Instead, it's simply retrieving pre-computed results that have been stored in dedicated cache tables.

The key insight is that while database access is indeed slower than memory access, modern databases have become remarkably efficient, often incorporating their own memory caches. Additionally, the storage capacity of databases far exceeds what's typically available for memory caching, and at a significantly lower cost.

This means you can cache more data for longer periods without worrying about memory constraints. In many applications, this expanded caching capability can lead to overall performance improvements despite the slightly slower individual cache lookups.

When to consider Solid Cache

Solid Cache isn't a universal replacement for memory-based caching, but it offers compelling advantages in several scenarios:

Large data volume requirements

If your application needs to cache substantial amounts of data, the cost of maintaining enough memory can become prohibitive. Solid Cache allows you to maintain a much larger cache at a fraction of the cost.

For example, an e-commerce application might need to cache product information, pricing data, user preferences, and recommendation algorithms. With a memory-based cache, you might be forced to limit what you cache or set aggressive expiration policies. Solid Cache enables you to maintain a more comprehensive cache that includes historical data, rarely-accessed items, and larger result sets.

Simplified infrastructure

Managing a separate Redis instance adds complexity to your deployment architecture. If you're already maintaining a database for your application, Solid Cache lets you leverage that existing infrastructure without introducing additional dependencies.

This simplification is particularly valuable for smaller teams or applications where operational overhead is a significant concern. By reducing the number of services you need to monitor, backup, and maintain, you can focus more resources on developing your application's core functionality.

Tolerance for slight latency increases

The primary trade-off with Solid Cache is slightly increased latency for individual cache lookups. If your application can tolerate a few extra milliseconds of access time, the benefits of expanded caching capabilities may outweigh this cost.

Many web applications fall into this category. While a real-time trading platform might require the absolute fastest cache access, a content management system or e-commerce platform likely wouldn't be affected by a small increase in cache retrieval time, especially if it enables more comprehensive caching strategies.

Implementing Solid Cache in your Rails application

Adding Solid Cache to your Rails project is straightforward. Let's walk through the implementation process step by step.

First, add the gem to your Gemfile:

Gemfile
gem "solid_cache"

Then install it using Bundler:

 
bundle install

Next, you need to create the necessary database tables. Solid Cache provides a generator to create the required migrations:

 
bin/rails solid_cache:install:migrations

This command generates migration files that will create the tables Solid Cache needs to store your cached data. Apply the migrations to your database:

 
bin/rails db:migrate

Configuration

Once installed, you can configure your application to use Solid Cache by updating your environment configuration files. For example, to use Solid Cache in production:

config/environments/production.rb
config.cache_store = :solid_cache_store

That's it! Your application will now use the database for caching instead of memory.

If you want to apply this change to other environments as well (like development or staging), you'll need to update those configuration files too.

Solid Cache also supports additional configuration options for more advanced scenarios. For example, you can specify database connection options:

config/environments/production.rb
config.cache_store = :solid_cache_store, {
  database: {
    adapter: "postgresql",
    database: "my_app_cache",
    username: "cache_user",
    password: ENV["CACHE_DB_PASSWORD"]
  }
}

This allows you to use a separate database for your cache, which might be useful in larger applications where you want to isolate cache performance from your main database.

You can also configure expiration sweep frequency to control how often expired cache entries are removed:

config/environments/production.rb
config.cache_store = :solid_cache_store, {
  expires_in: 1.day,
  sweep_frequency: 1.hour
}

In this example, cached items will expire after one day by default, and the system will clean up expired entries once per hour.

Using Solid Cache effectively in your application

Once you've set up Solid Cache, you can use it just like any other Rails cache store. The Rails.cache methods work the same way, but now they're storing and retrieving data from your database rather than memory.

Let's look at some practical examples of how to leverage caching effectively in your application.

Caching complex database queries

Complex queries with multiple joins or aggregations are prime candidates for caching:

complex_query_cache.rb
def dashboard_statistics
  Rails.cache.fetch("dashboard_stats/#{Date.today}", expires_in: 1.day) do
    {
      new_users: User.where("created_at >= ?", 24.hours.ago).count,
      active_projects: Project.where(status: "active").count,
      revenue_mtd: Order.where("created_at >= ?", Date.today.beginning_of_month).sum(:amount)
    }
  end
end

This example caches dashboard statistics that require multiple database queries. The cache key includes the current date, so new statistics will be calculated each day.

Caching API responses

External API calls are often slow and may have rate limits, making them excellent caching candidates:

api_cache.rb
def weather_forecast(city)
  Rails.cache.fetch("weather/#{city}", expires_in: 30.minutes) do
    WeatherAPI.get_forecast(city)
  end
end

This caches weather forecast data for each city for 30 minutes, reducing API calls and improving response times.

Caching with cache versioning

When dealing with models that change frequently, you might want to invalidate caches automatically when the model is updated:

cache_versioning.rb
class Product < ApplicationRecord
  after_save :invalidate_caches

  private

  def invalidate_caches
    Rails.cache.delete("product/#{id}/details")
  end
end

# In your controller
def show
  @product = Product.find(params[:id])
  @product_details = Rails.cache.fetch("product/#{@product.id}/details") do
    {
      full_description: @product.generate_detailed_description,
      specifications: @product.compile_specifications,
      average_rating: @product.calculate_average_rating
    }
  end
end

This example manually invalidates the cache when a product is updated. Alternatively, you can use cache versioning based on the model's updated_at timestamp:

timestamp_versioning.rb
def show
  @product = Product.find(params[:id])
  @product_details = Rails.cache.fetch("product/#{@product.id}/details/#{@product.updated_at.to_i}") do
    # Complex calculations here
  end
end

By including the timestamp in the cache key, you automatically get a new cache entry whenever the product is updated.

Caching fragments of views

While this article focuses on low-level caching, it's worth mentioning that Solid Cache works seamlessly with Rails' fragment caching as well:

_product_list.html.erb
<% cache [current_user.role, "product_list", Product.maximum(:updated_at).to_i] do %>
  <div class="product-list">
    <% @products.each do |product| %>
      <% cache product do %>
        <div class="product-card">
          <h3><%= product.name %></h3>
          <p><%= product.description %></p>
          <span class="price"><%= number_to_currency(product.price) %></span>
        </div>
      <% end %>
    <% end %>
  </div>
<% end %>

This example caches the entire product list (with a key that varies based on the user's role and changes when any product is updated) as well as individual product cards.

Performance considerations and optimization

When using Solid Cache, there are some performance considerations to keep in mind:

Cache key design

Effective cache key design is crucial for any caching system, but it becomes even more important with database-backed caches:

effective_keys.rb
# Avoid overly generic keys
# Bad example
Rails.cache.fetch("users") { User.all }

# Better: Include version information
Rails.cache.fetch("users/v1/#{User.maximum(:updated_at).to_i}") { User.all }

# Better still: Scope to relevant context
Rails.cache.fetch("company/#{company_id}/users/v1/#{User.where(company_id: company_id).maximum(:updated_at).to_i}") do
  User.where(company_id: company_id)
end

Well-designed cache keys help prevent cache collisions and ensure cache invalidation occurs at the right time.

Cache expiration strategies

With the increased storage capacity of Solid Cache, you might be tempted to set very long expiration times. However, you should still consider how fresh your data needs to be:

expiration_strategies.rb
# Frequently changing data
Rails.cache.fetch("trending_articles", expires_in: 15.minutes) do
  Article.trending
end

# Relatively stable data
Rails.cache.fetch("company/#{company_id}/organization_chart", expires_in: 1.day) do
  Company.find(company_id).organization_chart
end

# Reference data that rarely changes
Rails.cache.fetch("countries_list", expires_in: 1.month) do
  Country.order(:name).pluck(:id, :name)
end

Tailor your expiration times to the nature of the data and how critical freshness is for your application.

Database performance

Since Solid Cache uses your database, it's worth considering the impact on your database server:

  1. Consider using a separate database server or schema for your cache if you have high-traffic applications
  2. Ensure your database has adequate indexes on the cache tables (the migrations created by Solid Cache should handle this)
  3. Monitor your database performance to ensure cache operations aren't impacting critical application queries

Migrating from Redis to Solid Cache

If you're considering migrating an existing application from Redis to Solid Cache, here's a practical approach:

  • Install Solid Cache alongside your existing Redis cache.

  • Create a dual-caching adapter that writes to both caches but reads from Redis:

dual_cache_adapter.rb
  class DualCacheAdapter
    def initialize(redis_cache, solid_cache)
      @redis_cache = redis_cache
      @solid_cache = solid_cache
    end

    def fetch(key, options = {}, &block)
      # Try to read from Redis first
      value = @redis_cache.read(key)
      return value if value

      # If not in Redis, generate the value
      if block_given?
        value = yield
        # Write to both caches
        @redis_cache.write(key, value, options)
        @solid_cache.write(key, value, options)
        return value
      end

      nil
    end

    # Implement other cache methods as needed
  end
  • Use the adapter during a transition period to build up the Solid Cache.
  • Monitor performance metrics for both caching systems.
  • When confident, switch entirely to Solid Cache.

This gradual approach helps ensure a smooth transition without affecting application performance.

Final thoughts

Solid Cache represents an innovative approach to caching in Rails applications that challenges conventional wisdom. By leveraging database storage instead of memory, it offers expanded caching capabilities at a lower cost, which can lead to overall performance improvements in many applications.

While it's not the right solution for every use case—applications requiring the absolute fastest cache lookups might still prefer Redis—Solid Cache provides a compelling alternative that's worth considering, especially for applications with large data volumes or teams looking to simplify their infrastructure.

As with any performance optimization, the best approach is to test both options in your specific context, measuring real-world performance impacts rather than relying solely on theoretical considerations. The ease of implementation makes Solid Cache particularly attractive for experimentation, allowing developers to evaluate its benefits with minimal investment.

As Rails continues to evolve, tools like Solid Cache remind us that sometimes the most effective solutions come from questioning established patterns and exploring new approaches to familiar problems.

Author's avatar
Article by
Ayooluwa Isaiah
Ayo is a technical content manager at Better Stack. His passion is simplifying and communicating complex technical ideas effectively. His work was featured on several esteemed publications including LWN.net, Digital Ocean, and CSS-Tricks. When he's not writing or coding, he loves to travel, bike, and play tennis.
Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github