ARTICLE
EngineeringStart with Postgres. Almost always.
Database choice is the decision most likely to be made for the wrong reasons, and the most expensive to revisit. Here is the short version.
Choosing the database is one of the earliest decisions in a project and among the hardest to reverse. It also attracts more fashion-driven argument than almost anything else in the field.
My default is Postgres, and the bar for departing from it is high. Here is the reasoning, in a form you can push back on.
Your data is relational. It just is.
Customers have orders. Orders have items. Items reference products. Students belong to batches, batches have sessions, sessions have attendance. Nearly every business application is a graph of related things, and a relational database is what that shape was designed for.
The classic failure is picking a document store because early development feels faster, then spending the next year reimplementing joins in application code — slower, buggier, and without transactions.
Constraints are a feature, not friction
A foreign key that refuses to let an order exist without a customer prevents a class of bug permanently. A unique constraint on an invoice number means duplicate numbers cannot happen, no matter what the application code does under load. Rules enforced by the database hold even when your code is wrong — and your code will sometimes be wrong.
One database instead of four
Modern Postgres covers a startling amount of ground: JSON columns when data genuinely is unstructured, full-text search, vector similarity through pgvector, geographic queries through PostGIS, and a queue via SKIP LOCKED that is enough for most workloads.
Each of those has a specialist that beats it. The specialist also brings another thing to deploy, monitor, back up, secure and keep in sync. For a small team, one very good database usually beats four excellent ones.
Every additional datastore is a sync problem you have agreed to own forever.
When I would not use it
- Genuinely enormous append-only volumes — telemetry, sensor readings, clickstreams at real scale. A time-series or columnar store is the right tool.
- A cache. Redis exists and is excellent. Use it as a cache, not as your source of truth.
- Files. Images, videos and PDFs belong in object storage with the path in the database, never the file itself.
- Your platform has already decided. If you are deep in a stack with its own managed database, fighting it is rarely worth the win.
The honest summary
Almost no small or medium business application outgrows Postgres. Plenty outgrow the team's ability to operate five different datastores. Start boring; the exciting choice is still available later, and you will make it with real information.
The migration nobody budgets for
The argument for picking a specialised database early is usually that migrating later will be painful. That is true. It is also true that the migration you are trying to avoid is far less likely than the one you will cause.
In practice, moving from Postgres to something else because you genuinely outgrew it is rare, well-understood and happens when you have revenue and engineers. Moving from a document store to Postgres because you need transactions and joins is common, is done under pressure, and happens exactly when you can least afford it.
Optimising for the second scenario is the better bet, and it is the one people talk about less because it is embarrassing.
Things people believe Postgres cannot do
- “It can't handle unstructured data.” JSONB columns store arbitrary documents, can be indexed and queried, and sit beside your relational columns in the same row.
- “It can't scale.” Read replicas, connection pooling and a correctly indexed schema carry applications far beyond where most businesses ever reach. Most “Postgres doesn't scale” stories are a missing index.
- “You need a separate search engine.” Full-text search with ranking and stemming is built in and adequate up to a surprisingly large corpus.
- “You need a message queue.” SELECT ... FOR UPDATE SKIP LOCKED gives a safe work queue. Not the right answer at enormous throughput; entirely right for sending emails and generating reports.
How not to paint yourself into a corner
Choosing well matters less than a handful of habits that keep the choice reversible.
- Keep schema changes in version control as migration files, applied by a script. Never by hand on a live database.
- Do not scatter database queries through the application. Keep them behind a layer, so replacing the store later touches a boundary rather than everything.
- Use real types. Dates as dates, money as a decimal type — never a float, which will eventually lose you a paisa in a way that is maddening to trace.
- Let the database enforce what must be true. Foreign keys, unique constraints, not-null. Application code has bugs; constraints do not stop being enforced because a code path was missed.
- Test your restore, not your backup. A backup nobody has restored is a belief, not a backup.
The one-line version
Start with the boring, well-understood thing that does eighty per cent of everything adequately. Add a specialist when you have a specific measured problem it solves. That order costs less, breaks less, and leaves you with fewer things to be woken up by.
KEEP READING
More articles.
Server-rendered or single-page? A plain answer
The argument that has run for a decade, reduced to the two questions that actually decide it for your project.
Read it SecurityPasskeys, and when to bother
They are genuinely better than passwords and genuinely not a drop-in replacement. Where they fit, and the recovery problem nobody mentions.
Read it MobileBuild for the metro: offline is a product decision
Most apps are built on office wifi and used on a train. Treating the network as optional is a decision about who gets to use your product.
Read itGot an idea you've been sitting on?
Book a free call. Worst case, you walk away with free advice on what to build first.
Free 20-min idea call · No obligation