Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Strategic read cache

This document records the bounded read-path change for issue #63. Browsers still talk only to strategic-web; the generated SpacetimeDB SDK connection is server-side and is not an authorization boundary.

Route classification

Route/read familyClassificationBoundary
/characters and the shared character page modelCache-backed when the explicit subscription is readyPublic character projection only; the authoritative case-site projection remains HTTP SQL.
/ home routing and /camp active-character lookupCache-backed for character and party camp stateInventory, journey, itinerary, encounter, and owner-scoped rows remain HTTP SQL.
Party, settlement, quest, dialogue, investigation, evidence, and chat page modelsConsolidated/on-demand boundaryKeep HTTP SQL and reducer calls until each page has an authorization-scoped read model.
Settlement, item, travel-edge, world-clock, world-node, and world-import readsStatic/on-demandNot globally subscribed.
Reducers, authoritative fallback reads, and tactical stateOn-demand/authoritativeNever served from the strategic cache.

The remaining page surface is intentionally not described as cache-backed. Its many-row reads should be consolidated behind page-specific read models as those pages are migrated; this change does not put SQL or cache logic in templates.

Subscription policy

crates/strategic-web/src/live.rs contains the explicit STRATEGIC_CACHE_SUBSCRIPTIONS inventory and the matching add_query chain. The unit test in that module checks that every inventory entry appears in the chain and that large static/world/import tables do not. Mutable character, party, journey/itinerary, equipment/medication, inventory, and live invalidation projections remain subscribed. The cache is incomplete until on_applied; a connect, disconnect, or subscription error makes it unavailable again.

Authorization rule

Only typed public reads are exposed by LiveState: public character rows and a party's camp-present boolean. Private chat, dialogue, contract, investigation, disease, and case projections may remain in the SDK subscription solely so their callbacks can invalidate live UI, but no cache accessor returns those rows. The selected character_id cookie selects a presentation context; it does not prove ownership. Owner-scoped/private projections continue through authenticated HTTP SQL predicates and reducers. A future cache migration must add an explicit selected-character/party authorization boundary before exposing such rows.

Measurement procedure and current results

SpacetimeClient::query_metrics is a monotonic, clone-safe SQL counter. Take a snapshot immediately before a controlled page request and another immediately after it; use after.delta(before) for request count and total elapsed time. The elapsed value includes HTTP response parsing. Record the SSR boundary with the request's Instant/curl --write-out timing. Run one request at a time when attributing a page delta; the counters remain correct under concurrent requests because they are not reset destructively.

For a representative run:

$env:RUST_LOG = "strategic_web=info"
just web-isolated-strategic cache-measure 23100
# In a controlled harness, snapshot SpacetimeClient::query_metrics(), request
# /characters, /, and /camp, then snapshot again and emit after.delta(before).

The checked-in deterministic tests cover cache state transitions and an injected-latency counter delta. Live before/after values for this checkout are unavailable: the required disposable SpacetimeDB/web fixture was not running during remediation, and no subscription payload-byte telemetry is currently emitted by the SDK. Do not treat the following as measured values:

PageSQL requests/page beforeSQL requests/page afterInitial subscription rows/bytesSSR latency
/charactersunavailableunavailableunavailable / unavailableunavailable
/ home routingunavailableunavailableunavailable / unavailableunavailable
/campunavailableunavailableunavailable / unavailableunavailable

The procedure above is the reproducible follow-up measurement; payload bytes require SDK/client instrumentation or server WebSocket capture before they can be reported honestly.