Two True Facts and a False Connection
Fact one: most cards on the reader's screen had no picture. Fact two: thousands of articles in the store had never been through the enrichment passes that find pictures. Both were true, both were measured, and the connection between them was invented by everybody who looked at it, including me.
The plausible story writes itself — there is a backlog, the backlog is why things are unenriched, so raise the budget or run the passes more often. The measurement that killed it took one query: how many articles are actually on a screen? Union the visible head of every enabled shelf and deduplicate. The answer was a couple of hundred, against a store of several thousand. Around two percent.
The Other Ninety-Eight Percent Is Never Looked At
This is the part that makes the backlog not merely unimportant but actively misleading. Articles that fall off the visible head are not queued for later attention; retention composts them in a fortnight and nobody ever scrolls that far. So the backlog is not work waiting to be done — it is work that will never be needed.
Enriching it is not slow, or expensive, or premature. It is pointless, in the precise sense that the output has no consumer. And an enrichment budget aimed at it lands almost entirely on rows that will be deleted unread, no matter how generous you make the budget.
Then What Was Actually Wrong?
The same measuring pass answered that too, because once you are counting the visible head you may as well count what is in it. Seventy percent of the cards on screen carried no picture — and the overwhelming majority of those were a kind of row the picture-finding pass skips deliberately, without fetching, because that kind of row provably has no picture to find.
So the real situation was the exact inverse of the assumed one. The problem was not a budget too small to reach the backlog; it was that no budget of any size could ever have helped the specific cards that were failing. That is not a tuning problem. It is a different problem entirely, and you cannot see it from the backlog.
Compute the Head After the Projection, Not Before
One subtlety worth stating, because it is easy to get wrong when you build this: the visible head must be computed after everything that changes what reaches the screen — after duplicates are collapsed, after muting, after any promotion. Compute it from the raw query instead and you will aim your budget at rows that the projection is about to remove, which reintroduces the original problem in a form that looks like it was solved.