The SQL vs NoSQL debate is the wrong debate. There is no engine that wins everywhere — the job of a database decision matrix is to map your workload's access patterns, consistency requirements, and scale profile onto the storage engine that fits them. Get that mapping right and the "which database" question mostly answers itself.
What's at stake is real: choose a wide-column store for a join-heavy reporting workload and you'll fight the database for its entire lifetime; reach for sharding before you've exhausted read replicas and you'll pay a steep operational tax for capacity you didn't need. The cost of a wrong choice compounds because migrations are expensive and access patterns calcify once a schema is in production.
This guide takes a clear stance and shows the work behind it. We cover why PostgreSQL is the right default for most new projects, the ACID vs BASE and CAP vs PACELC trade-offs that actually govern the decision, a proprietary eight-family engine matrix, read-vs-write scaling strategy, a scored checklist for when to leave Postgres, and how polyglot persistence pays off — and where it bites.
- 01No single database wins.Map your workload's access patterns, consistency needs, and scale profile to the right engine. AWS's own guidance recommends a best-fit mix rather than one store for everything.
- 02Default to PostgreSQL.It's the most-used database among professional developers (55.6% in the 2025 Stack Overflow survey, up from 49% in 2024), and AWS estimates a well-run relational database serves roughly 95% of applications. Switching to NoSQL needs a concrete, measurable justification.
- 03PACELC is the honest framing, not CAP.CAP describes the partition case; PACELC adds the everyday trade-off — even without a partition, you choose between latency and consistency. That's the call you make on most production reads.
- 04Convergence is real at the API, not the engine.Postgres added JSONB and pgvector; MongoDB added ACID transactions; DynamoDB added PartiQL. The boundaries softened at the query layer — but the underlying storage engines and consistency models stay distinct.
- 05Polyglot persistence cuts latency, multiplies overhead.Letting each service own its optimal store is powerful at scale, but every added system is one more thing to provision, monitor, back up, and secure — and cross-database ACID isn't native. Reach for it deliberately.
01 — The DefaultStart with PostgreSQL, earn your way off it.
The single most useful heuristic in database selection is a default, not a flowchart: begin with PostgreSQL and stay there until a specific, measurable constraint forces a move. PostgreSQL is the most-used database among professional developers — 55.6% reported active use in the 2025 Stack Overflow Developer Survey, up from 49% in the 2024 survey and roughly 33% back in 2018. That trajectory matters because tooling, hosting, hiring, and institutional knowledge all follow adoption.
AWS's own architecture guidance lands in the same place: a well-operated relational database handles the large majority of applications, and specialized engines can be added later for specific use cases. The reason a default beats a decision tree is that most teams overestimate how unusual their workload is. A relational store with JSONB for the genuinely-fluid corners, read replicas for read scale, and pgvector for embeddings covers an enormous range before any of its limits actually bind.
"A well-operated PostgreSQL database on Aurora will serve 95% of applications just fine. You can always add specialized databases later for specific use cases."— AWS Architecture Blog, Selecting the Right Database for Your Workloads
That is not a claim that NoSQL is wrong — it's a claim about the order of operations. The cost of starting relational and migrating later is bounded and well-understood. The cost of starting on a specialized engine and discovering you needed joins, ad-hoc queries, or transactional integrity is open-ended. Default to the general- purpose store; specialize on evidence.
02 — Consistency ModelsACID vs BASE: the first fork in the road.
The earliest and most consequential decision is which consistency model your data needs. ACID (Atomicity, Consistency, Isolation, Durability) guarantees a transaction is treated as a single unit: all operations succeed, or the entire transaction rolls back. Relational databases — PostgreSQL, MySQL, Aurora — enforce ACID by default, which is exactly what you want for money, inventory, and anything where a partial write is a correctness bug.
BASE (Basically Available, Soft state, Eventually consistent) trades immediate consistency for higher availability and horizontal scale. NoSQL systems such as Cassandra, DynamoDB, and Couchbase operate under BASE semantics by default. The bargain is explicit: you accept that a read may briefly return stale data in exchange for the system staying available and scaling writes across many nodes.
ACID by default
Transactions are all-or-nothing. The right model for orders, payments, inventory, and any workload where a partial or stale write is a correctness bug rather than a tolerable blip.
BASE semantics
Eventually consistent, availability-first. The right model for high-write, single-key workloads — activity feeds, telemetry, sessions — where brief staleness is acceptable and scale is the priority.
03 — Distributed Trade-offsCAP gets the headlines. PACELC runs your day.
The CAP theorem (Brewer, 2000) states that a distributed system can guarantee at most two of three properties simultaneously: Consistency, Availability, and Partition tolerance. The popular "pick two of three" shorthand is slightly imprecise, though. In any real distributed system network partitions are unavoidable, so partition tolerance is effectively mandatory — which means the genuine choice is between consistency and availability when a partition happens: CP vs AP.
The more operationally honest framing is PACELC(2010), which extends CAP with the part that governs most of your production hours: even with no partition (the "Else" branch), there is still a trade-off between Latency and Consistency. Partitions are rare; the latency-vs-consistency decision is something your system makes on essentially every read. This is why distributed-systems practitioners increasingly argue that CAP alone is no longer the useful lens.
Consistency · Availability · Partition tolerance
Because partitions are unavoidable in a distributed system, partition tolerance is mandatory in practice — so the real-world choice during a partition collapses to CP vs AP.
Else: Latency vs Consistency
Even with no partition, you trade latency against consistency on nearly every read. This is the decision your production system actually makes all day — the operationally relevant one.
Jepsen-tested databases since 2013
Jepsen has found replica divergence, data loss, stale reads, and lock conflicts across production-grade systems. The recurring lesson: vendor consistency claims often do not match observed behavior under partitions.
04 — The MatrixEight engine families, one scannable table.
Every vendor publishes a decision matrix that conveniently omits its competitors. The table below consolidates eight engine families across the axes that actually drive the call: best access pattern, consistency model, schema flexibility, scaling shape, and — most usefully — when notto use each one. Cells are synthesized from the AWS and Google Cloud database selection guides, the Instaclustr Cassandra guide, YugabyteDB's data-modeling comparison, and MongoDB's own documentation.
- Best access pattern
- Joins, ad-hoc queries, transactions
- Consistency
- ACID
- Schema
- Rigid (with JSONB escape hatch)
- Scaling
- Read replicas first; shard last
- When NOT to use
- Avoid when write volume exceeds a single primary even after replicas
- Best access pattern
- Nested-document reads, flexible attributes
- Consistency
- Tunable; multi-doc ACID since 4.0
- Schema
- Fluid (BSON)
- Scaling
- Built-in sharding
- When NOT to use
- Avoid when relationships are highly normalized and join-heavy
- Best access pattern
- Cache, session, leaderboard, rate limit
- Consistency
- BASE; durability optional (RDB/AOF)
- Schema
- Schema-free structures
- Scaling
- Memory-bound; cluster mode
- When NOT to use
- Avoid as a system of record for un-reconstructable data
- Best access pattern
- Known single-key access at high write volume
- Consistency
- Tunable / BASE
- Schema
- Fluid but query-shaped upfront
- Scaling
- Linear horizontal writes
- When NOT to use
- Avoid for joins, ad-hoc queries, or unplanned access patterns
- Best access pattern
- Multi-hop relationship traversal
- Consistency
- ACID
- Schema
- Property graph (nodes + edges)
- Scaling
- Traversal-optimized; vertical-leaning
- When NOT to use
- Avoid for simple tabular reporting and aggregates
- Best access pattern
- Timestamped metrics, range scans, rollups
- Consistency
- ACID (Timescale on Postgres)
- Schema
- Rigid, time-partitioned
- Scaling
- Time-window partitioning
- When NOT to use
- Avoid for general-purpose relational workloads
- Best access pattern
- Semantic / similarity search on embeddings
- Consistency
- Varies by engine
- Schema
- Vector + metadata
- Scaling
- Index-type dependent (HNSW/IVF)
- When NOT to use
- Avoid as a primary transactional store
- Best access pattern
- Global ACID transactions at scale
- Consistency
- ACID (Spanner: global strong)
- Schema
- Rigid relational
- Scaling
- Managed horizontal scale
- When NOT to use
- Avoid when a single-region Postgres already meets the load
| Engine | Best access pattern | Consistency | Schema | Scaling | When NOT to use |
|---|---|---|---|---|---|
| PostgreSQL / MySQLRelational | Joins, ad-hoc queries, transactions | ACID | Rigid (with JSONB escape hatch) | Read replicas first; shard last | Avoid when write volume exceeds a single primary even after replicas |
| MongoDBDocument | Nested-document reads, flexible attributes | Tunable; multi-doc ACID since 4.0 | Fluid (BSON) | Built-in sharding | Avoid when relationships are highly normalized and join-heavy |
| RedisKey-value / in-memory | Cache, session, leaderboard, rate limit | BASE; durability optional (RDB/AOF) | Schema-free structures | Memory-bound; cluster mode | Avoid as a system of record for un-reconstructable data |
| Cassandra / DynamoDBWide-column | Known single-key access at high write volume | Tunable / BASE | Fluid but query-shaped upfront | Linear horizontal writes | Avoid for joins, ad-hoc queries, or unplanned access patterns |
| Neo4jGraph | Multi-hop relationship traversal | ACID | Property graph (nodes + edges) | Traversal-optimized; vertical-leaning | Avoid for simple tabular reporting and aggregates |
| TimescaleDB / InfluxDBTime series | Timestamped metrics, range scans, rollups | ACID (Timescale on Postgres) | Rigid, time-partitioned | Time-window partitioning | Avoid for general-purpose relational workloads |
| pgvector / PineconeVector | Semantic / similarity search on embeddings | Varies by engine | Vector + metadata | Index-type dependent (HNSW/IVF) | Avoid as a primary transactional store |
| Aurora / SpannerDistributed relational | Global ACID transactions at scale | ACID (Spanner: global strong) | Rigid relational | Managed horizontal scale | Avoid when a single-region Postgres already meets the load |
A few cells deserve a footnote. MongoDB's document model excels at nested-document reads and flexible attributes, but as one independent analysis put it, PostgreSQL's JSON support — while superficially similar — is "not optimized for using it as a document database" because of its block format, query planner, and index design. Cassandra and DynamoDB can scale writes nearly linearly, but only because they enforce strict access-pattern limits: single-table FROM clauses, a partition key required in the WHERE condition, no LIKE on clustering columns, and no multi-table joins. Those restrictions are the feature — predictable performance at scale — and the cost, in the form of upfront query planning that relational stores do not demand. For AI-adjacent workloads that need similarity search, see our deep dive on vector databases for AI agents.
05 — Scaling StrategyRead replicas first. Shard last.
Read-heavy and write-heavy workloads scale differently, and conflating the two is a frequent and expensive mistake. Read replicas scale read capacity by adding nodes that each serve SELECT queries — a near-linear win for the read side. Sharding distributes both reads and writes across nodes, which is the only way to scale write throughput past a single primary, but it introduces cross-shard query complexity, rebalancing, and a meaningfully harder operational story.
For the overwhelming majority of applications, the read-to-write ratio favors reads heavily, so read replicas are the correct first move. Sharding should be reached for only once write capacity is genuinely exhausted on a primary-plus-replica setup. Reaching for it early buys complexity you don't need and can't easily undo. If your real bottleneck is read latency rather than write volume, the answer is often a cache or a smarter index, not a distributed rewrite — our references on Redis caching strategies for production Next.js apps and database indexing for read-heavy applications cover both before you touch the topology.
The scaling ladder · climb in order, shard last
Source: PlanetScale scaling guide (interpreted as a staged path)06 — The Exit ChecklistWhen it's time to leave PostgreSQL.
"Default to Postgres" is an opinion until you make it auditable. The checklist below turns the decision into something a team can run against its own metrics. Score each criterion 0 (not applicable), 1 (approaching the threshold), or 2 (definitely hitting it). A total at or above 8 of a possible 12 is the signal to seriously evaluate a specialized engine — and even then, only for the specific workload that triggered it, not your whole system.
| Criterion | Threshold (score 0 / 1 / 2) | If triggered, consider |
|---|---|---|
| Write throughput ceiling | Sustained writes a single primary cannot absorb after replicas | Cassandra / DynamoDB (linear write scale) |
| Schema genuinely unpredictable | Document structure unknown at design time, varies per record | MongoDB (document model) — or Postgres JSONB first |
| Single-key-dominated access | The overwhelming majority of queries hit one partition key | DynamoDB single-table design |
| Multi-hop relationship traversal | Routinely traversing several relationship hops per query | Neo4j (native graph) |
| Sub-millisecond read latency | Hot reads need sub-millisecond P99 at scale | Redis / in-memory cache layer |
| Global multi-region strong consistency | Synchronous replication across multiple regions required | Cloud Spanner / distributed SQL |
The discipline here is the point. Each row maps to a single specialized engine for a reason: a sustained write ceiling points to Cassandra or DynamoDB; genuinely unpredictable structure points to a document store (though Postgres JSONB is the cheaper first experiment); single-key-dominated access is the textbook case for DynamoDB single-table design; multi-hop traversal is where Neo4j's native graph leaves SQL joins behind; sub-millisecond reads call for an in-memory layer; and global strong consistency across regions is Spanner's territory. If you can't point to the row that triggered the move, the move is premature.
07 — The Convergence ParadoxThey're converging — at the surface.
It is fashionable to argue that the SQL/NoSQL distinction is dissolving, and at the API layer that's true. PostgreSQL gained JSONB (document-like storage with GIN-indexed path queries) and pgvector (vector similarity search). MongoDB gained multi-document ACID transactions and SQL-compatible aggregation operators. DynamoDB gained PartiQL, a SQL-compatible query language. On paper, everyone can do a bit of everything.
The paradox is that this convergence is largely cosmetic. The query interfaces are converging; the storage engines are not. A B-tree of relational rows, a collection of BSON documents, and an SSTable-based wide-column ring have fundamentally different performance characteristics at scale, and those characteristics — not the SQL dialect bolted on top — decide whether a workload thrives. PostgreSQL can store JSON, but it isn't a document engine; DynamoDB can speak PartiQL, but it still requires a partition key in the WHERE clause. The convergence is real enough to mislead and shallow enough to punish anyone who takes it literally.
Looking forward, expect this surface-level convergence to keep accelerating — managed services will compete on breadth of supported access patterns, and the marketing will increasingly blur the categories. The durable engineering skill won't be knowing which database "does NoSQL," it will be reading past the feature checklist to the storage engine and consistency model underneath, and matching those to your actual access patterns. The teams that win the next few years of data architecture will be the ones who treat convergence as a convenience, not a reason to stop choosing deliberately.
"Although PostgreSQL can superficially resemble a document database with JSON datatypes, it is not optimized for using it as a document database, because of its block format, query planner behavior, index possibilities, and other components not being optimized for document structures."— OpenSourceDB, Beyond the Hype: PostgreSQL JSONB vs MongoDB BSON
08 — Polyglot PersistenceMany stores, many bills.
Polyglot persistence is the practice of using multiple database types in a single application, with each service owning its optimal store. The canonical e-commerce shape: PostgreSQL for orders, inventory, and ACID transactions; MongoDB for the product catalog and its flexible attributes; Redis for sessions, carts, and caching; Elasticsearch for search; and a vector store for recommendation embeddings. Each engine does what it's best at, and latency drops where it matters.
The price is operational, and it is not small. Every store you add is one more system to provision, monitor, back up, secure, and staff knowledge for — and cross-database ACID transactions are not natively supported, which means consistency across stores becomes your application's problem to solve with patterns like outbox tables, sagas, or eventual reconciliation. The right time to go polyglot is when a specific workload has earned its own store via the exit checklist above, not because the architecture diagram looks more sophisticated with five database icons on it.
Orders, inventory, payments
ACID is non-negotiable here. Keep it on PostgreSQL (or Aurora for managed scale, Spanner for global strong consistency). This is the store that should never be the one you experiment with.
Product attributes
Genuinely variable, deeply nested attributes per product type are the textbook document case. Try Postgres JSONB first; move to MongoDB only if document query ergonomics or scale demand it.
Sessions, carts, rate limits
Sub-millisecond reads and ephemeral state belong in an in-memory store. Redis is the standard for caching, session storage, leaderboards, and rate limiting — with persistence available when you need it.
Search and recommendations
Full-text search wants Elasticsearch; similarity search over embeddings wants a vector index. pgvector keeps both inside Postgres for many teams before a dedicated vector store earns its place.
For teams weighing whether to keep everything in PostgreSQL or split into a polyglot stack, the deciding question is ownership, not capability: does a service have access patterns so distinct that a separate store materially improves latency or cost, and is the team ready to operate that store properly? If you're building AI-native features, the same logic applies to the vector layer — our walkthrough on building RAG with PostgreSQL and pgvector shows how far a single Postgres instance goes before you need a dedicated vector database. When the trade-offs get genuinely hard, our AI and digital transformation engagements start with exactly this kind of workload-by-workload data-architecture review.
09 — ConclusionChoose the workload, not the trend.
There is no best database — only the best fit for a specific workload.
The honest version of the SQL vs NoSQL question is that it's the wrong question. The right one is a sequence: default to PostgreSQL, understand whether your data needs ACID or BASE, reason about latency-vs-consistency with PACELC rather than CAP alone, and move to a specialized engine only when a measurable threshold in the exit checklist is crossed. That ordering protects you from the two most common failures — over-engineering a system that a single relational store would have handled, and under-provisioning one that genuinely needed a specialized engine.
Be skeptical of the numbers vendors hand you. AlloyDB's performance multipliers are Google-stated and lack independent confirmation; managed-Cassandra throughput figures come from vendors with a commercial interest; the "millions of writes per second" claim is true only for large, purpose-built clusters. Jepsen's years of testing are a standing reminder that advertised consistency and observed consistency diverge under partitions. Benchmark on your own data before you believe a marketing slide.
The convergence of SQL and NoSQL at the API layer is real, and it will keep blurring the categories — but the storage engines and consistency models underneath stay distinct, and those are what govern performance at scale. The durable skill is the discipline to map workload to engine deliberately, to reach for polyglot persistence only when a workload has earned its own store, and to treat every consistency setting and vendor benchmark as a hypothesis to verify. Choose the workload, not the trend.