Postgres is enough, until it is not

One database can be the queue, search index, vector store, and analytics layer for most products. The boring-stack case, and the signals that say add something.

Article

Most products we build start with one database, PostgreSQL, and it does jobs that a diagram from five years ago would have given to four separate systems. It is the queue, with a jobs table and row locking. It is the search index, with full-text search that is better than people remember. It is the vector store, with the pgvector extension. It is the analytics layer, with materialised views refreshed on a schedule. One system to back up, monitor, secure, and understand.

This is not laziness. Every additional system is a thing that can fail, a thing that can drift out of sync, a set of credentials, a page in the runbook, and a skill the team must hire for. A product with one database and clear tables is one a small team can operate. A product with six specialised services is one that needs a platform team.

What Postgres does well enough

Queues. A jobs table with a status column, SELECT ... FOR UPDATE SKIP LOCKED, and a worker loop handles thousands of jobs a minute with full transactional safety. A job that fails rolls back with the data change that enqueued it, which a separate queue cannot promise.

Search. Full-text search with ranking, stemming, and prefix matching covers most product search. It is not Elasticsearch, and for a product whose core is search it will run out. For "find the customer by name or email", it is more than enough and it is always in sync, because it is the same database.

Vectors. pgvector with an HNSW index handles retrieval for AI features into the millions of embeddings, alongside the metadata filters that a standalone vector database makes awkward. Filter by tenant, by date, by document type, then rank by similarity, in one query.

Analytics. Materialised views and a read replica serve most dashboards. The numbers agree with the product because they are computed from the product's tables.

The signals that it is time to add something

We add a system when Postgres shows a specific symptom, not when the architecture diagram looks too simple.

Add a real queue when jobs need fan-out to many consumers, ordered streams, or replay, or when queue traffic is measurably contending with product traffic. Kafka or a managed equivalent, and only for those flows.

Add a search engine when search is the product, when you need faceting and relevance tuning that users notice, or when the full-text index is the largest thing in the database.

Add an analytics database when dashboard queries slow the product even from a replica, when data volume makes daily aggregation impractical, or when analysts need to run arbitrary queries over years of events. ClickHouse, usually, fed by the same pipelines. This is the typical shape of our data engineering work: Postgres stays the system of record, and a column store takes the reporting.

Add a cache when the same expensive reads repeat and the replica cannot absorb them. Redis, and only for what is measured.

The order matters

Each addition should be provoked by a number: queue latency, query time, index size, replica lag. When a client asks for Kafka on day one of a SaaS build, we ask what number is forcing it. Usually none is, yet. The platform launches on one database, and the number that eventually forces the second system tells us exactly which system to add. That is a cheaper way to be right than guessing at the start.

Tell us what you are building.

We reply within one business day with how we would build it, what it would cost, and which engagement model fits.

  1. 01
    Tell us what you are building

    A short form or an email. No deck required, and "not sure yet" is a fine answer.

  2. 02
    A call with an engineer

    Within one business day. Technical questions get technical answers, from the person who would build it.

  3. 03
    A written scope and quote

    Fixed price where the scope is defined. The document is yours whether or not you go ahead.