Your service deletes a sign-off tuple then re-checks; how do you choose between demanding your own write is visible and taking a cheaper possibly-stale read?
answer
- how fresh must this read be
- the write hands back a marker
- grants self-heal, revocations do not
- carry it to the next reader
- name the staleness window in seconds
basics
~20 sA tuple write returns a consistency token naming the version it landed at; pass it back to demand a snapshot at least that fresh. Stale grants self-heal, stale revocations allow silently, so revocation paths carry it.
solid answer
~50 sA relation-tuple store replicates, so a read has to be told how fresh it must be. Writing a tuple returns a **consistency token** — an opaque marker of the version at which that write landed — and a read may carry it to demand a snapshot at least that fresh. Without it you get the cheapest recent snapshot, which is usually the right default for ordinary reads because the staleness window is short and bounded. The asymmetry decides it for you: a stale read after a **grant** produces a spurious denial the engineer retries, while a stale read after a **revocation** lets someone whose authority you just removed pass a check for the length of that window. So revocations, and any read the same flow makes immediately afterwards, carry the token; bulk listing traffic does not. The hard part is not the call, it is carrying the token from the service that wrote to whatever reads next.
code
pseudocode · 14 lines// remove an engineer's inspector authority on one airframe
token = tupleStore.write(delete: "airframe:G-ABCD#inspector@user:15")
// `token` names the version at which the delete became visible
// A — read back demanding our own write is visible
allowed = tupleStore.check("component:hyd-pump-77", "signoff", "user:15",
atLeastAsFreshAs: token)
// false: the delete is guaranteed to be in this snapshot
// B — cheapest read, no token
allowed = tupleStore.check("component:hyd-pump-77", "signoff", "user:15")
// may still be true, bounded by the store's snapshot window
store(objectId: "airframe:G-ABCD", lastWriteToken: token) // so later readers can ask for Ago deeper
Know that a tuple write returns a marker naming the version it landed at, and that passing it back on a read means the read is guaranteed to see that write. It is a store watermark, not a credential.
Explain the three read modes and their costs, and be able to say which one an ordinary listing read should use versus a read-back immediately after your own write.
Lead with the asymmetry: a stale grant is a retry, a stale revocation is a silent wrong allow. Name your store's staleness window in seconds and say which flows carry the token because of it.
Own where the marker lives across processes, queues and services, and the failure policy when a fresh read is unavailable. Deciding that authorization denies rather than degrades under store failure is a product-level call with a real availability bill.
## Why you get a choice at all A relation-tuple store is a replicated system serving an authorization question on the hot path of nearly every request. Always reading the freshest possible state would put a coordination cost on every check; never doing so would mean an engineer who was just granted authority cannot use it. So the store pushes the decision to the caller, per read. The mechanism is small. A write returns a **consistency token**: an opaque value naming the version of the store at which that write became visible. Note what this is not — it is not an access token, not a session identifier, and not a CSRF value. It is a watermark, and it is meaningless outside the store that issued it. ## The read modes | mode | what you get | what it costs | when to use it | |---|---|---|---| | cheapest recent snapshot | any sufficiently recent version | least latency, most cacheable | ordinary read traffic where a short window of staleness is acceptable | | at least as fresh as a token | a snapshot that includes the write that produced the token | a little more latency, less shared caching | the read that follows your own write, in the same flow | | fully fresh | the newest state the store can serve | most expensive and least available under load | rare, and usually a sign the token was not carried where it should have been | The middle mode is the one to reach for, because it asks for exactly the freshness you can justify — *my own write* — rather than for absolute freshness you cannot afford everywhere. ## The asymmetry that decides the policy Stale reads are not symmetrically harmful: - **After a grant**, a stale read denies someone who should be allowed. The engineer sees a refusal, retries, and it works. Annoying, self-healing, visible. - **After a revocation**, a stale read allows someone whose authority you removed. Nothing is visible to anyone. In a maintenance record system that is a sign-off entered by an engineer who was removed from the airframe's inspector list seconds earlier, and the record will not look wrong afterwards. So the rule is not *use the token everywhere*, it is *use it wherever a wrong answer is a wrong allow*. Revocation flows, privilege-reduction flows, and any read-back your own code performs immediately after its own write. ## Owning the lag in writing If you take the cheapest read, say how stale it can be. Real stores bound this — a snapshot window measured in seconds rather than minutes, often configurable — and the number is a property of your deployment, not a mystery. An answer that says *the revocation is immediate* without naming the window is wrong; an answer that says *the revoked engineer can still pass a check for up to the store's snapshot window, which for us is N seconds, and that is why the revocation endpoint reads with the token* is the one to give. ## Carrying the token is the real work The call is trivial. Getting the token to the next reader is not: 1. **Same request.** Trivial: hold it in a local variable across the write and the read-back. 2. **Same user's next request, different process.** The token has to live somewhere — most commonly stored beside the object it concerns, so any later read of that object can demand at least that freshness, rather than being pushed into the user's session where it will grow and go stale. 3. **Across an asynchronous hop.** A worker that processes a queued sign-off must receive the token in the message if its decision depends on a write the enqueuing request made. 4. **Across services.** A token is per-store; passing it between services is fine, but each hop needs a place to put it, and a token that is never used is just latency you paid for nothing. A common mistake is a single global watermark bumped on every write. It works, and it quietly converts every read into a fully-fresh read, which is the expensive mode wearing a disguise. ## What the enforcement point does when the read fails The store makes the decision; your endpoint enforces it. If a consistency-qualified read times out or the store is unavailable, the endpoint chooses — and for an authorization question the safe choice is to deny and surface a `403`, not to fall back to a cheaper read mode, because falling back is precisely the behaviour an attacker would want after a revocation. Log the failure as an availability event rather than as an access denial, so the two do not become indistinguishable in your dashboards. ## The review question For each write path in your service, ask: *if the next read of this object returns the state from just before this write, who is harmed and would anyone notice?* Every path where the answer is *someone gains authority they should not have, silently* carries the consistency token.
- Why not simply pass the newest consistency token on every read?Because that is the fully-fresh mode by another name: every read then needs the newest state, you lose the cheap shared snapshot, and latency and load rise across all traffic. The token is valuable precisely because it asks for one specific write to be visible, not for absolute freshness.
- Where do you keep the consistency token between requests?Beside the object it concerns is the usual answer — store the last write marker for that airframe or component, so any later read of it can demand at least that freshness. Keeping it in the user's session scales badly, because one user touches many objects and the session becomes a growing bag of watermarks.
- The consistency-qualified read times out during a revocation. What does the endpoint do?Deny, and record it as an availability failure rather than an access denial. Falling back to a cheaper read mode reintroduces exactly the stale allow you were guarding against, and it does so on the one path where the wrong answer is silent.
- Does this window also apply to a grant made through a group membership?Yes, and it is one hop longer: the check has to see both the membership write and any rewrite it feeds. The staleness that matters is of the whole traversal's snapshot, not of a single tuple, which is why the token is about a store version rather than about one row.
saying these in an interview costs you the question
- Calls it an access token or a session token rather than a store version marker
- Says a revocation takes effect instantly with no window
- Treats stale grants and stale revocations as equally harmful
- Falls back to a cheaper read mode when the fresh read times out
- Bumps one global watermark and passes it on every read
- Expects the tuple store to push invalidations to every caller