Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 995
Dave Avatar asked Apr 12 '17 16:04

Dave


People also ask

How can you implement caching in Rails?

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.

Does rails cache use Redis?

Rails 5.2 introduced built-in Redis cache store, which allows you to store cache entries in Redis.

Which of the following caching techniques is are available in Rails?

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.

What is Cache sweeper in rails?

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.


1 Answers

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.

like image 119
anothermh Avatar answered Oct 07 '22 00:10

anothermh