tech, developers, and the code underneath

issue 141· essay·

The database you should have chosen

Postgres. That's the article. Here's the longer version, including the cases where it's wrong.

For a new application, the default database choice is Postgres, and the burden of proof is on anything else.

This is not a controversial position anymore, which is itself notable, because a decade ago it was.

what it absorbed#

The reason the argument ended is that Postgres kept adding the things people left it for:

JSON. jsonb with indexing, operators, and path queries. The document database use case — schemaless-ish data with nested structure — is handled well enough that the specialized option is rarely worth a second system.

Full-text search. Built in, with ranking, stemming, and index support. Not as good as a dedicated search engine at large scale or with complex relevance requirements. Good enough for the search box on most applications, and one fewer system to operate.

Vector search. pgvector with HNSW indexing. Again: not the best available at extreme scale, and entirely adequate for most retrieval workloads, with the enormous advantage that your vectors sit next to your metadata and you can filter and join in one query.

That last point is underrated. A dedicated vector database that cannot join to your relational data means you do the join in application code, badly.

Time series. Partitioning, BRIN indexes, and extensions cover a lot of the use case.

Geospatial. PostGIS remains the best geospatial implementation in any database, period.

Queues. SELECT ... FOR UPDATE SKIP LOCKED gives you a correct, transactional job queue in about twenty lines. For anything under high-thousands of jobs per second — which is most systems — you do not need a dedicated queue, and the transactional property (enqueue in the same transaction as the state change) removes an entire class of consistency bug.

the operational reality#

One system to operate. Every additional datastore is a backup strategy, a monitoring setup, an upgrade path, a failure mode, an on-call runbook, and a set of consistency questions between it and everything else.

The cost of a second datastore is not the license. It is the permanent operational surface, and teams consistently underestimate it by a large factor.

Transactions across your data. If your users and your documents and your embeddings are in one database, a change to all three is one transaction. Split across three systems, it is a distributed consistency problem you now own.

when it is genuinely wrong#

Being honest about this matters, because "just use Postgres" as a reflex is the same failure mode as any other reflex.

Extreme write throughput on a single logical dataset. Postgres scales writes vertically, well, up to a point. Past that point you need sharding, and Postgres's sharding story is real but less mature than systems designed for it from the start. If you genuinely need millions of writes per second, look elsewhere.

Analytical queries over very large datasets. Postgres is a row store. Scanning a billion rows to compute an aggregate is what column stores are for, and the difference is orders of magnitude. Use a column store — several integrate cleanly with Postgres.

Global multi-region with low write latency everywhere. Postgres replication is primary-based. If you need writes accepted in multiple regions with low latency, that is a distributed database problem and Postgres is not one.

Extremely high-volume caching. Redis exists and is better at being a cache. This is a legitimate second system.

Embedded / on-device. SQLite. Different problem, different answer.

the thing to actually do#

Start with Postgres. Put everything in it. Measure.

When something is genuinely the bottleneck — and you will know, because you will have the metrics — extract that one thing to a specialized system with a clear reason.

That order matters. Teams that start with five specialized systems on the theory that they will need them end up operating five systems for a workload one would have handled, and the complexity is permanent.

the version note#

Whatever you are running, be less than two major versions behind. Postgres's release quality is high, the upgrades are usually boring, and the performance improvements between versions are substantial and free.

The people who have bad Postgres upgrade experiences are the ones who skipped four versions and tried to do it in one jump during an outage.

Dom, March 30, 2026

get README in your inbox

One dispatch, no noise. Tech and developer news, plus the occasional long piece on the craft.

subscribe →