NoSQL Databases: Key-Value, Document, Wide-Column and Graph
Why NoSQL exists (schema rigidity, horizontal scaling), the four families with examples, when to use NoSQL and what it gives up compared with SQL.
Relational databases are excellent at structured data with complex relationships, but they struggle with two modern problems: schemas that keep changing and scaling writes across many machines. NoSQL ("Not Only SQL") databases give up some SQL guarantees to solve those two problems. The term covers four quite different families (key-value, document, wide-column and graph), each built around one data model and a few access patterns it serves extremely well. Knowing which family fits which problem matters more than knowing any one product.
Problem 1: schema rigidity
A social media startup keeps user profiles in PostgreSQL with a clean schema:
CREATE TABLE users (
user_id int PRIMARY KEY,
name varchar(255),
email varchar(255),
bio text
);Three months later the product team wants verified badges: add a column. Six months in, premium users get custom themes: another column. A year in, influencers need follower counts, engagement rates and top hashtags. Every feature means a migration on a 10-million-row table, each migration locks the table, and deployments take hours. A competitor on MongoDB adds fields instantly, with no migration and no downtime, and ships weekly while the SQL team ships monthly.
Rich nested data shows the same strain. An Instagram post has an image, caption, hashtags, location, timestamp and comments, and comments have authors and nested replies. In relational tables, reading one post joins Posts → Comments → Users → Locations, five tables for one screen. When the product adds "story reactions" one week and "polls" the next, every change needs coordinated migrations. A document database stores the whole post (image, caption, comments array, location object) as one document: no joins, and new fields need no migration.
{
"post_id": "p_9001",
"author": { "user_id": 42, "name": "Alice" },
"image_url": "https://cdn.example.com/p_9001.jpg",
"caption": "Sunset hike",
"hashtags": ["hiking", "sunset"],
"location": { "name": "Twin Peaks", "lat": 37.75, "lng": -122.45 },
"created_at": "2026-03-14T18:22:00Z",
"comments": [
{ "author": "bob", "text": "Wow!", "replies": [ { "author": "alice", "text": "Thanks!" } ] }
],
"poll": { "question": "Next trail?", "options": ["Mt Tam", "Big Sur"] }
}Problem 2: horizontal scaling
An e-commerce site keeps its product catalog in MySQL. Traffic grows until the database hits CPU limits. Vertical scaling buys time: 8 cores, then 16, 32, 64. Then hardware limits appear, and a 128-core server costs far more than two 64-core servers. Sharding a relational database means choosing partition keys by hand and routing in the application; joins across shards get expensive and foreign keys stop working. The team spends months on sharding logic instead of features. See scaling databases.
How NoSQL databases work
NoSQL databases store data without a fixed schema: different records can have different fields, and adding a field needs no migration. They also partition data automatically across nodes. Because a distributed system must keep working through network partitions, many NoSQL stores favor availability and offer eventual or tunable consistency rather than strong consistency by default: writes reach every replica within seconds, and strong consistency, where offered, costs some availability and latency. See CAP theorem.
Schema flexibility
Two user profiles can live side by side in the same collection, one with a badge and one without. The database accepts both; application code handles missing fields.
{ "user_id": 123, "name": "Alice", "email": "alice@example.com", "bio": "Software engineer" }
{ "user_id": 456, "name": "Bob", "email": "bob@example.com", "verified": true, "badge_color": "gold" }Automatic partitioning and replication
DynamoDB spreads items across servers by partition key; add capacity and it rebalances data automatically. Cassandra replicates each partition to several nodes with configurable consistency: for example, write to three replicas for durability and read from one for speed. The system stays available when individual nodes fail. See consistent hashing and quorums.
The four NoSQL families
Each family optimizes for a different access pattern. Choose by access pattern, not just by the shape of the data.
Key-value stores
The simplest model: a giant hash map from a key to a value. Operations are GET, PUT and DELETE by key, O(1) and easy to partition by hashing the key, but there are no queries across values. Full detail in key-value databases.
session:abc123 → { "user_id": 456, "login_time": "2026-03-14T09:12:00Z", "exp": 1773480720 }
cart:u456 → ["sku_1", "sku_9"]
rate:ip:1.2.3.4 → 27- Examples: Redis, Memcached (in-memory), DynamoDB, Riak, etcd (small, strongly consistent).
- Great for: caches, sessions, feature flags, rate-limit counters, shopping carts, idempotency keys. Redis keeps session data for logged-in users; DynamoDB handles high-throughput lookups with predictable performance.
- Limits: no queries by value; anything other than lookup by key needs a separate index you maintain.
Document stores
Values are structured JSON-like (JSON/BSON) documents that the database understands, so you can index and query fields inside them. A document is self-contained: an entity with its nested fields and arrays, matching how application code models objects. Full detail in document databases.
{ "_id": "prod_812", "name": "Trail running shoe", "brand": "Stride",
"variants": [ { "size": 9, "color": "blue", "stock": 14 } ],
"attributes": { "drop_mm": 6, "waterproof": false } }- Examples: MongoDB (product catalogs), CouchDB (content management), Couchbase, Firestore, Azure Cosmos DB, PostgreSQL
jsonb. - Great for: catalogs with varying attributes, user profiles, content management, user-generated content, data read as a whole object.
- Limits: joins across documents are limited or slow; data shared by many documents gets duplicated.
Wide-column (column-family) stores
Data lives in tables, but each row is identified by a partition key (which node stores it) and a clustering key (sort order within the partition). Rows in a partition are stored together, sorted, so range scans within a partition are fast, and data is organized by column families so reading specific columns is efficient while full-row scans stay expensive. They're built on LSM trees for very high write throughput.
-- Cassandra CQL: one table per query
CREATE TABLE messages (
conversation_id text,
sent_at timestamp,
message_id uuid,
author_id text,
body text,
PRIMARY KEY ((conversation_id), sent_at, message_id) -- partition key, then clustering keys
) WITH CLUSTERING ORDER BY (sent_at DESC, message_id DESC);
SELECT * FROM messages WHERE conversation_id = 'c42' LIMIT 50;- Examples: Apache Cassandra (time-series, messaging), HBase (analytics workloads), ScyllaDB, Google Bigtable.
- Great for: time-series, messaging, activity feeds, logs, IoT and event data: huge write volumes read by key and time range.
- Limits: you design one table per query; no joins; secondary indexes are weak. Picking the partition key is everything (see sharding).
Graph databases
Data is nodes and edges with properties, and relationships are first-class. Queries traverse relationships ("friends of friends who like this band"), which is fast because the database follows pointers directly. In a relational database each hop is a join, which gets expensive at several hops. Aggregations over the whole dataset remain slow.
// Cypher (Neo4j): friends of friends Alice doesn't follow yet
MATCH (a:User {name: 'Alice'})-[:FOLLOWS]->(:User)-[:FOLLOWS]->(fof:User)
WHERE NOT (a)-[:FOLLOWS]->(fof) AND fof <> a
RETURN fof.name, count(*) AS mutuals ORDER BY mutuals DESC LIMIT 10;- Examples: Neo4j (social networks), ArangoDB (recommendation engines), Amazon Neptune, JanusGraph, TigerGraph.
- Great for: social networks, recommendations, fraud rings (accounts sharing devices or cards), knowledge graphs, network topology.
- Limits: harder to partition across machines; not ideal for bulk aggregation over all data.
When to use NoSQL
- Unpredictable schemas: when features evolve fast and profile fields change weekly, document and wide-column stores absorb new fields without locking migrations on a large table.
- High write throughput: a single database can't take millions of writes per second. Discord stores trillions of messages; Cassandra-style stores partition writes across hundreds of nodes.
- Simple access patterns: shopping carts are key-value lookups by session ID, and session data needs no joins or transactions. Redis serves these from memory, so end-to-end latency is typically sub-millisecond (in-memory access takes microseconds; the network round trip is the rest).
- Global distribution: social feeds tolerate seconds of staleness; nobody notices if a like count updates slightly late. DynamoDB global tables replicate across regions with configurable consistency. See geo-replication.
- Large unstructured datasets: log aggregation can ingest gigabytes per second; Elasticsearch indexes and searches text without a fixed schema, where SQL full-text search becomes impractical. See full-text search.
What NoSQL gives up
- Eventual consistency complicates application logic. Two users editing the same document may see different versions, and the application must resolve conflicts. Amazon's Dynamo shopping cart merged conflicting versions by keeping every item ever added, so deleted items occasionally reappeared. See conflict resolution with CRDTs.
- No ACID transactions across records (or limited ones). A money transfer becomes two separate updates; if the system fails between them, money disappears or is duplicated. Applications fall back to sagas or accept some inconsistency.
- Limited queries mean denormalization. Without joins, data is duplicated to avoid them: a user's name is copied into each of their posts, and a name change means updating thousands of posts. See normalization vs denormalization.
- Operational complexity. SQL has mature tooling for backup, monitoring and debugging. NoSQL stores need new tools and skills: Cassandra's eventual consistency makes debugging harder, and DynamoDB's pricing model needs careful capacity planning.
Common implementations
| Product | Family | Known for | Use it for |
|---|---|---|---|
| MongoDB | Document | The most popular document database; rich queries with filters, aggregations and text search | Product catalogs, user-generated content, documents with varying schemas |
| Redis | Key-value (in-memory) | Sub-millisecond latency; lists, sets, sorted sets beyond plain values. See Redis | Caching, real-time leaderboards, rate limiting, sessions |
| Apache Cassandra | Wide-column | Distributed with no single point of failure; millions of writes per second across nodes | Time-series data, event logging, high-write applications |
| Amazon DynamoDB | Key-value and document (managed) | Auto-scaling, single-digit millisecond latency at any scale | Serverless apps, unpredictable traffic |
| Neo4j | Graph | Native graph storage and the Cypher query language | Social graphs, recommendations, fraud detection |
Choosing between them
| Need | Family | Example product |
|---|---|---|
| Sub-millisecond lookup by key, caching, counters | Key-value (in-memory) | Redis |
| Durable key-value at any scale, managed | Key-value / wide-column | DynamoDB |
| Rich objects with varying fields, queried by field | Document | MongoDB |
| Massive writes, read by key and time range | Wide-column | Cassandra, Bigtable |
| Multi-hop relationship queries | Graph | Neo4j, Neptune |
| Full-text search and relevance | Search engine | Elasticsearch. See full-text search |
| Metrics over time with downsampling | Time-series | Prometheus, InfluxDB. See time-series databases |
Need millions of writes per second? Cassandra. Schema changes deploying daily? MongoDB. Only simple key lookups? Redis. Otherwise a relational database is usually the simpler start; see SQL vs NoSQL.
Where it’s used
Stored trillions of messages in Cassandra (later ScyllaDB), partitioned by channel and time bucket, for fast writes and recent-history reads.
Use Redis and Memcached heavily as caches and for counters, timelines and sessions in front of durable stores.
Document stores hold listings whose attributes differ by category.
Graph analysis links accounts, devices and cards to spot fraud rings that row-by-row checks miss.
In an interview
- Name the family before the product, and tie it to the access pattern.
- For wide-column stores, state the partition key and clustering key for each query.
- Say what you give up (cross-record transactions, joins, sometimes strong consistency) and how the design copes.
- Use Redis as a cache or for counters, not as the only copy of important data unless persistence is configured and justified.
- Reach for a graph database only when multi-hop traversals are core to the product.
Further reading
- How Discord stores trillions of messagesDiscord
- Dynamo: Amazon's Highly Available Key-value StoreAll Things Distributed