What is the default expiry time for Rails cache? - ruby-on-rails

I've done some googling and couldn't find the answer to this question. Rails allows to specify expiry times for its cache like that:
Rails.cache.fetch("my_var", :expires_in => 10.seconds)
But what happens if I specify nothing:
Rails.cache.fetch("my_var")
It never expires? Is there a default value? How can I explicitly define something that never expires?

It really depends on which cache storage you're using. Rails provides several, one of them most popular is Memcached. One of key features of Memcached is that it automatically expires old unused records, so you can forget about :expire option.
Other Rails cache storages, like memory storage or redis storage will keep will not expire date unless you explicitly specify when to do that.
More about how cache key expiration works in Rails.

Using Dalli for memcached (who doesn't), the default expiry time is never, as #Rahul says. You don't have to worry about garbage collection, as #icem says, memcached throw out the old unused records.
See the official dalli documentation:
Expires_in default is 0, which means never
https://github.com/mperham/dalli#configuration
you can set the global expiry time for dalli
config.cache_store = :dalli_store, { expires_in: 1.day}
and for better individual control:
Rails.cache.write "some_cache_key", some_cachable_string, expires_in: 3.hours
the new doc http://apidock.com/rails/ActiveSupport/Cache/Store/write doesn't say much, but the old does:
http://apidock.com/rails/ActiveSupport/Cache/MemCacheStore/write
manually expire a cache (if some event occurred):
Rails.cache.delete "some_cache_key"

They never expires. (for FileStore based cache, which is default in Rails)
If they key is found in the cache store, the value would be used. Thus it is always recommended to add atleast any expiry time.

Related

Proper Rails low-level caching with concurrency

I want to run my Rails 5 app on Puma. I use low-level caching and suppose the way to have thread-safe caching:
# somewhere in a model ...
##mutex = Mutex.new
def nice_suff
Rails.cache.fetch("a_key") do
##mutex.synchronize do
Rails.cache.fetch("a_key", 60) do
Model.stuff.to_a
end
end
end
end
Will this be working fine?
The proper way to handle concurrent cache access is already built-in.
val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
Model.stuff.to_a
end
Setting :race_condition_ttl is very useful in situations where a cache entry is used very frequently and is under heavy load. If a cache expires and due to heavy load several different processes will try to read data natively and then they all will try to write to cache. To avoid that case the first process to find an expired cache entry will bump the cache expiration time by the value set in :race_condition_ttl. Yes, this process is extending the time for a stale value by another few seconds. Because of extended life of the previous cache, other processes will continue to use slightly stale data for a just a bit longer. In the meantime that first process will go ahead and will write into cache the new value. After that all the processes will start getting the new value. The key is to keep :race_condition_ttl small.

Memcache not able to fetch value

I set 10000 keys in memcache
for i in 1..10000
Rails.cache.write("short_key#{i}", i)
end
After ~500s (not benchmarked but happens around 10m), when I do
_random = rand(10000)
Rails.cache.read("short_key#{_random}")
returns nil. This is fine. Memcached LRU policy might have destroyed those keys.
But, issue is I see a lot of free memory on server.
Also, when I run following command in telnet session,
stats cachedump 1 10
I get some random keys which I set earlier in loop and even when I try to fetch them via rails or telnet/get, memcached is not able to read that value.
Those key/values are eating up memory but somehow getting destroyed.
I use dalli to connect with memcached.
How can I correct this?
At first glance, this seems possible if the default keep alive time value is low (10 minutes or 500 seconds are both possible default values).
Since you are not setting up the expires_in (or equivalent time_to_live field), the key will be setup for default time, after which the value will expire.
Referring here:
Setting :expires_in will set an expiration time on the cache. All caches support auto-expiring content after a specified number of seconds. This value can be specified as an option to the constructor (in which case all entries will be affected), or it can be supplied to the fetch or write method to effect just one entry.

Cache with expiring keys

I'm working on a mashup site and would like to limit the number of fetches to scrape the source sites. There is essentially one bit of data I need, an integer, and would like to cache it with a defined expiration period.
To clarify, I only want to cache the integer, not the entire page source.
Is there a ruby or rails feature or gem that already accomplishes this for me?
Yes, there is ActiveSupport::Cache::Store
An abstract cache store class. There are multiple cache store
implementations, each having its own additional features. See the
classes under the ActiveSupport::Cache module, e.g.
ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the
most popular cache store for large production websites.
Some implementations may not support all methods beyond the basic
cache methods of fetch, write, read, exist?, and delete.
ActiveSupport::Cache::Store can store any serializable Ruby object.
http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
cache = ActiveSupport::Cache::MemoryStore.new
cache.read('Chicago') # => nil
cache.write('Chicago', 2707000)
cache.read('Chicago') # => 2707000
Regarding the expiration time, this can be done by passing the time as a initialization parameter
cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
If you want to cache a value with a different expiration time, you can also set this when writing a value to the cache
cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
See Caching with Rails, particularly the :expires_in option to ActiveSupport::Cache::Store.
For example, you might go:
value = Rails.cache.fetch('key', expires_in: 1.hour) do
expensive_operation_to_compute_value()
end

why is there need to set an expiry time for caching?

I don't see this issue explained in the Rails caching guide http://guides.rubyonrails.org/caching_with_rails.html, so I wonder if I might ask how caching is working exactly in this example. On my user profile page, I cache the languages the user speaks and set an expiry for 15 minutes. When I did this, I assumed that if the user update his languages before those 15 minutes expired, then the updated languages wouldn't show, because the cache hadn't expired. However, when I test this on my app, the updated languages are showing immediately, so I assume that updating breaks the cache. If that's the case, then why wouldn't I set the expiry date to 1 hour or infinity?
#languages = Rails.cache.fetch("lang", :expires_in => 15.minutes) do
Language.where({:user_id => #user.id})
end
Note, I'm using Rails 4 with memcached if that's important.
Update, if the expiry time is just about clearing the cache due to size limitations, how long should I set the expiry for?
I have a lot of information (about 15 queries similar to below) on my profile pages that I'd prefer to cache if a user keeps refreshing the page, therefore I was just going to do this
#endorsements = Rails.cache.fetch("endorsements", :expires_in => 15.minutes) do
Endorsement.where({:subject_id => #user.id})
end
#languages = Rails.cache.fetch("lang", :expires_in => 15.minutes) do
Language.where({:user_id => #user.id})
end
Here's what you need to do in Rails4 to get the caching to work (in development) as you'd expect:
Add 'dalli' to your Gemfile
add config.cache_store = :mem_cache_store to your config/environments/development.rb
add config.action_controller.perform_caching = true to your config/environments/development.rb
(I know you already have #3 done)
Once this is complete, you won't see the "SELECT *" in your logs anymore, and when you update your models, it will not automatically update your cache.
UPDATE:
Like #FrederickCheung says, you need to cache objects, not relations (queries). Best way is to call "to_a" on them.
You're not actually caching anything: you are just caching ActiveRecord::Relation objects (which is pretty much just a ruby description of a query), rather than the query results itself.
Each time the code runs, this query is pulled in its unexecuted state and then run again. To achieve what you wanted to do, you need to force the query to be executed, for example
#endorsements = Rails.cache.fetch("endorsements", :expires_in => 15.minutes) do
Endorsement.where({:subject_id => #user.id}).all
end
Cache expiry can be tricky - it's sometimes easier just to have cached items expire automatically rather than ensuring that every single way of changing the data clears the cache. In some cases you may not even know when the data changes, for example if you are caching the results of an external api call.

Rails cache expire

I have a rails application, in that I am using simple rails cache. My testing is as follows:
Rails.cache.write('temp',Date.today,:expires_in => 60.seconds)
I can read it through Rails.cache.read('temp') and Rails.cache.fetch('temp').
The problem is it doesn't expire. It will still be alive after 60 seconds. Can any one tell me what is missing here.
FYI: I have declared in my development.rb as follows :
config.action_controller.perform_caching = true
config.cache_store = :memory_store
Is there anything I missed out? I want to expires my cache.
After some search, I have found one possible reason why the cache is not cleaned after 60 seconds.
You call Rails.cache.write which is documented here.
It calls write_entry(namespaced_key(name, options), entry, options), where your option :expires_in is one part of the options argument.
The implementation of write_entry has the following condition:
if expires_in > 0 && !options[:raw]
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
So there are 5 minutes added to your 60 seconds. 2 possible solutions:
Just live with it :-)
Try to include the option :raw => true, perhaps this will skip the condition, so that your expiry works as suspected.
The :expires_in option only works with compatible stores (eg memcached) - not memory.
From http://guides.rubyonrails.org/caching_with_rails.html:
Finally, if you are using memcached or Ehcache, you can also pass
:expires_in. In fact, all parameters not used by caches_action are
sent to the underlying cache store.
You should use fetch method with do block instead of Rails.cache.write. Even though the name is "fetch", it writes to the cache if you put inside do block. You also do not need to clear cache entry manually, just use the "fetch" with expires_in and race_condition_ttl. Keep the ttl value under expiry time and keep it as small as possible.
output_json = Rails.cache.fetch('temp',expires_in: 1.minute,race_condition_ttl:3) do
my_expansive_operation().to_json
end

Resources