PgBeam Docs

Caching

How PgBeam caches query results at the edge, when it bypasses cache, and how to turn it on safely for your workload.

PgBeam caches query results at the data plane level. When a cached result is available, PgBeam returns it directly without touching the upstream database. This reduces upstream load, lowers read latency, and saves you money on database compute.

Each region keeps its own independent cache. There is no cross-region coherence layer — that is a deliberate tradeoff. Synchronizing caches across regions would add latency to every read, which defeats the purpose of caching.

Caching is opt-in

Caching starts disabled for new databases. This is the safe default. Turn it on after traffic is flowing and you know which reads are stable enough to benefit from caching.

When caching helps

Caching is most effective when your workload has repeated reads that return the same data across many requests. Common examples:

  • Product catalogs and category listings
  • Configuration tables read on every request
  • User profile lookups that change infrequently
  • Leaderboards, stats, and aggregation queries that can tolerate staleness
  • Reference data (countries, currencies, feature flags)

Caching is not a good fit for:

  • Queries that must always return the latest data (e.g., account balances)
  • Write-heavy workloads where data changes between reads
  • Queries with highly variable parameters that produce unique results each time

How caching works

Query classification

PgBeam classifies each incoming query. Read statements (SELECT) are candidates for caching. Writes (INSERT, UPDATE, DELETE), DDL (CREATE TABLE, ALTER), session-changing commands (SET, DISCARD), and queries with volatile functions (NOW(), RANDOM(), pg_advisory_lock()) are never cached.

Cache key generation

For cacheable queries, PgBeam generates a cache key from the normalized SQL text plus parameter values, scoped to the project. Normalization means whitespace and casing differences do not create separate cache entries.

The same query template with different parameter values produces different cache entries:

-- These produce two separate cache entries
SELECT * FROM products WHERE id = 1;
SELECT * FROM products WHERE id = 2;

Cache lookup

PgBeam checks the local cache and then the regional shared cache for a matching entry:

  • Hit — A fresh cached result is returned immediately. The upstream database is never contacted.
  • Stale hit — The cached result has passed its TTL but is within the stale-while-revalidate (SWR) window. PgBeam returns the stale result immediately and refreshes the cache in the background.
  • Miss — No cached result exists. The query goes to the upstream database, and the result is stored in the cache for future requests.

Cache defaults

SettingDefaultWhat it controls
TTL60sHow long a cached result is considered fresh
SWR30sExtra window after TTL where stale results can be served while refreshing
Max entries10,000Approximate upper bound of cached entries per project
EnabledfalseCaching is off by default for new databases

Understanding TTL and SWR

TTL and SWR work together to balance freshness and performance:

 ◀──── TTL (60s) ────▶◀── SWR (30s) ──▶
 │     fresh hit      │   stale hit     │  miss
 │  (instant return)  │ (instant return │ (upstream query,
 │                    │  + bg refresh)  │  cache updated)
 ┼────────────────────┼────────────────┼──────────
 cache write          TTL expires      SWR expires
  • During the TTL window, cached results are returned without any upstream query.
  • During the SWR window, the stale result is returned immediately (keeping latency low), and PgBeam refreshes the entry in the background.
  • After the SWR window, the entry is treated as a miss and the upstream is queried.

Ways to control cache behavior

PgBeam checks cache controls in priority order. The first match wins:

PrioritySourceExampleScope
1SQL annotation/* @pgbeam:cache noCache */Per query
2Session override offSET pgbeam.cache = offPer session
3Cache ruleDashboard rule for query shapePer query shape
4Session override onSET pgbeam.cache = onPer session
5Database defaultDatabase-level cache settingAll queries
6FallbackOff

This precedence lets you set broad defaults and override them precisely where needed.

SQL annotations

Embed cache directives directly in your SQL as comments. PgBeam strips the annotations before forwarding to the upstream, so your database never sees them.

