Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Rails does not have a built-in mechanism to prevent cache stampedes.</p> <p>According to the README for <code>atomic_mem_cache_store</code> (a replacement for <code>ActiveSupport::Cache::MemCacheStore</code> that mitigates cache stampedes):</p> <blockquote> <p>Rails (and any framework relying on active support cache store) does not offer any built-in solution to this problem</p> </blockquote> <p>Unfortunately, I'm guessing that this gem won't solve your problem either. It supports fragment caching, but it only works with time-based expiration.</p> <p>Read more about it here: <a href="https://github.com/nel/atomic_mem_cache_store" rel="nofollow">https://github.com/nel/atomic_mem_cache_store</a></p> <h3>Update and possible solution:</h3> <p>I thought about this a bit more and came up with what seems to me to be a plausible solution. I haven't verified that this works, and there are probably better ways to do it, but I was trying to think of the smallest change that would mitigate the majority of the problem.</p> <p>I assume you're doing something like <code>cache model do</code> in your templates as described by DHH (<a href="http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works" rel="nofollow">http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works</a>). The problem is that when the model's <code>updated_at</code> column changes, the <code>cache_key</code> likewise changes, and all your servers try to re-create the template at the same time. In order to prevent the servers from stampeding, you would need to retain the old cache_key for a brief time. </p> <p>You might be able to do this by (dum da dum) caching the cache_key of the object with a short expiration (say, 1 second) and a <code>race_condition_ttl</code>.</p> <p>You could create a module like this and include it in your models:</p> <pre><code>module StampedeAvoider def cache_key orig_cache_key = super Rails.cache.fetch("/cache-keys/#{self.class.table_name}/#{self.id}", expires_in: 1, race_condition_ttl: 2) { orig_cache_key } end end </code></pre> <p>Let's review what would happen. There are a bunch of servers calling <code>cache model</code>. If your model includes <code>StampedeAvoider</code>, then its <code>cache_key</code> will now be fetching <code>/cache-keys/models/1</code>, and returning something like <code>/models/1-111</code> (where 111 is the timestamp), which <code>cache</code> will use to fetch the compiled template fragment.</p> <p>When you update the model, <code>model.cache_key</code> will begin returning <code>/models/1-222</code> (assuming 222 is the new timestamp), but for the first second after that, <code>cache</code> will keep seeing <code>/models/1-111</code>, since that is what is returned by <code>cache_key</code>. Once 1 second passes, all of the servers will get a cache-miss on <code>/cache-keys/models/1</code> and will try to regenerate it. If they all recreated it immediately, it would defeat the point of overriding <code>cache_key</code>. But because we set <code>race_condition_ttl</code> to 2, all of the servers except for the first will be delayed for 2 seconds, during which time they will continue to fetch the old cached template based on the old cache key. Once the 2 seconds have passed, <code>fetch</code> will begin returning the new cache key (which will have been updated by the first thread which tried to read/update <code>/cache-keys/models/1</code>) and they will get a cache hit, returning the template compiled by that first thread.</p> <p>Ta-da! Stampede averted.</p> <p>Note that if you did this, you would be doing twice as many cache reads, but depending on how common stampedes are, it could be worth it.</p> <p>I haven't tested this. If you try it, please let me know how it goes :)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload