HOT-set management
Purpose: how to manage the snippet’s redirect / HOT set — the per-id pins that make the edge snippet 307 deterministically to a pre-rendered variant object, instead of falling through to the worker.
Applies to: the watermark-router edge snippet (apps/snippet/watermark-router.snippet.js), its HOT set, and the generate / verify / deploy / purge tooling under scripts/cloudflare/. For any concrete host/customer/id, decode the code names (H01–H14, C01/C02, S01–S04, K01, …) in the internal decoder (not published).
What the HOT set is
Section titled “What the HOT set is”The snippet is a referrer-blind, binding-less edge function that runs before the worker. For a request it can do one of three things:
- Pass through to the worker (
fetch(request)) — the default, and the source of truth for clean-vs-watermarked. 307to the public variant object (cache.vumbnail.com/thumbnails/{id}/wm.jpg), skipping the worker entirely.- Short-circuit malformed paths to a shared error asset.
The 307 redirect path fires only when both conditions hold:
const watermark = shouldWatermarkRequest(request); // referrer host in WM_DOMAINS, not payingif (!watermark) return fetch(request);if (!HOT.has(videoId)) return fetch(request); // <-- the HOT-set gatereturn Response.redirect(buildWatermarkRedirectLocation(videoId), 307);So HOT is the per-id allowlist that decides which ids get the cheap deterministic edge redirect vs. which fall through to the worker. It is a pure cost / determinism lever — it is not a correctness boundary (the worker still decides clean-vs-wm for anything that passes through).
Why it exists
Section titled “Why it exists”Every HOT hit is a worker request (and ~1.13 KV reads) avoided. At illustrative blended rates of ~$0.50/1M worker requests and ~$0.50/1M KV reads, the avoided-worker path is worth roughly $1.07/1M skipped requests. The HOT set concentrates that savings on the highest-volume reviewed referrer ids. See cost-safe-cache-purging for the billing-shift caveat (worker+KV → R2 Class B).
Decay semantics (read this before pinning anything)
Section titled “Decay semantics (read this before pinning anything)”A HOT id is just a string pin. When a catalog rotates and an id stops being requested, it is eventually pruned out of HOT. Decay does not fall back to clean.
| Belief | Reality |
|---|---|
| ”Rotated-out id leaves HOT → snippet passes through → served clean” | FALSE for any host still in the worker referrer cohort. |
| What actually happens | The id silently leaves the redirect set and falls back to the worker path. If the worker still watermarks that referrer cohort, the id is still watermarked — by the worker, not the edge. |
| What decay actually erodes | Delivery determinism, not coverage. The mark stays; the cheap deterministic edge 307 is what is lost. |
Consequences:
- Determinism (and the cost savings) holds only for HOT ids of hosts that have been removed from the worker cohort. While a host stays in the cohort, a non-HOT id re-pins
wmvia the worker. See referrer-cohort-and-delivery-policy. - “Cost looks fine” is not a success signal for a rotating host. A decayed HOT set bills cleanly while determinism rots. Track coverage (
currentHotCoveragePct), not dollars, as the health metric. - Disabling the snippet does not restore clean — it routes traffic back to the still-cohorted worker. HOT-set work never substitutes for cohort changes.
Refresh cadence by catalog churn
Section titled “Refresh cadence by catalog churn”Cadence is driven by how fast the host’s catalog rotates, not by its volume:
| Catalog type | Cadence | Rationale |
|---|---|---|
| Rotating / fast-decay (e.g. auction lots, seasonal) | Daily | Top ids churn day-to-day; stale pins decay out fast. |
| Stable catalog | Weekly (or on coverage drop) | Top ids are durable; refresh only when coverage dips. |
| Single-hero / single-video host | Pin once | One id carries ~all traffic; no recurring refresh. |
Reference instances (decode in the internal decoder (not published)): a rotating-catalog host like H03 → daily; stable hosts → weekly; a single-hero host like H02 (and a pin-once host like H07) → pin once.
Coverage floors that gate a refresh / hold:
- Rotating host: hold and refresh if
currentHotCoveragePct < 0.85or it drops>5absolute points batch-over-batch. - Stable host: floor
0.80.
scripts/cloudflare/analyze-whale-hotset.ts computes currentHotCoveragePct per host but is report-only today — it does not exit non-zero below the floor. Until a coverage-floor exit code exists, a rotating host kept in HOT requires either a wired alarm or a written manual daily re-verify.
The 32KB package budget — PRUNE, do not append
Section titled “The 32KB package budget — PRUNE, do not append”The snippet package has a hard 32 KB (32,768 B) Cloudflare limit. The HOT set is the dominant variable-size payload, so it must be actively pruned, never blindly grown.
Rules:
- Prune, don’t append. A daily refresh splices in the new top ids for a host and removes the decayed ones. Appending forever blows the budget.
- Per-host id caps. Cap each rotating host at roughly 20–30 ids. Past the cap you are pinning long-tail ids that decay before they pay off.
- Keep ≥4 KB margin. Fail any generated/
--writeoutput that projects> 28,672 B(4 KB margin under the 32 KB cap). Several KB of headroom must stay free for comments, tests, and small safety checks. - Per-id referrer comments are load-bearing. The verify/analyze parsers string-scan
const HOT = new Set([…]);and the trailing// hostcomments. Any writeback must preserve them. - Never reformat the file during a refresh/flip.
bunx biome formaton the snippet reflows theHOTset and blinds the string-scan parsers → false-green gates. Forbidden during any HOT-set change.
Approximate per-id cost: id.length + 6 bytes (quotes, comma, indent). Use that to project a candidate before writing.
Size-budget sizing reference
Section titled “Size-budget sizing reference”Illustrative scenario sizing from a prior expansion (your numbers will differ):
| Scenario | Est. source size | Headroom under 32 KB |
|---|---|---|
| Baseline snippet | ~12,000 B | ~20,700 B |
top50-per-host | ~15,200 B | ~17,500 B |
top100-per-host | ~18,300 B | ~14,500 B |
top200-per-host | ~22,500 B | ~10,300 B |
coverage50-per-host | ~25,200 B | ~7,600 B |
| ”everything at once” | ~32,100 B | ~600 B — do not ship |
Generation + verify + purge workflow
Section titled “Generation + verify + purge workflow”HOT-set changes are externalized and gated. Never hand-edit ids straight into a deploy.
1. Analyze / select
Section titled “1. Analyze / select”Pull fresh path-referrer and cohort artifacts, then rank ids per host:
bun scripts/cloudflare/analyze-whale-hotset.ts \ --path-referrer-json <fresh-path-referrer.json> \ --cohort-report-json <fresh-cohort-report.json> \ --previous-path-referrer-json <yesterday.json> \ --out-json artifacts/cloudflare/whale-hotset-opportunity.<date>.jsonThis reports per-host concentration, coverage targets, top-id overlap vs. the previous snapshot (stability), how many ids each scenario adds, and whether the resulting snippet still fits the 32 KB limit. Use top-set overlap to judge churn: low overlap = rotating = shorter cap + daily cadence.
2. Splice into HOT (prune + cap)
Section titled “2. Splice into HOT (prune + cap)”Apply the delta for rotating hosts — add the new top ids, drop the decayed ones, honor the per-host cap, preserve referrer comments. A --write / CONFIRM_HOTSET_WRITE=YES mode must fail before writing if the projected source exceeds the 28,672 B budget.
3. Backfill the variant object BEFORE it can be redirected to
Section titled “3. Backfill the variant object BEFORE it can be redirected to”A HOT id that has no wm.jpg in R2 would 307 users straight into a 404. So ensure the object exists before the pin can go live:
# verify only (gate — exits non-zero on any missing wm.jpg)bun scripts/cloudflare/verify-watermark-snippet-hotset.ts verify
# backfill missing objects (renders wm.jpg from the clean source)CONFIRM_ENSURE=YES CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... \ bun scripts/cloudflare/verify-watermark-snippet-hotset.ts ensureverify parses every quoted id out of the HOT set and HEADs each …/wm.jpg; any non-200 fails. ensure pulls the clean source (R2 clean.jpg, or the worker with a clean-forcing referer), applies the watermark, and writes wm.jpg. For a rotating host, source the clean image from R2 clean.jpg, not a worker fetch — a worker fetch for a cohorted host can return an already-watermarked image and bake a double mark that the 200-check won’t catch.
Backfill spends money / writes R2 objects — keep estimates under the repo cost-estimate threshold or commit a written estimate (see cost-safe-cache-purging).
4. Cross-check the parse count
Section titled “4. Cross-check the parse count”Catch a reflow-blinded parser: the parsed HOT count must equal the rg -c quoted-id count must equal pre-edit + added. A mismatch means the file was reformatted and the gates are lying.
5. Deploy, then purge public-cache-lag 404s
Section titled “5. Deploy, then purge public-cache-lag 404s”After deploy goes live (content sha matches, rule enabled), purge the bare cached keys whose prior 404/clean cache entries would otherwise mask the new pins:
- Just-in-time re-HEAD each
wm.jpgand require200before purging its key; skip + flag any404(do not purge into a 404 storm). - A newly-backfilled object can return a cached
404at the edge from a prior miss — purge those specific public URLs so the first real viewer gets the object, not the stale miss. - Respect purge rate limits and use the correct zone (a wrong-zone purge silently no-ops). Full purge mechanics live in cost-safe-cache-purging.
Pre-deploy gates (HOT-set specific)
Section titled “Pre-deploy gates (HOT-set specific)”-
verify-watermark-snippet-hotset.ts verify→ 0 missingwm.jpg. - Parse-count cross-check passes (parsed ==
rg -c== pre-edit + added). - Projected source size
< 28,672 B(4 KB margin under 32 KB). - Per-id referrer comments intact; file not biome-reformatted.
- Every new id maps to a public
wm.jpg(200) from multiple colos. - Coverage above floor for every rotating host being kept (or written manual re-verify).
- Rollback snapshot regenerated for this batch (see safe-deploys).
Common failure modes
Section titled “Common failure modes”| Symptom | Likely cause | Fix |
|---|---|---|
HOT id 307s to a 404 | wm.jpg never backfilled, or purged while still HOT | Re-ensure; never delete a still-HOT id’s warm wm.jpg. |
| Gates green but flip didn’t take | File was biome-reformatted → parsers string-scanning HOT blinded | Restore formatting; re-run parse-count cross-check. |
| Cost looks fine but determinism eroding | Decayed HOT set on a rotating host (silent worker fallback) | Track currentHotCoveragePct, not dollars; refresh HOT. |
| Snippet over 32 KB after refresh | Appended instead of pruned; per-host cap exceeded | Prune decayed ids; enforce ~20–30/host cap. |
| Non-HOT id still watermarked after purge | Host still in worker cohort → worker re-pins wm | Remove host from cohort in the same release (referrer-cohort-and-delivery-policy). |
Related runbooks
Section titled “Related runbooks”- referrer-cohort-and-delivery-policy — the worker cohort that the HOT set’s determinism depends on.
- cost-safe-cache-purging — purge mechanics, rate limits, zone selection, cost estimates.
- staged-rollouts — flipping hosts in batches; HOT/cohort ordering.
- safe-deploys — snippet rollback snapshots and the git-is-the-only-content-rollback rule.
- The internal decoder (not published) — decoder for every host / customer / id code name used above.