/* @pgbeam:cache */ SELECT * FROM products WHERE active = true;
-- Cache for 5 minutes, serve stale for 1 minute during refresh
/* @pgbeam:cache maxAge=300 swr=60 */ SELECT * FROM products;
-- Force this query to always hit the upstream
/* @pgbeam:cache noCache */ SELECT NOW();
/* @pgbeam:replica */ SELECT * FROM events ORDER BY created_at DESC LIMIT 100;

Annotation reference

AnnotationEffect
/* @pgbeam:cache */Enable caching with default TTL and SWR
/* @pgbeam:cache maxAge=N */Cache for N seconds
/* @pgbeam:cache swr=N */Set SWR window to N seconds
/* @pgbeam:cache maxAge=N swr=M */Set both TTL and SWR
/* @pgbeam:cache noCache */Bypass cache for this query
/* @pgbeam:replica */Route to a read replica

Session overrides

Control caching for an entire connection session. Useful for debugging or for application code that needs to temporarily force cache behavior.

Session-level cache control
-- Enable caching for this session
SET pgbeam.cache = on;

-- Set TTL for this session
SET pgbeam.cache_ttl = 300;

-- Enable debug output (see cache hit/miss notices)
SET pgbeam.debug = on;

-- Check current settings
SHOW pgbeam.cache;
SHOW pgbeam.cache_ttl;
SHOW pgbeam.debug;

When pgbeam.debug is on, every query returns a NOTICE with cache information:

SET pgbeam.debug = on;
SELECT * FROM users WHERE id = 1;
-- NOTICE: pgbeam: cache=hit age=12s ttl=60s swr=30s

Cache bypasses

PgBeam skips the cache entirely when any of these conditions are true:

ConditionWhy it bypasses
Caching not enabled for the queryNo rule, annotation, or session override
Statement mutates dataINSERT, UPDATE, DELETE, TRUNCATE
Statement changes session stateSET, DISCARD, RESET
Query uses volatile functionsNOW(), RANDOM(), pg_advisory_lock(), etc.
Query is inside an explicit transactionBEGIN ... COMMIT blocks
SQL annotation says noCacheExplicit opt-out
Session override is pgbeam.cache = offSession-level opt-out

Cache layers

PgBeam uses a two-layer cache for speed and resilience:

LayerScopeLatencyFailure behavior
L1Per-processMicrosecondsProcess-local; failure is extremely rare
L2Shared per regionSub-millisecondFail-open: queries go to upstream

L1 is checked first. On an L1 miss, L2 is checked. On an L2 miss, the query goes to the upstream database. Results from the upstream are written back to both layers.

If the shared cache is unavailable, PgBeam falls through to the origin database. Your application continues working — it just loses the caching benefit until the cache recovers.

Cache rules

Cache Rules give you a dashboard-based way to enable caching for specific query shapes without changing application code. PgBeam automatically tracks the distinct query shapes flowing through your project, so you can see what your traffic looks like before you start caching anything.

View cache rules

Open your database in the dashboard and go to Cache Rules. You will see a list of detected query shapes with frequency and timing data.

curl -H "Authorization: Bearer <api-key>" \
  https://api.pgbeam.com/v1/projects/{projectId}/databases/{dbId}/cache-rules
pgbeam projects cache-rules list --database-id <id>

Enable caching for a query shape

Once you identify a high-frequency read query that would benefit from caching, enable it from the dashboard or API:

Find the query shape in Cache Rules and toggle caching on. You can optionally set a custom TTL and SWR for that shape.

curl -X PUT \
  https://api.pgbeam.com/v1/projects/{projectId}/databases/{dbId}/cache-rules/{hash} \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{"cache_enabled": true}'
  1. Let traffic flow for a while — PgBeam needs to see your query patterns before you can make informed caching decisions.
  2. Start with high-frequency, stable reads — Look for queries that run hundreds or thousands of times per day and return data that changes infrequently.
  3. Avoid caching queries with volatile data — If a query returns different results every time, caching wastes memory without saving upstream load.
  4. Monitor cache hit rates — Use the dashboard or Query Insights to see whether caching is actually helping.

Further reading

On this page