Before You Switch to NoSQL, Exhaust PostgreSQL First

4 min read

Most teams blame PostgreSQL for problems it didn't cause. Here's how far Postgres actually goes before you need something else.

PostgreSQLNoSQLDatabaseBackendPerformance

page speed results They don't hit PostgreSQL's ceiling though. They hit their own schema's ceiling, or a connection limit nobody thought to address. Then they decide Postgres doesn't scale and spend three months migrating to NoSQL.

How far does Postgres actually go?

Further than you probably think. Instagram ran on PostgreSQL well into its growth before sharding became a real conversation. Shopify still runs a heavily customized PostgreSQL setup. GitHub uses it. Notion uses it. These aren't toy apps with simple data models. They're companies with hundreds of millions of rows, real relational complexity and serious traffic.

What they have in common is that they took the database seriously. They didn't abandon it the first time a query got slow.

The index problem

If PostgreSQL is slow for you, start here. A sequential scan across ten million rows is painful in any database. A B-tree index on the column you're filtering by turns that same query into something that finishes in milliseconds.

Teams ship applications with no indexes beyond the primary key all the time. They add columns to WHERE clauses without checking what the query planner does. They run EXPLAIN ANALYZE never, or once, when something catastrophically breaks.

PostgreSQL's query planner is genuinely good. Give it something to work with.

Partial indexes are worth understanding too. If you're always filtering by status = 'active', index only those rows. Composite indexes matter when you filter on multiple columns together and the order of columns in that index matters.

Joins aren't the problem

A common argument for NoSQL is that joins are slow. They can be. They can also be fast. It depends on whether you indexed the join columns.

PostgreSQL handles hash joins and merge joins on indexed foreign keys well. What it handles badly is a nested loop join that triggers a sequential scan on a large table because nobody added a foreign key index.

Connection pooling

Sometimes what looks like a performance problem is actually connection exhaustion. PostgreSQL has a fixed connection limit. If your application opens a new connection per request under high traffic, you'll hit that ceiling and everything falls over. Use PgBouncer before you migrate to Cassandra.

PgBouncer sits in front of PostgreSQL and pools connections so hundreds of application threads share a small number of actual database connections. It costs almost nothing to set up and buys a lot of headroom. Teams skip this step, hit connection limits and then conclude PostgreSQL can't scale. Next is they migrate to something that has its own scaling problems.

Read replicas

Read traffic will eventually outgrow a single database server but that's not a reason to abandon relational databases, yet.

PostgreSQL has built-in streaming replication. Point your read-heavy queries at a replica, reserve the primary for writes. It's a well-understood pattern, straightforward to configure and lets you scale reads horizontally without touching your data model.

Most teams reach for microservices and separate NoSQL stores before trying this. The replica approach is cheaper and simpler. It doesn't require you to rewrite your application too.

When you actually need NoSQL

None of this means NoSQL is the wrong choice. There are cases where it genuinely fits better.

If your data is document-shaped with no meaningful relationships between records, a document store makes sense. Ingesting time-series sensor data at high volume? Use a purpose-built time-series database. If you need a key-value cache with sub-millisecond reads. Use Redis.

Understand the cost before you migrate

If you eventually decide to upgrade. I need you to understand the cost. Switching databases isn't free and the costs are easy to underestimate. You give up transactions, foreign key constraints and the ability to run ad hoc queries across your data. You trade known problems for unknown ones.

Document stores push consistency work to the application layer. Your code now handles the things the database used to handle. That means more code and more places to get it wrong.

A lot of teams that migrate to NoSQL early end up rebuilding relational guarantees in application code. Then they look at that code and wonder why they didn't just keep the relational database.

Start with PostgreSQL. Index properly. Add connection pooling. Set up read replicas when read traffic demands it. Partition large tables. Profile your slow queries. You can get very far down that list before you hit a problem that actually requires a different database.