The “just use Postgres” trend is real in 2026, and not because Postgres is fashionable. It’s because the alternatives — separate Redis, Elasticsearch, vector DB, queue — each carry operational cost that adds up. This post is the practical case for keeping Postgres as your default for many use cases.
What Postgres can do (in 2026)
- Relational — primary use.
- JSON with JSONB.
- Full-text search with tsvector + GIN.
- Vector search with pgvector + HNSW.
- Queue with SKIP LOCKED.
- Pub/sub with LISTEN/NOTIFY.
- Time-series with TimescaleDB extension.
- Analytics with extensions or warehouse extensions.
- Geospatial with PostGIS.
- Graph with recursive CTEs / pg_age.
Postgres as queue
SELECT * FROM jobs WHERE status = 'pending'
ORDER BY created_at LIMIT 1
FOR UPDATE SKIP LOCKED;
Up to ~10k jobs/sec. See Postgres as a Queue .
Pair with LISTEN/NOTIFY for low-latency wake-up:
NOTIFY job_inserted;
Postgres as cache
CREATE UNLOGGED TABLE cache (
key text PRIMARY KEY,
value jsonb,
expires_at timestamptz
);
CREATE INDEX ON cache (expires_at);
UNLOGGED = no WAL = much faster, lost on crash. Suitable for cache.
Get/set:
INSERT INTO cache (key, value, expires_at)
VALUES ($1, $2, now() + $3)
ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = now() + $3;
SELECT value FROM cache WHERE key = $1 AND expires_at > now();
For Redis-class throughput: graduate. For typical app caching: this is fine.
Postgres as full-text search
ALTER TABLE posts ADD COLUMN search_vec tsvector;
UPDATE posts SET search_vec = to_tsvector('english', title || ' ' || body);
CREATE INDEX ON posts USING GIN (search_vec);
SELECT * FROM posts WHERE search_vec @@ websearch_to_tsquery('english', $1)
ORDER BY ts_rank(search_vec, websearch_to_tsquery('english', $1)) DESC;
Up to ~10M docs with reasonable performance. Beyond that or for typo tolerance: Meilisearch / Elasticsearch.
See Search System Design .
Postgres as vector store
CREATE EXTENSION vector;
CREATE TABLE docs (
id bigserial PRIMARY KEY,
body text,
embedding vector(1024)
);
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);
SELECT * FROM docs ORDER BY embedding <=> $1 LIMIT 10;
pgvector handles up to ~10M-100M vectors well. Beyond: dedicated vector DB. See Embedding Databases .
Postgres as time-series
TimescaleDB:
CREATE EXTENSION timescaledb;
CREATE TABLE metrics (
ts timestamptz NOT NULL,
metric text NOT NULL,
value double precision NOT NULL
);
SELECT create_hypertable('metrics', 'ts', chunk_time_interval => interval '1 day');
-- Continuous aggregates
CREATE MATERIALIZED VIEW metrics_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', ts) AS hour,
metric,
avg(value), max(value), min(value)
FROM metrics
GROUP BY 1, 2;
Auto-partitions, compression, downsampling. Competes with InfluxDB / Prometheus for many use cases.
Postgres as pub/sub
LISTEN orders;
-- in another session:
NOTIFY orders, '{"id": 42}';
Sub-millisecond message delivery. Limited to ~ thousands of msg/sec; payload size limited (~8KB by default). For more: Kafka / NATS.
But for “wake me when there’s work”: LISTEN/NOTIFY is brilliant.
Postgres as analytics
For OLAP-heavy workloads, Postgres struggles. But:
- Read replica running heavy queries off primary.
- citus for distributed analytical queries.
- pg_duckdb for vectorized analytics on Postgres data.
- Materialized views for precomputed aggregates.
For real warehouse needs: ClickHouse / DuckDB / BigQuery. For “ad-hoc dashboards over OLTP”: Postgres is fine.
When Postgres is wrong
- Sustained 100k+ writes/sec to one table. Need sharded / Cassandra-class.
- Petabyte analytics. Use Snowflake / BigQuery / ClickHouse.
- Globally distributed writes. Use Spanner / CockroachDB / etc.
- Specialty data shapes (graph traversal at scale, vector at >100M).
For everything else: Postgres handles it.
Operational simplicity
[App] ──▶ [Postgres]
(queue, cache, search, vectors, primary data)
vs
[App] ──▶ [Postgres]
└→ [Redis]
└→ [Elasticsearch]
└→ [Pinecone]
└→ [Kafka]
Each line is operations: monitoring, backups, security, scale, failure modes. The first wins on operational cost until you hit specific scale limits.
The graduation criteria
| Component | Graduate when |
|---|---|
| Queue | >10k jobs/sec sustained |
| Cache | Redis hot keys would beat Postgres |
| Search | Need typo tolerance / faceted / >50M docs |
| Vector | >100M vectors with sub-100ms p99 |
| Pub/sub | Need persistence / replay / fanout |
| Analytics | Multi-billion row queries |
Graduate one component at a time, not all at once.
Real-world stack
A team I worked with handled $2M ARR on:
- Postgres (relational + queue + cache + FTS + vector + pub/sub).
- One web service (FastAPI).
- Object storage (S3).
- Cloudflare in front.
That’s it. 4 components. They scaled to 10k MAU on this stack before graduating queue (to Postgres-native River) and adding Redis for cache.
Common mistakes
1. Premature optimization
“We might need Kafka.” 5k msg/sec? Postgres. Add Kafka when load demands.
2. One Postgres for everything at scale
10TB OLTP + analytics queries on the same instance: writes slow down. Use a replica or extract.
3. Skipping Postgres tuning
shared_buffers default; queries slow. Tune your one DB. See Postgres Performance Tuning
.
4. No partitioning on big tables
Tables grow forever; vacuum chokes; queries crawl. Partition. See Postgres Partitioning .
5. Treating Postgres like noSQL
Schemaless JSONB everywhere. You lose constraints, types, performance. Use columns where structure exists.
What I’d ship today
For a new app at <1M MAU:
- Postgres as primary + queue + cache + FTS + vector.
- Object storage (S3) for blobs.
- CDN in front for static assets.
- One web tier (FastAPI / Hono / Axum).
- Add specialized services only when measurements demand.
Read this next
If you want my Postgres-as-everything reference architecture, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .