Ognjen Regoje bio photo

Ognjen Regoje
But you can call me Oggy


I make things that run on the web (mostly).
More ABOUT me and my PROJECTS.

me@ognjen.io LinkedIn

Using Elasticsearch results to generate cache keys in Rails

#devops #elasticsearch #rails #technical

A pretty neat thing I realized while building the search for Supplybunny is that you can use the results from elastic search to generate the cache keys that rails generates.

The default format of cache key that rails would generate for a Post model would be of the format posts/<id>-<updated_at> where <id> is the actual ID and <updated_at> is post.updated_at.utc.strftime("%Y%m%d%H%M%S000000000").

So, <%cache post%> is equivalent to <%cache "posts/#{post.id}-#{post.updated_at.utc.strftime("%Y%m%d%H%M%S000000000")}%>.

If we are caching a fragment, for instance :comments, that isn’t tied directly to the Post cache key, we just need to add that as a string to that cache key. For the post above the cache line would end up being: <%cache "posts/#{post.id}-#{post.updated_at.utc.strftime("%Y%m%d%H%M%S000000000")}/comments%>.

All segments are joined with / so we can push keys into an array and then .join("/") them.

Putting all of the above together, we get something like this:

  z = post.to_h["_source"]
  # Where post is a member of the results array from Elastic search.
  # We need to access the _source since that's what contains the actual Post instead of the
  # elastic search information such as score.

  h = ["posts", "#{z["id"]}-#{DateTime.parse(z["updated_at"].to_s).utc.strftime("%Y%m%d%H%M%S000000000")}"]
  # Cache key for the post itself.


  # Following is an example to illustrate a point, there are of course better ways to
  # cache has_many relationship. You would also need to have the comment content indexed
  # which is not a good idea either.

  h.push z["comments"].map{|c| ["comments", "#{z["id"]}-#{DateTime.parse(z["updated_at"].to_s).utc.strftime("%Y%m%d%H%M%S000000000")}"]}
  # Go through the comments and generate the cache keys just like for post.

  # Cache it separately from the post itself.
  h.push "post_with_comments"

  # Then the final cache key ends up being:

  cache h.join("/")

  # which is equal to

  cache [post, post.comments, :post_with_comments]
  # with the big benefit of not having hit the database at all.

This made a big difference in preformance, especially in JSON requests that could be cached in a similar way.