Skip to main content
Version: Next

Query Cache

QueryFlux can transparently cache deterministic query results in a pluggable storage backend, eliminating repeated backend roundtrips for identical queries.

How it works​

Client → QueryFlux
│
├── Cache hit? → replay Arrow IPC from storage → respond immediately
│
└── Cache miss? → execute on backend cluster
└── TeeResultSink writes to cache + responds to client
  1. Cache key — computed from the exact SQL text, authenticated user, bind parameters, cluster group name, and current catalog/database. Queries differing only in literal values or parameters produce distinct cache keys.
  2. Determinism guard — queries containing non-deterministic functions (NOW(), RAND(), UUID(), CURRENT_TIMESTAMP, etc.) are never cached.
  3. Arrow IPC storage — results are stored as streaming Arrow IPC files with optional LZ4 or ZSTD compression.
  4. TTL expiration — each entry has a configurable time-to-live. A background cleanup task periodically removes expired entries.

Configuration​

Cache backend (startup-only)​

The cacheBackend block configures where cached data is stored. It uses Apache OpenDAL under the hood, so any OpenDAL-supported service can be used without code changes.

FieldDescription
schemeOpenDAL service scheme: fs, s3, gcs, azblob, oss, etc. Default: fs
compressionArrow IPC body compression: none, lz4 (default), zstd
cleanupIntervalSecsHow often the background task deletes expired entries. Default: 300
optionsFlat key→value map passed directly to Operator::via_iter(scheme, options). Keys are service-specific — see OpenDAL service docs.

Local filesystem example​

queryflux:
cacheBackend:
scheme: fs
compression: lz4
options:
root: /var/lib/queryflux/cache

S3 / MinIO example​

queryflux:
cacheBackend:
scheme: s3
compression: lz4
options:
bucket: queryflux-cache
endpoint: http://minio:9000
region: us-east-1
access_key_id: ${MINIO_ACCESS_KEY}
secret_access_key: ${MINIO_SECRET_KEY}

Google Cloud Storage example​

queryflux:
cacheBackend:
scheme: gcs
compression: zstd
options:
bucket: my-queryflux-cache
root: cache/
credential_path: /etc/queryflux/gcs-key.json

Per-group settings (hot-reloadable)​

Each cluster group can independently enable caching and control TTL/size limits. These settings are hot-reloadable via Studio or the admin API.

FieldDescription
enabledWhether caching is active for this group. Default: false
ttlSecsTime-to-live for cached entries in seconds. Default: 300
maxEntrySizeMbMaximum size (in MB) of a single cached result. Results exceeding this limit are not cached (the query still succeeds). Optional — unlimited if omitted.
clusterGroups:
analytics:
members: [trino-1, trino-2]
maxRunningQueries: 50
cache:
enabled: true
ttlSecs: 600
maxEntrySizeMb: 128

Per-query cache hints​

Clients can opt-in to caching on a per-query basis, even when the group-level cache is disabled:

MechanismExample
HTTP headerX-QueryFlux-Cache: true (optionally X-QueryFlux-Cache-TTL: 600)
Query tagSession property queryflux:cache with optional value ttl=N
SQL comment/* queryflux:cache:ttl=120 */ SELECT ...

Hints are evaluated in the order above (first match wins).

What gets cached​

  • Only deterministic queries are cached. The determinism check is a lightweight regex that detects common non-deterministic functions.
  • Only queries routed through the sync execution path (Arrow materialization) are cached. This includes all engines and Trino queries on groups with caching enabled.
  • Results exceeding maxEntrySizeMb are not cached (the query completes normally, only caching is skipped).
  • Failed or cancelled queries are never cached.

Cache invalidation​

Admin API​

# Invalidate all cached entries across all groups
curl -X DELETE http://localhost:9000/admin/cache

# Invalidate entries for a specific group
curl -X DELETE http://localhost:9000/admin/cache/analytics

Studio UI​

Use the Clear cache button in the cluster group editor dialog.

Automatic expiration​

The background cleanup task runs every cleanupIntervalSecs seconds and removes entries past their TTL.

Observability​

Prometheus metrics​

MetricTypeLabelsDescription
queryflux_cache_hits_totalCountercluster_groupCache hits served from storage
queryflux_cache_misses_totalCountercluster_groupCache misses (query executed on backend)
queryflux_cache_writes_totalCountercluster_groupNew entries written to cache

Query history​

Cached queries appear in Studio's query history with:

  • A CACHED badge
  • Engine type shown as Cache
  • Cluster shown as (cache)
  • Execution duration of 0ms

Architecture notes​

  • The cache layer sits before cluster slot acquisition — a cache hit never consumes a backend connection.
  • Arrow IPC streaming format enables zero-copy replay without deserialization overhead.
  • The TeeResultSink simultaneously streams results to the client and writes to cache, so there's no latency penalty on cache-miss writes.
  • Cache metadata (key, group, TTL, size) is stored in Postgres (or in-memory for dev), while the actual Arrow data lives in the configured OpenDAL backend.