The indexer
There is a copy of your data that exists so pages can be fast. Here's the specific way it lies to you when it falls behind.
A client emailed to say a product was showing the old price. I opened the product page and it showed the new price. I said so, slightly too quickly, in the tone of somebody who has checked.
They sent a screenshot. Category page. Old price.
Both pages, same product, same minute, two different numbers. And the thing that took me three days to properly absorb is that neither page was wrong. They were reading from two different places, and one of those places was eleven days stale, and that is not a bug. It is the entire design, working, with a job stuck behind it.
Why a second copy of the data exists
The product page asks a narrow question: tell me everything about this one item. A database is good at that. Join a few tables, one row, done.
The category page asks a much worse question: give me the 40 items in this category, with their current prices for this customer group, in stock, matching six filters, sorted by price, and tell me how many there are in total.
Answered honestly, from the normalized tables, that is a genuinely expensive query, and it’s expensive on the page that gets the most traffic, run slightly differently for every combination of filters anybody clicks.
So the platform doesn’t answer it honestly. It keeps a second set of tables that are flat, denormalized, and shaped exactly like the questions the category page asks. One row per product per store per customer group, with the price already computed and the stock already resolved. The category page reads from those, and it’s fast, because all the thinking happened earlier.
The indexer is the job that does the thinking earlier.
An index isn't a cache of the page. It's a pre-computed answer to a question that would be too expensive to ask honestly, and it's only true until somebody changes the inputs.
The two modes, and the one that bites
You get a choice about when the recompute happens, and it is presented as a configuration detail rather than the operational decision it is.
On save. Change a price, the reindex runs immediately, the flat tables are correct within seconds. Lovely, and fine for a catalog of 400 products. Ruinous for 40,000, because now every price change in a bulk import triggers a full pass, and an import of 2,000 rows becomes 2,000 reindexes and the site is on the floor for an hour.
On schedule. Changes are noted and the recompute happens on a cron run. The site stays responsive during imports. The cost is a window. Between the change and the next successful run, the flat tables are knowingly out of date, and every listing page is serving the old answer with total confidence.
Which is what had happened. Scheduled mode, and the cron had been failing silently since the last deploy, and nothing anywhere said so. The product page kept telling the truth because it never reads the index. The category page kept telling a consistent, stable, eleven-day-old lie.
Three new habits
Check the index before the code. When two views of the same data disagree, the question is not “where’s the bug.” It’s “which of these is reading a pre-computed table, and when was it computed.” That reframe has saved me days since. It’s the first thing I check now and it’s right often enough to be worth checking first.
Know which mode each install is in, and why. Nobody documents this and everybody assumes it’s the default. On a store with a nightly ERP import it must be scheduled or the import will take the site down. On a store where a merchandiser changes three prices a week by hand, on-save is fine and removes a whole category of confusion. The wrong choice is invisible until the day it isn’t.
Treat “reindex everything” as a smell, not a fix. It is the universal remedy and it works, which is the problem, because it means the underlying question, why did this go stale, reliably never gets asked. I have run it and moved on and had the same ticket back two weeks later, which is entirely my own fault. It resets the symptom and leaves the cron still broken.
Everything fast is pre-computed
Any system fast enough to be pleasant has pre-computed something. Search indexes, materialized views, denormalized counts, the number next to the tab, a CDN.
Every one of those is the same bargain: you accept a window in which the copy disagrees with the truth, in exchange for not paying the cost of the truth on every request. And every one of them has a job somewhere keeping the window small, and the job is usually invisible, and it usually has no alerting on it because it worked on the day it was set up.
The failure is never dramatic. It’s a number that’s quietly a bit old, on the page you look at least, in the one view you never open because you check the other one.
Eleven days
The client was right, I was right, the database was right, and the system was doing exactly what it had been configured to do.
I’ve since developed an involuntary flinch about any number I didn’t watch get calculated, which I think is roughly the correct professional posture and is not doing much for me socially 😬