Markdown Editor

Zanzibar-Style Authorization Service System Design Template

Relationship-based authorization service: relation tuples, namespace config, Check/Expand/List/Watch APIs, consistency tokens, caching, sharding, failure modes

系统设计完整Zanzibar (USENIX ATC 2019); OpenFGA and SpiceDB conceptsdesignauthorizationservicemath

使用方法: Use it when several products need one place to decide who can access what, with sharing, groups and folder inheritance. Fill the namespace configuration and the consistency contract (when clients pass a token) first. The main pitfall is serving checks from stale snapshots, the new-enemy problem, because clients drop the token returned by writes.

预览

System Design: [Authorization service name]

Reference design for a relationship-based authorization service in the style of Google Zanzibar: access rules are stored as relation tuples (object, relation, subject), a namespace configuration defines how relations combine and inherit, and applications call Check, Expand, List and Watch APIs instead of embedding permission logic. It covers consistency tokens ("zookies") and the new-enemy problem, check evaluation and caching, deeply nested groups, reverse lookups, latency SLOs and sharding of the tuple store. Concepts follow Pang et al., "Zanzibar: Google's Consistent, Global Authorization System" (USENIX ATC 2019) and the public documentation of the open-source OpenFGA and SpiceDB projects. It follows the 12-section structure of the System Design Document template. Replace every [bracketed] value and every row marked Example.

Document control

FieldValue
Document ID[SDD-AUTHZ-001]
System / feature[Central authorization service]
Version[0.1]
Status[Draft / In review / Approved / Implemented / Superseded]
Owner[@owner]
Author(s)[@author]
Reviewers[@reviewer-1], [@reviewer-2]
Last updatedYYYY-MM-DD
Classification[Internal]
Schema owner[@team]
Related[Identity provider design], [Threat model], [ADR: consistency contract]

Approvals

RoleNameDecisionDate
Tech lead[@name][Approved / Changes requested]YYYY-MM-DD
Staff / principal engineer[@name][Decision]YYYY-MM-DD
Security architect[@name][Decision]YYYY-MM-DD
SRE / operations[@name][Decision]YYYY-MM-DD
Representative client team[@name][Decision]YYYY-MM-DD

Revision history

VersionDateAuthorChange summary
0.1YYYY-MM-DD[@name]First draft for design review
0.2YYYY-MM-DD[@name]Example: Added group membership index after nested-group latency tests
0.3YYYY-MM-DD[@name]Example: Consistency contract changed from bounded staleness to tokens
1.0YYYY-MM-DD[@name]Example: Approved

Contents

1. Overview

1.1 Problem statement

[Example: Each product (documents, projects, billing) implements its own permission checks against its own tables. Sharing a folder with a group works in the documents product but not in search, which shows titles the user cannot open. A permissions bug last quarter exposed private projects to former team members because the project service cached roles for 30 minutes after removal. Adding a new sharing feature takes each team weeks.]

1.2 Proposed solution

