PostgreSQL proxy for query aware caching, using CDC for cache maintenance.
Rust
63
767 commits
updated Sep 25, 2026
A PostgreSQL cache that never serves stale data.
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.
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:
No Redis, no schema migration, no manual invalidation logic.
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.
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_pgrxalready preloaded) and wraps all of the above behind a single--upstream postgres://…flag.
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:
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.
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.
pgcache is under active development, pre-1.0, and has not been battle-tested at scale.
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.
pgcache is source-available under the Elastic License 2.0.
763 commits
4 commits
Rust
99.8%
PostgreSQL proxy for query aware caching, using CDC for cache maintenance.
Rust
63
767 commits
updated Sep 25, 2026
A PostgreSQL cache that never serves stale data.
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.
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:
No Redis, no schema migration, no manual invalidation logic.
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.
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_pgrxalready preloaded) and wraps all of the above behind a single--upstream postgres://…flag.
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:
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.
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.
pgcache is under active development, pre-1.0, and has not been battle-tested at scale.
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.
pgcache is source-available under the Elastic License 2.0.
763 commits
4 commits
Rust
99.8%