--- layout: default title: "PostgreSQL row cache extension | pg_local_cache" seo_title: "PostgreSQL Row Cache Extension | pg_local_cache" description: Cache PostgreSQL primary-key rows in shared memory with explicit SQL mget and transaction-aware invalidation. Try the local demo and compare it with batched SQL. image: /assets/social-card.png last_modified_at: "2026-09-14" home: true permalink: / ---

A row cache
inside PostgreSQL.

Read frequently requested rows by primary key with local_cache.mget. Writes stay in PostgreSQL; the extension invalidates affected cache entries.

Explicit SQL API · PostgreSQL 14–18 · Open source

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.

psql │ primary-key reads
CREATE TABLE public.items (
  id bigint PRIMARY KEY, value text
);
INSERT INTO public.items VALUES
  (42, 'cached'), (7, 'cached');
SELECT local_cache.attach_table(
  'public.items'::regclass
);

-- mget returns text[]; unnest shows each entry.
SELECT unnest(local_cache.mget(
  'public.items'::regclass,
  ARRAY[42, 7, 42, NULL]::bigint[]
));

{"id":42,"value":"cached"}
{"id":7,"value":"cached"}
{"id":42,"value":"cached"}
-- final row is SQL NULL
{% include diagrams/read-path.html id="home-read" %}
What a row-cache hit avoids
SQL mget

Up to 1,024 keys per call. Order, duplicates, and NULL positions are preserved.

Ordinary writes

INSERT, UPDATE, and DELETE invalidate affected entries.

Fixed capacity

Allocate the shared row cache at PostgreSQL startup.

Snapshot checks

Ineligible reads use the source table under PostgreSQL visibility rules.

Compare before installing.

Measure against a warm primary-key index and a batched query.

The benchmark uses the same Node.js driver and returns the same ordered rows through mget and WHERE id = ANY($1). It measures warm reads, cold cache fills, reads mixed with updates, and the write cost of an attached table.

Record throughput, p50/p95/p99 latency, cache counters, and the test configuration. Run the comparison on your hardware before deciding whether to use the cache.

Commands and methodology

Is this your workload?

Worth measuring

  • Repeated complete primary-key lookups.
  • A small hot set of whole rows.
  • READ COMMITTED on one writable primary.
  • An application that can call the explicit SQL API.

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.

The Docker demo builds pg_local_cache 2.0.1 from a pinned commit. It creates its own PostgreSQL data directory in memory and binds only to loopback. No host database is mounted or restarted.

From the repository root; Docker Compose required
docker compose -f examples/compose.yaml up --build --wait

Get the files, run the queries, and remove the demo. For an existing server, use the installation guide.

Installing on an existing server requires a PostgreSQL restart.

Documentation

{% for item in site.data.navigation %} {{ item.title }}{{ item.description }} {% endfor %}

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 explicit local_cache.mget calls use the SQL cache. Your existing queries keep PostgreSQL's normal execution path.

Does it replace Redis?

No. The optional RESP2 endpoint is limited. There is no general-purpose Redis command set, TTL, pub/sub, or distributed coordination.

How do I check updates and rollback?

The invalidation guide includes a two-session test. The runnable example checks uncommitted writes, read-your-writes, rollback, and committed updates.