[Example: A central authorization service stores every access fact as a relation tuple, such as doc:readme#viewer@group:eng#member. A versioned namespace configuration defines how relations combine: an editor is also a viewer, and a viewer of a folder is a viewer of every document in it. Products call Check on every request, ListObjects to filter search results, and Write when users share or unshare. Every write and check returns a consistency token; when content changes, the product stores a fresh token with the new content version, and later checks pass it so a check never uses data older than the last permission change that matters. Tuples live in a sharded, replicated database; check results are cached per snapshot and distributed across servers by consistent hashing.]

1.3 Decisions requested from reviewers

IDQuestionRecommended optionSection
Q-01Example: Consistency contract for clients?Tokens with "at least as fresh" semantics; full consistency only on request7.1
Q-02Example: Tuple store technology?Distributed SQL with external-consistency timestamps5.4, 10
Q-03Example: How to handle groups nested 10 levels deep?Precomputed group membership index for selected namespaces7.3
Q-04Example: How do search and list pages filter results?ListObjects for small result sets; pre-filter index for search7.4
Q-05Example: Fail open or closed when the service is unreachable?Fail closed, with a short client-side cache of positive results9.1
[Q-nn][Question][Option][Section]

1.4 45-minute interview path

MinutesCoverOutput on the whiteboard
0 to 5Clarify: who calls, what objects, roles or relationships, scale, consistency expectationCore and below-the-line lists (2.1, 2.3)
5 to 10Estimate checks per second, tuples, fan-out per checkThree numbers and their consequences (3.6)
10 to 15API: Check, Write, Read, Expand, ListObjects, WatchSignatures with the consistency token (4.1)
15 to 20Data model: tuple, namespace configuration, access patternsTuple table and one rewrite rule (5.2, 5.3)
20 to 28High-level design: check servers, tuple store, cache, changelogOne diagram (6)
28 to 40One deep dive: the new-enemy problem and tokens (7.1) is the strongest choice; caching (7.2) if the interviewer pushes on latencyOptions, choice, reason
40 to 45Failure modes, fail-closed decision, what changes at 10xTwo rows of the failure table (9.1)

2. Requirements

2.1 Core functional requirements

IDRequirementPriority
FR-01Example: Answer "does subject S have relation R on object O?" (Check)Must
FR-02Example: Store and delete relation tuples, with optional preconditions (Write)Must
FR-03Example: Support usersets: a subject can be "members of group G"Must
FR-04Example: Support relation inheritance within an object (owner implies editor implies viewer)Must
FR-05Example: Support inheritance across objects (viewer of a folder views its documents)Must
FR-06Example: Return a consistency token on every write; accept one on every readMust
FR-07Example: List objects of a type a subject can access (ListObjects)Must
FR-08Example: Show why access is granted (Expand, as an effective userset tree)Should
FR-09Example: Stream tuple changes to subscribers in commit order (Watch)Should
FR-10Example: Version the namespace configuration and reject unsafe changesMust
[FR-nn][Requirement][Must / Should / Could]

2.2 Non-functional requirements

IDQualityTargetMeasured as
NFR-01Check latencyExample: p95 at or below 10 ms, p99 at or below 30 ms, in regionServer-side timing per API
NFR-02AvailabilityExample: 99.99% monthly for CheckSuccessful responses / valid requests
NFR-03CorrectnessExample: No check evaluated at a snapshot older than the token suppliedConsistency test suite with injected lag
NFR-04ThroughputExample: 500 000 checks/s at peak, 5 000 tuple writes/sLoad test
NFR-05Write latencyExample: p99 at or below 100 msServer-side timing
NFR-06IsolationExample: One tenant's hot object cannot raise p99 for othersHot-spot test
[NFR-nn][Quality][Target][Measurement]

2.3 Below the line (out of scope)

  • [Example: authentication and user identity (the identity provider issues sub; see Identity Provider and SSO System Design Template (system-design/design-identity-sso-provider))]
  • [Example: attribute conditions such as time of day or IP range (phase 2, as caveats evaluated on top of tuples)]
  • [Example: policy-as-code for infrastructure (stays in the existing policy engine)]
  • [Example: end-user audit log UI (products read the Watch stream)]
  • [Item deferred, with the release that will take it]

3. Capacity estimation

3.1 Assumptions

InputSymbolValueSource
Peak checks per second, all regionsCCExample: 500 000Gateway metrics of all products x growth
Peak tuple writes per secondWWExample: 5 000Sharing and membership events
Stored tuplesTTExample: 20 billionObjects x average ACL entries
Bytes per tuple including index overheadbbExample: 100 BSchema in 5.2
Replication factorffExample: 3Store configuration
Average storage reads per uncached checkkkExample: 4Fan-out measured on the schema in 5.3
Cache hit ratio for sub-problemshhExample: 0.8Prototype measurement
Reads per second one shard serves at target latencyrrExample: 20 000Benchmark

3.2 Storage

S=T×b×f=2×1010×100 B×3=6 TBS = T \times b \times f = 2 \times 10^{10} \times 100 \ \text{B} \times 3 = 6 \ \text{TB}

Consequence: 2 TB of logical data does not fit on one node with headroom, so the tuple store is sharded from day one, by object (section 5.4).

3.3 Storage reads

R=C×k×(1−h)=5×105×4×0.2=4×105 reads/sR = C \times k \times (1 - h) = 5 \times 10^{5} \times 4 \times 0.2 = 4 \times 10^{5} \ \text{reads/s}
Nshards=Rr×1.5=4×1052×104×1.5=30⇒32 shardsN_{shards} = \frac{R}{r} \times 1.5 = \frac{4 \times 10^{5}}{2 \times 10^{4}} \times 1.5 = 30 \Rightarrow 32 \ \text{shards}

Consequence: the cache hit ratio is the dominant lever. If hh drops from 0.8 to 0.6, reads double to 800 000/s and the shard count doubles, so the cache gets its own deep dive (7.2) and its own SLO. Each shard then holds about 6 TB / 32, or roughly 190 GB, which is comfortable.

3.4 Changelog

L=W×200 B×86 400 s=5 000×200×86 400≈86 GB/dayL = W \times 200 \ \text{B} \times 86\,400 \ \text{s} = 5\,000 \times 200 \times 86\,400 \approx 86 \ \text{GB/day}

Consequence: seven days of Watch history is about 600 GB, small enough to keep in the same store as a time-ordered table, so Watch needs no separate log system at this scale.

3.5 Latency

A check that walks six levels of nesting with one storage read per level costs six sequential round trips. At 1 to 2 ms per read that is 6 to 12 ms, already at the p95 budget. Consequence: evaluate branches of a union in parallel, cache intermediate results, and index deeply nested groups (7.3).

3.6 Summary

QuantityEstimateDesign consequence
Checks500 000/sStateless check servers, scaled horizontally
Logical tuple data2 TB (6 TB replicated)Shard by object from day one
Storage reads400 000/s at 80% hit32 shards; cache hit ratio monitored as an SLO
Changelog~86 GB/dayWatch served from a changelog table, 7-day retention
Nested check6 to 12 ms if sequentialParallel evaluation, caching, group index

4. API design

4.1 Operations

OperationInputOutputNotes
Checkobject, relation, subject, consistencyallowed (bool), checked_at tokenHot path; must be cheap
BatchCheckup to [100] checks, consistencylist of resultsFor list pages
Writetuples to add, tuples to delete, preconditionstokenAtomic per request
Readtuple filter, consistency, page tokentuples, next page tokenAdmin and debugging
Expandobject, relation, consistencyuserset treeExplains effective access
ListObjectsobject type, relation, subject, consistencyobject IDs (bounded)Reverse lookup (7.4)
ListSubjectsobject, relation, subject type, consistencysubject IDs (bounded)Who can see this
Watchnamespaces, start tokenstream of tuple changes with tokensCommit order per shard
WriteSchemanamespace configurationschema versionValidated before activation (7.5)

Consistency modes accepted by every read operation:

ModeMeaningTypical use
minimize_latencyAny recent snapshot the server choosesLow-risk reads such as UI hints
at_least_as_fresh(token)Snapshot no older than the tokenDefault for content checks
at_exact_snapshot(token)Exactly the token's snapshotPaging through Read or List results
fully_consistentLatest committed dataRight after a security-sensitive change

4.2 Request and response examples

A check that protects content changed after a permission write passes the token saved with that content. A write that removes one viewer and adds a group is atomic, guarded by a precondition on the owner tuple.

POST /v1/check
Content-Type: application/json

{"object": "doc:readme", "relation": "view", "subject": "user:alice",
 "consistency": {"at_least_as_fresh": "zk_CAESBgjM8tK9Bg"}}

HTTP/1.1 200 OK
{"allowed": true, "checked_at": "zk_CAESBgjQ8tK9Bg"}
POST /v1/write
Content-Type: application/json

{"writes": [{"object": "doc:readme", "relation": "viewer", "subject": "group:eng#member"}],
 "deletes": [{"object": "doc:readme", "relation": "viewer", "subject": "user:mallory"}],
 "preconditions": [{"must_exist": {"object": "doc:readme", "relation": "owner", "subject": "user:alice"}}]}

HTTP/1.1 200 OK
{"written_at": "zk_CAESBgjR8tK9Bg"}

4.3 Errors and limits

ConditionStatusBody type
Unknown namespace or relation400problems/schema-mismatch
Precondition failed on Write409problems/precondition-failed
Token older than retained history410problems/snapshot-expired (retry with at_least_as_fresh)
Check exceeded depth limit [25]422problems/max-depth
Tenant quota exceeded429RFC 9457 problem details with Retry-After
Deadline exceeded504Client treats as deny (9.1)

5. Data model

5.1 Entities

Tuples are the bulk of the data; the configuration, changelog and group index are derived from or describe them.

5.2 Tuple format

A tuple is object#relation@subject, where the subject is either a direct subject (user:alice) or a userset (group:eng#member, meaning "every subject with relation member on group eng"). Example tuples:

TupleMeaning
doc:readme#owner@user:aliceAlice owns the document
doc:readme#viewer@group:eng#memberMembers of group eng can view
doc:readme#parent@folder:handbookThe document sits in folder handbook
group:eng#member@group:backend#memberGroup backend is nested in eng

5.3 Namespace configuration

Rewrite rules compute a relation from other relations. The Example below uses a neutral notation; translate it into the schema language of the engine you choose.

type user

type group
  relation member: user | group#member

type folder
  relation owner: user
  relation viewer: user | group#member
  permission view = viewer + owner

type doc
  relation parent: folder
  relation owner: user
  relation editor: user | group#member
  relation viewer: user | group#member
  permission edit = editor + owner
  permission view = viewer + edit + parent->view

+ is union; parent->view follows the parent tuple and checks view on the folder (a tuple-to-userset rewrite). Intersection and exclusion (viewer & not blocked) are possible but expensive; allow them only where a reviewer has measured the fan-out.

5.4 Access patterns

IDQueryServed byPartition key rationale
AP-01 (Example)Direct tuples for (object, relation) during CheckPrimary key prefix (namespace, object_id, relation)Shard by (namespace, object_id) keeps an object's ACL on one shard
AP-02 (Example)Is subject a direct member of userset?Same key plus subject columnsPoint read on the object's shard
AP-03 (Example)Follow parent from a documentAP-01 on relation parentSame shard as the document
AP-04 (Example)Groups containing a member, transitivelyGROUP_INDEX by member_id (7.3)Separate index sharded by member
AP-05 (Example)Objects a subject can view (ListObjects)Reverse index by (subject, namespace) plus check filter (7.4)Secondary index sharded by subject
AP-06 (Example)Changes since token for WatchCHANGELOG by (shard, commit_ts)Time order per shard; merged by the Watch service
AP-07 (Example)Current schemaNAMESPACE_CONFIG, cached in every serverTiny, global, versioned

Consequence of AP-01: a "hot" object such as a company-wide group lives on one shard. Section 7.2 handles it with caching rather than by splitting the object.

6. High-level design

ComponentResponsibilityScales by
Check serversParse request, pick snapshot, evaluate rewrite rules, fan outStateless replicas per region
Result cacheCache sub-problem results keyed by (object, relation, subject, snapshot)Memory per server; peers own key ranges
Tuple storeDurable tuples with commit timestamps, multi-region replicationShards by object
ChangelogOrdered record of every tuple changeShards, retained 7 days
Watch serviceMerge changelog shards and stream to subscribersSubscribers
Group indexFlattened transitive group membershipMembers
Schema adminValidate, version and roll out namespace configurationSingle service

7. Detailed design

7.1 Deep dive: consistency and the new-enemy problem

Problem. A permission change and a content change arrive in order, but a check reads a stale replica and evaluates the new content against the old permissions. Two classic forms:

  1. Alice removes Bob from a folder, then adds a confidential document to it. A check that sees the document but not the removal lets Bob read it.
  2. Alice removes Bob from a document's viewers, then asks Charlie to add sensitive text. A check that ignores the order of the two changes lets Bob see the new text.
OptionHowCostVerdict
A. Always fully consistentEvery check reads the latest committed dataCross-region reads on every check; caches uselessRejected: misses NFR-01
B. Bounded stalenessChecks read a snapshot at most N seconds oldWindow of N seconds where the new-enemy problem occursRejected for content; acceptable for UI hints
C. Consistency tokensWrite returns a token encoding its commit timestamp; the product stores it with the content version; checks pass it as "at least as fresh"Clients must store and forward tokensChosen

Choice and reason. Option C makes freshness proportional to need. Most checks run at a snapshot a few seconds old, which caches well, yet any check that protects content changed after a permission change is guaranteed to see that change. The tuple store must assign commit timestamps that respect real-time order across shards (external consistency); a store without this property breaks the guarantee for multi-shard writes.

Client contract. On every content change, run a content-change check (fully_consistent, or at least as fresh as any permission write the product just made) and store the returned checked_at token next to the new content version (for example in the document row). Storing only the token of an earlier permission write is not enough when another user changes the content later. Pass the stored token on every check of that content. When no token exists, use minimize_latency for listings and fully_consistent only for security administration pages.

7.2 Deep dive: check evaluation and caching

OptionHowCostVerdict
A. No cacheEvery check reads storage2 million reads/s at peak (3.3 with h=0h = 0)Rejected
B. Per-server cacheEach server caches its own resultsLow hit ratio as fleet grows; hot keys duplicated everywherePartial
C. Distributed cache by consistent hashingEach sub-problem key has an owner server; servers forward sub-checks to ownersOne extra in-cluster hop for some sub-checksChosen

Choice and reason. Snapshots are quantized (for example rounded down to the nearest second) so that many checks share one snapshot and therefore share cache keys. The owner of a key deduplicates concurrent identical requests: if a thousand requests ask about the same company-wide group at the same snapshot, one storage read serves all of them. Cache entries never need invalidation because they are keyed by snapshot; they expire by age.

Evaluation rules:

  • Evaluate union branches in parallel and return on the first true.
  • Cap recursion depth (4.3) and detect cycles with a visited set.
  • Hedge slow storage reads: after the p95 latency of that shard, send the same read to another replica and take the first answer.
  • Never cache a result computed at a snapshot newer than the one requested under a key for the older snapshot.

7.3 Deep dive: deeply nested groups

Organisations nest groups (team in department in division). A check that walks ten levels makes ten sequential reads.

OptionHowCostVerdict
A. Recursive walkFollow group#member usersets at check timeLatency grows with depthDefault for shallow namespaces
B. Flattened membership indexAn index builder consumes the changelog and maintains (group, transitive member) pairs; checks do one lookupWrite amplification when a large group changes; index lagChosen for group namespace
C. Restrict nestingForbid nested groupsProduct limitationRejected

Choice and reason. The index is a set computation over the changelog, maintained incrementally and versioned by commit timestamp. A check that needs a snapshot newer than the index falls back to option A for the missing interval, so correctness never depends on index lag. Measure write amplification: adding a group of 50 000 members to a parent group creates up to 50 000 index entries.

7.4 Deep dive: reverse lookups for lists and search

OptionHowCostVerdict
A. Check each candidateProduct fetches candidates, then BatchCheckWasted work when most candidates are deniedFor pages of at most [100] items
B. ListObjectsService walks the graph backwards from the subjectExpensive for subjects in many groups; must be boundedChosen for small result sets
C. Pre-filter in search indexSearch documents carry the set of principals allowed to view; queries filter by the user's groups; final page is re-checkedIndex lag; ACL changes reindex documentsChosen for full-text search

Choice and reason. Combine C with a final BatchCheck of the returned page so that index lag can hide a result but never leak one.

7.5 Schema changes

Treat the namespace configuration like a database migration: validate types, reject removal of a relation that still has tuples, run the new version in shadow mode (evaluate both versions and compare results on sampled traffic), then activate. Every check response reports the schema version used.

How real systems do it: Google Zanzibar

The Zanzibar paper (Pang et al., USENIX ATC 2019) describes a global system that stores access control lists as relation tuples in Spanner and evaluates them with namespace configurations made of userset rewrite rules. Its consistency token, called a zookie, encodes a commit timestamp; clients receive one when they change content and send it back with later checks, which is how Zanzibar addresses the new-enemy problem while still serving most checks from slightly older snapshots. The paper describes forwarding sub-requests to peers chosen by consistent hashing so caches are shared, a lock table that merges identical concurrent requests, request hedging against slow replicas, and a separate indexing system (Leopard) that flattens deeply nested group membership. The authors report 95th-percentile latency under 10 ms and availability above 99.999% over three years of production use. This box is a paraphrase; read the paper for the actual architecture and measurements.

8. Scalability and performance

ConcernApproachLimit before redesign
Check rateStateless servers, distributed cacheExample: Cache memory per region
Hot objectsOwner deduplication, quantized snapshotsOne owner server's CPU per key
Storage reads32 shards, hedged readsExample: 640 000 reads/s at 20 000 per shard
Write rateSharded commits, batched changelogCross-shard writes needing coordination
Nested groupsFlattened indexIndex rebuild time after a large group change
ListObjectsBounded results, pre-filter for searchSubjects in more than [10 000] groups

Latency budget for an uncached check at p95 (target 10 ms):

StepBudget
Parse, authenticate caller, pick snapshot0.5 ms
Forward to cache owner (in-cluster hop)1.0 ms
Up to two parallel storage read rounds5.0 ms
Group index lookup1.5 ms
Headroom2.0 ms

9. Reliability, security and operations

9.1 Failure modes

ComponentFailureDetectionBlast radiusMitigationRTO
Check serversExample: Region fleet unavailableHealth checks, error rateAll products in regionRoute to nearest region; clients fail closed5 min
Tuple store shardExample: Leader lossStore metrics, read errors on shardObjects on that shardAutomatic leader election; reads from replicas at older snapshots where tokens allow1 min
Result cacheExample: Fleet restart empties cachesHit ratio below 0.6Storage load doublesGradual rollout; warm from peers; admission control15 min
Group indexExample: Builder stallsIndex lag above 30 sNested-group checks slowerFall back to recursive walk (7.3)30 min
Schema rolloutExample: Bad rewrite rule activatedShadow diff, deny-rate spikeEvery check in the namespaceShadow mode; one-step rollback to previous version5 min
ClientExample: Product drops tokensShare of checks without tokenNew-enemy exposure in that productClient library defaults; conformance testn/a
[Component][Failure][Signal][Scope][Mitigation][RTO]

Fail closed. When a check cannot complete within its deadline, the client library denies. Clients may cache positive results for at most [5] seconds to ride out short blips; they never cache across a permission write they made themselves, and never answer a check that carries a token newer than the cached result's snapshot from that cache.

9.2 Security

ThreatControl
Product service writes tuples outside its namespacesPer-caller authorization on Write by namespace, enforced by the service
Privilege escalation through schema changeSchema changes require review by the schema owner and security; audited
Enumeration via ListSubjectsRestricted to callers with an admin scope
Tuple data reveals org structureEncrypt at rest; tuples are confidential data
Denial of service by deep or wide graphsDepth limit, fan-out limit, per-tenant quotas
[Threat][Control]

Cross-check with the Threat Model Template (security/threat-model) and the policy decision point role in the Zero Trust Architecture Plan Template (security/zero-trust-architecture).

9.3 Observability

SignalMetricAlert threshold
Check latencyp95 and p99 per API and regionExample: p95 above 10 ms for 10 min
Cache hit ratioPer regionExample: below 0.7 for 15 min
Snapshot age servedDistribution of (now minus snapshot)Example: p99 above 10 s
Token usageShare of checks with a token, per productDrop of 20% vs. 7-day baseline
Depth and fan-outp99 sub-requests per checkAbove 50
Watch lagOldest unconsumed change per subscriberAbove 60 s

9.4 Cost estimate

ItemUnit costQuantityMonthly cost
Check servers[USD per instance-month]Example: 60 per region x 3[USD]
Tuple store[USD per node-month and per TB]Example: 32 shards x 3 replicas, 6 TB[USD]
Group index store[USD][Size][USD]
Cross-region replication traffic[USD per GB][GB][USD]
Total[USD]

10. Trade-offs and alternatives

DecisionOptionsChosenWhyWhat we give up
ModelRoles in each app; attribute policies; relationship tuplesRelationship tuplesSharing, groups and hierarchy expressed as dataLearning curve; attributes need caveats later
ConsistencyFull; bounded staleness; tokensTokensCorrect where it matters, cached elsewhereClients must store tokens
StoreKey-value store; sharded SQL; distributed SQL with external consistencyDistributed SQLSnapshot reads at a timestamp across shardsCost and operational expertise
Build or adoptBuild; adopt OpenFGA or SpiceDB; managed service[Decision][Reason][Cost]
ListsCheck each; ListObjects; search pre-filterHybrid by result sizeBounded cost per callTwo code paths
At 10x (5 million checks/s)Cache hit ratio and hot-key deduplication become the whole design; consider per-region read replicas of the group index and stricter fan-out limits
At 0.1x (50 000 checks/s)A single-region managed database plus an open-source engine is enough; drop the group index and distributed cache

11. Delivery plan and risks

11.1 Milestones

MilestoneScopeExit criterionTarget date
M1Schema for documents and groups; Check and Write in one regionConformance tests pass, including new-enemy testsYYYY-MM-DD
M2Dual-run: products check both old logic and service, log differencesDifference rate below 0.01% for 14 daysYYYY-MM-DD
M3Service authoritative for documentsNo P1 permission incidents for 30 daysYYYY-MM-DD
M4ListObjects and search pre-filterSearch leakage test passesYYYY-MM-DD
M5Multi-regionRegional failover drill within RTOYYYY-MM-DD

11.2 Migration and rollback

[Example: A backfill job converts existing ACL rows into tuples; a change-data-capture stream keeps them in sync during dual-run. The product flag authz.source switches between legacy and service per namespace; rollback flips the flag and takes effect within 60 s.]

11.3 Risks

IDRiskLikelihoodImpactMitigationOwner
R-01Example: Products ignore tokens, reopening the new-enemy problemHighHighClient library stores and forwards tokens by default[@name]
R-02Example: Fan-out of one schema rule breaks latencyMediumHighFan-out budget per rule in schema review[@name]
R-03Example: Backfill misses legacy edge casesMediumHighDual-run comparison before cut-over[@name]
R-04Example: Service becomes a single point of failure for all productsMediumHighMulti-region, fail-closed client behaviour tested in game days[@name]
[R-nn][Risk][L / M / H][L / M / H][Mitigation][@name]

12. Appendices

12.1 Traceability

RequirementDesign elementVerification
FR-01, NFR-017.2 caching, 8 latency budgetLoad test
FR-06, NFR-037.1 tokensConsistency test with injected replica lag
FR-055.3 parent->viewSchema conformance test
FR-077.4Leakage test against search
FR-107.5Shadow diff report

12.2 Open questions

IDQuestionOwnerDue
OQ-01Example: Do we need attribute conditions (caveats) in phase 1?[@name]YYYY-MM-DD
OQ-02Example: How long do we retain snapshots for at_exact_snapshot paging?[@name]YYYY-MM-DD
[OQ-nn][Question][@name]YYYY-MM-DD

12.3 Glossary

TermMeaning
Relation tupleA stored fact object#relation@subject
UsersetA set of subjects defined as "everyone with relation R on object O"
Rewrite ruleA rule computing one relation from others (union, intersection, exclusion, tuple-to-userset)
Consistency token (zookie)Opaque value encoding a snapshot timestamp, returned by writes and accepted by reads
New-enemy problemA check evaluated against permissions older than the content it protects
ReBACRelationship-based access control
Fan-outNumber of sub-checks or reads one check causes

12.4 References

ReferenceUse
Pang et al., "Zanzibar: Google's Consistent, Global Authorization System", USENIX ATC 2019, pp. 33 to 46Model, tokens, caching, indexing; 7.5 box
OpenFGA documentation (openfga.dev), consistency modes and query APIsCheck, Expand, ListObjects, ListUsers; consistency options
SpiceDB documentation (authzed.com/docs), consistency and ZedTokensConsistency modes in 4.1
RFC 9457 Problem Details for HTTP APIsError bodies
System Design Document Template (system-design/system-design-document)Parent structure
Distributed Key-Value Store System Design Template (system-design/design-distributed-kv-store)Storage trade-offs
Threat Model Template (security/threat-model)Threat analysis of this service

Standard and licence note

Based on the public Zanzibar paper and the public documentation of the OpenFGA and SpiceDB projects cited in 12.4, and on common industry practice for authorization services. This is a reference design with illustrative numbers, not a description of any vendor's product; check the current documentation of the engine you adopt, because consistency options and API names differ between engines and versions. Original text written for this collection. Template licence: MIT.

相关模板