Loading...
Loading...
MongoDB, CouchDB, and document-oriented databases
Key-value stores ended with microsecond reads gated by stampedes and hot keys: a thousand simultaneous misses on one expired homepage key hit the backend at once, and one celebrity profile at 100,000 reads a second hashes to a single node. Exact labels also assume the reader knows the label. A product catalog starts clean with name, price, and photo, then sellers add books with page counts, shirts with sizes, and laptops with fourteen specs nobody planned. In a relational table, which is a grid demanding every row share declared columns, each new shape needs a migration, new nullable columns, and a meeting about schema. The product moves weekly while the schema cannot.
Stuffing every variant into sparse columns with hundreds of nulls fails next: rows bloat, constraints turn meaningless, and each query null-checks fields that exist for one category only. A second tempting fix is an entity-attribute-value table with one row per field, and it fails on assembly cost with numbers: a laptop with fourteen specs becomes fourteen rows, so rendering 50 products costs 1 plus 700 lookups, the N+1 pattern at catalog scale, and every range filter becomes a self-join the planner cannot rescue. Splitting each category into its own table fails differently, because the application joins a growing constellation of tables and each new product type needs a new join.
A document store, which is a database keeping each record as a self-contained JSON-like object with whatever fields it arrived with, gives up on shared shape. Think of a post office that accepts any parcel shape rather than forcing every sender into one envelope: one analogy for the whole idea, where fields can nest as deep as needed and next month's new field simply appears without altering old parcels.
One sentence version: stop splitting each object across tables and store it whole, shaped however it arrived, so reads fetch one parcel instead of assembling pieces.
Embedding, which is nesting related data inside the same document, replaces joins for data always read together. The profile, recent orders, and settings travel in one fetch, so rendering a user page costs one lookup rather than three queries plus stitching. The cost moves to updates, because changing one nested item rewrites its enclosing document.
{
"_id": "user_123",
"name": "Alice Smith",
"email": "alice@example.com",
"profile": {
"bio": "Software Engineer",
"avatar": "https://..."
},
"orders": [
{ "id": "ord_1", "total": 99.99, "date": "2024-01-15" },
{ "id": "ord_2", "total": 149.99, "date": "2024-02-20" }
],
"preferences": {
"theme": "dark",
"notifications": true
}
}Profile, orders, and settings leave the database together. One read returns the person with no assembly, and a new preferences field next month needs no migration.
The same person in relational form was three tables with two joins, which is flexible for unanticipated questions but an assembly job on every read. Asking all users who ordered over $100 in January has no join to help in document form, so the engine scans documents and filters in code unless a secondary index, which is a sorted shortcut maintained per queried field, exists for that exact question. Whole-object reads got cheap while cross-object questions got expensive, and that asymmetry decides every fit decision that follows.
users plus profiles plus orders
Fetch user, then fetch profile, then fetch orders, then stitch in code
Flexible unanticipated questions, paid for with joins and stitching on the hot path.
users collection, which is the named set holding these documents
{ name, email, profile: {...}, orders: [...] }
Assembled once at write time, then opened whole on every read.
MongoDB, which is the dominant document database with rich queries and aggregation pipelines, is the default unless offline sync or zero operations dictates otherwise. CouchDB, which is the document store speaking plain HTTP with device synchronization, fits when the client itself behaves as a replica that goes offline. Firestore, which is the hosted document service with live subscriptions managed by its cloud vendor, fits mobile apps needing realtime updates without running servers.
Rich queries, aggregation pipelines, which are staged data transformations inside the database, and tooling everywhere. Reach here first and justify anything else against it.
Speaks plain HTTP and synchronizes with devices that lose connectivity. Pick it when the client itself must keep a working copy offline.
Hosted by its cloud vendor while the app subscribes to live updates. Suits mobile apps needing realtime sync without a backend team.
The fit follows the access shape. Catalogs where every item differs, profiles read whole and written rarely, and schemas evolving faster than migrations allow all favor storing each object whole. Money moving between accounts needs multi-record atomicity, reports slicing across everything six ways need flexible joins, and data whose shape finally stabilized gains more from constraints than from freedom. Those belong back in tables.
MongoDB caps a document at 16 MB, which sounds generous until an ever-growing array lives inside. A user with a decade of orders, a device with years of readings, or a post with a million comments swells the parcel, and each one-line edit rewrites megabytes while replication ships the whole document. The sizing rule is mechanical: embed data read together that stays bounded, such as profile, settings, and recent orders, and reference, which means storing an identifier and fetching the related document separately, anything growing without bound such as full order history and event streams. Budget documents in kilobytes: a 5 KB user document at 2,000 reads per second moves 5,000 times 2,000 bytes, which is 10,000,000 bytes per second or 10 MB/s and comfortable, while a 2 MB document at the same rate needs 4 GB/s and collapses.
| Pattern | Use it for | Edge case to watch |
|---|---|---|
| Embed | Profile plus preferences read together and bounded | Bloat past hundreds of kilobytes slows every update |
| Reference | Order history, comments, and events without bound | Application joins that need the referenced key indexed |
| Bucket | One document per user per month for time series | Queries must target the bucket range or they fan out |
A question like all users who ordered over $100 in January runs weekly, then daily, then on every dashboard load, scanning millions of documents each time because the nested field was never indexed. Adding five secondary indexes to cover every imagined question swings too far the other way: each insert updates six structures, writes slow, and disk grows. The working knobs are compound indexes ordered by equality first, then sort, then range, covered queries, which answer from the index alone without fetching documents, and an honest placement check on whether the cross-document question belongs in a warehouse, which is a separate analytical store. Search-adjacent workloads at large consumer companies trace the same arc: documents hold the entity while something else serves the analytics.
Schemaless means the database stops enforcing shape, not that shape stops mattering. Six months of unvalidated evolution leaves four address formats, two date formats, and a field that is sometimes a string and sometimes an array, and every reader null-checks three shapes forever while no index can rescue a field whose type changes row to row. The discipline is validation at the collection level for queried fields covering types, required keys, and allowed values, a version number stored per document, lazy migration on read for cold data plus eager batched migration for hot paths, and a schedule for retiring old versions.
Past hundreds of gigabytes MongoDB shards, which means splitting documents across machines by a shard key, and the same cardinality rules from relational sharding apply: a monotonically increasing identifier funnels all writes onto the newest chunk, while a hashed user key spreads them. Replicas, which are live copies serving reads, carry the same asynchronous lag contract as any replica set, so reading your own edit still means pinning fresh reads to the primary or waiting for majority-committed reads, which are acknowledged by most replicas. The failure to name is the unshardable hot document, where one viral record every request touches lives on one shard regardless of key choice, and only caching or splitting that document relieves it.
Documents handle varied records beautifully by storing each one whole, with the 16 MB ceiling and embed-versus-reference rule keeping parcels bounded. Varied parcels assume each record is read whole. Billions of uniform events that need scanning by time or counting across everything, such as a billion sensor rows a month where nobody ever fetches reading number 4,000,002, want a layout built for oceans rather than parcels, which is the columnar store.