Additive schema migration with cache staleness vs. disabling the dispatcher cache during a SpiceDB cutover
0 reputation · 23 Jul 2023, 09:21 UTC
0 reputation · 23 Jul 2023, 09:21 UTC
I'm planning a zero-downtime migration of a small application's authorization data into SpiceDB (assume v1.30+). The intended path is the documented additive pattern: push a new schema version that only adds relations, backfill relationships with batched WriteRelationships calls, switch reads to the new relations, and drop the old relations in a later schema push.
The unresolved point is the dispatcher cache. Because cache entries are keyed by schema version, a schema write invalidates them, but in-flight CheckPermission requests can still be served from pre-invalidation entries for up to dispatcher_cache_max_staleness (10s by default). There appears to be no documented cache-invalidation hook tied to schema migrations, so my options seem to be: accept the staleness window, set dispatcher_cache_max_staleness=0 during the cutover (cluster-wide cache off, higher tail latency and datastore load), or add external coordination such as pausing writes and waiting out the TTL.
A zedtoken from a write guarantees read consistency at that revision, but I understand it does not bridge a schema-version transition.
For a small app with low write volume, which trade-off is safer in practice: accepting up to ~10s of stale checks, or temporarily disabling the cache? Is there any supported way to scope the staleness risk to only the namespaces being migrated? Does pausing writes plus waiting the TTL actually guarantee all nodes have dropped the old entries?
32275 reputation · 23 Jul 2023, 09:35 UTC
For a small, low-write application, temporarily disabling the dispatcher cache is the safer trade-off. Set --dispatch-cache-max-staleness=0 (or the equivalent config value) on every node before the schema push, run the additive migration, switch reads, and restore the original staleness setting afterward. The cost is bounded and measurable — higher per-check latency and more datastore queries — while the cost of accepting the staleness window is a brief, hard-to-detect period where authorization decisions contradict your intended state.
The confirmed mechanics, as you describe them: dispatcher cache entries are keyed by schema version, so a schema push logically invalidates them, but in-flight CheckPermission calls can still be served from pre-invalidation entries for up to dispatcher_cache_max_staleness (10s by default). There is no documented cache-invalidation hook tied to schema migrations, and no supported way to scope invalidation to specific namespaces — invalidation behavior is cluster-wide.
The problem with "just accept 10 seconds" is not the duration; it's the shape of the failure. During the window, some checks resolve against the old schema and some against the new one, split across nodes and cache states you can't observe from the client. For a low-traffic app, the absolute latency cost of disabling the cache is small precisely because your check volume is small. You're buying determinism at a discount.
Pausing writes and waiting out the TTL does not guarantee all nodes have dropped old entries. It guarantees only that no new writes reference the old schema. In-flight checks that hit stale cache entries during the TTL window still resolve against the old state, and you have no cross-node signal confirming every node's cache has aged out. Treat "pause writes + wait" as a useful complement to disabling the cache, not a substitute for it.
You're also correct that a zedtoken guarantees consistency at a revision but does not bridge a schema-version transition — it can't help you here.
WriteRelationships, capturing a zedtoken from the final batch so your read switch can require at-least-that revision.Watch the dispatcher cache metrics (hits, misses, evictions) before, during, and after: expect hits to drop to zero while staleness is 0 and recover after you restore it. Rehearse the full sequence in staging with the same cache configuration as production, and compare check latency with the cache on vs. off so the production latency bump holds no surprises. One caveat worth flagging: exact flag names and available debug endpoints vary by version, so confirm them against your deployed v1.30+ build rather than assuming they match this write-up.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.