Cache engineering / Cache Index Lab

Design a Cache Index API with Explicit Freshness and Invalidation

A useful cache API needs more than get and set. Define identity, freshness, generations, and failure behavior so clients know when a response is safe to reuse.

Neon cache API diagram card connecting a key, metadata record, payload, and invalidation generation.

A cache index API should help a client answer a precise question: is there a stored result that this request is allowed to reuse? A list of keys is not enough. The client also needs to understand identity, freshness, dependencies, access scope, and what happens when a record is invalidated while another request is still computing its replacement.

This guide develops a conceptual contract for an application cache. The examples describe design choices for your own system, not a hosted CacheIndex.com endpoint. Begin with the smallest useful model, then add behavior only when a real consumer needs it. An explicit contract is easier to reason about than a broad set of operations whose consistency rules remain implicit.

Separate the index from the payload

Use the index to describe a reusable object and locate its payload. A record might contain an opaque entry identifier, namespace, key digest, payload reference, creation time, expiry time, content revision, and dependency generation. The payload could be a document, computed value, or permitted model response. Keeping these roles distinct allows a client to inspect reuse conditions without downloading a large body.

Define how a missing payload is handled. An index hit with an unavailable payload is not a successful cache hit. It should produce a clear miss or integrity outcome according to the contract, with enough internal telemetry to repair the orphaned record. The cache index API overview introduces the interface concepts; this design focuses on the decisions that make them operationally meaningful.

Treat identity as a versioned contract

Construct a key from every input that can change the result within the intended reuse scope. Depending on the application, that may include resource identity, normalized parameters, locale, tenant, authorization scope, and computation version. Hashing the serialized input can produce a compact identifier, but hashing does not compensate for an omitted field. Two semantically different requests remain indistinguishable if their distinguishing input never enters the key.

Version the serialization and normalization rules. Decide whether order matters for a collection, whether an omitted value differs from an explicit default, and whether case changes meaning. Preserve distinctions until the application can justify collapsing them. Store a key-schema version in metadata so a migration does not accidentally compare records produced under incompatible rules. Use an opaque identifier when exposing raw keys would reveal sensitive request content.

Keep HTTP behavior and application behavior clear

RFC 9111 defines HTTP caching semantics, including request method and target URI as minimum key inputs, with variation based on nominated request headers. Its unqualified no-cache directive requires successful validation before reuse; no-store governs storage. These meanings should not be replaced with convenient but incompatible interpretations in an HTTP-facing API.

Your application cache may have additional rules, such as a dependency revision or tenant-specific reuse policy. Document those separately. For example, an API response containing cache metadata has its own HTTP delivery behavior, while the object described by that response has application-level eligibility rules. A client should not mistake a recently delivered metadata response for proof that the underlying computed object is still valid.

Define lookup outcomes explicitly

A lookup can return a usable entry, a miss, a stale candidate, an invalidated entry, or an unavailable cache dependency. Decide which distinctions are public and which remain in internal logs. A client that receives a stale candidate needs an explicit policy before serving it. A lookup failure should not silently become permission to reuse the last object the client happened to retain.

  • Entry identity: identifies the exact stored object.
  • Reuse state: explains whether this request may use it now.
  • Freshness boundary: states the expiry or validation condition.
  • Version evidence: records the content and dependency revisions used.
  • Payload reference: locates the object within the caller's authorized scope.

Use server-side time consistently when enforcing expiry. A timestamp supplied to a client is useful for display and planning, but should not become the only protection against reuse after a server-side invalidation.

Make writes conditional where races matter

Consider two workers computing the same object. Without a conditional write, the slower worker can overwrite a newer result after it finishes. Require an expected revision or generation when committing a replacement. If the state changed during computation, reject the stale write and let the worker follow a documented retry path. This is a design requirement for the backing store and API together, not something a response field can enforce alone.

Define retry identity

Decide how retries behave when a client loses the response after a successful write. An idempotency mechanism can identify a repeated operation, but its scope and retention need definition. Keep the original result associated with the operation identity for the supported retry window. Do not let a reused token authorize an unrelated write with a different body.

Design invalidation as a state change

Deleting a key is simple until an in-flight worker recreates the old value. A generation-based design addresses that race by associating work with a current generation. Suppose a computation begins under generation seven. An invalidation advances the relevant namespace to generation eight. The later write from generation seven must fail its commit check, even if its payload was successfully computed.

Specify the scope of invalidation: one entry, a dependency group, or a namespace. Broad invalidation may be easier to reason about but can trigger a burst of recomputation. Narrow invalidation requires reliable dependency tracking. The cache index fundamentals guide helps frame that tradeoff. Whichever design you choose, record the invalidation event and make its completion semantics clear to callers.

Plan for misses and unavailable dependencies

A cache should have a defined fallback path. On a miss, a client may compute a replacement, ask another service, or return an explicit unavailable result. Coordinate concurrent fills where duplicate computation is expensive. Bound the time spent waiting for another worker and specify what happens if that worker fails. A lock without expiry or ownership rules can turn a performance optimization into a persistent outage.

Separate a valid empty result from an error. If you cache negative results, give them a deliberate policy and a way to invalidate them when the underlying resource appears. Avoid caching transient upstream failures as durable absence. For action-taking workflows, the agents cache index guide provides additional context: a previously generated suggestion does not prove that a current action is authorized or still appropriate.

Authorize before revealing cache contents

Check the caller's access scope before lookup results disclose whether an object exists. Enforce the same boundary when retrieving a payload. A namespace label alone does not provide isolation unless every operation uses it correctly. Include list, inspect, invalidate, and export operations in the access model; administrative metadata can reveal sensitive information even when payload bodies are hidden.

Store only the diagnostics you need. Prefer reason codes, revisions, and timings to full request bodies in routine logs. Define deletion behavior across the index, payload store, replicas, and diagnostic records according to the application's requirements. Treat a user-visible deletion acknowledgment as a contract that must match the actual completion guarantee.

Define what listing the index means

Administrative listing introduces another consistency question. If entries change during pagination, will a caller see a stable snapshot or a best-effort traversal? Specify the behavior and use an opaque continuation token that preserves the authorized scope. A listing should not be treated as proof that every displayed entry remains reusable. Require the normal eligibility checks at retrieval time, and make bulk invalidation operate on an explicit scope instead of an accidental collection of stale listing results.

Test the contract through its failure cases

Verify cross-tenant lookups, expiry boundaries, concurrent writes, invalidation during computation, missing payloads, and retry ambiguity. Check whether a model or prompt revision changes the correct namespace; the semantic cache migration guide explores that case. Measure useful hits separately from rejected or stale candidates, and record why a lookup missed. A high hit rate is unhelpful if the reused objects violate the contract.

Conclusion: make reuse explainable

A dependable cache index API makes identity, eligibility, and state transitions explicit. Start with metadata and payload boundaries, define conditional writes and invalidation, then test the races that can return the wrong object. The result is an interface that helps clients make a correct reuse decision, with clear evidence when the answer is no.

Continue the reading path through the subjects covered in this article.

Connected reading

Keep the ideas moving.

All Lab articles