Organization-wide Cache Invalidation

Key-Tracking — A cache-invalidation strategy for organizations

Ikechi Michael
3 min readJun 8, 2024

Caching Invalidation is hard, Yes. And it’s even harder when you have to do it at scale in an organization. Keeping track of cache keys to be invalidated when you don’t even know all the possible keys that can exist is impossible without a good strategy.

In this article, I will be presenting one such strategy, and aiming to do it in a way that is easy to understand.

We will have things called Cache-Key Trackers. An HTTP request will “tag” these Key-Trackers during its lifecycle, and every Cache Entry persisted after, will have its Cache Key tracked within by active Key-Trackers in the request’s lifecycle.

These Key-Trackers will be available and known organization-wide (such as via a reusable package), and have known Parameters that can be invoked with arguments e.g.

Trackers.User(Guid UserId);
Trackers.Book(Guid BookId);

Key-Tracking in the Request Pipeline

So let’s say:

  • an HTTP request tags the User Key-Tracker when it authorizes a User with Trackers.User(id)
  • it caches the User’s information under a cache key /users/:id
  • when the User selects a Book to read, we tag Trackers.Book(bookId) and cache the book under /books/:id
  • Note that now, Trackers.User(id) is tracking two keys, /users/:id and /books/:id , while Trackers.Book(bookId) is tracking just /books/:id

So how does this help us?

Let’s say the User updates their profile name. We have to invalidate the cache, and instead of invalidating the /users/:id Key, we use Trackers.User(id).Invalidate() , which then invalidates all cache keys that it is currently tracking.

Now imagine that the tracked cache keys are not stored in-memory, but are persisted in an organization-wide storage.

So Trackers.User(id).Invalidate() , has access to all the keys being tracked by that tracker, across all applications in the organization, and it can invalidate all of them at once.

Doing this, will invalidate both /users/:id and /books/:id , and we can do the same when we want to invalidate Trackers.Book(bookId).Invalidate() , which will invalidate /books/:id .

Restricting Key-Trackers to Glob Patterns

You might be asking,

“But what if I just want to invalidate /users/:id and not /books/:id ”?

And that’s a great question.

We can have each Key-Tracker filter the keys by a List of Glob patterns e.g.

The Tracker.User(id) could internally filter by: /users/* , so that it will only track and invalidate/users/:id and not /books/:id .

Caveat: Using glob patterns will require that you know upfront, what the key patterns will look like, because if someone caches with key “users-{id} instead of /users/:id , such a key will not be tracked.

Key-Trackers can tag each other

What about if we wanted to invalidate cache entries for all users i.e. /users/* in the organization?

We could have the Tracker.User(id) , internally tag a Tracker.Users() Key-Tracker, so that we have the ability to call Tracker.Users().Invalidate() whenever we feel like it.

Summary

The Key-Tracking Cache Invalidation Strategy gives an organizations a much smaller set of things to worry about for efficient cache-invalidation.

Instead of keeping track of individual keys, an organization can keep track of Key-Trackers, which are like storage-buckets for cache keys that can be invalidated at once.

--

--

Ikechi Michael

I’ve learned I don’t know anything. I've also learned that people will pay for what I know. Maybe that's why they never pay.