Topic 10

NoSQL Databases

Not every problem fits neatly into tables and rows. Sometimes your data looks more like a JSON blob, a social graph, or a time-series of sensor readings. NoSQL databases are purpose-built for these shapes. This topic explains what they trade away from relational databases, and what they gain in return.

MongoDBCassandraRedis DynamoDBCouchbaseNeo4j

At a Glance

Core Concepts

CAP Theorem

Imagine a database spread across two data centers. The network cable between them gets cut. Now what? If you want both sides to agree on the latest data (Consistency), you have to stop accepting new requests until the connection is restored. If you want both sides to keep serving requests (Availability), you accept that they might disagree on some values. You cannot have both at once during a network split. This trade-off is the CAP theorem: when the network fails, choose between Consistency and Availability. Partition Tolerance (staying up despite the split) is non-negotiable for any distributed system.

Document Store

Think of a document store like a folder of JSON files. Each document can have a different structure — one user might have three email addresses, another might have none. There is no rigid schema forcing every row to look identical. Related data is usually embedded directly inside the document rather than spread across separate tables. This makes reads fast (one fetch gets everything) but makes queries across documents more limited. Used by MongoDB, Couchbase, and Firestore.

Wide-Column Store

A wide-column store is like a spreadsheet where different rows can have completely different columns. Data is organized by a row key, and within each row, you can store as many or as few named columns as you want. Columns that belong together are grouped into column families and stored physically close on disk, making it efficient to read just a specific set of columns across millions of rows. This design is a natural fit for time-series data: each device ID is the row key, and each timestamp becomes a column. Used by Cassandra, HBase, and Google Bigtable.

Eventual Consistency

When you update a row in a database that has copies in multiple data centers, those copies do not all update at the exact same instant. For a brief window, different data centers may return different values for the same piece of data. Eventually, all copies will converge to the same value. This is called eventual consistency. It lets the database remain fast and available even across geographic distance. The tradeoff: if you read immediately after a write, you might get the old value. For things like social media likes or view counts, this is fine. For a bank balance, it is not.

How It Works

CAP Theorem Trade-offs

C

Consistency

Every read returns the most recent write. To achieve this, the database must confirm that all replicas have the update before responding. This means waiting for network round trips, which adds latency. If the network is broken, the database may have to refuse requests entirely rather than risk returning stale data. Examples: PostgreSQL running on a single node, ZooKeeper, HBase.

A

Availability

Every request gets a response, even during failures. The database never says "I am unavailable." The tradeoff is that the response might not reflect the very latest write — a replica that has not yet received the update might serve the old value. The system stays up and responsive, but correctness is slightly relaxed. Examples: CouchDB, Cassandra (at low consistency settings), DynamoDB.

P

Partition Tolerance

Network partitions — where some servers can no longer communicate with others — are inevitable in any real distributed system. Partition tolerance means the system keeps working despite this. Because partitions cannot be prevented, every distributed database must be partition tolerant. The real choice, then, is not C vs A vs P — it is: when a partition happens, which do you sacrifice: Consistency or Availability?

+

PACELC Extension

CAP only describes behavior during a network failure. The PACELC model adds what happens during normal operation: even when there is no partition, you still choose between Latency (respond fast, possibly stale) and Consistency (respond correctly, but slower). DynamoDB optimizes for low latency in normal operation. Google Spanner optimizes for consistency in all cases. This extended view is more useful for everyday database selection than CAP alone.

TypeExamplesData ModelBest ForWeak At
DocumentMongoDB, CouchbaseJSON/BSON docsFlexible schemas, nested dataMulti-doc transactions, complex JOINs
Key-ValueRedis, DynamoDBKey → blobCaching, sessions, leaderboardsQuerying by value fields
Wide-ColumnCassandra, HBaseRow key + dynamic colsTime-series, write-heavy IoTComplex queries, JOINs
GraphNeo4j, TigerGraphNodes + edgesSocial graphs, recommendationsTabular analytics
SearchElasticsearchInverted indexFull-text search, logs, metricsTransactional writes
Redis Data Structures

Redis Beyond Simple Caching

Redis is not just a key-value cache. It is a data structure server. The choice of data structure determines what operations are O(1) vs O(n) and what problems each one solves efficiently.

