Movatterモバイル変換


[0]ホーム

URL:


BigBinary Logo

Recyclable cache keys in Rails

Taha Husain avatar

Taha Husain

August 6, 2019

Recyclable cache keys orcacheversioning was introduced in Rails 5.2. Large applications frequently need toinvalidate their cache because cache store has limited memory. We can optimizecache storage and minimize cache miss using recyclable cache keys.

Recyclable cache keys is supported by allcache storesthat ship with Rails.

Before Rails 5.2,cache_key's format was{model_name}/{id}-{update_at}. Heremodel_name andid are always constant for an object andupdated_at changeson every update.

Rails 5.1

>> post = Post.last>> post.cache_key=> "posts/1-20190522104553296111"# Update post>> post.touch>> post.cache_key=> "posts/1-20190525102103422069" # cache_key changed

In Rails 5.2,#cache_key returns{model_name}/{id} and new method#cache_version returns{updated_at}.

Rails 5.2

>> ActiveRecord::Base.cache_versioning = true>> post = Post.last>> post.cache_key=> "posts/1">> post.cache_version=> "20190522070715422750">> post.cache_key_with_version=> "posts/1-20190522070715422750"

Let's updatepost instance and checkcache_key andcache_version'sbehaviour.

>> post.touch>> post.cache_key=> "posts/1" # cache_key remains same>> post.cache_version=> "20190527062249879829" # cache_version changed

To use cache versioning feature, we have to enableActiveRecord::Base.cache_versioning configuration. By defaultcache_versioning config is set to false for backward compatibility.

We can enable cache versioning configuration globally as shown below.

ActiveRecord::Base.cache_versioning = true# orconfig.active_record.cache_versioning = true

Cache versioning config can be applied at model level.

class Post < ActiveRecord::Base  self.cache_versioning = trueend# Or, when setting `#cache_versioning` outside the model -Post.cache_versioning = true

Let's understand the problem step by step with cache keys before Rails 5.2.

Rails 5.1 (without cache versioning)

1. Writepost instance to cache usingfetch api.

>> before_update_cache_key = post.cache_key=> "posts/1-20190527062249879829">> Rails.cache.fetch(before_update_cache_key) { post }=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 06:22:49">

2. Updatepost instance usingtouch.

>> post.touch   (0.1ms)  begin transaction  Post Update (1.6ms)  UPDATE "posts" SET "updated_at" = ? WHERE "posts"."id" = ?  [["updated_at", "2019-05-27 08:01:52.975653"], ["id", 1]]   (1.2ms)  commit transaction=> true

3. Verify stalecache_key in cache store.

>> Rails.cache.fetch(before_update_cache_key)=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 06:22:49">

4. Write updatedpost instance to cache using newcache_key.

>> after_update_cache_key = post.cache_key=> "posts/1-20190527080152975653">> Rails.cache.fetch(after_update_cache_key) { post }=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 08:01:52">

5. Cache store now has two copies ofpost instance.

>> Rails.cache.fetch(before_update_cache_key)=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 06:22:49">>> Rails.cache.fetch(after_update_cache_key)=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 08:01:52">

cache_key and its associated instance becomes irrelevant as soon as aninstance is updated. But it stays in cache store until it is manuallyinvalidated.

This sometimes result in overflowing cache store with stale keys and data. Inapplications that extensively use cache store, a huge chunk of cache store getsfilled with stale data frequently.

Now let's take a look at the same example. This time withcache versioning tounderstand how recyclable cache keys help optimize cache storage.

Rails 5.2 (cache versioning)

1. Writepost instance to cache store withversion option.

>> ActiveRecord::Base.cache_versioning = true>> post = Post.last>> cache_key = post.cache_key=> "posts/1">> before_update_cache_version = post.cache_version=> "20190527080152975653">> Rails.cache.fetch(cache_key, version: before_update_cache_version) { post }=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 08:01:52">

2. Updatepost instance.

>> post.touch   (0.1ms)  begin transaction  Post Update (0.4ms)  UPDATE "posts" SET "updated_at" = ? WHERE "posts"."id" = ?  [["updated_at", "2019-05-27 09:09:15.651029"], ["id", 1]]   (0.7ms)  commit transaction=> true

3. Verify stalecache_version in cache store.

>> Rails.cache.fetch(cache_key, version: before_update_cache_version)=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 08:01:52">

4. Write updatedpost instance to cache.

>> after_update_cache_version = post.cache_version=> "20190527090915651029">> Rails.cache.fetch(cache_key, version: after_update_cache_version) { post }=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 09:09:15">

5. Cache store has replaced old copy ofpost with new version automatically.

>> Rails.cache.fetch(cache_key, version: before_update_cache_version)=> nil>> Rails.cache.fetch(cache_key, version: after_update_cache_version)=> #<Post id: 1, title: "First Post", created_at: "2019-05-22 17:23:22", updated_at: "2019-05-27 09:09:15">

Above example shows how recyclable cache keys maintains single, latest copy ofan instance. Stale versions are removed automatically when new version is addedto cache store.

Rails 6 added#cache_versioning forActiveRecord::Relation.

ActiveRecord::Base.collection_cache_versioning configuration should be enabledto use cache versioning feature on collections. It is set to false by default.

We can enable this configuration as shown below.

ActiveRecord::Base.collection_cache_versioning = true# orconfig.active_record.collection_cache_versioning = true

Before Rails 6,ActiveRecord::Relation hadcache_key in format{table_name}/query-{query-hash}-{count}-{max(updated_at)}.

In Rails 6, cache_key is split in stable partcache_key -{table_name}/query-{query-hash} and volatile partcache_version -{count}-{max(updated_at)}.

For more information, check outblog on ActiveRecord::Relation#cache_key in Rails 5.

Rails 5.2

>> posts = Post.all>> posts.cache_key=> "posts/query-00644b6a00f2ed4b925407d06501c8fb-3-20190522172326885804"

Rails 6

>> ActiveRecord::Base.collection_cache_versioning = true>> posts = Post.all>> posts.cache_key=> "posts/query-00644b6a00f2ed4b925407d06501c8fb">> posts.cache_version=> "3-20190522172326885804"

Cache versioning works similarly forActiveRecord::Relation asActiveRecord::Base.

In case ofActiveRecord::Relation, if number of records change and/orrecord(s) are updated, then samecache_key is written to cache store with newcache_version and updated records.

Conclusion

Previously, cache invalidation had to be done manually either by deleting cacheor setting cache expire duration. Cache versioning invalidates stale dataautomatically and keeps latest copy of data, saving on storage and performancedrastically.

Check out thepull request andcommitfor more details.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.


[8]ページ先頭

©2009-2025 Movatter.jp