PgCache/pgcache

PostgreSQL proxy for query aware caching, using CDC for cache maintenance.

Rust

63

767 commits

updated Sep 25, 2026

See the code

See what people are saying

SourceMessageScoreDate

Which Postgres queries can I cache? Free WebAssembly tool (runs locally in your browser) (r/webdev)

We've been thinking a lot about caching this past year. (1) There's a nice pattern for figuring out opportunities to optimize a Postgres workload, but the downside is that it takes work. (2) There's lots of generic Postgres-health tooling out there, but we haven't seen any good free & private…

0

Sep 26, 2026

README

pgcache

A PostgreSQL cache that never serves stale data.

License: Elastic License 2.0 PostgreSQL 16 | 17 | 18 Status: active development

Most caches force a choice between speed and correctness. pgcache doesn't. It's a wire-compatible PostgreSQL proxy that caches query results and keeps them consistent with the origin in real time using logical replication — every insert, update, and delete is either applied in place to the cached result or the affected query is invalidated. A cached query is therefore always either up to date or transparently forwarded to origin. Never a stale read, and not a line of application code changes.

Think of it as a smart read replica that copies only the data you actually read — the hot working set driving most of your traffic, instead of a full second copy of a database that only keeps growing. Learn more at pgcache.com.

What it is

pgcache sits between your application and PostgreSQL and speaks the same wire protocol, so applications connect to it exactly as they would to Postgres. For each query it:

  • Analyzes the query to decide whether it is cacheable.
  • Serves cacheable queries from its local cache.
  • Forwards everything else straight to the origin database, unchanged.
  • Maintains consistency by subscribing to the origin's logical replication stream (CDC) and reconciling every committed change against the cache.

No Redis, no schema migration, no manual invalidation logic.

Will it fit your workload?

Find out before you install anything. The Fit Analyzer takes your real queries and shows how much of your workload pgcache can cache. It runs the same query analysis as the proxy, compiled to WebAssembly and executed in your browser, so your queries are never uploaded.

The quickest input is a pg_stat_statements export, which weights each query shape by calls and execution time:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;  -- once, then let it accumulate
\copy (SELECT query, calls, total_exec_time FROM pg_stat_statements ORDER BY calls DESC LIMIT 1000) TO 'workload.csv' WITH CSV HEADER

To also get a hit-rate estimate, use a PostgreSQL csvlog, which preserves arrival order. Pasted SQL and stderr logs (best-effort) work too. Setup for each is under How to capture a trace on the Fit Analyzer page.

Quickstart

pgcache needs three things: your origin database with logical replication enabled, a dedicated cache PostgreSQL that has the pgcache_pgrx extension, and a config file (or CLI flags) telling it how to reach both.

1. Enable logical replication on the origin:

# postgresql.conf (origin)
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10

pgcache creates and manages the publication and replication slot itself (named in the config below) — you don't create them by hand.

2. Tell pgcache how to reach both databases. The simplest way is a config file:

# pgcache.toml
num_workers = 4

[origin]
host = "your-db-host"
port = 5432
user = "pgcache"
database = "myapp"

[cache]
host = "localhost"
port = 5432
user = "postgres"
database = "cache"

[cdc]
publication_name = "pgcache_pub"
slot_name = "pgcache_slot"

[listen]
socket = "0.0.0.0:6432"
pgcache -c pgcache.toml

Every setting also has an equivalent CLI flag (--origin_host, --cache_host, --cdc_publication_name, --listen_socket, --num_workers, …) and environment variable; run pgcache --help for the full list.

3. Point your application at pgcache by changing one connection string:

- DATABASE_URL=postgres://user@your-db-host:5432/myapp
+ DATABASE_URL=postgres://user@pgcache-host:6432/myapp

Writes still go to your primary; reads are served from cache when safe and forwarded to origin otherwise.

Just want to try it? The Docker image bundles the cache PostgreSQL (with pgcache_pgrx already preloaded) and wraps all of the above behind a single --upstream postgres://… flag.

How it works

                 reads + writes
   application ───────────────────▶  pgcache  ──────────────▶  origin
       ▲                             │  cache       writes &     PostgreSQL
       └─────────────────────────────┘  cacheable   forwarded
            results                       reads      queries
                                            ▲
                                            │  logical replication (CDC)
                                            └──────────────────────────────
                                               every commit, in real time

