Architecture note: Using Spicedb's relational tuple model with watch API for low‑latency permission checks
Guidance on deploying Spicedb for low‑latency permission checks, covering requirements, minimal dev setup, trust boundaries, ops checks, failure modes, and when to redesign.
08 Aug 2026, 04:36 UTC

Requirements
Sub‑second authorization latency, support for hierarchical object relationships, eventual consistency with optional strong reads, schema‑defined object types/relations, and gRPC‑based integration with existing services.
Smallest suitable design
For development and testing a single Spicedb node with its built‑in in‑memory datastore is sufficient. The node exposes a gRPC endpoint and the watch API to push permission changes to services. In production the design expands to a three‑node cluster backed by PostgreSQL with TLS.
Example schema
# schema.zetti
definition document {
relation viewer: user
permission view = viewer
}
definition user {}
Running a dev node
Run the command on a workstation with Docker installed (you need permission to pull images and create containers).
docker run -d --name spicedb-dev \
-p 50051:50051 \
authzed/spicedb:latest \
--grpc-preshared-key=dev-key \
--datastore-engine=inmem \
--log-level=info
Writing a tuple
Use the Spicedb CLI (or the spicedbctl binary) with the node’s gRPC address and preshared key.
spicedbctl write \
--grpc-address=localhost:50051 \
--preshared-key=dev-key \
"document:doc1#viewer@user:alice"
Checking permission
spicedbctl check \
--grpc-address=localhost:50051 \
--preshared-key=dev-key \
"user:alice" "view" "document:doc1"
The expected result is PERMITTED (no output is invented here).
Watching for changes
spicedbctl watch \
--grpc-address=localhost:50051 \
--preshared-key=dev-key \
"document:doc1#viewer"
After updating the tuple (e.g., removing the relation), the watch stream should emit a DELETE event within a few seconds.
Trust and data boundaries
Treat Spicedb as a trusted internal service. Expose only the gRPC port (default 50051) to authorized services, enforce mutual TLS and token‑based authentication, and keep the PostgreSQL backend in a private VPC accessible solely by the Spicedb nodes.
Operational checks
- Monitor
CheckPermissionRPC latency (target p99 < 100 ms). - Watch stream lag (time between a tuple change and its delivery) – aim for < 5 s.
- Datastore health via Prometheus metrics (e.g.,
spicedb_datastore_query_latency_seconds). - Set alerts on latency spikes or watch lag exceeding thresholds.
- Perform periodic PostgreSQL snapshots and restore tests to verify backup integrity.
Failure modes
- Network partitions: May cause stale reads; mitigate by using strong consistency reads (
ReadYourWritesflag) or quorum reads. - Datastore corruption: Can lead to permission denial; mitigate with regular backups and health‑check alerts.
- Watch API disconnects: May miss updates; mitigate with automatic reconnect with exponential backoff and checkpoint‑based resync.
Conditions that would change the design
- Sub‑millisecond latency needs → consider an in‑memory datastore or experimental fast‑path cache.
- Audit‑log requirements → enable Spicedb’s audit log and forward entries to a SIEM.
- High traffic spikes → add read replicas or tenant‑based sharding.
- Need for global ordering of watch events → implement application‑level sequencing or avoid reliance on strict ordering.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.