NoSQL: documents and key-value
Lesson 5 of 5
5NoSQL: documents and key-value
The big pictureSQL splits data into tables and joins it at read time. A document store keeps the whole thing in one JSON-like document, so you read it in one go. Neither is better outright; each suits a shape of data.
What the picture shows: The four NoSQL families. Each is built for a particular access pattern, and documents are the most common in web apps.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
A SQL database is an archive with a customers ledger, an orders ledger and a products ledger linked by numbers. A document store is a folder per order holding everything: the customer's name, the items, the address. Opening one folder is faster, but if the customer changes their name you must edit every folder.
NoSQL usually gives up some ACID or JOINs in exchange for a flexible schema and horizontal scaling (sharding). MongoDB stores BSON, supports secondary indexes, an aggregation pipeline and multi-document transactions since version 4. The central decision: embed what is read together and stays bounded, reference what grows without limit or is shared. Redis is an in-memory key-value store with structures (hash, list, sorted set) and TTL expiry, used for caching, sessions and queues. The CAP theorem reminds you that a network partition forces a choice between consistency and availability.
Before You Start
🗄️SQL
The SQL and relationships lessons in this track.
🧾JSON
You know nested dictionaries and lists.
The Core Idea
1The four families
Key-value (Redis): the fastest thing when you know the key. Documents (MongoDB): flexible JSON objects. Wide-column (Cassandra): huge distributed writes like logs. Graph (Neo4j): branching relationships like "friends of my friends".
2Design the document around reads
Ask: what does the page show in one go? The order page shows items and address together, so we embed them in the order document. The customer is shared by many orders, so we reference them by id. Here it is simulated with Python dictionaries:
customers = {"c1": {"_id": "c1", "name": "Sara", "city": "Riyadh"}}
orders = [
{"_id": "o1", "customer_id": "c1", "status": "paid",
"items": [{"name": "Latte", "qty": 2, "price": 14},
{"name": "Cookie", "qty": 1, "price": 6}]},
{"_id": "o2", "customer_id": "c1", "status": "new",
"items": [{"name": "Mocha", "qty": 1, "price": 16}]},
]
def find(collection, **query):
return [doc for doc in collection if all(doc.get(k) == v for k, v in query.items())]
for order in find(orders, customer_id="c1"):
total = sum(i["qty"] * i["price"] for i in order["items"])
print(order["_id"], order["status"], total, customers[order["customer_id"]]["name"])3MongoDB by example
With PyMongo: insert_one, find with a filter, update_one with operators like $set and $inc, and an index on fields you search often.
from pymongo import MongoClient
db = MongoClient("mongodb://localhost:27017")["cafe"]
db.orders.insert_one({"customer_id": "c1", "status": "new",
"items": [{"name": "Latte", "qty": 2, "price": 14}]})
for order in db.orders.find({"status": "new"}, {"items": 1}):
print(order)
db.orders.update_one({"customer_id": "c1", "status": "new"},
{"$set": {"status": "paid"}})
db.orders.create_index([("customer_id", 1), ("status", 1)])4Redis for caching
The cache-aside pattern: read from Redis, and on a miss read from the database and store the result with an expiry. Here it is simulated with a dict and an expiry time:
import time
cache = {}
db_reads = 0
def slow_menu():
global db_reads
db_reads += 1
return ["Espresso", "Latte"]
def get_menu(ttl=60):
hit = cache.get("menu")
if hit and hit[1] > time.time():
return hit[0]
value = slow_menu()
cache["menu"] = (value, time.time() + ttl)
return value
for _ in range(5):
get_menu()
print("database reads:", db_reads)r.set("menu", data, ex=60) and r.get("menu"). Remember to delete the key when the menu changes.Worked example
Problem: an app shows a user profile with the last 5 orders and preferences whose fields vary, and the profile page is slow because of 4 JOINs. Analysis: they are always read together, and the preferences have no fixed shape. Fix: a user document embedding the preferences, and a separate orders collection with an index on (user_id, created_at). Two fast reads instead of four joins, and orders do not bloat the user document.
Myth vs Reality
| Myth | Reality |
|---|---|
| NoSQL is always faster than SQL. | Faster for the access pattern it was designed for, and possibly much slower for a query you did not foresee. |
| NoSQL means no schema. | The schema lives in your code instead of the database. Without discipline, data arrives in clashing shapes. |
Where you meet it at work
🛒Product catalogues
Products with different attributes (size, colour, capacity) in flexible documents.
⚡Caching and sessions
Redis in front of the database takes the load off.
📊Logs and events
Huge continuous writes in wide-column stores.
Test your understanding
When do you embed data in a document?
What is read together and stays bounded is embedded; what is shared or grows is referenced.
Which store suits caching with an expiry?
Redis is in memory and supports a TTL per key.
A bank system with transfers between many accounts. First choice?
Relations and strict transactions are SQL's strong point.
Quick review cards
Check Your Understanding
Why not embed all comments in the post?
Because they grow without limit, nearing the document size cap and making every update heavy.
What do you do when cached data changes?
Delete or update the key right away, and rely on the TTL as a safety net.
When do you stay with SQL?
When the data is heavily relational, queries are varied and unforeseen, and transactions matter.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Done with this lesson?
Sign in to save your progress.