Skip to content

Invalidations

Invalidation is the hard part of caching. It’s easy to stash data away for a rainy day, but it’s hard to know when the sun comes out and that stashed data is no-longer valid. While t87s can’t help you know when to invalidate things, it allows you to do it with pizzazz.

// Invalidate one user
await cache.invalidate(cache.tags.users('123'));
// Invalidate ALL users (nuclear option)
await cache.invalidate(cache.tags.users);
// Invalidate a specific post
await cache.invalidate(cache.tags.users('123').posts('p1'));

When you invalidate a tag, everything under that tag goes stale too. Invalidating users('123') also invalidates users('123').posts('p1') and users('123').posts('p1').comments('c9') and so on. This is called prefix matching, and it’s almost always what you want.

If you’re updating a user, you probably want their cached posts and comments to refresh too—they might depend on the user’s name or avatar or some other field that just changed. t87’s prefix matching handles this automatically. And it’s how the vast majority of caching libraries that t87s ripped off handle this as well.

Sometimes you don’t want the cascade. Maybe you updated a user’s email but their posts and comments are genuinely unaffected. In that case, you can do an exact invalidation:

// Only invalidate users:123, NOT users:123:posts:*
await cache.invalidate(cache.tags.users('123'), true);
// Or via primitives:
await cache.primitives.invalidate([['users', '123']], { exact: true });

Be careful with exact invalidation. It’s an optimization, and like most optimizations (except caching), it’s often not necessary. When in doubt, let the prefix cascade happen.

In a caching system, you should invalidate data when the data changes. But where you put that invalidation call matters.

After writes, not before. You want to invalidate after the database write succeeds, not before. If you invalidate first and then the write fails, you’ve just cleared good data for no reason.

Close to the mutation. Put your invalidation calls near the code that mutates the data. If updateUser() changes the database, it should also invalidate the cache. Don’t scatter invalidations across unrelated files where you’ll forget about them.

Consider side effects. Sometimes updating one thing affects another. If updating a user’s name should also invalidate their comments (because comments display the author name), make sure that invalidation happens. This is the part people forget.

I’m not going to insult your intelligence with even more AI-generated bullet points than the three you read above (jk, AI didn’t generate them—it generated this paragraph though… joke’s on you…). But hopefully you get the point—this is the hard part of caching. And the whole reason t87s was built is not because a lib magically fixes it, but because LLMs are so powerful that, fed the right data, they can correct a cache based on historical data in near real-time.