Skip to content
Kien

A cache key is a contract about what "the same search" means

· 4 min read

1 key · 2 tenants

A key says two requests deserve the same answer, and every field left out of it says that field cannot change the result. In supplier aggregation the tenant, its supplier set and its markup all change the result — so a key built from the traveller's search parameters alone serves one tenant's prices to another, and nothing errors.

  • Redis
  • caching
  • architecture
  • suppliers
On this page
  1. What a key claims
  2. What "the same search" means here
  3. The opposite mistake also costs money
  4. Invalidation is the second half of the contract
  5. What changed
  6. The rule

What a key claims

A cache key is not an identifier. It is a claim: any two requests that produce this key deserve the same answer. Read it that way and every field you left out becomes a second claim — that this field cannot change the result. The cache bugs I have actually had to fix were almost never eviction or serialisation bugs. They were one of those second claims being false.

What "the same search" means here

Hotel search is asynchronous: a queue with one job per supplier, each guarded by a Redis lock, workers writing into a shared cache hash plus a meta hash marking which suppliers have finished, the HTTP layer aggregating whatever has landed and reporting a progress fraction, with an absolute 30-second deadline over all of it. The cache is not an optimisation bolted onto the side of the search. It is where the answer is assembled. That makes the key the identity of the answer itself, and a wrong key does not slow the search down — it returns someone else's search.

The traveller-facing parameters are the obvious half: destination, dates, occupancy. The trap is that they are not sufficient. Tenants — "switches", in our vocabulary — each have their own supplier set and their own markup configuration. Two tenants sending byte-identical searches are not asking the same question: one may have suppliers the other cannot see, and each applies different markup to whatever comes back. Leave the tenant out of the key and the second one is served the first one's prices, from the first one's suppliers. Nothing throws. The prices are real prices, just not theirs.

White-labelling makes this sharper rather than softer. One integration is resold under many brand codes, with a sparse per-supplier settings table holding only the keys that differ from the defaults. A setting that differs is by definition a field that changes the answer — so either it belongs in the key, or the tenant identity that determines it does.

The opposite mistake also costs money

Overcorrect and you get the mirror failure: a key that carries a request ID, or a timestamp at second resolution, or anything else unique per call. Now the key is never reused. Every search fans out to every supplier again — one job per supplier, each carrying that supplier's own latency inside a 30-second budget — while the cache fills, evicts, occupies memory and never once hits.

This one is quieter than serving the wrong tenant's prices, because nothing is wrong. It is only slow and expensive, and from the outside it looks exactly like the suppliers being slow, which is a diagnosis nobody argues with. Both failures are the same mistake with opposite signs: nobody wrote down what the key was supposed to assert.

Invalidation is the second half of the contract

The contract has a second clause — and when this stops being true, someone flushes. The usual trigger is the mapping data underneath a region: change which hotels belong to it and every cached search over that region becomes a stale answer, correctly computed against data that no longer exists.

"Flush everything" is not the fix, it is a stampede. Dropping the whole cache sends every subsequent search back out to every supplier at once, against exactly the rate limits the cache exists to stay under. The flush has to be scoped to the keys the change actually touched — and that is only possible if the key structure lets you name them. Which is a property the key's design decides months before anyone needs it.

What changed

The key stopped being built and started being declared. Before the key is written, the assertion is written in words, in the design note: two requests are owed the same answer when they share tenant, supplier set, markup configuration, destination, dates and occupancy. Then every field in the key has a sentence behind it, every field left out had to be argued out loud, and the invalidation clause has somewhere to live — this change touches these keys.

I have no before-and-after hit-rate number for this. Cache hit rate per key shape was not something we tracked, and I am not going to invent one; the argument here is about correctness of the assertion, and the money side of it I can only describe, not quantify.

The rule

Write down what the key asserts, in words, before you write the key. If the sentence is hard to finish, the key is not ready — and if the sentence is easy but a field is missing from it, you have just found the tenant whose prices somebody else is about to see.