TypeCommandsO(n) operationsUse Case
StringGET, SET, INCR, APPENDString ops on large valuesCache, counters, distributed locks (SET NX EX)
ListLPUSH, RPOP, LRANGELINDEX, LINSERTMessage queues, activity feeds, job queues
HashHGET, HSET, HMGETHGETALL on large hashesObject storage (user profile), session data
SetSADD, SISMEMBER, SUNIONSMEMBERS on large setsUnique visitors, tag systems, friend lists
Sorted SetZADD, ZRANGE, ZRANKZRANGEBYLEX on large setsLeaderboards, rate limiters, time-series indexes
StreamXADD, XREAD, XGROUPFull stream scanEvent sourcing, CDC, Kafka-like message log
BitmapSETBIT, GETBIT, BITCOUNTBITPOS on large bitmapsDaily active users (1 bit/user/day = 125MB for 1B users)
HyperLogLogPFADD, PFCOUNTN/A (O(1) always)Approximate unique count with fixed 12KB memory
Cassandra Data Modeling

Partition Key and Clustering Key

In Cassandra, data modeling is driven by access patterns — not by normalization rules. The partition key determines which node holds the data. The clustering key determines sort order within a partition. Getting this wrong means scatter-gather queries or hot partitions.

1

Partition key

In Cassandra, the partition key decides which physical server stores your data. Think of it as the address on a letter: all rows with the same partition key land on the same machine and its replicas. A query that includes the partition key goes straight to that one machine — fast and efficient. A query without it must ask every machine in the cluster — slow and expensive. Good partition keys have many distinct values that are spread evenly: user_id, device_id, or tenant_id. Bad choices are things like status (only a handful of values) or date alone (all writes for today pile onto one server).

2

Clustering key

Once the partition key routes you to the right server, the clustering key determines the sort order of rows within that partition. This is what makes range queries fast inside a partition. For example, if your partition key is user_id and your clustering key is event_time, then WHERE user_id = 42 AND event_time > '2024-01-01' goes straight to user 42's partition and scans forward in time order — no random searching needed. You can also use a compound partition key like (user_id, month) to prevent any single partition from growing too large over time.

3

Denormalization is required

Cassandra has no JOINs. If you need to look up the same data two different ways — for example, "get orders by user" AND "get orders by status" — you must store it in two separate tables, each optimized for one access pattern. Your application writes to both tables whenever data changes. This feels redundant compared to relational design, but it is the deliberate trade-off: Cassandra gives up flexibility in querying in exchange for extremely fast, predictable writes and reads at any scale.

4

Tunable consistency

Cassandra lets you dial in how strict you want consistency to be on each operation. CONSISTENCY ONE returns as soon as the first replica responds — fastest, but possibly stale. CONSISTENCY ALL waits for every replica to confirm — slowest, but always up to date. CONSISTENCY QUORUM is the sweet spot: it waits for a majority (more than half) of replicas. If you use QUORUM for both writes and reads, you are guaranteed to see the latest write, because the majority that served the read will always overlap with the majority that acknowledged the write. This is called strong consistency with RF=3 and quorum.

Graph Databases

When Native Graphs Win

Relational databases represent relationships with foreign keys and JOINs. For shallow relationships (1–2 hops), JOINs are fast. For deep or variable-depth relationships (friendship networks, dependency graphs, recommendation paths), JOIN count grows exponentially — graph databases win decisively.

1

Native graph storage (Neo4j)

In Neo4j, relationships between nodes are stored as physical pointers — following a relationship from one node to another is a direct memory jump, not a table scan. Compare this to a relational database: finding "friends of friends" requires two JOINs, each scanning rows to find matches. At five hops deep, the relational JOIN grows enormous. In a graph database, each hop is constant-time regardless of how large the overall graph is. This is why graph databases dominate for social networks, recommendation engines, and fraud detection — these problems are all about following chains of relationships.

2

Cypher query language (Neo4j)

Graph databases have their own query language. Neo4j uses Cypher, which is designed to look like the graph you are drawing. The pattern (Alice)-[:KNOWS]->(Bob) reads almost literally: "Alice knows Bob." To find everyone within 3 hops of Alice: MATCH (user:Person {name: 'Alice'})-[:KNOWS*1..3]-(friend:Person) RETURN DISTINCT friend.name. The *1..3 means "follow 1 to 3 KNOWS relationships." Writing this same query in SQL would require three separate self-joins and becomes difficult to read and slow at scale.

3

When NOT to use a graph DB

