Emmanuel EbriEmmanuel Ebri
Back to Blog
PostgresArchitectureDatabases

Postgres Is Eating Your Stack

Where one database is genuinely enough, and where it stops

5 min read

Postgres Is Eating Your Stack cover

A pattern I keep seeing on small teams: an architecture diagram with five data stores on it, run by three people, where four of those stores exist because a blog post said they should.

Postgres, plus Redis for caching, plus a message broker for jobs, plus a search cluster, plus a vector database because there is an AI feature now. Each was added for a defensible reason. Together they are five things to operate, secure, back up, upgrade, monitor and pay for, and the team is small enough that nobody owns most of them.

The counter position has become popular for good reason. One database, until it visibly stops working. Here is where that holds and where it does not, based on the parts I have actually run rather than the parts I have read about.

Jobs and queues

This is the clearest case. If your queue depth is thousands of jobs rather than millions, you do not need a broker.

Postgres has had the primitive that makes this work for years, and it is not a hack.

A durable job queue in the database you already run. No broker, no second thing to operate.

The lock is held by the row, so workers pull without stepping on each other. Jobs survive a restart because they are rows in a durable database. Retry counts and scheduling are columns you already know how to query, which means debugging a stuck queue is a select statement rather than a broker-specific tool you use twice a year.

What you also get, and this is the underrated part, is transactional enqueue. Creating an order and scheduling its confirmation email happen in one transaction. Either both exist or neither does. With a separate broker, that is the classic dual write problem, where the order commits and the enqueue fails and a customer never hears from you. Solving that properly needs an outbox table, which is a queue in your database, which is where we started.

Where it stops: when you need fan out to many consumers, cross-service event streaming, or throughput that makes your primary database the contention point for everything else in the product.

Caching

Redis is genuinely better at caching than Postgres, and I still would not add it on day one.

Most caching people implement early is either unnecessary, because the query is fast and the traffic is low, or better solved by an index, or better solved at the HTTP layer where a CDN can hold the response and your server never sees the request. In-process memory covers a surprising amount of the rest at a single instance.

Where it stops: shared state across instances. The moment you run more than one process and need them to agree on a rate limit counter, a session, or a lock, you want Redis and you should take it. That is a specific trigger and it is easy to recognise, unlike "we might need caching."

Search

Full text search in Postgres covers more than people expect. Tokenisation, stemming, ranking, weights across columns, prefix matching. For a catalogue of courses or products in the thousands, it is fine, it is one join away from the data it describes, and it stays consistent because it is the same transaction.

Where it stops: relevance tuning as a first class product concern, faceting across many dimensions at scale, typo tolerance that users actually notice, and multi million document corpora. If search is the product, use a search engine. If search is a filter box, you already have one.

Vectors and the AI feature

The vector database market exists partly because it arrived before Postgres had a good answer, and that gap has closed. pgvector handles embedding storage and similarity search well within the sizes most product features need.

The advantage is not raw speed. It is that your embeddings sit next to the rows they describe, so a similarity query can filter by tenant, by permission, by date, by anything already in your schema, in one query with real joins. Keeping vectors in a separate store means either duplicating that metadata or filtering after retrieval, which is worse in both directions.

Where it stops: very large corpora, heavy re-indexing, or latency requirements that need a purpose built engine.

The real argument

The point is not that Postgres is technically superior to specialised tools. On their own axis, it is not, and the people who built those tools were not wasting their time.

The point is that operational surface is a cost that gets ignored during architecture and paid every week afterwards. Each additional store means another failure mode, another set of credentials, another backup you should test and probably have not, another upgrade path, another thing that breaks at two in the morning while you are learning its diagnostic tooling for the first time under pressure.

For a small team, the correct question is not which tool is best for this workload in isolation. It is whether this workload is painful enough, right now, to justify permanently operating another system.

How to decide without guessing

Write down the trigger before you need it, the same way you would for splitting a service. Not a feeling, a measurement.

Move jobs to a broker when queue latency under normal load exceeds what the product can tolerate, or when job volume starts affecting query performance for user facing requests. Add Redis when you run more than one instance and need shared counters or locks. Move search out when relevance work becomes a recurring product task rather than a one time configuration.

Then run one database until a trigger fires, and when one does, add exactly the one thing the trigger names.

The five store diagram is not the sign of a sophisticated team. Usually it is the sign of a team that made five decisions early, in the abstract, and now maintains all of them.

I build EdTech and fintech products at E-Bringgs Technologies, where the database is deliberately the boring part.