PostgreSQL can do more than you think-queues, full-text search, JSON, geospatial, and more. Here's when to lean into Postgres and when to reach for specialized tools.
The boring answer is usually right
Most stacks I see have too many databases. A relational store for the main data, a document store for "flexibility," a queue for background jobs, a separate search engine, a key-value cache, and sometimes a vector database on top. Each one solves a real problem, but each one also brings its own operational tax: migrations, backups, monitoring, failure modes, on-call knowledge.
The first thing I ask on most projects is whether Postgres can do this job. Surprisingly often the answer is yes, and the system gets simpler, cheaper, and easier to reason about. This is not Postgres maximalism. There are real workloads it should not handle. But the default reach for a specialist tool deserves more scrutiny than it usually gets.
What Postgres quietly does well
A short list of jobs I have happily kept inside Postgres:
- JSON documents with
jsonb, GIN indexes, and rich operators - Full-text search for most product-search needs, especially with the
pg_trgmextension for fuzzy matching - Geospatial data through PostGIS, which is genuinely best-in-class
- Time series for moderate volumes, especially with TimescaleDB or partitioning
- Background job queues with
SELECT ... FOR UPDATE SKIP LOCKED - Event log and outbox patterns for reliable messaging
- Vector search with pgvector for retrieval workloads up to a few million embeddings
- Caching when the working set fits and you do not need sub-millisecond reads
In my experience, a single well-tuned Postgres handles all of these for the vast majority of products that ever ship. The teams that reach for a separate engine for each typically do it before they have proven they need to.
The single-database advantage
The biggest argument for Postgres-for-everything is not technical. It is operational. One backup. One restore drill. One replication topology. One set of credentials and IAM rules. One place to run a migration. One on-call playbook.
Earlier in my career working on regulated platforms, I saw how quickly the operational surface expanded as we added specialized stores. Each one was a defensible choice in isolation. Together they were a tax that grew faster than the team. Consolidation onto Postgres bought back hours every week.
Transactional integrity is the other underrated win. When your queue, your business data, and your outbox all live in the same database, you can wrap them in a single transaction. No two-phase commits, no eventual-consistency edge cases, no "the queue accepted it but the row never wrote" incidents.
Where I do reach for something else
I do not pretend Postgres is the answer for everything. The honest list of cases where I pull in a specialist tool:
- Sub-millisecond key-value access at high RPS. Redis or a similar in-memory store wins
- Massive analytical queries over hundreds of millions of rows. A columnar warehouse like ClickHouse, BigQuery, or Snowflake will be orders of magnitude faster
- Text search with sophisticated ranking, multi-tenant indexes, or huge corpora. A real search engine like OpenSearch or Meilisearch starts to earn its keep
- Vector search beyond a few million high-dimensional embeddings. A dedicated vector database with proper ANN indexes and resharding will scale farther
- Streaming pipelines with strict ordering and high throughput. Kafka and friends exist for a reason
The trick is to push back the moment you actually need them, not the moment you suspect you might.
Practical patterns
Some patterns I reuse across projects.
A queue inside Postgres
A jobs table, a status column, and a worker that selects rows with FOR UPDATE SKIP LOCKED, combined with LISTEN and NOTIFY for low-latency wakeups, is more than enough for tens of thousands of jobs per minute. Tools like graphile-worker and river have built whole job systems around this primitive.
The outbox pattern
When I need to publish events reliably, I write them to an outbox table inside the same transaction as the business change. A separate process drains the outbox and pushes to the broker, with at-least-once delivery and a deduplication key downstream. This eliminates the dreaded "data committed but the message never sent" class of bug.
JSON for shape, columns for query
For data with variable or evolving structure, I use jsonb for the long tail of fields, and promote heavily-queried fields to real columns with proper indexes. This gives me the schema flexibility of a document store with the query performance of a relational system.
Vector search with pgvector
For most retrieval workloads I have shipped, pgvector with HNSW indexes is fast enough and saves an entire piece of infrastructure. I would consider a dedicated vector database when index size or query rate truly demands it, not before.
Operating it well
A few habits that keep a Postgres-heavy stack healthy:
- Treat
autovacuumas a first-class citizen and tune it per workload - Use a connection pooler (PgBouncer or RDS Proxy) in front of any serverless caller
- Monitor
pg_stat_statementsfor slow queries and act on them weekly - Keep replicas warm enough to take over and rehearse failovers
- Apply schema changes online with
pg_repackor partitioning as the table grows
If you want help making these calls, architecture review is exactly the engagement.
The takeaway
Postgres is not a silver bullet. It is, however, a remarkably broad tool that absorbs many of the jobs we reflexively hand to specialist systems. Lean on it until it tells you to stop. The simplicity dividend on a small team is enormous, and the operational cost of premature specialization is one of the most common ways product teams lose months they did not need to lose.
References
Tagged
Sri Vardhan
Independent technology studio of one. I help founders and small teams ship serious software without the consultancy overhead. More about me.