A read replica spends a fair part of its life storing data nobody reads. You copy the entire database to take read load off your primary. It works, but only part of it is actually being used: the handful of tables your app hammers all day. The rest of the data (what we call the cold 80%) mostly just sits there. To serve the slice that runs hot, you provision and pay for all of it.
A smart read replica keeps the hot slice and skips the rest.
PgCache stays current with your primary the way a replica does, except it only holds the data your traffic actually touches, and it leaves everything else where it belongs, on the primary.
A full copy never has to think about which data matters. A smart replica does, every minute it is running. So the obvious question, and the one this post is mostly here to answer, is this: how does it know what to keep?
Why we call it a Smart Read Replica
PgCache is a category defining tool - sort of a read replica, sort of a cache (hence the name). “Smart Read Replica” is the best way we have found to describe it.
Starting from “read replica” seems to help people grasp the core mechanic quickly: Just like a full read replica, we use PostgreSQL logical replication to keep the data fresh.
From there, the “smart” part is an easy step: The way data makes it into PgCache in the first place is via a caching mechanism that is triggered by inbound read queries.
Mechanically, a small daemon watches your inbound queries on the proxy layer and caches their results in a Postgres instance that sits in front of your primary. It is simpler, it is more efficient, and it is all Postgres-native. (That Postgres-native bet keeps paying us back in unexpected ways, and we wrote about why.)
What a full copy actually costs you
A traditional replica mirrors your entire database, cold rows and all, just so it can answer the handful of queries that actually run hot. You provision compute, storage, and transfer for the whole thing, and most of it sits there warm and idle, doing nothing.
On read-heavy workloads, caching only the hot path can cut IOPS by 40 to 60%. If you are on RDS or Aurora Standard, where you pay per read I/O, that idle full copy is a line item you can actually feel. If you’re on Aurora Optimized … maybe you don’t have to be, if you put PgCache in front of your origin.
The cost advantage is nice, but it is not really the point. The point is that PostgreSQL primitives make it possible to manage the read load problem in an efficient and intentional way that doesn’t involve the effort of setting up a manually configured cache, or the brute force of a full read replica.
How does it know what to keep?
It’s a fair question, and the answer is most of the design. Here is how PgCache does it.
Demand-driven, not pre-warmed. PgCache watches real query traffic on the proxy layer and caches whatever gets hit. There is no guessing or manual config on a dataset you have not profiled yet. The cache fills itself from what your app actually reads.
Kept in sync in place, with no TTL to manage. This is the “Read Replica” part. PgCache subscribes to your Postgres logical replication stream. The moment your primary writes, the affected cache entries are updated in place, the same way a logical replica stays current. No TTL or tactical staleness. When a clean in-place update is not possible, the entry is invalidated and re-fetched on the next read. Either way, you are not the one managing it. It’s a cache you don’t have to think about. We think that’s pretty great.
Cold data stays on your primary. Where it belongs. You are not paying to keep it warm in two places.
The result is a replica that is the shape of your traffic, not the shape of your primary.
I am not an engineer by training, so I tend to think about it in plain terms: keep what gets read, drop what does not, and don’t waste time managing things you shouldn’t have to.
A replica is only one of the places you can put a cache. For the full set of options, and how to tell which one your workload actually needs, see our guide to PostgreSQL caching.
Try it
PgCache works with PostgreSQL 16, 17, and 18, and with any ORM or driver that speaks the Postgres wire protocol. You point your connection string at it and it starts caching the common read patterns automatically. No application code changes, no Redis layer, no separate invalidation logic to write. Uncacheable queries just pass through to your origin.
There is a free tiny tier on AWS Marketplace (and a free trial for larger instances), so you can point PgCache at a real workload and make sure it helps. We also have a docker instance you can play with, if you prefer.
We run a Discord where users connect and learn more, so if this project is of interest and you’re thinking of trying it out, join us there!
I’m also curious to hear from you. If you were building a smart read replica from scratch, what is the first thing it would need to get right? Is there anything you think we’re missing? Are you excited about what we’re doing and simply want to learn more? Shoot me an email at [email protected].