Our marketing pages were the only ones never cached, and one line of personalisation was why
We audited what our own site actually answers, and the result came out backwards from what we assumed.
A couple of hundred pages, most of them a template matrix built for search traffic. Those were cached properly. The pages the whole matrix funnels into, the homepage and pricing and the free generators and the legal pages, were answered no-store, every one of them. The pages we care most about were the only ones a shared cache was never allowed to keep.
The cause was one line each, and none of it was a config mistake. Our generator page had a link meaning save this to my account, and it was computed from whether you were signed in, so a signed-in visitor skipped the signup step. One branch, on the highest traffic page we have, and it gives the page two bodies. A page with two bodies cannot be handed to a shared cache, so the framework did the correct thing and marked it uncacheable, silently, for months.
What fixed it was not caching harder. It was moving the decision to the last place that still has a session to make it with. The link now points at signup for everybody, and signup itself forwards a visitor who already has one. The page went from two bodies to one, and became cacheable as a side effect.
Nine pages moved. One deliberately did not, and that is the more interesting half. Our pricing page branches on the same thing, but the branch does not change wording, it changes where the button goes: a customer is sent to the page that sells them more, everyone else to signup. Pinned to the signed-out answer, a customer pressing the main button lands on a signup form, and our signup ends by authenticating you, so filling it in with a second address moves their session into a new empty account. The rule we wrote down afterwards is that a branch which only changes wording costs nothing to pin, and a branch that changes a destination is not the same thing and does not go on the list.
Then the part I did not see coming. A cached page can fail on who is asking, and it can also fail on when. Our homepage argues on price, so it has to print one, and our packs reprice upwards as they sell. The page is held for five minutes and may be served stale for up to a day, so a figure that was true when it rendered can be quoted to somebody a day later, below what they would actually be charged. Nothing looks wrong at any point. The page renders correctly, and the number was right when it was made.
The rule that came out of it is that a cached page may print only a figure that no band moves, and that figure has to be selected by the property that makes it safe rather than by its name, so a repricing withdraws it from the page instead of quietly making the page wrong. The test reads the rendered body rather than the method behind it, because the next hard coded price written into the prose would never go near that method.
Two questions, and I would rather have the second one answered. What did you find your cached pages were varying by? And has anyone got a check that catches a value going stale inside a cache, as opposed to a value being wrong at the moment it was rendered?
Replies
the property that decides your second question is whether the cached value is idempotent to recompute or just advisory. a stale price is dangerous specifically because its advisory, its printed once and then trusted with no live check behind it. the catch has to live on the read path rather than the write path, you dont ask whether the cache is within its ttl, you sample some percentage of served responses and diff the printed number against a live recompute done right then. disagreement tells you the cache actually lied and for how long, the ttl only ever tells you the window it could have lied in, never whether it did.
my own cached pages vary by locale and a signed in flag, same shape as yours, one branch nobody had flagged as a branch until it started causing problems. the part that got me wasnt the caching layer, it was that the same flag existed in three places, the page, the api response, and a document generated later from that response, and only two of the three read it the same way. fixing the cache did nothing for the third one