---
title: "Caching"
description: "How PgBeam caches query results at the edge, when it bypasses cache, and how to turn it on safely for your workload."
canonical: "https://pgbeam.com/docs/cache"
last-updated: "2026-09-14T19:37:21.000Z"
---

# Caching

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

URL: https://pgbeam.com/docs/cache

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 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:

## 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

Setting

Default

What it controls

**TTL**

`60s`

How long a cached result is considered fresh

**SWR**

`30s`

Extra window after TTL where stale results can be served while refreshing

**Max entries**

`10,000`

Approximate upper bound of cached entries per project

**Enabled**

`false`

Caching is off by default for new databases

## Understanding TTL and SWR

TTL and SWR work together to balance freshness and performance:

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.

## Invalidation: writes do not auto-clear the cache

PgBeam does **not** automatically invalidate cached results when a write flows
through the proxy. Writes are never cached, but a `SELECT` cached before the
write keeps being served until its TTL (and SWR window) expire. Plan for a
staleness window of up to `TTL + SWR` after any change.

If you need cached data cleared sooner than its TTL, opt in to event-based invalidation. PgBeam listens on the `pgbeam_invalidate` channel on your upstream database and evicts cached entries for a table when it receives a notification naming that table. You emit those notifications from triggers you install on the tables you cache:

The `NOTIFY` payload is the table name (bare or schema-qualified). PgBeam evicts the cached entries that read from that table.

`LISTEN`/`NOTIFY` requires a session-level connection. PgBeam opens one direct
session connection to your upstream to listen on `pgbeam_invalidate`. If the
upstream connection details you gave PgBeam point at a PgBouncer-style
transaction pooler (PlanetScale, the Supabase pooler, RDS Proxy), the `LISTEN`
silently receives nothing and invalidation does nothing, with no error. Point
PgBeam's upstream at a direct, session-capable endpoint (not the pooler port)
if you want `NOTIFY` invalidation to work.

If a table changes often and must never be served stale, do not rely on invalidation timing. Either skip the cache for those reads with `/* @pgbeam:cache noCache */`, or give them a very short TTL (for example `/* @pgbeam:cache maxAge=1 */`) so the freshness window is bounded regardless of whether a notification arrives.

## Ways to control cache behavior

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

Priority

Source

Example

Scope

1

SQL annotation

`/* @pgbeam:cache noCache */`

Per query

2

Session override off

`SET pgbeam.cache = off`

Per session

3

Cache rule

Dashboard rule for query shape

Per query shape

4

Session override on

`SET pgbeam.cache = on`

Per session

5

Database default

Database-level cache setting

All queries

6

Fallback

Off

n/a

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.

## Annotation reference

Annotation

Effect

`/* @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.

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

## Cache bypasses

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

Condition

Why it bypasses

Caching not enabled for the query

No rule, annotation, or session override

Statement mutates data

`INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`

Statement changes session state

`SET`, `DISCARD`, `RESET`

Query uses volatile functions

`NOW()`, `RANDOM()`, `pg_advisory_lock()`, etc.

Query is inside an explicit transaction

`BEGIN` ... `COMMIT` blocks

SQL annotation says `noCache`

Explicit opt-out

Session override is `pgbeam.cache = off`

Session-level opt-out

## Cache layers

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

Layer

Scope

Latency

Failure behavior

**L1**

Per-process

Microseconds

Process-local; failure is extremely rare

**L2**

Shared per region

Sub-millisecond

Fail-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.

## 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.

## Recommended approach

**Let traffic flow for a while**: PgBeam needs to see your query patterns before you can make informed caching decisions.

**Start with high-frequency, stable reads**: Look for queries that run hundreds or thousands of times per day and return data that changes infrequently.

**Avoid caching queries with volatile data**: If a query returns different results every time, caching wastes memory without saving upstream load.

**Monitor cache hit rates**: Use the dashboard or Query Insights to see whether caching is actually helping.

## Further reading

Read Replicas: Route reads to replicas for load distribution

Plans & Limits: Cache entry limits and defaults per plan tier

Troubleshooting: Debugging "cache is not doing anything"