I stopped opening a new database for every problem. Here’s when I’d still leave PostgreSQL
James Okonkwo
September 23, 2026
For a few years I treated every shiny datastore as a career move. Search felt incomplete without Elasticsearch. Sessions “needed” Redis. Analytics “needed” ClickHouse. Event streams “needed” Kafka plus something to land in. The architecture diagrams looked sophisticated. The on-call rotations looked like a tax on that sophistication.
I stopped opening a new database for every problem. Not because PostgreSQL is perfect — it isn’t — but because most of the problems I was solving with a second engine were actually schema, indexing, or access-pattern problems wearing a vendor costume. The default that stuck for me is boring: put the business state in Postgres, stretch it further than the conference talk said you should, and only leave when the failure mode is structural, not fashionable.
This is the line I use now — and the handful of cases where I still walk away from Postgres on purpose.
The habit that cost me weekends
The pattern was always the same. A feature arrived with a sentence that sounded like a product category. “We need full-text search.” “We need real-time presence.” “We need a time-series dashboard.” Someone on the team had used the specialist tool at a previous job. The spike took a week. The production cutover took a month. The second month was spent teaching everyone else how backups, migrations, and local environments worked for the new box.
None of that was free. Every additional datastore bought you:
- another backup story that nobody drills until restore day
- another connection pool and credential rotation path
- another “why is staging empty?” mystery when the sync job lagged
- another place for N+1-style bugs to hide, just with different jargon
I used to argue that specialization was worth the ops tax. Sometimes it is. More often I was paying the tax to avoid learning EXPLAIN, GIN indexes, or a slightly uglier SQL query that would have been fine at our scale.

Why the default shifted under me
Postgres did not become my default because of marketing. It became the default because the gap closed. JSONB with constraints. Partial indexes. Logical replication. Decent full-text search for anything that is not “search is the product.” Extensions when you need them, without pretending every extension is free lunch. The industry also got better at running one solid relational system than at running five mediocre ones.
If you want the longer story of how SQL absorbed the NoSQL era’s lessons and why so many teams landed on Postgres as the boring center of gravity, that trajectory is already mapped in why PostgreSQL became the default database. This piece assumes you already feel that pull — and asks the opposite question: when is the default the wrong call?
What “leave PostgreSQL” actually means
Leaving does not always mean deleting the Postgres cluster. It usually means one of three moves:
- Offload a workload — keep system of record in Postgres, put a specialized engine next to it for one shape of query or traffic.
- Replace for a bounded domain — a service owns its own store because the consistency model truly differs.
- Start elsewhere — greenfield where the product is the specialized access pattern, not a CRUD app with extras.
I still default to (1) when I leave at all. Full rewrites of the system of record are how you get dual-write hell and two sources of truth that both look authoritative in demos.
When I still leave — and why
1. Search is the product, not a feature
Postgres full-text search is fine for admin panels, support tools, and “find this order by customer name.” It is not fine when ranking quality, typo tolerance, facets, and relevance tuning are what users pay for. If your roadmap has a search team, synonym lists, A/B tests on ranking, and “why didn’t this document surface,” you have left the land of tsvector.
Then I reach for a search engine — Elasticsearch, OpenSearch, Meilisearch, Typesense, depending on ops appetite and whether you need the kitchen sink. Postgres stays the source of truth; the search index is a derived view you can rebuild. The leave is about the query shape and the product surface, not about JSON documents being “unstructured.”
2. Write volume that turns WAL into a furnace
High-cardinality telemetry, clickstreams, IoT heartbeats — the kind of append-only firehose where you never update a row and you mostly care about aggregates over time windows. You can shove that into Postgres. I have. It works until autovacuum becomes a character in your incident channel and disk growth is the quarterly planning topic.
For that shape I still pick a purpose-built time-series or columnar store: Timescale if I want to stay in the Postgres ecosystem and the volume is merely large; ClickHouse or a managed analytics warehouse when the query pattern is “scan billions of rows and return a chart.” The leave criterion is simple: if the hot path never needs row-level transactions with the rest of the business data, stop making the OLTP database pretend it is a lake.
3. Ephemeral coordination that should die with the process
Presence, rate-limit counters, distributed locks with short TTLs, job leases — state that is valuable for seconds and meaningless after a crash if you rebuild it. Redis (or an equivalent) still wins here because the mental model is “cache with teeth,” not “durable ledger.”
The mistake I made early was promoting Redis into a second primary database for shopping carts and user profiles “because it’s fast.” Fast and durable are different jobs. If I would cry when the Redis AOF is corrupt, that data belonged in Postgres. If I shrug and the system heals in thirty seconds, Redis is the right leave.

