When a PostgreSQL row cache helps
A database can serve every page from memory and still spend time executing queries, checking visibility and constructing results. A row cache tries to avoid part of that repeated work. It also adds key handling, cache checks and serialization costs. The useful question is whether the complete application request becomes cheaper for your workload.
pg_local_cache exposes an explicit local_cache.mget API. Ordinary SELECT
queries retain their normal PostgreSQL execution path. A warm shared_buffers
cache and a warm row cache are therefore different experimental conditions.
Write down the result contract first
Compare the same keys, columns and output shape. If the application needs only two columns, comparing that SQL projection with serialized whole rows measures different work. If callers expect duplicates, input order and a null result for each missing key, include that alignment work in every client.
The batch lookup guide gives both an
ANY baseline and an ordered WITH ORDINALITY baseline. Neither requires the
extension. Establish the SQL baseline before adding a cache.
Change one workload dimension at a time
| Experiment | What to hold fixed | What it reveals |
|---|---|---|
| Warm repeated reads | Keys, result shape, connections | Reuse of already populated entries |
| Cold or missing keys | Request distribution and batch size | Source-table and negative-result costs |
| Larger batches | Total requested keys and payload shape | Round-trip savings versus per-key work |
| Concurrent writes | Read/write mix and transaction boundaries | Invalidation, refill and visibility costs |
| Wider rows | Key distribution and client placement | Serialization, transport and row-size bypass |
An ineligible read can legitimately use the source table. Inspect counter
deltas around each experiment; a low hit rate alone does not diagnose a broken
installation. Keep SQL and RESP counters separate. The
technical reference describes
local_cache.stats() and local_cache.health().
Use the shared runner, then inspect the evidence
After the quickstart, run the repository’s comparison:
./examples/benchmark.sh all > comparison.json
python3 scripts/benchmark_report.py comparison.json
The benchmark guide lists prerequisites, workload controls and metrics. Keep the raw JSON. Record the extension and harness revisions, PostgreSQL version, machine, connection count and client placement. Compare repeated runs, latency distributions and server resource use alongside throughput. A short correctness smoke run is not a publishable speed result.
Decide from the application boundary
SQL mget and RESP MGET use different transports and result handling. A gain
for one does not establish a gain for the other. The project’s
dated Go measurements include a single-key case
where SQL mget was slower than prepared SQL. That is a reason to test, not a
universal prediction.
Keep ordinary SQL when joins, projections, locking or unsupported table shapes are required, or when the cache brings no measured benefit. For repeated whole-row reads by primary key, test the explicit API with the same client work your application actually performs. Continue with the caching decision guide and the invalidation experiment.