Graph databases have clear weaknesses. Aggregating data across all nodes — like "count all users by country" — is not what they are built for; a relational or columnar database does this better. Write throughput is lower than what Cassandra or Kafka can handle. If your main queries are simple lookups like "get user by ID," the overhead of a graph database adds no value. Use a graph database when the core of your problem is navigating multi-step relationships, and the relationships themselves carry data (like the strength of a connection or the distance of a route).

Interactive Demo

Document vs Column Store Comparison

Compare how a query for a specific column is handled differently in a document store (reads entire document) vs a column store (reads only target column).

Consistency all nodes see same data Availability always responds Partition Tolerance survives net split CA systems CP systems AP systems Cassandra HBase PostgreSQL DynamoDB Pick any two during a network partition

CAP theorem says a distributed system can guarantee only two of three properties during a network partition. In practice, partition tolerance is non-negotiable at scale, so the real choice is CP (strong consistency, possible unavailability) vs. AP (always available, eventual consistency).

Document vs Column Store Access Patterns

Bytes-read bar shows the dramatic I/O savings of column stores for selective column queries.
Anti-patterns

Using NoSQL just because it's "web scale"

NoSQL doesn't automatically scale better than SQL. PostgreSQL handles millions of rows trivially. Choose NoSQL when you have a specific use case (flexible schema, write-heavy, extreme scale) not based on hype.

Ignoring consistency requirements for "eventual consistency"

Eventual consistency means reads CAN return stale data. For financial transactions, inventory counts, or anything requiring correctness, eventual consistency is not acceptable without additional application-level conflict resolution.

Modeling NoSQL data like a relational schema

Normalizing documents into separate collections and joining them in application code undoes NoSQL's benefits. Design around access patterns: embed data that's always read together. Reference (FK-style) only when data is large and rarely accessed together.

