How can I create a second Rails in-memory store cache?
Asked Answered
D

1

13

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.

Dyanne answered 12/4, 2017 at 16:50 Comment(2)
Just use a different key?Erotomania
Yeah but if I use a different key aren't there now competing entries in a single cache? I don't want my keys for storing files to evict LRU entries used for storing queries ... maybe I'm not understanding the cache though.Dyanne
S
9

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.

Schuller answered 17/4, 2017 at 19:36 Comment(1)
I was about to answer this as I was preparing my answer. Saw you already did. Great explanation!Hazelton

© 2022 - 2024 — McMap. All rights reserved.