How I Put PgCache in Front of a 16-Million-Row Postgres Database

How I Put PgCache in Front of a 16-Million-Row Postgres Database

2026年9月1日2 次浏览来源:Dev.to阅读原文

Disclaimer: This is a side project, not a production story.

The slow-query problem is real, but the database is synthetic data I generated to make it show up on demand.

I have no connection to PgCache.

Everything here is in a repo you can clone and run.

I tested version 0.6.2.

A handful of dashboard queries on one of my projects were fine for a year and then weren't: count users by tier, revenue grouped by country, best-selling products per category.

Nothing exotic, just aggregates and joins over tables that had gotten big.

The usual fixes didn't sit right with me.

A materialized view means picking a refresh interval and serving slightly stale numbers in between.

Redis in front of Postgres means writing and maintaining code that knows which cache entries to throw away on every write.

A read replica just runs the same slow query on another machine.

PgCache offers a different trade.

It's a proxy that talks the Postgres wire protocol, so your app connects to it as if it were the database.

It caches reads.

And instead of expiring entries on a timer, it follows Postgres's replication stream and refreshes a cached result when the rows behind it change.

That stream is the same feed Postgres uses to copy data to a standby server , a running log of every insert, update, and delete.

The "no timers, no manual invalidation" part is the interesting claim.

Here's how it held up.

A database big enough to be slow First I needed a database where "slow" was real and not a rounding error.

I wrote a seed script for a small e-commerce schema and filled it to about 16 million rows: Table Rows Notes 1,000,000 10 countries; tiers 50% free / 33% pro / 17% enterprise 2,000 10 categories 5,000,000 four statuses, random totals, spread over two years 10,000,000 about two per order I added indexes on every foreign key and on every column the test queries filter or group by.

That was on purpose.

I wanted to compare PgCache against a Postgres that had been tuned properly, not one left slow so that any cache would look good next to it.

Getting it running The repo has a Docker Compose file with two containers: the origin Postgres on port 5433, and PgCache on

5432.

The setup was smaller than I expected.

On the Postgres side you turn on logical replication () and give the login role permission to replicate.

That's the whole list.

I had started with a hand-written rule and a line in the init script, then deleted both once I checked what PgCache does on its own at startup: it creates its own publication and replication slot, and it scopes that publication to just the four tables it ends up caching.

I confirmed it afterwards by querying .

The connection it opens for replication authenticates with the ordinary password.

The one rough edge is memory.

PgCache keeps its cache in and won't start unless that's more than twice the size of its internal .

The error message tells you the number it wants, so it's a one-line change in , but it will stop the first boot cold.

After that, the pitch was accurate: change the port your app dials from 5433 to 5432 and leave everything else alone.

The speed test runs four queries 150 times each at concurrency 10, against the origin directly and through PgCache.

It warms both sides first, so the comparison is warm cache against warm cache ,not a cold database against a primed proxy.

After the proxy run it reads PgCache's own hit counter and prints how many of the 150 queries were actually served from cache, so a fast number that quietly came from the database can't hide.

All 150 were cache hits every run.

Query Origin Through PgCache point lookup by id 0.3 ms 0.3 ms count users by tier (1M rows) 140 ms 0.5 ms revenue by country (5M-row join) 1.4 s 0.5 ms top products per category (10M-row join) 2.9 s 0.6 ms The point lookup is a wash, and I left it in for that reason.

It's already sub-millisecond on the origin, so sending it through another process just adds a hop.

That's the honest answer to "does this speed up everything": no, not th

分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools