Reverse Proxy / Edge Cache Analysis Note
Table of Contents
Reverse Proxy / Edge Cache Analysis Note #
This note captures the full step-by-step analysis for a reverse proxy / edge cache system: origin truth, local edge cache state, cache policy, request routing, invalidation/refresh, and bounded-stale serving.
Step 1 — Normalize #
Assume the baseline prompt is:
- design a reverse proxy / edge cache
- clients send requests to the proxy
- proxy routes to origin/backends and caches cacheable responses
- cache entries expire or are invalidated
- system scales across many edge or proxy nodes
| Requirement | Actor | Operation | State touched | Priority |
|---|---|---|---|---|
| Client request is served from cache or proxied upstream | Client | read projection | S1read projection targetCacheEntry | C1 |
| Origin/backend response metadata is updated | Client | overwrite state | S1update targetOriginState | C1 |
| Proxy refreshes missing/stale cache entry from upstream | System | async process | S1hidden write targetCacheEntry | C1 |
| System invalidates cache entry | System | state transition | S1update targetCacheEntry | C1 |
| Admin updates cache/routing policy | Admin | overwrite state | S1update targetProxyPolicy | C1 |
| System propagates config snapshot to proxy nodes | System | async process | S1hidden write targetProxySnapshot | C1 |
| Client reads proxy/cache status | Client | read projection | S1read projection targetProxyStatusView | R2 |
Notes on normalization:
- hot request path is
read projection- cached response is a derived projection of origin truth
- origin metadata/state is overwrite-state truth
- refresh is async process
- invalidation is a cache-entry lifecycle transition
- policy update is overwrite-state control-plane config
- snapshot propagation is async dissemination
This is a composition of:
Origin Projection + Edge Delivery PlaneControl Plane + Data Plane
Step 2 — Critical Path Selection #
| Requirement | Priority class | Why |
|---|---|---|
| Serve/proxy request | C1 | wrong/stale content or wrong upstream routing breaks serving correctness |
| Update origin/backend state | C1 | origin truth changes what cache should represent |
| Refresh missing/stale cache entry | C1 | cache correctness depends on valid refresh |
| Invalidate cache entry | C1 | invalidation is the main freshness safety path |
| Update cache/routing policy | C1 | changes future serving behavior |
| Propagate proxy snapshot | C1 | stale proxy nodes can enforce wrong cache/routing policy |
| Read status/analytics | R2 | operational only |
Critical paths:
P1serve/proxy requestP2update origin/backend stateP3refresh cache entryP4invalidate cache entryP5update policyP6propagate proxy snapshot
Step 3 — Primary State Extraction #
| Candidate object label | Candidate source | Candidate needed for C1/R1? | Candidate decomposition action | Class | Primary? | Owner | Evolution | Scope kind | Scope value |
|---|---|---|---|---|---|---|---|---|---|
| OriginState | direct noun | Yes | keep as candidate | entity | Yes | service | overwrite | instance | cache_key or upstream_object_key |
| CacheEntry | hidden write target | Yes | keep as candidate | projection | Yes | service | overwrite | relation | proxy_node_id + cache_key |
| InvalidationState | lifecycle object | Yes | keep as candidate | process | Yes | service | state machine | instance | cache_key or invalidation_id |
| ProxyPolicy | direct noun | Yes | keep as candidate | entity | Yes | service | overwrite | instance | proxy_scope or route_scope |
| ProxySnapshot | hidden write target | Yes | keep as candidate | projection | Yes | service | overwrite | instance | proxy_node_id |
| ProxyStatusView | derived read model | No | reject as UI artifact | projection | No | derived | overwrite | collection | proxy_cluster |
Minimal primary set:
OriginStateCacheEntryInvalidationStateProxyPolicyProxySnapshot
Important modeling choices:
OriginState #
Primary because:
- origin/backend truth determines what a valid cached representation should be
CacheEntry #
Modeled explicitly because:
- reverse proxy correctness depends on current cached object version, expiry, validation metadata, and cacheability state
InvalidationState #
Worth keeping explicit because:
- invalidation/ban/purge flows are a real lifecycle control path
ProxySnapshot #
Important because:
- proxy nodes enforce cache and routing policy from local versioned state
Step 4 — Hard Invariants #
| Path | Tier | Type | Invariant statement |
|---|---|---|---|
P1 serve/proxy request | HARD | eligibility | serve_request is valid only if selected cached response or proxied upstream response is eligible under current cache state, policy, and freshness/invalidation rules for that request scope. |
P2 update origin/backend state | HARD | ordering | Origin/backend-state revisions are ordered by monotonic version within object or route scope. |
P3 refresh cache entry | HARD | accounting | CacheEntry(proxy_node, cache_key) equals function of current origin/backend truth modulo bounded refresh/invalidation lag. |
P4 invalidate cache entry | HARD | eligibility | invalidate_cache_entry is valid only if current invalidation lifecycle and cache state allow transition for that cache scope. |
P5 update cache/routing policy | HARD | ordering | Proxy-policy revisions are ordered by monotonic config version within policy scope. |
P6 propagate proxy snapshot | HARD | freshness | ProxySnapshot(proxy_node) reflects authoritative invalidation and policy state within configured propagation bound. |
What matters most:
- proxy must not serve invalidated or excessively stale cached content beyond configured bounds
- cache entry must track origin version semantics
- proxy policy and invalidation state must move monotonically
Step 5 — Execution Context #
| Field | Value | Why |
|---|---|---|
| Topology | single service distributed | one logical reverse-proxy/cache system with many serving nodes |
| Write coordination scope | per object scope | correctness is per cache key, invalidation scope, and proxy snapshot scope |
| Read consistency target | bounded stale allowed | hot path serves from local cache and snapshot, not strong origin/control reads |
| Holder model | none | no lease-like per-request ownership is central |
| Compensation acceptable? | No | wrong/stale response delivery is not a compensable workflow |
Derived:
bounded_staleness_allowed = trueexclusive_claim_required = falseguarded_by_current_state = true
This implies:
- origin truth plus bounded-stale proxy projection
- versioned invalidation/policy propagation
- local hot-path serving
Step 6 — Deterministic Mechanism Selection #
| Path | Write shape | Base mechanism | Required companions |
|---|---|---|---|
P2 update origin/backend state | overwrite current value | CAS on version | object version/ETag |
P3 refresh cache entry | overwrite current value | single writer refresh or CAS on version | TTL/ETag/validation metadata |
P4 invalidate cache entry | guarded state transition | CAS on (state, version) | invalidation version |
P5 update cache/routing policy | overwrite current value | CAS on version | policy version |
P6 propagate proxy snapshot | overwrite current value | single writer snapshot publication | snapshot version |
Hot path P1 is a read path.
Why these fit:
- origin and policy are current-state config/truth
- cache entry refresh is current-state overwrite from origin
- invalidation is lifecycle/state-machine logic
- proxy snapshots are versioned overwrite dissemination
Step 7 — Read Model / Source of Truth #
| Concept | Truth | Read path | Rebuild path |
|---|---|---|---|
C1 origin/backend truth | OriginState | read source directly | authoritative origin/backend store |
C2 cache entry | CacheEntry | read projection | refresh from origin/backend truth |
C3 invalidation lifecycle | InvalidationState | read source directly | authoritative invalidation state |
C4 proxy policy | ProxyPolicy | read source directly | authoritative policy store |
C5 proxy local snapshot | ProxySnapshot | materialized view | rebuild from latest invalidation + policy state |
C6 proxy status/analytics | derived | materialized view | recompute from primary state |
Important point:
For the hot path:
- proxy node reads local cache entry + local snapshot
- not origin or control plane on every request
Step 8 — Failure Handling #
| Path | Retry | Competing writers | Crash after commit | Publish failure | Stale holder |
|---|---|---|---|---|---|
| origin/policy update | retry with version | stale update loses CAS | committed origin/policy survives crash if persisted | proxy snapshot propagation may lag | n/a |
| cache refresh | retry with current validation metadata | latest valid refresh wins | refresh crash delays update; next request/refresher retries | upstream fetch retry/backoff | n/a |
| invalidation | retry with invalidation version | stale invalidation transition loses CAS | committed invalidation survives crash if persisted | snapshot propagation may lag | n/a |
| snapshot propagation | retry with versioned snapshot | older snapshot loses to newer version | proxy keeps last good snapshot until refresh | failed push retried or pulled | n/a |
| request serving | request retries are application-level | many proxy nodes can serve concurrently from local cache/snapshot | one proxy crash only affects local requests | n/a | stale cache/snapshot bounded by TTL/invalidation/version rules |
What matters most:
- invalidation and snapshot versions move monotonically
- stale cache serving is bounded by explicit TTL/revalidation rules
- cache refresh should use version/ETag semantics where possible
Step 9 — Scale Adjustments #
| Hotspot | Type | First response |
|---|---|---|
| very high request volume | read hotspot | add more proxy nodes and keep hot path local |
| cache-miss storms | contention hotspot | request coalescing and stale-while-revalidate |
| invalidation storms | fan-out hotspot | batch invalidations and publish incremental proxy updates |
| large hot objects | read hotspot | segmented caching and tiered cache hierarchy |
| policy/config churn | fan-out hotspot | incremental snapshot propagation and pull-on-version-miss |
| analytics/status reads | read hotspot | derived views only |
Canonical design conclusion:
- archetype composition:
Origin Projection + Edge Delivery PlaneControl Plane + Data Plane
- primary truth:
OriginStateCacheEntryInvalidationStateProxyPolicyProxySnapshot
- hot path:
- local cache read + local routing/invalidation snapshot
Concrete Substrate #
- origin/backend truth in authoritative app/origin store
- control plane in
Go/Java - invalidation/policy state in strongly consistent control-plane store
- proxy fleet with local cache and versioned config snapshots
- cache refresh with ETag/version validation against origin
- snapshot propagation via watch streams / push / pull-on-version-miss
Operation Layer #
HandleRequest(host, path, headers, context)
- entry point: proxy node
- authoritative decider: local
CacheEntry+ProxySnapshot - transition: none on source truth
- response: cached response, proxied response, or refresh-triggered response
PutOriginState(cache_key, metadata, expected_version?)
- entry point: origin/control API
- authoritative decider: origin-state owner
- transition: overwrite
OriginState
Invalidate(cache_key or prefix, expected_version?)
- entry point: invalidation API
- authoritative decider: invalidation-state owner
- transition: guarded update to
InvalidationState
PutProxyPolicy(scope, config, expected_version?)
- entry point: control-plane API
- authoritative decider: policy owner
- transition: overwrite
ProxyPolicy
- internal refresh
- validate cache entry against origin (ETag/version/TTL)
- overwrite
CacheEntry
- snapshot propagation
- publish latest
ProxySnapshot(version)to proxy nodes
Entry Point vs Decider vs Responder #
| Path | Entry point | Authoritative decider | Physical responder | Logical responder |
|---|---|---|---|---|
| request handling | proxy node | local cache entry + local snapshot | proxy node | reverse proxy/cache |
| origin update | origin/control API | origin-state owner | control-plane node | reverse proxy/cache |
| invalidation | invalidation API | invalidation-state owner | control-plane node | reverse proxy/cache |
| policy update | control-plane API | policy owner | control-plane node | reverse proxy/cache |
| cache refresh | proxy node | local refresh worker + origin validation | proxy node | reverse proxy/cache |
| snapshot propagation | proxy / control plane | snapshot publisher | control/data-plane | reverse proxy/cache |
Concrete HLD #
Main components:
- origin/backend service
- control-plane API
- invalidation/policy state store
- snapshot distribution layer
- proxy/cache fleet
- refresh/coalescing logic
- derived status/analytics views
Short interview version #
“I’d design the reverse proxy / edge cache as origin truth plus bounded-stale local projection. The hot path on each proxy node reads local cache entries and local policy/invalidation snapshots, not origin or control plane. Origin metadata and cache policy are authoritative in control plane, invalidation is versioned, and proxies refresh cache entries from origin using TTL and ETag/version validation. The main correctness boundary is bounded stale serving under explicit invalidation and refresh rules.”