A row cache
inside PostgreSQL.

Read rows by primary key over authenticated RESP MGET. PostgreSQL writes automatically invalidate affected cache entries.

Authenticated RESP API · PostgreSQL 14–18 · Open source

3.1.0 adds byte-based slab capacity for millions of small keys when the budget fits, partitioned locks, allocation-free single-key hits, cheaper write tracking, and deferred locked misses. Upgrade guide.

3.1.0 · final build c431bcc

210krequests/s · RESP MGET

Per pinned physical server core
hot keys · 256 clients · Go/pgx

Valkey · io-threads=2N
91.5k req/s
Prepared SQL
23.2k req/s
Private 10 GbE VMs · Xeon Gold 6354 · pinned to one physical core.
At one core, Valkey io-threads=1 reached 180k req/s and used the least server CPU per request.
Results, raw data & conditions

One call. Two read paths.

An eligible hit returns the stored row. A miss or bypass reads the source table. Your ordinary SELECT queries keep their existing path.

RESP2 │ primary-key reads
-- Attach your table once.
SELECT local_cache.attach_table('public.items');

-- Read rows by primary key.
MGET CRUD:pglc_demo.public.items:{"id":42} CRUD:pglc_demo.public.items:{"id":7}
The RESP MGET read path The application sends authenticated RESP MGET. An eligible cache hit returns the stored whole-row payload. A miss or bypass reads the source table through the configured worker role and returns the row over RESP. PostgreSQL Application RESP MGET Eligibility + snapshot Hit Miss / bypass Storedrow Sourcetable
Both paths return whole rows over RESP. A source-table read can fill the cache.
What a row-cache hit avoids
RESP MGET

Authenticated and bounded to 1,024 keys per request. Missing rows return null.

Ordinary writes

INSERT, UPDATE, and DELETE invalidate affected entries.

Fixed capacity

Allocate the shared row cache at PostgreSQL startup.

Fence-generation and payload validation

Ineligible reads use the source table under PostgreSQL visibility rules.

Run the same test.
On every client.

Node.js and Go. Prepared SQL and RESP MGET.

One runner uses the same keys, batch sizes, connection counts, duration, and result checks. Compare throughput, latency, and PostgreSQL CPU and memory on your machine.

Run the comparison

Is this your workload?

Choosing a cache? Compare PostgreSQL pages, rows, materialized views, and Redis.

Worth measuring

  • Repeated complete primary-key lookups.
  • A small hot set of whole rows.
  • READ COMMITTED on one writable primary.
  • An application that can use authenticated RESP MGET.

Keep ordinary SQL

  • Joins, ranges, aggregates, or arbitrary query results.
  • RLS, partitioned, or inherited tables.
  • A database where you cannot install a native extension.
  • Workloads without a measured benefit.

Run a local demo.

Clone the repository and start a disposable PostgreSQL server with sample rows. Docker builds the extension and keeps the demo separate from your databases.

Git and Docker Compose required
git clone https://github.com/profundium/pg_local_cache.git
cd pg_local_cache
docker compose -f examples/compose.yaml -f examples/compose.resp.yaml up --build --wait

Read sample rows and check cache hits. Already cloned the repository? Run the last command from its root. For an existing server, use the installation guide.

Installing on an existing server requires a PostgreSQL restart.

Documentation

Try pg_local_cache locallyRun pg_local_cache 3.1.0 in disposable PostgreSQL, read sample rows over RESP, inspect cache hits, test updates, and remove the demo without changing an existing database. Install pg_local_cache on PostgreSQL 14-18Install a verified Debian or RPM package, use PGXN or PGXS, configure preload, restart PostgreSQL, and initialize, upgrade, or uninstall pg_local_cache. RESP clientsConnect Redis-compatible clients to pg_local_cache with RESP2 MGET, token authentication, and native TLS. PostgreSQL cache benchmarks, 3.1.0Two-machine pg_local_cache 3.1.0 results for pinned and full-server reads, write overhead, and reads during writes, with raw JSONL and reproduction steps. pg_local_cache technical referenceReference for RESP reads, supported tables, transaction fences, TLS, shared memory, metrics, and PostgreSQL settings. Upgrade pg_local_cache to 3.1.0Upgrade pg_local_cache from 3.0.0 or 2.x, review 3.1 capacity and worker settings, and verify the restarted extension. PostgreSQL caching decision guideCompare PostgreSQL page caching, prepared SQL, whole-row caching, materialized views, and Redis by the work each read path avoids. Batch PostgreSQL primary-key lookupsAvoid N+1 row reads with ordered RESP MGET batches and bounded response sizes. PostgreSQL and Redis cache-asideCompare application-managed Redis cache-aside with pg_local_cache RESP reads, including stale-fill races and write invalidation. PostgreSQL row cache vs shared_buffersCompare PostgreSQL page caching with pg_local_cache whole-row caching: source work avoided, cache costs, and workloads that should keep ordinary SQL. Transaction-aware cache invalidation in PostgreSQLSee how PostgreSQL triggers fence RESP row reads across commit, rollback, and concurrent cache fills.

Practical notes on PostgreSQL caching

All articles →

Before you try it

Does this replace shared_buffers?

No. PostgreSQL caches database pages. This extension separately caches serialized whole rows by primary key. See the read-path comparison.

Does it cache ordinary SELECT queries?

No. Only authenticated RESP MGET uses the shared row cache. Your existing queries keep PostgreSQL’s normal execution path.

Does it replace Redis?

No. The RESP2 endpoint is limited. There is no general-purpose Redis command set, TTL, pub/sub, or distributed coordination. Compare the PostgreSQL and Redis cache-aside paths.

How do I check updates and rollback?

The invalidation guide explains write behavior. The upgrade guide covers 3.0.0 to 3.1.0 and the earlier 2.x migration.