Postgres Until It Hurts: The Operational Case for One Database Doing Several Jobs
The pendulum swung back to one relational database for documents, queues, search and vectors: the case for it, and where a specialised system still wins.
6 min read
On this page
Sit in on an architecture review at a small company and you will still occasionally see it: six datastores on the diagram, four engineers in the room. A relational database for the entities. A document store because some of the data was "flexible". A broker for background jobs. A search cluster for one search box. A cache. And, recently, a vector store for a retrieval feature.
Every one of those decisions was individually defensible. Together they describe a team that will spend most of its engineering capacity keeping data consistent between systems, and none of it on the product.
The default has been quietly shifting back the other way, and the reason isn't that one database got faster than the specialists. It's that the specialists were never the expensive part.
Count failure modes, not services
An extra datastore is not one more box. It's another replication topology to reason about, another backup you are obliged to restore periodically rather than merely take, another upgrade cadence, another authentication surface, another set of dashboards nobody looks at until an incident, and another way to lose data that nobody on the team has seen yet.
But the compounding cost is consistency. The moment a single entity exists in two stores you own a synchronisation problem, and it never stays small. You write to both and one write fails. You add an outbox to make that honest, and now you own an outbox. You add a reconciliation job because the outbox drifts. Then you change the schema and discover you need a backfill for every derived copy, plus a plan for the window where the copies disagree.
None of that appears on the architecture diagram. All of it appears on your on-call rota.
What one relational database absorbs comfortably
The reason consolidation is more viable than it was isn't headline features, it's that each of these workloads can now live next to the rows it relates to.
Documents. JSON columns with real indexes give you schemaless writes where the schema is genuinely unsettled, while the rest of the row stays typed and constrained. You get flexibility for the parts that need it without giving up joins and transactions for the parts that don't.
Queues. Row-level locking with skipped locks makes a competent job queue out of a table. The decisive advantage is not throughput — it's that you can enqueue the job in the same transaction as the state change that caused it, which eliminates the entire family of bugs where the event was published and the write rolled back, or the write committed and the event was lost.
Search. Full-text indexing in the database is comfortably good enough for an admin filter, an internal document search, or a product catalogue of modest size — and it can be combined with ordinary predicates, permissions and ordering in a single query.
Vectors. Approximate nearest-neighbour indexes sitting in the same table as the record they describe turn a filtered similarity query into a WHERE clause. Compare that with the external-index version, where filtering means either fetching too many neighbours and discarding most of them, or keeping a shadow copy of your access rules in a second system.
The pattern repeats every time: the win isn't the capability, it's the transaction and the join.
Where a specialised system genuinely wins
Consolidation is a default, not a doctrine. There are workloads where the specialist is the right answer and holding out is stubbornness.
Queues at sustained high churn. A heavily worked queue table generates dead tuples faster than routine cleanup reclaims them, and you end up tuning autovacuum for one table's benefit. More importantly, brokers exist for semantics the table doesn't have: consumer groups, replay from an offset, retention, and fan-out to independent consumers each tracking their own position. If you're reaching for those, you want a broker.
Search as the product. Language-specific analysis, typo tolerance, synonyms, faceting, per-field boosts, learned ranking signals. If search is a feature, the database handles it. If search is what users come for, you need a search engine and someone who enjoys relevance tuning.
Very large vector corpora. Index build time, memory footprint and the recall-versus-latency trade-off all become first-order concerns at scale, and you'll want to tune and scale that index independently of your transactional workload.
Analytics. Wide aggregations over large row counts want a column store. The stronger argument is isolation rather than speed: analytical scans evict your transactional working set from shared memory and compete for the same I/O, so a slow report becomes slow checkout.
Caching. Sub-millisecond reads of expendable data with an eviction policy, absorbing read spikes without consuming database connections. Nothing about that wants durability or transactions.
The honest risk of consolidating
One database means one blast radius. A runaway analytical query, connection exhaustion, a lock held by a long migration — any of them takes every workload down simultaneously, where the sprawling architecture would have lost one feature.
That risk is manageable, but only deliberately: separate connection pools per workload so the job runner can't starve the API, statement timeouts set per role rather than globally, read replicas dedicated to the workloads that tolerate lag, and resource limits on the roles that run reports. The other constraint is that you can no longer scale one dimension alone — the search workload's growth now argues for a bigger primary.
Knowing when it hurts
The useful part of "until it hurts" is naming the signal in advance, so the migration is a decision rather than a reaction. In practice it's one of four:
You are tuning the same database in two directions at once, and a setting that helps one workload measurably harms another. Isolation has become the requirement, not throughput — you need one workload's failure to stop mattering to the rest. The specialised capability has become the product rather than a feature of it. Or the database has grown to where you cannot restore it inside your recovery objective, which is a failure you discover at the worst possible time if you aren't rehearsing restores.
Any of those is a good reason to move a workload out. None of them is a reason to start with six stores.
Start consolidated, move out with evidence
Extracting a workload from a relational database is a well-understood project with a clear before and after. Merging five stores back into one after the fact is not — you'll be reconciling three years of divergent copies while the product keeps writing to all of them.
So the asymmetry favours starting in one place. Put everything in the relational database, instrument each workload separately so you can see which one is straining, and let a specialist earn its way in by demonstrating a limit you actually hit. That's a much better conversation than the one that starts with six boxes and a whiteboard.