Quiz
Question 1 of 3
The CAP theorem states a distributed system can guarantee at most:
AAll 3: Consistency, Availability, Partition tolerance
B2 of Consistency, Availability, Partition Tolerance
C2 of the 4 ACID properties
DConsistency only — availability and partition tolerance are impossible
Question 2 of 3
MongoDB stores data as:
ARows and columns like a relational table
BSimple key-value pairs only
CBSON (Binary JSON) documents in collections
DColumn families like Cassandra
Question 3 of 5
For time-series data with extremely high write throughput, the best NoSQL type is:
ADocument store (MongoDB)
BGraph database (Neo4j)
CKey-value store (Redis)
DWide-column store (Cassandra, HBase)
Question 4 of 5
In Cassandra, the partition key determines:
AThe sort order of rows within a table
BWhich node(s) store the data — all rows with the same partition key are co-located
CHow many replicas are created for each row
DThe consistency level for reads and writes
Question 5 of 5
Redis HyperLogLog is useful for:
AExact unique user counting with zero error
BApproximate unique count with ~0.81% error using only 12KB of memory
CStoring all unique user IDs for later retrieval
DImplementing a leaderboard with rankings
Interview Q&A
When would you choose NoSQL over a relational database?
Choose NoSQL when: (1) Schema flexibilitydata structure varies per record (e.g., product catalog with different attributes per product type). (2) Extreme write throughputCassandra/HBase handle millions of writes/sec. (3) Horizontal scale-outdata too large for a single machine and JOINs aren't needed. (4) Specific access patternsgraph traversal (Neo4j), full-text search (Elasticsearch), sub-millisecond cache (Redis). (5) Operational simplicityschema-less development in early-stage products. Stick with SQL when: ACID transactions required, complex queries with JOINs, strong data integrity, team SQL expertise.
What is eventual consistency and what are its implications?
Eventual consistency means that given no new writes, all replicas will converge to the same value — eventually. In the window before convergence, different nodes may return different values for the same key. Implications: (1) Don't use for money transfers, inventory counts, or any scenario where stale reads cause business errors. (2) Design around it: use append-only event logs, CRDTs (Conflict-free Replicated Data Types), or vector clocks for conflict resolution. (3) In Cassandra: CONSISTENCY QUORUM (read+write quorum) gives you strong consistency at the cost of availability. CONSISTENCY ONE is eventual. (4) The "eventual" window can be milliseconds (same DC) or seconds (cross-region). Measure it for your workload.
How does MongoDB's document model differ from a relational schema design-wise?
Relational: normalize data — one fact in one place, JOINs to reconstruct. MongoDB: design around access patterns — embed data that's read together, reference data that's large or shared. Example: a blog post with comments. Relational: posts table + comments table, JOIN on query. MongoDB: embed comments array inside the post document (if always read together). Trade-offs of embedding: single document read (fast, atomic), but document grows unboundedly and you can't easily query comments across posts. Trade-offs of referencing: JOINs via $lookup (2 queries), but modular and supports independent comment queries. The right choice depends on your dominant read pattern.
Explain the BASE model and how it contrasts with ACID.
BASE (coined for NoSQL systems): Basically Available — system is available even during partial failures. Soft state — state may change over time even without input (replicas catching up). Eventually consistent — all replicas converge given no new writes. ACID vs BASE: ACID is pessimistic (prevent inconsistency) and offers strong guarantees at the cost of availability and throughput. BASE is optimistic (tolerate inconsistency temporarily) and offers high availability and throughput at the cost of immediate consistency. Modern NewSQL databases (Spanner, CockroachDB) provide ACID + horizontal scale, blurring the boundary.
How does DynamoDB handle high availability and consistency?
DynamoDB replicates data across 3 AZs in a region. By default it offers eventually consistent reads — might return stale data from a replica not yet synchronized; fastest and cheapest. Strongly consistent reads route to the primary replica and always return the latest committed data — costs 2x read capacity units. DynamoDB is classified as AP in CAP. For global tables (multi-region), it uses last-write-wins conflict resolution. Transactions: DynamoDB supports ACID transactions across multiple items via TransactWriteItems / TransactGetItems — serializable but limited to 25 items and single-region. Pricing is capacity-unit based — design access patterns to be efficient. Single-table design patterns use composite keys and GSIs (Global Secondary Indexes) to support multiple access patterns without JOINs.
Explain Redis data structures and which to use for a leaderboard.
Redis Sorted Sets are the native leaderboard data structure. ZADD leaderboard 15230 "alice" adds alice with score 15230. ZREVRANK leaderboard "alice" returns alice's rank (0-indexed from highest). ZREVRANGE leaderboard 0 9 WITHSCORES returns the top 10 with scores. Score updates: ZINCRBY leaderboard 500 "alice" atomically adds 500 to alice's score. Why not a SQL table with an index? SQL requires a query + sort + limit — works fine for small leaderboards. For leaderboards with millions of entries updated thousands of times per second, Redis Sorted Set's O(log n) write and O(log n + k) range read are ideal. Combine with Redis HyperLogLog for "unique players today" metrics and a Hash for player metadata (name, avatar URL) — three data structures, three problems solved.
What are CRDTs and when are they used in distributed databases?
CRDTs (Conflict-free Replicated Data Types) are data structures mathematically designed so that any concurrent updates can always be merged without conflict. The merge operation must be commutative (A merge B = B merge A), associative ((A merge B) merge C = A merge (B merge C)), and idempotent (A merge A = A). Examples: G-Counter (grow-only counter — only increments, each replica maintains its own counter, total is sum); 2P-Set (two-phase set — add and remove sets, a removed element can never be re-added); LWW-Register (Last Write Wins — each value tagged with timestamp); PN-Counter (positive-negative counter — supports decrement via two G-Counters). Used by: Riak (peer-to-peer KV), Redis (CRDT-based counters), collaborative editing tools (Google Docs uses operational transforms, a related concept), Dynamo-style databases. CRDTs are suitable when you need eventual consistency without conflict resolution logic and the data structure semantics match the CRDT constraints.
When should you use MongoDB versus PostgreSQL?
Use MongoDB when: (1) Data is genuinely document-shaped and varies between records (e-commerce product catalog where electronics have different attributes from clothing). (2) You need flexible schema evolution without ALTER TABLE migrations. (3) Data is always accessed as a whole document (embedded comments, blog posts with metadata). (4) Team prefers JSON-native APIs and doesn't need complex SQL analytics. Use PostgreSQL when: (1) Data has regular structure and strong relationships (users, orders, products with FK constraints). (2) ACID transactions across multiple tables required. (3) Complex analytical queries with JOINs, window functions, CTEs. (4) Schema validation and constraint enforcement needed. (5) Full MVCC and point-in-time recovery needed. Note: PostgreSQL with JSONB columns can serve many document store use cases while retaining full SQL power — a common "best of both" choice.
Further Reading
Previous
Storage Engines