The NoSQL decision is usually presented as a technology choice and is almost always an access pattern choice. A team that stores product catalogs reads one document per product page and never joins; a team that writes sensor data at a million points per second writes append only and queries by time window and device. Those two teams have legitimate reasons to leave the relational default, and the teams that copy them because the conference talk was inspiring do not. This article draws the honest line: what MongoDB earns for document shaped domains, what Cassandra earns for write volume with known queries, the specific failure of using either as a default relational store, and the boundary rule that keeps one system of record intact.

The access pattern that decides the store before any feature list

Every store is an index structure optimized for a question. The relational store answers arbitrary joins over arbitrary subsets, and it pays for that generality with per row overhead and the join machinery on every plan. The document store answers one document by key or by a bounded set of filters, and it pays with rigid denormalization choices baked into the data. The wide column store answers a partition lookup with a fixed ordering inside it, and it pays by refusing every query that does not start with the partition key.

The audit that decides the store is therefore a list of queries, not a list of features. What are the five statements that will carry the load? Do they share a leading key? Do they need joins, or can the read shape be embedded in the write shape? What happens to data after ninety days, and does the store retire it cheaply? A team that cannot write those five queries down should not pick a store at all, because both MongoDB and Cassandra punish wrong answers structurally, with data shaped for the wrong questions, while the relational store tolerates wrong answers at a mere performance cost. That tolerance is exactly why relational remains the default, and also exactly why the teams with genuinely lopsided access patterns have somewhere better to go.

MongoDB where documents match the shape the domain reads

MongoDB stores BSON documents, and its strength is the case where the application reads and writes exactly one document per interaction. The product catalog is the canonical example, because products genuinely vary in attributes, a book has an author and a page count, a laptop has a socket and a screen size, and a relational schema answers that variability with either a sparse column farm or an entity attribute value table that turns one product page into a dozen joins. The document version stores what the page displays:

{
  "_id": "sku-1009",
  "title": "Steel Kitchen Knife 20cm",
  "attributes": { "blade": "steel", "length": 20 },
  "variants": [
    { "size": "standard", "price": 39 },
    { "size": "chef",    "price": 54 }
  ],
  "stock": 12
}

and the product page is one find by identifier, one round trip, one structure that already matches the view. Flexibility is the second draw, and it is real but bounded: fields can vary between documents freely, which serves catalogs and content stores well, and the schema validation introduced in modern MongoDB versions exists precisely because teams learned that unbounded flexibility is a debt, not a feature. The query side goes beyond point lookups through the aggregation pipeline, a staged dataflow where each stage transforms the stream:

db.orders.aggregate([
  { $match: { status: "paid" } },
  { $group: { _id: "$region", revenue: { $sum: "$total" } } },
  { $sort: { revenue: -1 } }
])

The pipeline answers real reporting questions, and indexes support the filters that start it, but the honest boundary is the lookup stage, the join equivalent, which exists, works, and becomes the slowest part of any pipeline that leans on it. The relational instinct of normalizing everything and joining at read time is the anti pattern inside MongoDB, and the teams that ship it rebuild the join cost they left behind plus the mapping layer.

Transactions deserve one honest sentence: MongoDB has had multi document ACID transactions on replica sets since version 4.0, and they work. They also do not scale like the rest of the engine, because coordinating a transaction across shards reintroduces the machinery the design omitted, so their presence is a safety net for rare multi document rules, not a license to build the ledger on it.

Cassandra where the write volume and the partition key decide

Cassandra answers a narrower question at an extreme scale: given a partition key, store and return a large, time ordered partition, at write throughput that saturates disks, with no single coordinator in the write path. The write path explains the speed. Every write hits a commit log for durability and a memory resident memtable, then flushes to an immutable sorted file, an SSTable, and the only synchronous work is the log append. Nothing locks a row, nothing checks an index tree, and the throughput is limited by the disk, which is why metrics, sensor and event pipelines that would grind a relational engine into replication lag stay flat here.

The price is that the data model is the query, written down before the data exists. A time series table for device readings:

CREATE TABLE readings (
  device_id uuid,
  bucket text,          -- day, e.g. 2026-10-17
  ts timestamp,
  value double,
  PRIMARY KEY ((device_id, bucket), ts)
) WITH CLUSTERING ORDER BY (ts DESC)
    AND default_time_to_live = 7776000;

