I'm using Rails 5. I'm currenlty using Rails in-memory cache to cache db query results, for instance, this is in my state.rb
model ...
def self.cached_find_by_iso_and_country_id(iso, country_id)
if iso
Rails.cache.fetch("#{iso.strip.upcase} #{country_id}") do
find_by_iso_and_country_id(iso.strip.upcase, country_id)
end
end
end
My question is, how can I create a second in-memory Rails cache (I need one for storing files I have downloaded from the Internet) that doesn't interfere with the query cache I have above? I don't want the entries in my file cache to cause entries in my query cache to be evicted.
You can create your own custom cache store by simply extending ActiveSupport::Cache::Store and implementing the appropriate methods. This way, you can swap in any number of caching technologies into your Rails application. To use a custom cache store, simply set the cache store to a new instance of your custom class.
Rails 5.2 introduced built-in Redis cache store, which allows you to store cache entries in Redis.
This is an introduction to three types of caching techniques: page, action and fragment caching. Rails provides by default fragment caching. In order to use page and action caching, you will need to add actionpack-page_caching and actionpack-action_caching to your Gemfile.
Cache sweeping is a mechanism which allows you to get around having a ton of expire_{page,action,fragment} calls in your code. It does this by moving all the work required to expire cached content into na ActionController::Caching::Sweeper class.
Yes, you can do this with Rails. You need to create a second cache and make it available in your app as a global variable, then call the appropriate cache depending on the context. Each cache is assigned its own chunk of memory (32 MB by default) and if one cache fills up it will not affect the other cache. This is done with ActiveSupport::Cache::MemoryStore.new
.
I will demonstrate that the two caches do not impact each other:
First, generate two text files that will be used to test the cache, one 10 MB and one 30 MB:
dd if=/dev/zero of=10M bs=1m count=10
dd if=/dev/zero of=30M bs=1m count=30
Open a Rails console and read these into strings:
ten = File.read("10M"); 0
thirty = File.read("30M"); 0
Store ten
in the cache:
Rails.cache.fetch("ten") { ten }; 0
Confirm the data was cached:
Rails.cache.fetch("ten")[0..10]
=> "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
Store thirty
in the cache:
Rails.cache.fetch("thirty") { thirty }; 0
Confirm this was not saved (too large to save in the cache when expanded as a string):
Rails.cache.fetch("thirty")[0..10]
NoMethodError: undefined method `[]' for nil:NilClass
Confirm this has busted the entire cache:
Rails.cache.fetch("ten")[0..10]
NoMethodError: undefined method `[]' for nil:NilClass
Now create a second cache and confirm it behaves identically to the original cache:
store = ActiveSupport::Cache::MemoryStore.new
store.fetch("ten") { ten }; 0
store.fetch("ten")[0..10]
=> "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
store.fetch("thirty") { thirty }; 0
store.fetch("thirty")[0..10]
NoMethodError: undefined method `[]' for nil:NilClass
store.fetch("ten")[0..10]
NoMethodError: undefined method `[]' for nil:NilClass
Now there are two empty caches: store
and Rails.cache
. Let's confirm they are independent:
Rails.cache.fetch("ten") { ten }; 0
Rails.cache.fetch("ten")[0..10]
=> "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
store.fetch("thirty") { thirty }; 0 # bust the `store' cache
Rails.cache.fetch("ten")[0..10]
=> "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
If the two caches interfered then the last store.fetch
call would have busted both caches. It only busts store
.
To implement a second cache in your app, create an initializer config/initializers/cache.rb
and add:
$cache = ActiveSupport::Cache::MemoryStore.new
Call the new cache in your code the same way you would Rails.cache
:
$cache.fetch("foo") { "bar" }
Some of these details were taken from this answer. The new cache supports additional options; check MemoryStore and Caching with Rails for more information on customizing the cache.
This solution will work for small apps. Note this comment from the MemoryStore docs:
If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then this means that Rails server process instances won't be able to share cache data with each other and this may not be the most appropriate cache in that scenario.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With