RedisMemo v1.0.0 - a new way of caching!

Hi there :wave:,

I’m excited to share a tool we built at CZI to cache database queries and 3rd API calls efficiently and robustly: https://github.com/chanzuckerberg/redis-memo

With a few line of configurations and a bit of magic :mage: ,

class Post < ApplicationRecord
  extend RedisMemo::MemoizeQuery
  memoize_table_column :id
end

all the ActiveRecord querying methods that send a SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 query will be cached (and invalidated) automatically!!

  • Post.where(id: [1,2,3]).to_a
  • Post.find(1)
  • Post.find_by_id(2)

With RedisMemo, add caching is super easy – you don’t need to change the call sites one by one and worry about breaking things!

It shines when caching records association. With the old fashioned way, you’d need to do something like this:

class User < ApplicationRecord
  def invalidate_cache
    posts.each do |post| # this could take many seconds
      CachedPost.new(post.id).invalidate
    end
  end
end

If the post cache contains some information related to a specific user. And of course, looping through all the posts is slow and expensive!

With RedisMemo, invalidation is a super-fast (<= 2ms). If a user record has changed, millions or even billions of accoicated cache entry will be invalidated almost instantly.

class Post < ApplicationRecord
  extend RedisMemo::MemoizeMethod
  def post_cache
    "#{author.display_name} wrote #{title}"
  end
  memoize_method :post_cache do |post|
    depends_on Post.where(id: post.id) # extracts dependency from the ActiveRecord::Relation
    depends_on User.where(id: post.author_id) # no database queries being sent
  end
end

Prior to RedisMemo, adding caching correctly has been a struggle for us:

  • when to invalidate the cache? especially in database transactions
  • how to combine the caching API with the ActiveRecord querying interface?

With RedisMemo, my team was able to move half of the database read traffic to Reids, by adding few lines of configurations on some heavily used models :star_struck:

Check it out! You might find it useful :slight_smile:

2 Likes

We also made a blog post: RedisMemo: Caching is made easy. A novel caching system for Ruby… | by Donald Dong | CZI Technology | Medium

The blog post has more in depth explanations – about how it all works under the hood!

Please feel free to reach out too :slight_smile: thanks!

In order to use depends_on to extract dependencies from a Relation, we need to memoize the referenced table columns on the Post and User model:

class Post < ApplicationRecord extend RedisMemo::MemoizeQuery memoize_table_column :id end It’s also possible to pull in existing dependencies on other memoized methods and perform hierarchical caching.

We’re aware of Shopify/identity_cache, a gem that provides query caching with automatic cache invalidation; however, it is affected by most of the other issues we want to address when caching queries at the application-level. You can learn more about the challenges with using the Rails low-level caching API or other caching technologies such as IdentityCache here.

1 Like