Repository navigation
Description
What was measured
On a faceted retail deployment (2026-08-13), sampling the live target registry:
- 94.2% of catalog targets are discovered, not sitemap-listed — ~224k urls against ~38k sitemap locs, roughly 6x the declared corpus.
- The discovered set is a different SHAPE from the declared one. Facet-count distribution:
facets in CN |
discovered | sitemap |
|---|---|---|
| 1–3 | 8% | 54% |
| 4–6 | 66% | 45% |
| 7–9 | 25% | 1% |
Crawlers walk the facet navigation combinatorially (Size:2T+Gender:Boys+Color:Blue+Brand:…+AgeAppropriate:…), and every combination becomes a permanent target. The site's own sitemap tops out at 7 facets and is mostly ≤4 — it is declaring the shallow, valuable pages and deliberately omitting the deep tail.
Only 6.8% of these ever suppress. The origin self-canonicals them, so by every check the plugin makes they are legitimate pages. Nothing in the current pipeline distinguishes "page the site published" from "page a crawler assembled".
Why it is not self-limiting
Two mechanisms that look like they should bound this, and don't:
1. The demand ladder cannot reach them if the route's interval is at the ladder floor. render.demand's ladder only moves a target to a rung FASTER than the route's granted cadence — never slower. A route granted the fastest rung therefore has a one-entry ladder and no decision is possible. Measured before the fix, with a 6h catalog route against the default [6h, 12h, 24h, 48h] ladder:
{"decisions":246,"graded":116,"singleRung":130,"levels":{"6h":1,"12h":7,"24h":13,"48h":95}}singleRung was over half of all decisions and levels contained no catalog at all; every one of 2,826 sampled discovered catalog targets had demandInterval unset. So the entire discovered corpus rendered at the route cadence forever, regardless of whether any bot had ever requested it.
That specific trap is worth surfacing in its own right — see "Ladder floor" below. It was worked around on that deployment by raising the route to 24h.
2. Nothing prunes on absence of demand. A discovered target is created on a single request and then renders on its cadence indefinitely. Suppression only fires on non-indexability, which these pages never trigger.
Why it matters
At a 6h cadence the discovered catalog corpus alone demanded ~75,000 renders/hr against a measured fleet ceiling of ~71,289/hr — i.e. the crawler-assembled tail oversubscribed the entire fleet, starving the declared corpus. Raising the route interval fixes the arithmetic but not the premise: the fleet is still spending renders on pages the site chose not to publish.
Possible directions
Not a proposal yet — these want discussion, and the right answer is likely site-contingent (which is the usual bar for a plugin option here):
- A facet-depth / query-complexity cap at discovery. Cheap and effective on this shape — a cap of 4–5 segments would have excluded the 7–9 facet tail (25% of discovered, 1% of declared). Needs to be expressed generically, not as a
CN-specific rule. - Sitemap-membership as a discovery gate for a route. "Prerender only what the sitemap declares; proxy the rest." Defensible for a route whose corpus is genuinely enumerable, and it makes the site's own declaration the authority. Pairs with Crawler-invented malformed URLs become immortal Targets: no validation at discovery + 403 exempt from suppression #81.
- Prune on demonstrated absence of demand. The ladder already tracks visits; a target that reaches the slowest rung and still records no visit over N windows is a retirement candidate. This is the general fix and needs care: deleting is self-healing (rediscovery restores it), so the failure mode is churn rather than loss.
- Let the ladder go SLOWER than the route grants. Currently the route interval is a hard floor on cadence. Allowing demotion past it would let a cold facet decay to 48h+ without changing the route, which is a smaller change than any of the above.
Ladder floor — the trap worth fixing regardless
Independent of the discovery question: a route whose renderInterval equals the fastest ladder rung silently disables the ladder for every url that route matches, and the only signal is the singleRung counter in a periodic histogram. That is very easy to configure by accident — the natural instinct is to set an aggressive route interval for an important route, which is exactly what switches the mechanism off.
Worth at least a config warning when a prerender route's interval is at or below render.demand.ladder[0] while render.demand.enabled is true.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
harper-joseph commentedon Sep 16, 2026
Closing — the gate and the purge shipped, and the ladder trap is now bounded and observable.
#127 (
prerender-v0.54.0) took directions 2 and 3:ingress.routes[].discoverTargets(defaulttrue) — a gated route's unknown URLs are still served (origin proxy on a miss) but never create a target, while sitemap-declared URLs on the same route schedule exactly as before. That is "the site's own declaration is the authority", expressed per route rather than as aCN-specific facet rule.POST /prerender_admin/discovery-purge— paced, owner-scoped,dryRun: trueby default, deletes through the resource class so the cascade holds. Two interlocks are refusals rather than documentation: a bare-origin prefix is always rejected, and a prefix whose route still allows discovery is rejected withoutforce: true— gate first, then purge, or crawlers re-mint what the purge removes.By the time that landed the corpus measured here had grown from ~224k to an estimated ~3M+ discovered targets, which is what made the ordering interlock worth encoding in the API.
The ladder floor trap. #128 (
prerender-v0.55.0) added per-routedemandFloor, which is the same lever from the other end: it bounds how fast the ladder may grant a route's pages, so a deployment can keep fast global rungs for a corpus that earns them without a breadth-sweeping crawler promoting a much larger corpus onto them. The single-rung condition this issue found (over half of all decisions, with no catalog reaching a rung at all) is now published asdemand_laddermethodsingle_rung, and thedemandFlooroption text names the behaviour directly: "A floor at or above the granted cadence leaves the route resting at that cadence (single-rung)."Not shipped: the config warning when a prerender route's
renderIntervalis at or belowrender.demand.ladder[0]. The metric and the option text cover it, but a warning would still be the cheaper signal. Also not shipped: direction 1 (a generic facet-depth / query-complexity cap at discovery) and direction 4 (letting the ladder demote past the route interval) — both are now much less pressing with the route gate available, which is why this closes rather than staying open on them.