unnamed.png
Ë
Jeff Dwyer By Jeff Dwyer • May 29, 2018

What Makes a Good Cache Key?

Your fingers are poised above the keyboard, just waiting to pop something in the cache... but what should I choose for my cache key? Let's take a look at some important considerations:

What makes a good cache key?

  1. It should be unique
  2. It should be clear
  3. It should be easy to invalidate

 

Unique & Clear

Never trust that you are the only client for your cache. I like to assume that at some point some other rogue app will start sharing my cache and nobody will think to mention that to me. My presumption is that this rogue app will manage to pick the EXACT SAME keys that I picked and suddenly my app will be reading who knows what. Two techniques to avoid this:

  1. Prefix/namespace the snot out of you cache keys.
  2. Pick good clear names

 

Bad Cache Key Examples:

  1. counter
  2. page_contents

 

Good Cache Key Examples:

  1. PrefabCloud:API:TokenBucket:UsageCounter
  2. PrefabCloud:www:documentation:page_contents

 

 

Easy to invalidate

Ahhh invalidation. As we know caching is easy, invalidation is hard. There’s a few ways to invalidate a cache key:

  1. Manually delete the key
  2. Time based expiry 
  3. Start using a different key

Let's look at some advantages and disadvantages of each.

 

#1 Manual Deletion

In general if you can possibly avoid #1 on this list you’re going to be happier. It sounds easy, but I’m going to go out on a limb and say that 5% of JIRA tickets say “Bug: need to clear cache in this weird edge case”. It’s tricky, error prone and adds nasty tendrils into your code. Blech.

#2 Time based expiry

Expiring after X seconds is a great tool in general… but there’s nothing that stinks more than knowing your app is erroring because of something in the cache and not being able to do anything until the cache clears itself in an hour/day or whatever your TTL is. No PM ever has enjoyed hearing “we need to wait for the cache to expire”.

#3 Start using a different key

A powerful ally this one and my default choice for cache key invalidation strategy. How key-based cache expiration works is a really nice description of how this works in Rails. It is an elegant way to not have to think about cache invalidation very often.

But!! What happens when you want to change what’s in the cache? Imagine you’re doing local/staging development and you want to change the format of the JSON blob you’ve put in the cache but every time you do you need to futz with the cache key so it doesn’t load old things. Sound familiar? If you're like me your cache key starts getting semi-random letters tacked on to the end of it that I then have to remember to avoid checking into source control. What if we made this key futz-ing a bit more systematic.

The Dynamic Cache Key Postfix

postfix = config.get('doc.contents.cache.postfix')
cache_key = "prefab:www:doc:contents#{postfix}"

In this case I’d set the postfix to “v0”. You can go about your merry way, but now, whenever you need to you can go into your configuration management tool of choice, something like Consul, Zookeeper, Prefab.cloud and change doc.contents.cache.postfix = v1 to immediately force a “mass eviction” of this particular section of the cache. Brilliant!

Mostly it’s good advice to avoid writing code you don’t know you’ll need, but I’ve found that cache postfixes are something that can worth be using prophylactically. When you realize that something has gone wrong and your cache is bogus, the ability to simply change “v0” to “v1” and instantaneously invalidate a section of your cache without deploying a fix is pretty awesome.

For local & staging development it can be super useful too. Pretty often during development I will find that I want to change the format / signature of what is being cached. If I’ve already put some things in the cache now I need to deal with weird errors when old versions are loaded. With a postfix I can just tweak a config value and not need to commit a new cache key.

Any downsides? Not really. As a reminder the Prefab config.get() call is not an API call. It’s just a hash lookup, so we’re not making our cache calls significantly slower.

Screen Shot 2018-05-28 at 9.05.50 PM

Recap:

When in doubt, prefer “reading a new key” to “deleting invalid keys”. Consider adding dynamic cache key postfixes to your keys to make it easy to invalidate parts of your cache without a deploy.

Happy caching!