The cache is kept consistent through PostgreSQL's logical replication stream rather than by expiring data on a timer. When a change arrives, pgcache decides whether it can be applied to the cache directly or whether the affected query must be invalidated:

  • Single-table queries are never invalidated. An insert, update, or delete can always be applied directly to the cached result, keeping it consistent without re-fetching.
  • Joins and complex queries turn on whether the change requires fetching more data from origin or if the data already in the cache can still serve the query after the change. If the change requires fetching data from origin, the query is invalidated, and the up-to-date data will fetched on the next request.

At any moment a cached query is either up to date with every relevant committed change, or it is not served from cache at all — so applications never see data a committed write has superseded.

The pgcache_pgrx extension

pgcache keeps its cached data in a dedicated PostgreSQL instance, and that instance must have the pgcache_pgrx extension installed. pgcache runs CREATE EXTENSION pgcache_pgrx when it initializes the cache database on startup, and it will not run without it.

pgcache_pgrx is a small extension (built with pgrx) that provides the generation-based tracking pgcache uses to garbage-collect the cache. The extension records which cached rows are still in use so unused rows can be reclaimed.

Because it allocates shared memory, it must be preloaded, not just created:

# postgresql.conf of the cache database
shared_preload_libraries = 'pgcache_pgrx'

You usually don't set this up by hand: the Docker image and the AWS Marketplace AMI bundle a PostgreSQL that already has pgcache_pgrx built, installed, and preloaded. You only need to install it yourself when running pgcache from source or against a PostgreSQL you manage — see pgcache_pgrx for build and install steps.

Status

pgcache is under active development, pre-1.0, and has not been battle-tested at scale.

  • PostgreSQL: 16, 17, and 18.
  • Deployment: AWS (Marketplace AMI), Docker, or bare metal.
  • Caching scope: tables must have a primary key to be cached; queries against tables without one are forwarded to origin unchanged.

Design & internals

Full documentation lives at pgcache.com/docs.

The architecture and the reasoning behind it are documented in-repo as Architecture Decision Records. Start with the ADR index for a map of the subsystems — query parsing and cacheability analysis, the CDC apply model, cache population, and the generation-based garbage collector.

License

pgcache is source-available under the Elastic License 2.0.

Contributors

tempest98

763 commits

PgCache/pgcache

PostgreSQL proxy for query aware caching, using CDC for cache maintenance.

Rust

63

767 commits

updated Sep 25, 2026

See the code

See what people are saying

SourceMessageScoreDate

Which Postgres queries can I cache? Free WebAssembly tool (runs locally in your browser) (r/webdev)

We've been thinking a lot about caching this past year. (1) There's a nice pattern for figuring out opportunities to optimize a Postgres workload, but the downside is that it takes work. (2) There's lots of generic Postgres-health tooling out there, but we haven't seen any good free & private…

0

Sep 26, 2026

README

pgcache

A PostgreSQL cache that never serves stale data.

License: Elastic License 2.0 PostgreSQL 16 | 17 | 18 Status: active development

Most caches force a choice between speed and correctness. pgcache doesn't. It's a wire-compatible PostgreSQL proxy that caches query results and keeps them consistent with the origin in real time using logical replication — every insert, update, and delete is either applied in place to the cached result or the affected query is invalidated. A cached query is therefore always either up to date or transparently forwarded to origin. Never a stale read, and not a line of application code changes.

Think of it as a smart read replica that copies only the data you actually read — the hot working set driving most of your traffic, instead of a full second copy of a database that only keeps growing. Learn more at pgcache.com.

What it is

pgcache sits between your application and PostgreSQL and speaks the same wire protocol, so applications connect to it exactly as they would to Postgres. For each query it:

  • Analyzes the query to decide whether it is cacheable.
  • Serves cacheable queries from its local cache.
  • Forwards everything else straight to the origin database, unchanged.
  • Maintains consistency by subscribing to the origin's logical replication stream (CDC) and reconciling every committed change against the cache.

No Redis, no schema migration, no manual invalidation logic.

Will it fit your workload?

Find out before you install anything. The Fit Analyzer takes your real queries and shows how much of your workload pgcache can cache. It runs the same query analysis as the proxy, compiled to WebAssembly and executed in your browser, so your queries are never uploaded.

The quickest input is a pg_stat_statements export, which weights each query shape by calls and execution time:

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;  -- once, then let it accumulate
\copy (SELECT query, calls, total_exec_time FROM pg_stat_statements ORDER BY calls DESC LIMIT 1000) TO 'workload.csv' WITH CSV HEADER

