4.0 KiB
4.0 KiB
Polymech Cache Invalidation & Mirroring
This document outlines the architecture for caching data on the Polymech platform, specifically how mutations invalidate Server/Client state and mirror those invalidations to secondary servers (e.g. from local to production instances).
Core Mechanisms
Cache logic splits into:
- Server-Side Cache (
redisormemoryviagetCache()): Used in API routes to hold heavy queries andpages. - Client-Side Cache (
@tanstack/react-query): The frontend data store holding currently loaded application state. - Server-Sent Events (SSE): Event streams connecting the frontend clients to the backend to get real-time cache mutations.
- Cache Mirroring: HTTP Broadcasts mapping local actions outwards to production domains using Service Accounts.
Flow of a Cache Invalidation
When a user modifies data (e.g. Updating a Post) locally:
- Mutation: A request is sent to an API endpoint (e.g.
PATCH /api/posts/:id). - Local Invalidation:
- The handler calls
appCache.invalidate('post')inside[server/src/cache.ts](../server/src/cache.ts). - The Cache clears the specific entities and walks the dependency tree (e.g.,
postdepends onpicturesandcategories).
- The handler calls
- SSE Broadcast:
- The handler issues
appCache.notify('post', id, 'update'). - The backend pushes an SSE
app-updatedown the wire.
- The handler issues
- Client-Side Reset:
- The React frontend listens to the stream in
[src/components/StreamInvalidator.tsx](../src/components/StreamInvalidator.tsx)via[src/contexts/StreamContext.tsx](../src/contexts/StreamContext.tsx). - Depending on the entity,
StreamInvalidatorresets the specificpostID in React Query, and forcefully refetches the overarchingposts,pages, andfeedcaches.
- The React frontend listens to the stream in
- Mirror Broadcast:
- During
appCache.invalidate(), if mirroring isn't explicitly disabled,[server/src/mirror.ts](../server/src/mirror.ts)takes thetypeand broadcasts it to the domains listed in.envunderCACHE_MIRRORS. - It acquires a JWT token utilizing
SERVICE_EMAILandSERVICE_PASSWORD(orTEST_EMAIL). - A
POSTgoes out tohttps://service.polymech.info/api/cache/invalidateincludingmirror: falsein the payload (to prevent an infinite broadcast loop).
- During
- Remote Revalidation:
- The remote (e.g., Production) instance receives the
invalidaterequest at[server/src/products/serving/index.ts](../server/src/products/serving/index.ts). - It internally invalidates its own Redis/Memory cache utilizing
appCache.invalidate(type, true /* preventMirror */). - It issues an SSE to all its connected clients
appCache.notify(type, null, 'update'), which triggers Step 4 for public visitors!
- The remote (e.g., Production) instance receives the
Dependency Graph Configuration
In [server/src/cache.ts](../server/src/cache.ts), dependencies orchestrate cascade invalidation:
private static DEPENDENCIES: Record<string, string[]> = {
'posts': ['categories', 'pictures'], // Changing a picture/category invalidates posts
'pages': ['categories', 'pictures', 'translations'],
'categories': ['types'],
'translations': [],
'feed': ['posts', 'pages', 'categories'],
'auth': []
};
Adding a New Mirrored Cache Endpoint
- If it represents a new domain model, add it to
DEPENDENCIESinAppCache. - Add its alias/hook inside
[src/components/StreamInvalidator.tsx](../src/components/StreamInvalidator.tsx). Note: The mirror sends pluralized definitions (pages,types,posts) whereas standard CRUD routes may notify singularly (page,type,post). Map both aliases to the same React Query keys. - Utilize
appCache.invalidate('entityName')inside your API logic. Mirroring happens automatically!
Debugging
Mirroring successes and errors are not logged to stdout. All mirror.ts and cache.ts activities are strictly written to:
[server/logs/cache.json](../server/logs/cache.json) using the internal pino cacheLogger.