The composite partition key spreads writes across the ring while keeping one device day together, the clustering order serves the dominant query, the latest readings first, and the time to live retires data at the storage layer with no delete job anywhere. The query that works reads one device, one day, ordered by time. The query that reads all devices for one minute, the cross partition scan, is the one the data model forbids, and it must be answered by a second table written for it at ingest time. This is the design discipline: tables are materialized views of the application's questions, written at insert time, and a new question type means a new table and a data migration, not a clever join.

Consistency is a dial on this model. Each read and write carries a consistency level, from ONE, the fastest, to QUORUM, which reads or writes a majority of replicas, to ALL. The dial trades latency for certainty, and the practical settings are well known: QUORUM reads and writes keep replicas in agreement under failures, ONE behind a cache serves dashboards, and lightweight transactions, the compare and swap mechanism built on a consensus protocol, exist for the rare must be unique case and cost an order of magnitude in latency, which is the engine telling its users not to make a habit of it. Tombstones are the operational sharp edge: deletions write markers that must be compacted away, and mass deletion patterns that flood the store with tombstones turn reads into scans of the dead, which is why time to live expiry is the preferred retirement everywhere it fits.

The failure mode of using NoSQL as a default relational store

The failure arrives as a slow realization over about two quarters. The team migrates a perfectly relational workload because the talk was convincing, and then the joins move into the application, where every page assembles its data from several lookups, in code, with the network latency per lookup and the race conditions the database used to prevent. The constraints move into the application too, and the application enforces them somewhere, eventually, on some code path, until a script bypasses it and the store keeps the duplicates the schema would have refused. The consistency surprises arrive third: Cassandra reads at ONE serve the stale replica after a network wobble, MongoDB secondary reads serve the document before the update, and the bug report says the user saw their old email after saving the new one.

The reporting pain arrives last and stays longest. Ad hoc analysis, the accidental queries every business runs, is what relational was built for, and on a document or wide column store it means either shipping the data to an external analytics engine or writing the aggregation pipeline that the analyst would have written as a SELECT. None of these failures mean the store is bad, they mean the workload was wrong for it, and the honest postmortem almost always ends with the same sentence: the access pattern was relational, and the migration was a rewrite of the database's job in application code.

Where relational remains the correct answer

The relational engine stays correct wherever several of these hold: entities relate through foreign keys that the schema should enforce, transactions span multiple entities, money moves between rows, the query set evolves with the business rather than being fixed at design time, and reporting is a first class workload. An accounts ledger, an order pipeline with inventory, a booking system, the standard operational core of most products, satisfies all of these at once, and the decade of tooling around SQL, from migrations to BI connectors to the analysts who already speak it, is part of the platform rather than a detail.

The scaling fear that historically drove the exodus is also smaller than it was. Modern PostgreSQL comfortably serves mixed workloads in the hundreds of gigabytes with the right indexes, partitioning from the previous articles, and read replicas, and the genuine Cassandra scale, millions of writes per second across a ring, is a scale most products never reach and should be honest about. Reaching for the specialized store because the relational one might become slow one day optimizes the wrong uncertainty, and it does so by paying the structural cost up front.

The polyglot boundary with one system of record

The mature architecture usually ends up polyglot with a spine. The relational system of record holds the entities, their relations and the transactions, and the specialized stores hold derived data shaped for specific access: the catalog denormalized into MongoDB or the session store, the event stream landing in Cassandra for the time window dashboard, the search index for text queries. The derivation is one directional and automated, change data capture streams the source of truth into each projection, and the projection is rebuildable, which is the property that keeps the boundary honest: derived stores may lag or be lost, because the system of record can rebuild them.

The rule that fails gracefully is worth stating in one line: only one store is allowed to be right, and every other store must be rebuildable from it. Teams that follow it get the performance of specialized storage with the integrity of the relational core, and teams that let two stores both be authoritative get the reconciliation scripts, the nightly repair jobs, and the incident reviews that the architecture was supposed to prevent.

The adoption checklist for a NoSQL proposal

The proposal that survives review answers questions before they are asked, and the questions are the same in every organization:

  1. List the five dominant queries with their access keys and expected volumes;
  2. State the consistency requirement per query, including whether reads must see the last write;
  3. Name the system of record and the rebuild path if this store is derived, or justify why it is not;
  4. Show the retirement strategy for data after its useful life, cheap expiry or detach, not deletes;
  5. Benchmark the same workload on the relational default at realistic scale before claiming the ceiling;
  6. Write down the queries the store refuses, and confirm the application will never need them.

The stores are excellent at what they were designed for, and the design left gaps on purpose. The team that picks a specialized database because its access pattern genuinely matches the design gets years of flat latency at scales the relational default finds painful, and the team that picks it as an identity does the application's job in the application, slowly, one improvised join at a time.