Enforce revocation even when a CDN already has the contentLESSON 10.06 · 6 OF 20 IN CHAPTER
PART C / Data systems at scale
Step 159 of 252
LESSON 10.06 · 6 OF 20 IN CHAPTERHands-on

Enforce revocation even when a CDN already has the content

Application and assignment

Ana shares a private shift schedule using a link token. A CDN stores the response bytes after the first request. Ana then revokes the link. If only the origin checks access, a later cache hit can return those bytes without visiting the origin at all.

Choose an immediate or explicitly bounded revocation policy, then trace the same warmed token through the local delivery model. Your design must also distinguish another tenant’s token and an unavailable authorization service. Revocation controls future delivery decisions, not copies already downloaded.

Contract and starting evidence

Constructed candidate brief: “Ana shares a private schedule with token A. Its first GET warms a CDN. Ana revokes the token one second later. Define what the next identical GET should do, including when the origin is unavailable.”

Prerequisite: cache hits versus shared loads. Choose the contract before opening LinkDelivery in reference.py (download file, source below).

Read the supplied code · reference.py
reference.py · reference.py
"""Deterministic in-process boundary model, not a Redis or fleet-lock client."""
import asyncio
from collections import deque


class Unavailable(Exception):
    pass


class Clock:
    def __init__(self):
        self.now = 0.0

    def advance(self, seconds):
        self.now += seconds


class OriginBudget:
    """Shared model of atomic admission: rolling one-second cap plus in-flight cap."""
    def __init__(self, clock, per_second=100, concurrent=10):
        self.clock, self.per_second, self.concurrent = clock, per_second, concurrent
        self.starts = deque()
        self.active = self.peak = self.total = 0

    def enter(self):
        while self.starts and self.starts[0] <= self.clock.now - 1:
            self.starts.popleft()
        if len(self.starts) >= self.per_second or self.active >= self.concurrent:
            raise Unavailable("origin admission exhausted")
        self.starts.append(self.clock.now)
        self.active += 1
        self.total += 1
        self.peak = max(self.peak, self.active)

    def leave(self):
        self.active -= 1


class CacheAside:
    def __init__(self, clock, budget, ttl=5):
        self.clock, self.budget, self.ttl = clock, budget, ttl
        self.values, self.flights = {}, {}
        self.cache_available = True

    async def get(self, key, loader):
        cached = self.values.get(key) if self.cache_available else None
        if cached and self.clock.now < cached[0]:
            return cached[1]
        task = self.flights.get(key)
        if task is None:
            task = asyncio.create_task(self._load(key, loader))
            self.flights[key] = task
            # Retrieve even an unobserved exception if every waiter leaves.
            task.add_done_callback(lambda done: None if done.cancelled() else done.exception())
        # A disconnected waiter must not cancel work shared with other waiters.
        return await asyncio.shield(task)

    async def _load(self, key, loader):
        entered = False
        try:
            self.budget.enter()
            entered = True
            value = await loader(key)
            if self.cache_available:
                self.values[key] = (self.clock.now + self.ttl, value)
            return value
        finally:
            if entered:
                self.budget.leave()
            self.flights.pop(key, None)


def sticky_read(primary, replica, now, pin_until):
    return primary if now < pin_until else replica


def strict_read(primary, replica, minimum_version):
    if replica is not None and replica >= minimum_version:
        return replica
    if primary is not None and primary >= minimum_version:
        return primary
    raise Unavailable("no source has the session watermark")


class LinkDelivery:
    """Explicitly separate authorization-decision age from object cache age."""
    def __init__(self, clock, policy="immediate", auth_ttl=5):
        if policy not in {"immediate", "bounded"}:
            raise ValueError(policy)
        self.clock, self.policy, self.auth_ttl = clock, policy, auth_ttl
        self.tokens = {"A": ("tenant-a", "schedule-1"), "B": ("tenant-b", "schedule-1")}
        self.objects = {("tenant-a", "schedule-1"): "Ana 09:00", ("tenant-b", "schedule-1"): "Ben 10:00"}
        self.decisions, self.cache = {}, {}
        self.auth_available = self.origin_available = True
        self.origin_calls = 0

    def get(self, token):
        decision = self.decisions.get(token)
        fresh = self.policy == "bounded" and decision and self.clock.now < decision[0]
        if fresh:
            identity = decision[1]
        else:
            if not self.auth_available:
                return 503, None  # Never extend an expired decision during outage.
            identity = self.tokens.get(token)
            if identity is None:
                return 403, None
            self.decisions[token] = (self.clock.now + self.auth_ttl, identity)
        # Authorization precedes EVERY object-cache lookup; identity scopes bytes.
        if identity in self.cache:
            return 200, self.cache[identity]
        if not self.origin_available:
            return 503, None
        self.origin_calls += 1
        value = self.objects[identity]
        self.cache[identity] = value
        return 200, value
Input Immediate policy Five-second decision policy
GET A at t=0 200, Ana 09:00 Same
Revoke A after warming, repeat before t=5 403 May return 200 until t=5
GET A at t=5 exactly 403 403; reads do not extend decision expiry
Authorization service unavailable 503, including warm bytes Existing decision only until original expiry; then 503
Token B with same pathname Ben 10:00 Same; tenant identity scopes object bytes

The bounds describe new delivery decisions. Revocation cannot erase bytes a recipient already downloaded. This exercise deliberately excludes preventing screenshots or revoking an in-progress response after it has been authorized.

Baseline: the origin never sees the second request

Diagram: Baseline: the origin never sees the second request

Trace the warmed request before choosing infrastructure. Putting token A in the cache key avoids mixing A with B, but the same revoked A still hits. An origin check cannot authorize a request that never reaches it.

Move authorization onto the delivery path

  1. Choose immediate or bounded revocation and define the reference clock.
  2. Authorize before every object cache lookup, returning the permitted tenant and resource; don't trust caller-supplied ownership.
  3. Scope cached bytes to that tenant/resource. Protect the origin from direct unauthorized access and avoid separate ungoverned browser/proxy caches for sensitive responses (for example, use Cache-Control: no-store to clients).
  4. For strict decisions, fail closed when current authorization is unavailable. For bounded decisions, preserve the original expiry; outage and repeated hits must never extend it. Account for clock error and propagation within the SLA.
Diagram: Move authorization onto the delivery path

CloudFront serves cache hits without fetching the origin. A viewer-request authorization integration can therefore be one enforcement point, provided its revocation data actually meets the chosen consistency bound. Alternatively, disable shared caching and check authorization at every origin request. Signed URL expiry by itself promises expiration, not immediate per-token revocation. Invalidation requires a propagated, observable completion bound; do not label it instantaneous. See delivery behavior and private content. These undated technical docs were accessed 2026-09-22, not publication-dated interview evidence.

Follow-up: authorization decisions are cached globally

Predict what two regions may serve after revocation, then draw the expiry.

Diagram: Follow-up: authorization decisions are cached globally

Run the same local test command as the cache lab. Tests warm A, revoke, repeat the identical GET before object expiry, clear the object cache and repeat, compare tokens A/B at the same pathname, and inject origin and auth outages. The fake-clock boundary at t=5 is asserted exactly.

Senior follow-ups: justify cache headers and origin access restrictions; explain why origin availability and authorization availability are different. Lead follow-up: allocate a five-second SLA across propagation, decision TTL, clock skew, and already-in-flight work; define audit evidence in both regions. Acceptance requires the exact expected status/bytes on both warm and cold paths, and an honest statement of the chosen revocation bound.

Sources and further reading · 2