NJVERSE // BOOT
>NJVERSE OS v3.14 — BOOT SEQUENCE INITIATED
>loading kernel modules...
>mounting /sys/identity... OK
>applying user preferences...
>spawning interface threads...
>connecting BKK :: 13.7563°N, 100.5018°E
>SYSTEM READY
~/ / posts / 0x03
POST 0x03//perf2026.02.11 // 9 min read

the cache invalidation post you didn't ask for

naming things is hard. cache invalidation is harder. doing both at 2am with a P1 open is a personality disorder.

NJ
Nattapong Jaisabai
Software Engineer · published 2026.02.11

phil karlton said there are only two hard problems in computer science. they are, in order: cache invalidation, naming things, and off-by-one errors. this post is about the first one, which is to say, it is about all of them.

the symptom

stale data, served confidently. our cache layer was 'eventually consistent', which is industry shorthand for 'wrong, but only sometimes, and only when it matters'. a customer would update their email, see the new email on the dashboard, and then receive password resets at the old one. nobody on the team could reproduce it. nobody on the team disbelieved it either.

@cache(ttl=300)
def get_user(uid):
    return db.fetch(uid)

# elsewhere, in a different process, at 4am:
db.update(uid, {'email': new_email})
# the cache, of course, knows nothing.

the fix, and why it took three tries

write-through caches solve the problem when you have one writer. we had four. event-driven invalidation solves the problem if your bus delivers exactly-once. ours delivers somewhere between zero and several. eventually we reached for the classic answer: stop caching things you can't afford to be wrong about.

"if your cache and your db disagree, it is never the db that's lying."

we kept the cache for read-heavy listings where 'pretty close' is fine. we ripped it out from anything related to identity, money, or notifications. the dashboard got 40ms slower. the support queue got 60% lighter. tradeoffs.

EOF · 0x03 · last edit 2026.02.11// thanks for reading.
← PREVIOUS
vim, 10 years in: still no idea how to quit
2026.01.07 · //life · 4 min
NEXT →
building a 600-line container runtime in Go (because why not)
2026.03.30 · //systems · 18 min
← back to all posts