SQL vs NoSQL: When to Pick Which
easyThe SQL/NoSQL choice is really an access-pattern choice dressed up as a technology debate. Start from the top 3-5 queries and the answer usually falls out.
Key Concepts
Approach
- Enumerate the top access patterns explicitly: 'get user by id', 'list user's orders by date', 'count clicks per ad per minute'.
- Default to Postgres until you have a concrete reason to leave. Most products never outgrow a well-tuned Postgres + read replicas.
- Identify the workload that would actually hurt Postgres: 1M+ writes/s on a single table, 100+ TB of time-series data, single-key lookups at hundreds of K QPS with strict tail latency.
- Add a specialized store for that workload. Don't replace the relational store — add alongside it.
- Plan write paths to keep stores in sync. CDC (Debezium, Postgres logical replication) is the production-grade way to derive views.
- Only adopt NewSQL when you need multi-region ACID. Spanner/Cockroach add real operational overhead.
- Evaluate managed vs self-hosted honestly. Until you have a 20-person infra team, managed wins.
Family-by-family
Relational (Postgres, MySQL): ACID, joins, ad-hoc SQL, rich indexes (B-tree, GIN, GiST, BRIN). Vertical scale up to ~64-128 cores; horizontal via read replicas, Vitess/Citus, or partitioning.
Key-value (Redis, Memcached, DynamoDB): single-key get/put. Scales horizontally. Limited query surface. Predictable latency.
Document (MongoDB, Couchbase, Firestore): JSON-like documents keyed by ID, secondary indexes. Schema flexibility; transactions added later (Mongo 4+). Strong for hierarchical data.
Wide-column (Cassandra, Bigtable, HBase): sparse sorted map; partition key + clustering columns. Massive write throughput; range scans within partition. Eventual consistency default; LSM storage engine.
Graph (Neo4j, Neptune): nodes + relationships first-class; traversal is O(degree). Cypher / Gremlin query languages. Smaller-scale than wide-column but unique strength.
NewSQL (Spanner, CockroachDB, TiDB): SQL + ACID + horizontal scale + multi-region. Built on Paxos/Raft per shard. Highest operational complexity.
Components
- Schema design — driven by query patterns; in NoSQL design per-query, in SQL normalize first then denormalize for performance.
- Indexing strategy — primary key, secondary indexes, covering indexes, partial indexes (Postgres).
- Replication — sync, async, multi-master, leaderless quorum.
- Sharding — range, hash, directory, geo.
- Connection pooling — PgBouncer, ProxySQL, RDS Proxy.
- Migrations — Flyway, Liquibase, Atlas for schema evolution.
- Backups + PITR — base backups + WAL/binlog for point-in-time recovery.
- CDC pipeline — Debezium, Postgres logical decoding, Mongo change streams to derive read models.
Trade-offs
SQL: ACID and joins are powerful; vertical scale ceiling and rigid schema are the costs.
NoSQL: scale-out and flexibility; you give up ad-hoc query power and design per access pattern from day one.
NewSQL: bridges both; higher ops complexity and per-write latency.
Polyglot: best technical fit per workload; engineering cost of operating N stores instead of 1.
Managed vs self-hosted: managed loses some configurability and costs more at scale; self-hosted requires real DBA / SRE investment.
Anti-patterns
- Picking MongoDB because you 'don't know the schema yet'. The schema exists; you just haven't acknowledged it. Rigorous Postgres + JSONB columns is usually better.
- Picking Cassandra for a write-light workload. The operational tax isn't worth it under 10K writes/s sustained.
- Using Redis as a primary store without persistence and HA. Cache, lease, queue — yes; system of record — no.
- Splitting across N stores 'for purity' before you have the traffic to justify the ops cost.
- Treating NewSQL as a drop-in Postgres replacement. Cross-region writes have real latency; queries need explicit hints.