Cache control
On this page
Advertising a result as reusable by the client.
const CACHE_ETAG_KEY
Section titled “const CACHE_ETAG_KEY”const CACHE_ETAG_KEY = “vgi.cache.etag”const CACHE_EXPIRES_KEY
Section titled “const CACHE_EXPIRES_KEY”const CACHE_EXPIRES_KEY = “vgi.cache.expires”const CACHE_IF_MODIFIED_SINCE_KEY
Section titled “const CACHE_IF_MODIFIED_SINCE_KEY”const CACHE_IF_MODIFIED_SINCE_KEY = “vgi.cache.if_modified_since”const CACHE_IF_NONE_MATCH_KEY
Section titled “const CACHE_IF_NONE_MATCH_KEY”const CACHE_IF_NONE_MATCH_KEY = “vgi.cache.if_none_match”const CACHE_LAST_MODIFIED_KEY
Section titled “const CACHE_LAST_MODIFIED_KEY”const CACHE_LAST_MODIFIED_KEY = “vgi.cache.last_modified”const CACHE_NO_STORE_KEY
Section titled “const CACHE_NO_STORE_KEY”const CACHE_NO_STORE_KEY = “vgi.cache.no_store”const CACHE_NOT_MODIFIED_KEY
Section titled “const CACHE_NOT_MODIFIED_KEY”const CACHE_NOT_MODIFIED_KEY = “vgi.cache.not_modified”const CACHE_PARTITION_SCOPE_KEY
Section titled “const CACHE_PARTITION_SCOPE_KEY”const CACHE_PARTITION_SCOPE_KEY = “vgi.cache.partition_scope”const CACHE_PER_VALUE_KEY
Section titled “const CACHE_PER_VALUE_KEY”const CACHE_PER_VALUE_KEY = “vgi.cache.per_value”const CACHE_REVALIDATABLE_KEY
Section titled “const CACHE_REVALIDATABLE_KEY”const CACHE_REVALIDATABLE_KEY = “vgi.cache.revalidatable”const CACHE_SCOPE_CATALOG
Section titled “const CACHE_SCOPE_CATALOG”const CACHE_SCOPE_CATALOG = “catalog”const CACHE_SCOPE_KEY
Section titled “const CACHE_SCOPE_KEY”const CACHE_SCOPE_KEY = “vgi.cache.scope”const CACHE_SCOPE_TRANSACTION
Section titled “const CACHE_SCOPE_TRANSACTION”const CACHE_SCOPE_TRANSACTION = “transaction”const CACHE_STALE_IF_ERROR_KEY
Section titled “const CACHE_STALE_IF_ERROR_KEY”const CACHE_STALE_IF_ERROR_KEY = “vgi.cache.stale_if_error”const CACHE_STALE_WHILE_REVALIDATE_KEY
Section titled “const CACHE_STALE_WHILE_REVALIDATE_KEY”const CACHE_STALE_WHILE_REVALIDATE_KEY = “vgi.cache.stale_while_revalidate”const CACHE_TTL_KEY
Section titled “const CACHE_TTL_KEY”const CACHE_TTL_KEY = “vgi.cache.ttl”Description
Result-cache control metadata (vgi.cache.*).
A table function can advertise that its result is cacheable by the client
(the DuckDB extension) by attaching vgi.cache.* metadata to the first
data batch it emits. The vocabulary mirrors HTTP caching (RFC 9111/9110): a
freshness lifetime (ttl / expires), a reuse scope, validators
(ETag / Last-Modified) for conditional revalidation, and stale-serving grace
windows.
The key strings are the single source of truth shared with the C++ extension
(which reads them by string). {@link cacheControlMetadata} renders a
{@link CacheControl} to the Map<string, string> of vgi.cache.* keys that
rides on batch metadata:
out.emit(firstBatch, cacheControlMetadata({ ttl: 300 }));Pass extra to merge in metadata a function already emits per batch (for
example vgi_batch_index); the rendered cache keys win on collision.
Booleans render as "1" and are omitted when false; timestamps are RFC 3339
UTC strings; durations are whole seconds.
Ports vgi/cache_control.py from vgi-python.
interface CacheControl
Section titled “interface CacheControl”export interface CacheControlDescription
Cacheability advertised by a table function on its first result batch.
Presence of ttl or expires is what makes a result cacheable;
noStore overrides any freshness key.
Fields
ttlnumberoptionalFreshness lifetime in whole seconds, relative to full-result receipt (skew-immune; wins over
expires).expiresstringoptionalAbsolute RFC 3339 UTC deadline. Lifetime is
expires - nowat receipt.scopeCacheScopeoptionalReuse scope —
"catalog"(default; reusable across transactions within the calling catalog identity) or"transaction"(reused only within the same transaction).noStorebooleanoptionalExplicit “never cache”; overrides any freshness key.
etagstringoptionalStrong validator (opaque quoted string) for conditional revalidation.
lastModifiedstringoptionalWeaker RFC 3339 UTC validator; fallback when no ETag.
revalidatablebooleanoptionalThe worker can check freshness cheaply without recomputing; gates whether the client ever sends a conditional request.
staleWhileRevalidatenumberoptionalGrace window (seconds) to serve stale immediately while revalidating in the background.
staleIfErrornumberoptionalGrace window (seconds) to serve stale if a revalidation RPC fails.
notModifiedbooleanoptional304-equivalent — set on a 0-row batch in reply to a conditional request to assert the client’s stored payload is still fresh (the client reuses it instead of re-streaming).
partitionScopebooleanoptionalOpt in to per-PARTITION caching: in addition to the whole-scan entry, the client stores the result split by distinct partition tuple, so a later
=/IN-filtered scan on the partition column(s) can serve the requested partitions without calling the worker. Only meaningful for aSINGLE_VALUE_PARTITIONStable function; purely additive.perValuebooleanoptionalOpt in to per-VALUE memoization: in addition to the whole-result entry, the client memoizes each distinct worker-input tuple’s output keyed on that tuple, so the same value is served without calling the worker on a later chunk or a later query. Only meaningful for an exchange-mode MAP — a scalar, or a blended table-in-out invoked through a correlated
LATERAL.Default off, and it should stay off unless one call is genuinely expensive. This is not a free win: serving a value from the memo costs a key probe, a decode and a per-value assembly step, and that only pays back when it is cheaper than asking you for the answer. For a cheap map — arithmetic, a string tweak, a lookup in a table the worker already has in memory — it is a large net loss (the engine measures a per-value serve at roughly 50x the cost of just calling the worker for a simple arithmetic map). Only the function author knows which side of that line a call falls on, which is why the engine will not guess: turn it on for model inference, geocoding, an external API call, or anything rate-limited or billed per request.
Independent of freshness:
ttl/expiresmake a result cacheable at all, while this decides whether the per-value tier is additionally populated. Requires a deterministic, side-effect-free function, exactly like the rest of the result cache.
function cacheControlMetadata
Section titled “function cacheControlMetadata”export function cacheControlMetadata(cc: CacheControl, extra?: Map<string, string>): Map<string, string>Description
Render a {@link CacheControl} to the vgi.cache.* batch-metadata map.
scope is always emitted so the client never has to infer the default.
Unset optional fields are omitted; false booleans are omitted. Entries from
extra are written first, so a rendered cache key wins on collision.
Throws when scope is unrecognized or a duration is negative — a silently
ignored advertisement is far harder to debug than a stack trace at the
offending emit().
type CacheScope
Section titled “type CacheScope”export type CacheScope = typeof CACHE_SCOPE_CATALOG | typeof CACHE_SCOPE_TRANSACTION;Description
Reuse scope for a cacheable result.