C.W.K.
Stream
Lesson 09 of 10 · published

When PostgreSQL Is the Wrong Tool

~12 min · operations, philosophy

Level 0Schema Seedling
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The honest list

PostgreSQL is the right answer for almost every persistent-data problem. It is not the right answer for everything. Knowing where it stops winning is part of using it well.

Where you outgrow PostgreSQL

  • Petabyte-scale analytics — column stores (BigQuery, Snowflake, ClickHouse) are an order of magnitude cheaper for this shape.
  • Sub-millisecond cache — Redis is purpose-built for it; Postgres can serve some of these workloads with UNLOGGED tables but won't beat Redis on raw speed.
  • Multi-region active-active — Postgres has logical replication and tooling, but databases like CockroachDB and Spanner are designed for it from the ground up.
  • Time-series at the high end — TimescaleDB extension takes Postgres a long way, but a billion datapoints/sec belongs in InfluxDB or VictoriaMetrics.
  • Truly massive write fan-out — Cassandra, ScyllaDB, and similar wide-column stores eat throughput Postgres struggles with.
  • Pure event log — Kafka is a different kind of system; SQL doesn't model "append-only stream of bytes for many consumers" well.

The decision rule

Default to Postgres. Add a specialist database only when you have measured the limits — not when you suspect them. The cost of running multiple databases (operations, integration, on-call) is usually larger than the marginal performance gain.

Code

UNLOGGED tables — Postgres as a fast-but-volatile cache·sql
CREATE UNLOGGED TABLE session_cache (
    key    TEXT PRIMARY KEY,
    value  JSONB NOT NULL,
    expires_at TIMESTAMPTZ NOT NULL
);

-- Faster than logged tables (no WAL).
-- Survives normal operation, but is TRUNCATED on crash recovery.
-- A lightweight Redis substitute when you don't need cross-node sharing.
Postgres as a queue — when SKIP LOCKED is enough·sql
CREATE TABLE jobs (
    id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    payload JSONB NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Worker
BEGIN;
SELECT id, payload FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- ... process ...
UPDATE jobs SET status = 'done' WHERE id = ?;
COMMIT;

External links

Exercise

List every non-Postgres data store in your stack. For each, write one sentence on the measured Postgres limitation that justified bringing it in. Any answer that's hand-wavy is a hint that the dependency might not be earning its keep.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.