Choosing SpiceDB Consistency Modes: When Read-Your-Writes Matters More Than Latency
SpiceDB's per-request consistency modes let you choose between speed and read-your-writes guarantees. Treat the knob as a correctness decision: minimize latency for grants, at-least-as-fresh with ZedToken for revokes, and fully consistent only when you must.
04 Apr 2026, 11:05 UTC

The problem: a permission change that doesn't take effect
You revoke a user's access to a document. The write succeeds. Milliseconds later, the same user loads the document and still sees it. The culprit isn't a bug in your schema—it's the consistency mode you picked for the CheckPermission call that guards the page.
SpiceDB lets you choose, per request, how fresh the answer must be. The default is minimize latency, which often serves cached results. For a grant that's a minor UX hiccup. For a revocation it's a security exposure: the system fails open. Treating the consistency knob as a latency toggle instead of a correctness decision is the most common way teams ship authorization bugs.
Three modes you'll actually use
SpiceDB's Consistency enum (names vary slightly by client version) exposes four options. In practice you'll reach for three:
- Fully consistent — the read waits for the latest committed state. Highest latency, strongest guarantee.
- At least as fresh — you supply a
ZedTokenfrom a prior write; the read blocks until the datastore has caught up to that token. This is read-your-writes without paying for fully consistent on every call. - Minimize latency — served from the nearest cache replica. Fastest, but can trail the primary by tens to hundreds of milliseconds depending on your datastore replication lag.
The fourth mode, exact snapshot, pins a specific revision. It's useful for debugging or point-in-time audits, not for production latency optimization. Pinning old revisions broadly increases load and makes staleness reasoning harder.
Worked example: revoke-then-check
Imagine a document-sharing app. A user removes a collaborator. The backend calls WriteRelationships to delete the viewer relation, gets back a ZedToken, then immediately calls CheckPermission to decide whether to render the document.
# Pseudocode using the Go client (v1.20+)
resp, err := client.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{
Updates: []*v1.RelationshipUpdate{{
Operation: v1.RelationshipUpdate_OPERATION_DELETE,
Relationship: &v1.Relationship{
Resource: &v1.ObjectReference{ObjectType: "document", ObjectId: "doc-42"},
Relation: "viewer",
Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: "user-99"}},
},
}},
})
if err != nil { /* handle */ }
zedToken := resp.WrittenAt // opaque, version-specific
// Next RPC: enforce read-your-writes
check, err := client.CheckPermission(ctx, &v1.CheckPermissionRequest{
Resource: &v1.ObjectReference{ObjectType: "document", ObjectId: "doc-42"},
Permission: "view",
Subject: &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: "user-99"}},
Consistency: &v1.Consistency{
Requirement: &v1.Consistency_AtLeastAsFresh{
AtLeastAsFresh: zedToken,
},
}},
)
// check.Permissionship == PERMISSIONSHIP_NO_PERMISSION → safe to hide
If you omitted the Consistency field (or explicitly chose minimize latency), the check could return HAS_PERMISSION even though the delete succeeded, because the cache replica hasn't seen the write yet. The user sees the document until the cache catches up—a fail-open window.
Where the same decision reappears
LookupResources and LookupSubjects (the "list what a subject can access" and "list who has access to a resource" calls) honor the identical consistency modes. A dashboard that shows "your documents" after a revocation needs at_least_as_fresh with the same ZedToken, otherwise the stale list leaks the revoked item. Treat every authorization read path—checks, lookups, exports—with the same policy.
Trade-off: latency vs. fail-open risk
Fully consistent reads add a round-trip to the primary (or quorum) and can double p99 latency under load. At least as fresh adds latency only when the cache is behind the token you supplied; once caught up, it's as fast as minimize latency. The right pattern:
- Writes (grants, revokes, role changes) → capture the returned
ZedToken. - Immediate follow-up reads that must reflect those writes → pass the token with
at_least_as_fresh. - All other reads (search, browse, background sync) → minimize latency.
Never parse or synthesize ZedToken values. They're opaque, version-specific, and tied to the backing datastore's revision format. Treat them as a cookie you hand back.
What consistency cannot fix
An overly permissive schema—say, a wildcard relation that grants admin to everyone—will return HAS_PERMISSION under every consistency mode. Consistency only governs when a correct answer becomes visible, not whether the answer is correct. Get the relation graph right first.
Also, a single cached read elsewhere (CDN edge, client-side memo, another service's local cache) can still serve stale results even when SpiceDB itself is fully consistent. The consistency guarantee stops at the SpiceDB response.
Verify before you ship
- In a local SpiceDB (same version as production), write a relation, then immediately check with minimize latency, at-least-as-fresh using the write token, and fully consistent. Compare results and latency.
- Inspect the
checked_atZedTokenacross calls to confirm it advances after writes and thatat_least_as_freshhonors it. - Benchmark fully consistent vs. minimize latency against your actual datastore (PostgreSQL, CockroachDB, Spanner) at your target QPS. The gap is often smaller than teams assume.
- Check your generated client's
Consistencyenum—exact names and the unset default are version-sensitive.
Closing checklist
- Default to
minimize_latencyfor reads that tolerate stale grants. - Use
at_least_as_freshwith the writeZedTokenfor any read that must reflect a preceding write (revokes, role changes, access requests). - Reserve
fully_consistentfor audit logs, compliance checks, or when you cannot thread a token. - Apply the same mode discipline to
LookupResourcesandLookupSubjects. - Document the policy in your authorization README so future reviewers don't treat the knob as a latency optimization.
The consistency field is a correctness lever. Use it intentionally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.