#4462·civitai

moderator: page-access grant cache is on the system Redis client; it is a pure cache and belongs on the cache client

Author: ZacxDevCreated Aug 27, 2026Updated Aug 27, 2026

apps/moderator/src/lib/server/page-access.ts reads and writes its grant map through getSysRedis(). Under our cache-vs-durable split — a cache belongs on the cache client, durable or cross-app state belongs on the system client — it is on the wrong one.

It is unambiguously a cache:

  • readThrough() falls back to getPageAccessGrants(dbRead, APP), so Postgres is the source of truth
  • the entry is written with EX 300, and there is a 30 s in-process LRU (memo) in front of it
  • both failure paths are already written as cache failures — a read that throws falls through to Postgres, and publish() failing is logged as "loaded grants, but caching them failed" rather than raised

Losing the entry costs a Postgres read. Nothing is unrecoverable.

It is also the only system-client consumer in this app that is a cache of its own data. The others are on it for good reasons and should stay:

consumer why it belongs on the system client
sessions.ts cross-app session revocation registry — the comment is explicit that other apps must see a mute or force-logout immediately
user-restriction.service.ts lPush/lTrim on SUSPICIOUS_AUDIT_MATCHES, no TTL — the only store for those records
image-moderation-effects.ts, image-deletion.ts write CACHES.IMAGE_EXISTS for the main app to read; they must target whatever the main app reads, so this is not the moderator's choice

So the change is one import and two call sites in page-access.tsgetSysRedis()getRedis() — plus moving the key onto the cache key namespace.

Worth checking while in there

The cache key is currently built from REDIS_SYS_KEYS.APP.PAGE_ACCESS. Moving clients means moving key namespaces too, so the two must change together or the read and the write will disagree. There is also a one-time effect on deploy: the first request after the move misses and reads Postgres, which the read-through already handles.

Out of scope

This is separate from #4436, which is a deployment-side configuration problem, not app code. Do not fold them together — that issue's fix is infra-only and this one is code-only.

Closing condition

A PR moving page-access.ts from getSysRedis() to getRedis() (key namespace included) is merged to main, and after it deploys a page-access grant change made in the moderator UI takes effect within the 300 s TTL for a second moderator session — i.e. the cache is still doing its job on the new client.

Checked by: @ZacxDev, by making that grant change after the deploy and observing it apply.