NoSQL & Document Stores
Document stores like MongoDB keep self-contained JSON records and trade joins for denormalization. Understand the CAP theorem, embedding vs referencing, sharding, and when NoSQL beats a relational database.
What is this?
NoSQL is an umbrella term for databases that abandon the relational model for other shapes: key-value (Redis), wide-column (Cassandra, Bigtable), document (MongoDB) and graph (Neo4j). Document stores keep self-contained records as JSON-like documents and use the CAP theorem to reason about consistency, availability and partition tolerance. They shine when the write path, flexible schema or horizontal scale matters more than cross-record joins.
Why do you need it?
- A document holds a whole aggregate — order, customer and items — so one read replaces a multi-table join.
- Schemas are flexible: fields can vary per document, easing early-stage product churn.
- Horizontal scaling is a first-class goal, with data sharded across many nodes from day one.
- Some workloads — caches, sessions, leaderboards, event streams — fit a key or document model far better than SQL.
- Operational simplicity: replication and sharding are built in rather than bolted on.
Mental model
A packed suitcase for one trip
A document in MongoDB
You grab one bag instead of assembling pieces from several closets. But pack the same shirt into two suitcases and you must remember to update both — that is the denormalization cost.
How it works
- A document store serializes each record as BSON/JSON with a unique _id.
- The shard key routes each write to a specific shard, keeping related data together.
- Reads on _id hit the owning shard directly; queries on other fields use secondary indexes.
- Replication copies the oplog to replica-set members for durability and read scaling.
- Consistency is configurable: a write can be acknowledged by one node, a majority, or all nodes.
- Atomic operators like $set and $push modify embedded fields inside a single document atomically.
Relational join versus embedded document.
Key commands
mongosh mongodb://localhost:27017/shopConnect to a MongoDB server and the shop database.
db.orders.insertOne({ customer_id: 1, total: 42 })Insert a single document into a collection.
db.orders.find({ status: 'paid' }).limit(10)Query documents with a filter and a limit.
db.orders.createIndex({ customer_id: 1 })Create an index on customer_id for faster lookups.
db.orders.updateOne({ _id: 1 }, { $set: { status: 'shipped' } })Atomically set a field on one document.
db.orders.aggregate([{ $match: { status: 'paid' } }, { $group: { _id: '$customer_id', total: { $sum: '$total' } } }])Aggregate paid orders per customer with the aggregation pipeline.
Examples
Inserting an order document
db.orders.insertOne({ _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"), customer: { name: "Ada Lovelace", email: "ada@example.com" }, items: [ { sku: "LAP-001", name: "ThinkPad", price: 1299.00, qty: 1 }, { sku: "USB-004", name: "USB-C Dock", price: 89.00, qty: 2 } ], total: 1477.00, status: "pending", createdAt: new Date()});The whole order — customer snapshot, line items and totals — is one document in the orders collection. A page that shows an order reads one document instead of joining three tables. The trade-off: changing the customer's email requires updating every order that embedded it.
In production
- Content and catalog systems store rich, variable-shaped documents (articles, products, media) as documents.
- Session stores and caches use key-value databases for sub-millisecond reads.
- IoT telemetry writes millions of small records into wide-column stores optimized for append-heavy loads.
- Fraud and recommendation engines use graph databases to traverse relationships like friend-of-friend.
Best practices
- Model for your queries: shape documents around the reads you will issue, not the other way around.
- Keep aggregates denormalized but bounded — do not embed a thousand-element array in one document.
- Use secondary and compound indexes exactly as you would in SQL for hot filters.
- Choose write concerns and read preferences deliberately; 'majority' trades latency for safety.
- Cap embedded arrays and split hot aggregates so documents do not grow without bound.
Common mistakes
- Embedding data that changes in many places, then paying multi-document consistency costs on every update.
- Expecting multi-document transactions to be free — they work but are slower and best avoided on hot paths.
- Sharding with a low-cardinality or monotonically increasing shard key, creating hot spots on one node.
- Believing 'schema-less' means no schema discipline — you still need validation and evolution.
Common issues
BSONObj size: 16777228 ... must be between 0 and 16793600A document exceeds MongoDB's 16MB limit.
Split embedded arrays into a separate collection or store large blobs in an object store.
Cannot create index ... conflicts with shard keyThe index does not start with the shard key prefix.
Include the shard key as the leading field of the index.
WriteConcernError: 'majority' write concern not satisfiedThe replica set cannot meet the requested write concern.
Configure the write concern on the replica set, or lower it for this operation.
Debugging tips
- Run explain('executionStats') to see whether a query used an index or a collection scan.
- Check db.currentOp() for long-running or stuck operations.
- Watch mongostat or Atlas metrics for queueing, which points to hot shards or missing indexes.
- Inspect document sizes — the 16MB BSON limit and oversized payloads are common surprises.
Interview checkpoint
Hands-on lab
- Insert and query documents in MongoDB
- Model an order as an embedded aggregate
- Build an aggregation pipeline
- Create indexes and measure query performance
Modeling an E-Commerce Store
30 min · intermediate
Quick quiz
Which unit of data is the natural home for an order plus its line items in a document store?
Document databases provide no consistency guarantees at all.
Orders embed customer email and 20% of customers change their email monthly. What is the best design?
Cheat sheet
db.coll.insertOne(doc)Insert one documentdb.coll.find({ ... })Query documentsdb.coll.updateOne(f, { $set: ... })Atomic field updatedb.coll.deleteOne(f)Remove one documentdb.coll.aggregate([...])Pipeline aggregationsEmbedOne-to-few, read togetherReferenceOne-to-many, changes oftenDenormalizeRead-heavy, duplicates okShard keyHigh cardinality, even spreadWrite concern1, majority, or all nodes