4. Graph traversals that fight the relational model
Friend-of-friend, permission inheritance trees that fan out wildly, recommendation graphs where “N hops from this node” is the product question — you can encode graphs in SQL. Recursive CTEs are real. They also get ugly fast when the hop count grows and the cardinality explodes.
I do not reach for a graph database because a slide said “relationships.” I reach for one when the dominant query is traversal and the relational encoding forces either deep recursion or denormalized edge tables that recreate the graph engine poorly. Even then I am cautious: many “graph” problems are just join problems with better naming. Leave when the traversal is the product, not when the ERD has a self-reference.
5. Multi-tenant isolation that must be physical
Sometimes the leave is not a different engine — it is a different topology. Regulated customers who require their own database, or noisy neighbors who can starve shared buffers. Postgres can do row-level security and schemas-per-tenant. Those work until compliance or performance demands a hard wall.
Then I leave the shared cluster: dedicated Postgres instances (or logical databases with separate resources), not a trendy NoSQL store. The point is isolation, not abandoning SQL. People confuse “leave the shared box” with “leave the relational model.” I try not to.
6. The rare case where another SQL dialect is already the company’s muscle
This one is organizational, not technical. If the entire platform team, the existing tooling, and the disaster-recovery runbooks are MySQL-shaped, “Postgres is nicer” is not a migration plan. I have delayed greenfield Postgres in shops where MySQL expertise was the scarce resource that kept nights quiet. Leaving Postgres — or rather, never arriving — can be the mature call when staffing and muscle memory matter more than extension catalogs.
That is a different essay from “Postgres vs MySQL feature bingo.” Features rarely decide this; people and runbooks do.
When I refuse to leave (even when the ticket says otherwise)
I push back when the motivation is one of these:
- “Documents are flexible.” JSONB exists. So do check constraints and partial uniqueness. Schema-less often means schema-deferred until production invents three incompatible shapes.
- “We might be Google someday.” Premature distribution is how you buy Spanner-shaped complexity with startup-shaped traffic.
- “ORMs hate joins.” That is an ORM configuration problem, not a database problem. Fix the query layer before you buy a new engine.
- “Horizontal scale.” Vertical scale plus read replicas plus partitioning covers a shocking amount of real business. When you truly need write sharding, you still need a plan for cross-shard transactions — Postgres was not the only hard part.
- “The microservice already has its own database.” Fine if the bounded context is real. Not fine if you split stores to match repo boundaries and then invent distributed transactions to put the customer checkout back together.
The question I ask in design reviews is: what failure mode does the new database remove that indexing, caching at the edge, or a background projection cannot? If the answer is “developer excitement,” we keep Postgres.
How I decide in practice
I use a short checklist before I approve a second store:
- Name the query that Postgres cannot satisfy at our current scale with a straight face. Not a hypothetical future scale — the next twelve months of traffic with a 3× cushion.
- Name the consistency story. Dual writes? Outbox? Change-data-capture into the specialist? If nobody owns the sync, you do not have two databases; you have a future incident.
- Name the restore drill. Who restores the new system on a Saturday, and from what? If the answer is silence, you are not ready.
- Name the local-dev story. If every laptop needs Docker Compose with five engines and flaky ports, you have taxed every hire.
- Time-box a Postgres spike first. One honest week with the ugly SQL, the right indexes, and a load test. Many “must leave” tickets die in that week.
If those five answers are crisp, I leave — usually as an offload, with Postgres still holding the ledger. If they are hand-wavy, we stay.
The operational tax you only notice later
The hidden cost of leaving is not the license or the managed-service invoice. It is cognitive load. Onboarding doubles. Incident response needs two mental models. “Is this stale?” becomes a product bug class. Feature flags start gating not just UI but which store is authoritative for a field.
I have watched teams celebrate a successful Elasticsearch cutover and then spend a year arguing whether the product catalog lives in the search document or the row. That argument is the real migration cost. Postgres-as-default is partly about reducing the number of places truth can hide.
When I do leave, I write the ownership down like a contract: which system is source of truth, what is derived, how rebuild works, and which team gets paged when the projection lags. Without that paragraph in the design doc, the second database is a rumor.
A note on “polyglot persistence”
Polyglot persistence sounded wise in the mid-2010s. Use the right tool for each job. The missing half of the sentence was: each tool is also a job. Polyglot shops that thrived had platform teams, paved-road automation, and enough engineers to specialize. Polyglot shops that suffered had five databases and eight developers who all pretended to be generalists.
I am not anti-polyglot. I am anti-casual polyglot. Earn each engine with a workload that would otherwise break, not with a blog post that made the architecture look modern.
What I’d tell a team starting today
Start with PostgreSQL for the system of record. Put sessions and hot counters in Redis when they are truly ephemeral. Add a search engine when search quality is revenue. Add analytics storage when dashboards start hurting the OLTP box. Resist the urge to open a new database because a feature description sounded like a category name.
Leaving Postgres should feel slightly uncomfortable — like you are accepting ops debt in exchange for a clear, measurable win. If it feels exciting, wait a week and re-read the checklist.
I still leave. I just leave less often, and when I do, I can point at the query, the consistency plan, and the restore drill without improvising. That is the whole practice: fewer databases, sharper reasons, and a default that survives contact with production.