This might be helpful to you if you don’t understand why your Rails.cache.fetch
keeps missing, even though the value is persisted.
Let’s do a quick Friday night story time.
The other day I did this:
value = Rails.cache.fetch([current_user, 'value']) { current_user.get_value }
And later discovered that the cache was always showing a “miss” in my controller (but never in the rails console). What.
Even weirder, if I changed [current_user, 'value']
into [current_user.id, 'value']
the issue was gone. Cache was always a “hit”.
Even weirder still, the cache key in Redis definitely existed, and looked almost identical in both cases: "users/[id]/value"
vs "[id]/value"
, but this initially sent me on a wild goose chase “what is it about users/
that breaks things?”
After a bunch of debugging and messing with the Rails source, I discovered that Rails actually goes through every element of the cache key array, and tries to get its cache_version
if such method exists. User model (and all other models) does respond to cache_version
, using the updated_at
timestamp by default as the version. And because my controller kept updating last_request_at
on the user, I kept getting misses.
Mind you, I kinda knew something like that was probably happening. I didn’t know all the details of how “Russian doll caching” is facilitated, but I knew it was something like that.
What threw me completely off guard is that when Rails writes down your value into the cache, it also writes the cache_version
alongside your value. In the value, not in the key. It encodes the whole value as a binary that isn’t human-readable, and embeds the version into that binary. Then, when it reads the value, it considers the value missing if there’s a version mismatch, despite the value being stored at that key!
So… beware if you’re using Rails.cache.fetch
: it misses every time you update the model that you included in the cache key, even when the generated key string matches and exists in your cache store.
If you want to get a more controlled behavior, use model.id
, not model
in your cache key array.
I’m almost certain that there’s a good reason why it works this way, but for someone new to Rails this can cause a lot of frustration, so just want to put it somewhere on the internet.