Repository navigation
fix(#1621,#1592): cross-instance coherence for ContentCache + navigator cache - #1623
Merged
Merged
Conversation
…or cache The per-process ContentCache (bare-slug tutorial HTML + render:<slug> catalog pages) and the navigator response cache are invalidated only on the srv instance that handled a publish/admin write. With 2+ CF instances the others keep serving stale HTML until LRU eviction or restart — a publish serves divergent docs fleet-wide (#1621), and an Admin-UI reorder 'didn't propagate' (#1592). No pub/sub bus exists (websocket has no Redis adapter). Fix: keep the fast local caches but publish a shared generation token via the already-wired cds-caching service (store:'cds' = shared HANA table in hybrid/prod). On each content/catalog serve an instance does a TTL-gated (default 5s) read of the token; when it changed, it drops ALL local cache entries. Writers (publish/commit/rollback + admin.after) call bumpCacheGeneration() alongside their existing immediate local invalidation. Fail-open throughout (caching outage → today's behaviour, never a 500). Memory store in test/dev → no-op across processes, which is correct for single-process unit tests. - srv/lib/content-cache-coherence.js: shared-generation primitive (bump/refresh/ onChange), TTL-gated + coalesced reads, fail-open, test seams. - content-store.js: register full cache.invalidate() on generation change; refreshCacheGeneration() before both the render: and bare-slug cache reads; bumpCacheGeneration() after each cache.invalidate() (publish/commit/rollback). - navigator-catalog.js: register local clear + refresh before serving cache. - server.js: bumpCacheGeneration() in the admin.after nav-invalidating hook. - test/unit/content-cache-coherence.test.js: peer-bump detection, TTL gating, self-bump no-op, post-bump peer change, fail-open (read+write), null token. Cross-instance staleness bounded by CONTENT_CACHE_CHECK_TTL_MS (default 5s). Refs #1621, #1592
content-store.js now imports the new content-cache-coherence.js; without it in the tutorials-srv-qa build cp list, QA boot would crash on first use. Passes check-srv-qa-cp-list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Fixes #1621 (and the render-cache facet of #1592): the per-process
ContentCacheinsrv/lib/content-store.js— both bare-<slug>tutorial HTML andrender:<slug>catalog (group/mission SSR) entries — and the navigator response cache innavigator-catalog.jsare invalidated only on the srv instance that handled the publish/admin write. With 2+ CF instances the others keep serving stale HTML until LRU eviction orcf restart:hana-cloud-mission-trial-3) served two different documents round-robined across instances, bothX-Content-Source: cache, while/content/hashesreturned a single current hash — divergence was purely in-memory.Approach
There is no pub/sub bus available (CAP messaging unconfigured;
@cap-js-community/websockethas no Redis adapter, so broadcasts don't cross instances). Rather than move the hot HTML cache ontocds-caching(no L1 — every op is a HANA round-trip,deleteByTagis an O(n) full-store scan), this keeps the fast in-memory caches and adds a shared generation token:publish/commit/rollback+ theadmin.afternav-invalidating hook) callbumpCacheGeneration()— sets a new token in the already-wiredcachingservice. In hybrid/prod that service isstore: "cds"→ a shared HANA table, so the bump is visible to all instances. Writers still do their immediate local invalidation too, so the writing instance is correct with zero delay.refreshCacheGeneration()before trusting a local cache entry (both therender:catalog path and the bare-slug tutorial path, plus the navigator handler). It's TTL-gated (default 5s,CONTENT_CACHE_CHECK_TTL_MS) and coalesced, so it's at most one lightweightcaching.getper 5s per instance. When the token changed, all registered local invalidators fire (content-store drops the wholeContentCache; navigator clears its response).Fail-open everywhere: a caching outage or throw → falls back to today's behaviour (local cache only), never a 500. In the default/test profile the caching store is
memory(per-process), so it's a no-op across processes — correct, since unit tests are single-process.Staleness bound: cross-instance convergence within
CONTENT_CACHE_CHECK_TTL_MS(5s) of a write — well under the <=60s admin-to-visitor delay already documented for Alerts.Tradeoff (called out for review): one generation token covers both content and catalog caches, and a generation change triggers a full
cache.invalidate(). So a catalog admin write also drops peers' tutorial-content cache entries (mild over-invalidation; they repopulate from the DB, and content pages are Akamai-fronted). This keeps the mechanism simple and matches the publish-path's existing full-wipe semantics. Splitting into two tokens (content vs catalog) is a possible follow-up if the extra origin reads ever matter.Files
srv/lib/content-cache-coherence.js(new) — the shared-generation primitive:bumpCacheGeneration,refreshCacheGeneration,onCacheGenerationChange; TTL-gated + coalesced reads; fail-open; test seams.srv/lib/content-store.js— registercache.invalidate()on generation change;refreshCacheGeneration()before therender:and bare-slug reads;bumpCacheGeneration()after eachcache.invalidate().srv/lib/navigator-catalog.js— register local clear + refresh before serving cache.srv/server.js—bumpCacheGeneration()in theadmin.afternav-invalidating hook.test/unit/content-cache-coherence.test.js(new) — peer-bump detection, TTL gating, self-bump no-op, post-bump peer change, fail-open (read + write), null-token no-op.Testing
test/render-cache-invalidation.test.js,test/unit/content-cache-metrics.test.js,test/lib/content-store.test.js(47).On the #1621 acceptance "hybrid test that simulates two cache holders": a true 2-instance CF test isn't feasible in CI, but the coherence primitive that guarantees the fan-out is unit-tested directly (peer-bump -> local invalidators fire). Verified in PROD out-of-band that a
cf restartconverges instances (the manual workaround this replaces).Related
worktree-fix-1592-group-ordering): the IMS->CAP migration ordered group members byCHILD_TASK_IDinstead ofTASK_ORDER. This PR is the cache-coherence half only.