Caching Isn't Hard. Knowing When to Invalidate Is.

Learned the hard way that Redis isn't the hard part—cache invalidation is. Lessons from building a real-world backend.

When I started building Flick, I thought caching would be one of the easiest parts of the backend.

The plan sounded simple.

  • Read data from PostgreSQL.
  • Store it in Redis.
  • On the next request, return the cached response.

Done.

I wrote the code, tested it, and everything worked.

Then I edited a post.

That's when the easy part ended.

Suddenly I had questions I hadn't thought about before.

If a post changes, should I remove only that post from the cache?

What about the feed where the post appears?

What if it's cached in a user's profile?

Or under a search result?

The Redis code itself took an afternoon.

Figuring out what should happen after the data changed took much longer.

Why I Added Caching

After using Flick for a while, I noticed a pattern.

The same posts were being requested repeatedly.

The same user profiles were opened over and over.

The moderation service sometimes received identical text multiple times.

PostgreSQL handled those requests just fine.

But repeatedly asking the database the same question felt unnecessary when the answer hadn't changed.

That's when I introduced Redis.

My First Mistake

Like many developers, my first instinct was simple.

"If it's requested often, cache it."

It sounded reasonable.

A few days later I realized I had skipped the most important question.

"What happens when this data changes?"

That single question shaped almost every caching decision after that.

Redis Was Never the Hard Part

I don't think Redis itself was ever difficult.

The difficult part was deciding how long each piece of data should stay valid.

Not all data changes at the same rate.

A user's profile might change every few weeks.

A feed might change every few seconds.

A moderation result for identical text may never change unless the model is updated.

Treating all of those the same doesn't make sense.

Instead of thinking about caching as one feature, I started treating every type of data differently.

Each cache needed its own strategy.

The Bug That Changed My Thinking

While testing locally, I edited one of my posts.

The database contained the updated version.

Redis didn't.

The API kept returning stale data.

For a few minutes I thought Redis was broken.

It wasn't.

Redis was doing exactly what I had asked it to do.

I had forgotten to invalidate the cache after updating the database.

That bug completely changed how I thought about caching.

Before that day, I thought caching was mainly about performance.

After that, I realized caching is mostly about correctness.

A fast response is useless if it's wrong.

Cache Keys Matter More Than I Expected

As Flick grew, my cache structure started falling apart.

A post wasn't stored in just one place anymore.

It could appear in:

  • an individual post page
  • multiple feeds
  • a user's profile
  • search results

My original cache keys didn't scale with that growth.

I redesigned them more than once because the application kept evolving.

That taught me another lesson.

Your cache structure grows with your application.

You probably won't design the perfect cache keys on day one, and that's okay.

What I Cache Today

These days I don't cache everything.

I cache data that is expensive to generate or read frequently but changes rarely.

For example:

  • Posts, because they're read far more often than they're edited.
  • Moderation results, because running the same ML model on identical content is unnecessary.
  • Configuration, because things like banned-word lists rarely change.

Other data isn't cached unless there's a clear benefit.

Caching isn't free.

Every cache you add becomes another thing you have to keep correct.

The Question I Ask Before Adding a Cache

I used to ask:

"Should I cache this?"

Now I ask a different question.

"How will this cache disappear?"

Will I delete it after an update?

Will it expire automatically with a TTL?

Does it need both?

If I can't answer those questions, I don't add the cache yet.

Because adding Redis is easy.

Keeping cached data correct is the part that actually requires engineering.

And that has saved me far more debugging time than any Redis optimization ever has.


Related content