The concept of caching is an important one in computer science and is directly relevant to building web apps. At a high level, caching takes advantage of the fact that memory lookups are faster and less compute-intensive than database queries. If you have a database query that might get made repeatedly, caching the result in a cache speeds up that flow of execution. Rails applications are no exception as they often use memory-based caches like Redis to cache data and improve application performance.
The idea of using a database cache might sound unproductive. Still, using a slower database-backed cache like Solid Cache in Rails can counterintuitively speed up an application because cheaper storage allows us to cache more things for longer periods. In Rails 7.1, Solid Cache was introduced, making a database-backed application cache a convenient option for a Rails application!
How the Rails cache works
Caching in Ruby on Rails is an important part of optimizing application performance. There are several types of caching developers can use to enhance performance: page caching, action caching, fragment caching, low-level caching, SQL caching, and more. In this article, we'll mostly
discuss low-level caching, as it is straightforward and compatible with the configurable cache_store
.
ActiveSupport, a huge piece of the Rails framework, allows us to interact with and configure the cache via the cache_store
. In config/environments/
, there are files with each environment name in various configurations, including the choice of cache_store.
Setting a cache_store value
Redis is an incredibly popular cache store and has been widely adopted across the industry. If an application is using Redis for caching in production, you'll see the following line of Ruby in config/environments/production.rb
:
config.cache_store = :redis_cache_store
Redis is used because it's a memory cache, and memory has traditionally much faster read access times than disk access or a database query.
Caching a database query in Rails
You might be using low-level caching to store the result of an expensive database query that returns data that doesn't often change. It might look something like this:
Rails.cache.fetch("courses", expires_in: 12.hours) do
Courses.all
end
If you're using Redis as a cache store, then this code would first look to see if the key "courses" has a value from the last 12 hours. If it does, it will simply return that value without executing the code block. If the cache doesn't have that value, it's called a "cache miss". A cache miss would lead to the interpreter executing the block, storing the result in the cache, and then returning the result.
What is Rails Solid Cache?
The caching pattern we walked through above provides performance benefits because memory access is faster than, for example, API calls and database queries. However, memory is expensive, and therefore limited. Because memory is limited, we have to minimize what we store in the cache and for how long. You can't cache everything for indefinite amounts of time as you'd quickly run out of memory in your cache. The performance benefit from caching is all about tradeoffs. You cache some things for some time so that you don't have to repeat time-intensive tasks.
The Rails 7.1 release came with Solid Cache, an option for ActiveRecord::Cache::Store
that uses an SQL database for the cache store. Each read from the cache is slower, but thanks to both advances in technology and built-in memory caches in many databases, the difference is still beneficial.
Using a SQL database as a cache store gives you many times more storage at an even less cost, allowing your application to cache even more data for longer time periods. The Basecamp team wrote an article about how this sped up their application, which might seem counterintuitive.
When should you use Solid Cache?
While memory caches like Redis typically offer faster reads from the cache, Solid Cache presents a good alternative to those options in several scenarios.
If your application requires caching a large volume of data, storing it in memory can become prohibitively expensive. Solid Cache allows for a much larger cache size at a fraction of the cost.
If your application is not highly sensitive to a few milliseconds of added cache retrieval time, Solid Cache provides an excellent balance between performance and cost. If you can stomach the potential increase in latency, Solid Cache is a good solution.
Separately, in setups where managing a separate Redis instance is impractical, Solid Cache simplifies deployment by allowing caching to be handled by your existing database without needing to support Redis.
Installation of Solid Cache
The Solid Cache Readme has helpful instructions on introducing it into a Rails application. First, add the gem to your Gemfile:
gem "solid_cache"
Next, install it with Bundler:
bundle install
Next, add a migration so that your database will work with Solid Cache. Run the following:
bin/rails solid_cache:install:migrations
Finally, run the generated migration:
bin/rails db:migrate
Implementing Solid Cache in a Rails app
Swapping Solid Cache for an existing cache is usually straightforward. In your environment config, such as config/environments/production.rb
, change the cache_store
setting to the following:
config.cache_store = :solid_cache_store
This is all that's required to start using Solid Cache as your cache store! It supports all the other ActiveSupport::Cache::Store
configuration options, as well as additional
options to support database sharding or other unique needs.
You should note that this only changes the cache store in production, so if you'll be testing in other environments, you'll want to change the value there as well.
Will you switch to Solid Cache in Rails?
The introduction of Solid Cache in Rails 7.1 marked a significant development in the world of Rails caching strategies. This solution offers a new perspective on application performance optimization, challenging the traditional preference for memory-based caches like Redis.
By leveraging a SQL database for caching, Solid Cache enables applications to store a greater volume of data for extended periods, thereby overcoming the limitations imposed by the cost and capacity of memory storage. This approach not only broadens the quantity of what can be effectively cached but also has the potential to provide even greater performance benefits.
That said, the decision to switch depends on the specific needs of your application. If your application benefits significantly from high-speed caching and you have the infrastructure to support Redis, you may not need Solid Cache. However, if you’re looking for a cost-effective way to cache vast amounts of data without adding another service to your stack, Solid Cache might be worth exploring. Even if you just want to avoid having to support Redis, Solid Cache might be more than good enough for your app.
If you enjoyed this article, sign up for the Honeybadger newsletter to get more Ruby and Rails articles like this one in your inbox.