To also get a hit-rate estimate, use a PostgreSQL csvlog, which preserves arrival order. Pasted SQL and stderr logs (best-effort) work too. Setup for each is under How to capture a trace on the Fit Analyzer page.

Quickstart

pgcache needs three things: your origin database with logical replication enabled, a dedicated cache PostgreSQL that has the pgcache_pgrx extension, and a config file (or CLI flags) telling it how to reach both.

1. Enable logical replication on the origin:

# postgresql.conf (origin)
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10

pgcache creates and manages the publication and replication slot itself (named in the config below) — you don't create them by hand.

2. Tell pgcache how to reach both databases. The simplest way is a config file:

# pgcache.toml
num_workers = 4

[origin]
host = "your-db-host"
port = 5432
user = "pgcache"
database = "myapp"

[cache]
host = "localhost"
port = 5432
user = "postgres"
database = "cache"

[cdc]
publication_name = "pgcache_pub"
slot_name = "pgcache_slot"

[listen]
socket = "0.0.0.0:6432"
pgcache -c pgcache.toml

Every setting also has an equivalent CLI flag (--origin_host, --cache_host, --cdc_publication_name, --listen_socket, --num_workers, …) and environment variable; run pgcache --help for the full list.

3. Point your application at pgcache by changing one connection string:

- DATABASE_URL=postgres://user@your-db-host:5432/myapp
+ DATABASE_URL=postgres://user@pgcache-host:6432/myapp

Writes still go to your primary; reads are served from cache when safe and forwarded to origin otherwise.

Just want to try it? The Docker image bundles the cache PostgreSQL (with pgcache_pgrx already preloaded) and wraps all of the above behind a single --upstream postgres://… flag.

How it works

                 reads + writes
   application ───────────────────▶  pgcache  ──────────────▶  origin
       ▲                             │  cache       writes &     PostgreSQL
       └─────────────────────────────┘  cacheable   forwarded
            results                       reads      queries
                                            ▲
                                            │  logical replication (CDC)
                                            └──────────────────────────────
                                               every commit, in real time

The cache is kept consistent through PostgreSQL's logical replication stream rather than by expiring data on a timer. When a change arrives, pgcache decides whether it can be applied to the cache directly or whether the affected query must be invalidated:

  • Single-table queries are never invalidated. An insert, update, or delete can always be applied directly to the cached result, keeping it consistent without re-fetching.
  • Joins and complex queries turn on whether the change requires fetching more data from origin or if the data already in the cache can still serve the query after the change. If the change requires fetching data from origin, the query is invalidated, and the up-to-date data will fetched on the next request.

At any moment a cached query is either up to date with every relevant committed change, or it is not served from cache at all — so applications never see data a committed write has superseded.

The pgcache_pgrx extension

pgcache keeps its cached data in a dedicated PostgreSQL instance, and that instance must have the pgcache_pgrx extension installed. pgcache runs CREATE EXTENSION pgcache_pgrx when it initializes the cache database on startup, and it will not run without it.

pgcache_pgrx is a small extension (built with pgrx) that provides the generation-based tracking pgcache uses to garbage-collect the cache. The extension records which cached rows are still in use so unused rows can be reclaimed.

Because it allocates shared memory, it must be preloaded, not just created:

# postgresql.conf of the cache database
shared_preload_libraries = 'pgcache_pgrx'

You usually don't set this up by hand: the Docker image and the AWS Marketplace AMI bundle a PostgreSQL that already has pgcache_pgrx built, installed, and preloaded. You only need to install it yourself when running pgcache from source or against a PostgreSQL you manage — see pgcache_pgrx for build and install steps.

Status

pgcache is under active development, pre-1.0, and has not been battle-tested at scale.

  • PostgreSQL: 16, 17, and 18.
  • Deployment: AWS (Marketplace AMI), Docker, or bare metal.
  • Caching scope: tables must have a primary key to be cached; queries against tables without one are forwarded to origin unchanged.

Design & internals

Full documentation lives at pgcache.com/docs.

The architecture and the reasoning behind it are documented in-repo as Architecture Decision Records. Start with the ADR index for a map of the subsystems — query parsing and cacheability analysis, the CDC apply model, cache population, and the generation-based garbage collector.

License

pgcache is source-available under the Elastic License 2.0.

Contributors

tempest98

763 commits

Languages

Rust

99.8%