fix: negative-cache property/Connect JSON fetch failures - #407
fix: negative-cache property/Connect JSON fetch failures#407JakeSCahill wants to merge 12 commits into
Conversation
The property-tooltip script caches successful JSON fetches in localStorage for 24 hours but never caches failures, so when the referenced attachment does not exist (version drift between release tags and generated JSON), every page view re-requests a URL that is guaranteed to 404 (~17k requests/day). The Bloblang script has the same problem, plus a hardcoded fallback-version chain that multiplies the misses. Property tooltips now store a failure marker (1 hour TTL, versioned by latest-redpanda-tag) and resolve to an empty lookup while it is fresh. The Bloblang loader tracks per-URL failures for 1 hour and skips URLs that recently returned an error response. Preview mode is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Deploy Preview for docs-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe changes add localStorage-backed failure caching for Connect JSON and property JSON fetches. Connect requests skip URLs recorded as recently failed and record non-OK responses outside preview mode. Property data cache entries now distinguish failed fetches from successful results, using a one-hour failure TTL instead of the 24-hour success TTL, and failed fetches store version and timestamp metadata. Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant tryFetchConnectJSON
participant localStorage
participant ConnectJSONEndpoint
tryFetchConnectJSON->>localStorage: Read recent URL failures
localStorage-->>tryFetchConnectJSON: Return failure status
tryFetchConnectJSON->>ConnectJSONEndpoint: Fetch URL when not recently failed
ConnectJSONEndpoint-->>tryFetchConnectJSON: Return non-OK response
tryFetchConnectJSON->>localStorage: Record URL failure
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Right idea — an uncacheable failure retried on every page view is the actual mechanism behind the 404 storm, and negative caching is the correct defense-in-depth alongside redpanda-data/docs-extensions-and-macros#224. The Bloblang half looks good. The property-tooltips half has a problem worth fixing before merge. Fix before mergeThe failure marker writes to the same key as the successful cache, and it's written for far more than 404s. localStorage.setItem(
CACHE_KEY,
JSON.stringify({ version: cacheVersion, timestamp: Date.now(), failed: true })
)Two things compound here:
Together that means one transient blip does this: valid 24h cache destroyed, and for the next hour every page sharing that This also doesn't match the PR body, which says "Only deterministic HTTP errors are marked — transient network errors still retry." That's accurate for Two changes fix it:
SuggestionBloblang marks on any if (!response.ok) {
if (!isPreviewMode()) markFetchFailure(url)
return null
}Less severe than above — the failure is scoped to one URL and can't evict anything else — but a transient CDN 5xx still disables Bloblang tooltips for an hour. Same narrowing to 404/410 applies. Verified
Minor noteThe Bloblang fallback URL is built as |
Review findings: the failure marker shared CACHE_KEY with successful data and was written from the generic catch, so one transient blip (5xx, offline, parse error) wiped a valid 24h cache and left tooltips dead for an hour for that user. - The marker now lives under its own key (redpanda-properties-missing) and can never overwrite cached data. Valid data is preferred on read. - It is written only for HTTP 404/410 (the resource does not exist for this version); transient failures are not cached and simply retry on the next page view. - A successful load clears the marker.
|
Fixed per the review: the missing-resource marker now lives under its own key ( |
kbatuigas
left a comment
There was a problem hiding this comment.
Medium — Transient Connect JSON failures are cached for one hour
src/js/16-bloblang-interactive.js:243-246callsmarkFetchFailure(url)for every non-successful HTTP response. This includes temporary failures such as429,500, and503.After one transient response, subsequent pages skip that URL for an hour, leaving Connect/Bloblang documentation unavailable even after the service recovers. The property-data implementation in this PR correctly limits negative caching to permanent
404and410responses.Please apply the same
404/410restriction to Connect JSON caching and add tests confirming that temporary failures are retried.
Review feedback from kbatuigas: markFetchFailure fired for every non-OK response, so one transient 429/500/503 suppressed Connect JSON fetches for an hour. Now only deterministic missing-resource responses (404/410) are marked, matching the property-tooltips behavior. Also clear the properties missing-marker before the cache write so a quota error on setItem cannot leave a stale marker after a successful fetch. Adds a puppeteer test suite (tests/negative-cache) that serves the page from a fake production hostname via request interception and asserts: 404s are negative-cached and expire after the TTL, 429/5xx are retried on every view, and success clears the marker and populates the cache. Wired into npm run test:all and the Bloblang Tests workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from a deeper review pass on top of the kbatuigas fix: - The properties missing-marker was a single slot keyed only by tag, so a user browsing multiple doc versions with missing JSON thrashed it and kept firing 404s. It is now a per-URL map (matching the Connect side), pruned of expired entries on read, and a successful fetch clears only its own URL's entry. - The dataset cache and missing-marker reads now use separate try/catch blocks, so a corrupt cache entry cannot disable the marker check. - When the connect-json-url meta tag resolves, loadBloblangDocs now tries that URL exactly once instead of also walking the 5-version fallback loop (which could only re-request the same URL - up to 6 identical requests per view during a transient 5xx/429). The version lookup is skipped entirely in that case. - Corrected the Connect failure-map header comment (only 404/410 are recorded, not any error response). Test suite expanded to 45 assertions: 410 responses, network errors, JSON parse errors, preview mode (localhost) never negative-caching, a DOM assertion that a tooltip actually attaches after a successful fetch, and tightened exact request counts. Workflow path triggers now include package.json/package-lock.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@kbatuigas Fixed in 08f011c: Tests added as requested: a new puppeteer suite ( A follow-up review pass surfaced two more issues, fixed in 3082683:
|
The connect-json-url meta tag is now resolved at build time against the attachments that actually exist in the catalog (set-available-attachment-versions in docs-extensions-and-macros#224), and only the newest connect-<version>.json is hosted. That makes the client-side version discovery pure 404 fuel: - getConnectVersion() fetched rp-connect-docs/antora.yml from raw.githubusercontent.com, but that repo is now private, so the fetch 404s for every visitor and always returned null. - The hardcoded fallback lists (4.79.0-4.75.0 in 16-bloblang-interactive, 4.78.0-4.75.0 in bloblang-playground.hbs) name versions whose JSON no longer exists, so every walk was a guaranteed 404 chain. 16-bloblang-interactive.js now fetches only the meta-tag URL (or the static preview path). The playground's completion fetch prefers the meta-tag URL, keeps the GitHub latest-release lookup as a secondary (that version's JSON is the one that is hosted), and no longer guesses older versions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Follow-up in 45bd2d7, prompted by the observation that the site already hosts the source of truth for valid versions: the That made the client-side version guessing pure 404 fuel, so it's removed:
|
waitForSelector's default requestAnimationFrame polling can stall in headless Chrome on busy CI runners even though the element is present, which failed this assertion in CI while every other check in the scenario (fetch counted, cache written, marker cleared) passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The assertion fails only in CI; on failure, dump the page's code elements, cached dataset, and article presence, and echo page errors and console warnings throughout the run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
requestIdleCallback with no timeout can defer processCodeElements for seconds when the main thread never goes idle, so tooltips attach arbitrarily late (or a pending fetch smears into the next navigation). This is what made the CI DOM assertion fail: on the loaded runner, one page's idle callback fired seconds late while the next page's had not run at all within the assertion window. A timeout guarantees the callback runs within 500ms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The DOM assertion depended on the page's idle-scheduled tooltip pass, which behaves differently on the Linux CI runner than locally and made the suite flaky there. Asserting the cached lookup's actual content closes the same gap (a vacuously-empty lookup writing the cache key) without depending on render scheduling. Debug probes removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Problem
Two of the top 404 sources on docs.redpanda.com come from tooltip data fetches that retry a missing file on every page view:
19-property-tooltips.jscaches successful properties-JSON fetches for 24h in localStorage, but failures are never cached — when the referenced attachment doesn't exist (release tag drifted ahead of the generated JSON), every streaming page view fires a guaranteed 404 (~17.6k/day forredpanda-properties-v26.1.14.json).16-bloblang-interactive.jsbuilds the Connect JSON URL fromlatest-connect-versionand, on failure, walks a hardcoded fallback-version list — up to 6 404s per page view (~6.4k/day forconnect-4.102.0.json), with no caching of failures.Fix
redpanda-properties-missing), so a marker can never clobber the cached dataset and browsing multiple doc versions with missing JSON can't thrash a shared marker. Markers are written only for HTTP 404/410, expire after 1h (vs 24h for successes, so a fix deploy is picked up quickly), are additionally keyed bylatest-redpanda-tag, and a successful fetch clears its own URL's marker. The dataset-cache and marker reads use separate try/catch blocks so a corrupt cache entry can't disable the marker check.connect-json-fetch-failures, 1h TTL); URLs that recently returned 404/410 are skipped. Transient failures (429, 5xx, network errors, parse errors) are never marked and retry on the next view. The Connect JSON is now fetched only from theconnect-json-urlmeta tag, which the build resolves against the attachments that actually exist in the catalog (docs-extensions-and-macros#224). The client-side version guessing is removed entirely: theraw.githubusercontent.comantora.yml lookup 404s for all visitors now thatrp-connect-docsis private, and the hardcoded fallback versions (4.75.0–4.79.0) name JSON files that are no longer hosted — both in16-bloblang-interactive.jsand in the playground's completion fetch (bloblang-playground.hbs), which now prefers the meta-tag URL and keeps only the GitHub latest-release lookup as a secondary.localhost,127.0.0.1,docs-ui.netlify.app) is unaffected: failures there are never marked, and property-tooltip cache reads were already skipped in preview. Note that content-repo deploy previews (other*.netlify.apphosts) are treated as production.This is defense-in-depth for the storm; the root cause (meta tags referencing JSON that was never generated) is fixed at build time by redpanda-data/docs-extensions-and-macros#224.
Testing
tests/negative-cache/(45 assertions, wired intonpm run test:alland the Bloblang Tests workflow). It serves the page from a fake production hostname via request interception and verifies: 404/410 are negative-cached and not re-requested; markers expire after the TTL; 429/503/network/parse failures are never cached and retry on every view; success clears the marker, populates the cache, and actually attaches a tooltip in the DOM; preview mode (localhost) never negative-caches.node --checkandnpx eslinton both files (only pre-existing max-len warning remains)🤖 Generated with Claude Code