Course: System Design — Zero to Hero
Level: Beginner → Advanced
Phase: Database Fundamentals
Status: Complete
In the previous chapters, we studied database fundamentals and SQL databases in depth.
Now we move into NoSQL databases.
You will learn:
The most important lesson is:
NoSQL is not one database technology. It is a family of database models designed around different workloads and scalability requirements.
Historically, "NoSQL" was often interpreted as:
Not Only SQL.
Today, NoSQL generally refers to database systems that do not primarily use the traditional relational/table model.
NoSQL systems include several different families:
NoSQL
|
+-- Document
|
+-- Key-Value
|
+-- Wide-Column
|
+-- GraphThese systems can have very different:
Therefore:
Saying "we use NoSQL" is not enough information.
You should ask:
Which NoSQL data model and why?
Relational databases are extremely capable.
But some applications operate at very large scale or have workloads that are naturally represented differently.
Imagine:
Billions of records
Millions of requests/sec
Global traffic
Massive write volume
Flexible document structures
Distributed infrastructureA traditional relational design may become difficult or expensive to scale for certain workloads.
NoSQL systems were developed and adopted to address different classes of these problems.
Typical goals can include:
Important:
This does not mean NoSQL is universally more scalable than SQL.
Modern relational databases can also scale extremely far.
The correct choice depends on workload and requirements.
A useful simplified distinction is:
Start with:
Entities
Relationships
Constraints
Transactions
QueriesThen model data into tables.
Often start with:
Access patterns
Partitioning
Distribution
Latency
AvailabilityThen design the stored representation around those requirements.
This does not mean every NoSQL database must be designed this way, but it is an important system-design mindset.
NoSQL
|
+------------+------------+------------+
| | | |
Document Key-Value Wide-Column GraphExamples:
Document:
MongoDB
Key-Value:
Redis
Amazon DynamoDB
Wide-Column:
Apache Cassandra
ScyllaDB
Graph:
Neo4jSome products support capabilities that overlap categories, so the classification is based on the dominant data model.
A document database stores data as documents.
A document is often JSON-like.
Example:
{
"id": "user_123",
"name": "Anubhav",
"email": "anubhav@example.com",
"skills": [
"JavaScript",
"Node.js",
"System Design"
],
"address": {
"city": "Delhi",
"country": "India"
}
}The document can contain nested structures.
This can map naturally to application objects.
Suppose a user profile naturally looks like:
User
|
+-- name
+-- email
+-- skills[]
+-- address
+-- city
+-- countryA document database can store this as one document.
Instead of:
users
addresses
user_skillswe may have:
users
|
+-- embedded address
+-- embedded skillsThis can simplify reads.
Embedding means storing related information inside the same document.
Example:
{
"user_id": 123,
"name": "Anubhav",
"addresses": [
{
"type": "home",
"city": "Delhi"
},
{
"type": "work",
"city": "Gurugram"
}
]
}Advantages:
Potential disadvantages:
Instead of embedding:
{
"user_id": 123,
"address_id": 456
}The application can store related data separately.
Conceptually:
User
|
+---- AddressThis is useful when the related entity:
| Requirement | Often consider |
|---|---|
| Related data is small | Embedding |
| Related data is bounded | Embedding |
| Data is always read together | Embedding |
| Data changes independently | Referencing |
| Data is shared by many entities | Referencing |
| Related data is very large | Referencing |
| Independent lifecycle | Referencing |
This is not an absolute rule.
The access pattern should drive the decision.
Suppose we have an e-commerce product:
{
"product_id": "p100",
"name": "Laptop",
"price": 75000,
"brand": "Example",
"specifications": {
"ram": "16GB",
"storage": "1TB"
}
}Different products may have different attributes.
For example:
{
"product_id": "p200",
"name": "Phone",
"price": 50000,
"camera": {
"rear": "50MP",
"front": "12MP"
}
}A document model can represent these varying attributes naturally.
A common misconception:
NoSQL databases have no schema.
Incorrect.
Every production application still has a data model.
The difference is that some NoSQL systems allow more flexible or application-managed schema evolution.
For example:
Application v1
|
+-- name
+-- email
Application v2
|
+-- name
+-- email
+-- phoneThe application may need to support both document shapes during migration.
So:
Schema flexibility shifts some responsibility from the database to the application.
The simplest model is:
Key -> ValueExample:
user:123 -> {...}
session:abc -> {...}
cart:789 -> {...}The application knows the key.
It retrieves the associated value.
A common access pattern is:
GET keyor:
SET key valueThis is simpler than arbitrary relational querying.
Conceptually:
Application
|
| key = user:123
v
Key-Value Store
|
v
ValueThis model is particularly useful for predictable point lookups.
Examples:
session:abc123
->
user/session dataproduct:123
->
cached productrate:user:123
->
countervideo:123:views
->
1250000cart:user:123
->
cart dataRedis is a popular in-memory data store.
It supports more than simple strings.
Common structures include:
Strings
Hashes
Lists
Sets
Sorted Sets
StreamsExample conceptual data:
user:123
->
{
name: "Anubhav",
age: 28
}Redis is frequently used for:
Its exact suitability depends on the durability and consistency requirements.
Redis is often used as:
Application
|
v
Redis
|
+---- hit
|
+---- miss
|
v
DatabaseThis is why Redis is often discussed as a caching technology even though it is technically a data store with richer capabilities.
Wide-column databases store data in a distributed structure optimized for large-scale workloads.
Examples:
Apache Cassandra
ScyllaDBThe important idea is:
Data is modeled around partition keys and query patterns.
These systems can be excellent for:
A simplified representation:
Partition Key
|
v
+-----------------------+
| Partition |
| |
| clustering columns |
| row |
| row |
| row |
+-----------------------+The partition key determines where data belongs in the distributed system.
This makes partition-key design extremely important.
One of the most important NoSQL concepts is:
Design the data model around the queries the application must perform.
Suppose the application needs:
Get messages for a conversation
ordered by timeInstead of first creating a generic normalized model, we ask:
What is the partition key?
What ordering is required?
What query must be fast?Possible conceptual model:
conversation_id
|
+-- message 1
+-- message 2
+-- message 3This is different from traditional relational modeling where normalization and relationships often come first.
Consider a chat application.
Required query:
Get latest 50 messages for conversation 123.A NoSQL-oriented design might use:
Partition key:
conversation_id
Sort/clustering key:
message_timestampThen:
conversation_id = 123
timestamp DESC
LIMIT 50can be efficient.
Suppose you design a NoSQL database without knowing the queries.
You may later discover:
Query A -> easy
Query B -> extremely expensive
Query C -> requires scanning
Query D -> impossible without another data modelNoSQL systems often encourage:
Start from access patterns.
Denormalization is common in distributed NoSQL systems.
Suppose we need:
Get user timelineInstead of dynamically joining:
users
posts
followswe may maintain a precomputed timeline:
timeline:user:123
|
+-- post 900
+-- post 870
+-- post 850This duplicates information.
But the read becomes much simpler.
Denormalization can provide:
Faster reads
Simpler query path
Predictable latencyBut it introduces:
More storage
More write complexity
Consistency challenges
Data duplication
Update propagationThis is one of the central NoSQL trade-offs.
Suppose one event must update:
User timeline
Follower timeline
Trending feed
Search index
Analytics storeOne logical action may produce many physical writes.
1 logical write
|
+----> store A
+----> store B
+----> store C
+----> store DThis is write amplification.
It can be acceptable if it produces scalable read performance.
Partitioning means dividing data into separate partitions.
Conceptually:
All Data
|
+---- Partition A
+---- Partition B
+---- Partition C
+---- Partition DThis allows different nodes to handle different portions of the data.
When partitions are distributed across multiple database nodes, we commonly use the term sharding.
Example:
Database
|
+------------+------------+
| | |
v v v
Shard 1 Shard 2 Shard 3Each shard owns a subset of the data.
Suppose one database server can process:
20,000 requests/secand the workload requires:
100,000 requests/secIf the workload can be partitioned effectively:
Shard 1 -> 20K
Shard 2 -> 20K
Shard 3 -> 20K
Shard 4 -> 20K
Shard 5 -> 20KThe theoretical aggregate capacity can increase.
Real systems have coordination, network, skew, replication, and operational overhead, so scaling is not perfectly linear.
The partition key determines where a record belongs.
Example:
user_idmight determine the partition.
Conceptually:
hash(user_id)
|
v
PartitionA good partition key distributes traffic and data sufficiently.
A good partition key often provides:
Many possible values.
Data is spread reasonably across partitions.
Requests should not disproportionately target one partition.
Common queries should be able to locate the relevant partition efficiently.
Suppose:
countryis the partition key.
If most users belong to a small number of countries:
India -> huge
USA -> huge
UK -> moderate
...some partitions may become much larger or hotter than others.
Suppose:
celebrity_id = 123receives millions of requests.
If all traffic maps to one partition:
Partition 7
|
+---- millions of requeststhe partition becomes a bottleneck.
This is a hot partition.
Possible strategies include:
For example:
celebrity:123:0
celebrity:123:1
celebrity:123:2
...Traffic can be spread across logical partitions.
But this makes reads more complex.
A common approach is:
partition = hash(key) % NExample:
hash(user_id) % 4maps users across:
0
1
2
3This can distribute data well if the keys are suitably distributed.
Another strategy is to divide data into ranges.
Example:
User IDs
0 - 999,999 -> Partition A
1M - 1,999,999 -> Partition B
2M - 2,999,999 -> Partition CRange partitioning can make range queries easier.
But it can create hotspots if new writes concentrate in one range.
| Feature | Hash | Range |
|---|---|---|
| Distribution | Often good | Can become uneven |
| Point lookups | Good | Good |
| Range queries | Less natural | Natural |
| Hotspots | Possible | Possible |
| Ordering | Not preserved | Preserved within ranges |
The correct strategy depends on workload.
Replication means storing copies of data on multiple nodes.
+--> Replica A
Primary ----+
+--> Replica BIn some NoSQL systems, replication is built into the distributed architecture rather than being a separate read-replica pattern.
Replication can improve:
But it also introduces:
One model:
Leader
|
+----> Follower
|
+----> FollowerWrites go to the leader.
Followers replicate data.
Reads may go to:
Leader
or
Followersdepending on consistency requirements.
Some distributed databases use leaderless approaches.
Conceptually:
Client
|
+----> Node A
+----> Node B
+----> Node CA write may be sent to multiple replicas.
Reads may also query multiple replicas.
This leads to quorum concepts.
Suppose:
N = 3 replicasA system might require:
W = 2 replicas for write
R = 2 replicas for readSince:
W + R > Nthe read and write sets overlap.
Conceptually:
3 replicas
A B C
Write -> A B
Read -> B C
Overlap -> BThis can help a read observe a recent write under appropriate system assumptions.
But:
Quorum mathematics alone does not fully describe consistency.
Real systems have additional details around failures, conflict resolution, repair, timestamps, and read/write semantics.
Eventual consistency means that replicas may temporarily disagree, but if updates stop and the system remains healthy, replicas are expected to converge.
Example:
Write:
User name = Anubhav
Replica A -> Anubhav
Replica B -> Anubhav
Replica C -> old valueFor a short period, different reads may return different values.
Later:
A -> Anubhav
B -> Anubhav
C -> AnubhavThis is eventual convergence.
Think of a spectrum:
Stronger consistency
|
v
More coordination
More latency in some designs
Potentially less availability during some failures
|
v
Weaker consistency
|
v
More asynchronous behavior
Potentially lower latency
Potentially stale readsThe correct point depends on business requirements.
Examples:
Social media likes
View counts
Recommendations
Search indexes
Activity feeds
AnalyticsA small delay may be acceptable.
Examples:
Financial balances
Payment state
Inventory constraints
Security permissions
Certain order statesThe exact requirements must be analyzed carefully.
Not every financial system needs the same consistency mechanism for every component.
We will study CAP deeply later.
For now, understand the basic idea:
In a distributed system subject to a network partition, you cannot simultaneously guarantee both:
Strong consistency
and
Availabilityin the simplistic sense captured by CAP.
CAP is about:
Consistency
Availability
Partition toleranceThe crucial point:
Network partitions are possible in distributed systems, so system design must define what happens when communication fails.
We will dedicate a later chapter to this.
A common misconception:
NoSQL databases do not support transactions.
Incorrect.
Many modern NoSQL systems support transactions, although capabilities vary considerably.
Examples may include:
The important question is:
What transaction scope and guarantees does this particular database provide?
Document databases often make operations on one document atomic.
Example:
{
"balance": 1000,
"status": "active"
}An update can modify the document atomically according to the database's semantics.
This is one reason document modeling often encourages embedding data that must change together.
A distributed database may support operations like:
Update only if version = 10Conceptually:
Current version = 10
|
v
Update succeeds
|
v
Version = 11If another writer changed the version first:
Current version = 11
Expected = 10
Update failsThis is useful for optimistic concurrency control.
Suppose:
Product stock = 5
Version = 10Two requests read:
stock = 5
version = 10Both attempt to update.
Request A:
version 10 -> 11succeeds.
Request B:
expected version = 10fails because the version is now 11.
This prevents blind overwrites.
NoSQL databases may support indexes beyond the primary key.
But secondary indexes can have different:
Always understand the database-specific implementation.
For distributed systems, a secondary index can itself become a distributed system component.
TTL means:
Time To Live.
A record can automatically expire after a configured duration.
Example:
session:abc
TTL = 30 minutesAfter expiration, the data can be removed according to the database's TTL mechanism.
Common uses:
Some NoSQL systems are themselves used as caching layers.
Example:
Application
|
v
Redis
|
+---- hot dataBut not all NoSQL databases are caches.
For example:
Cassandra
MongoDB
DynamoDBcan be durable databases for primary application data depending on architecture.
Always distinguish:
Database
vs
Cachebased on durability and source-of-truth requirements.
NoSQL does not automatically mean:
in-memorySome NoSQL systems are durable disk-backed databases.
Others may be primarily memory-oriented.
The correct question is:
What durability guarantees does the specific system provide?
Large systems may use multiple database technologies.
Example:
Application
|
+--------------+--------------+
| | |
v v v
SQL DB Redis Document DB
payments cache profilesAnother example:
Transactions -> PostgreSQL
Cache -> Redis
Events -> Cassandra
Search -> Search engine
Analytics -> Data warehouseThis is called polyglot persistence.
Because different workloads have different requirements.
For example:
Payments
-> strong transactional guarantees
Sessions
-> fast key lookup
Product catalog
-> document-like structure
Search
-> inverted indexes
Analytics
-> large scans/aggregationsOne database may not be ideal for every workload.
Benefits:
Best-fit technology
Workload specialization
Potential scalabilityCosts:
Operational complexity
Multiple backup systems
Multiple monitoring systems
Data synchronization
More developer knowledge
More failure modesTherefore:
Do not introduce multiple databases without a strong reason.
Suppose we have:
User profileIf the profile is mostly retrieved as one complete object:
user
|
+-- profile
+-- preferences
+-- addressesa document model may be convenient.
If the system needs complex relationships:
users
orders
payments
products
subscriptionswith multi-entity transactions and queries, a relational model may be a natural fit.
Suppose an application stores:
billions of eventsand primarily queries:
events for user X ordered by timestampA distributed wide-column system may be attractive if its operational characteristics fit the workload.
But if the application requires:
arbitrary joins
complex transactions
ad-hoc relational queriesa relational database may be more appropriate.
Suppose the workload is:
session_id -> sessionThe application knows the key.
A key-value database can be a natural fit.
But if the requirement becomes:
Find all users
whose subscription expires
within 7 days
and whose last purchase
exceeded ₹10,000a relational or analytical system may be more suitable.
Ask these questions:
Relational?
Document?
Key-value?
Wide-column?
Graph?Point lookup?
Range query?
Aggregation?
Relationship traversal?Data size
Reads/sec
Writes/sec
Traffic growthStrong?
Eventual?
Application-specific?Single record?
Multiple records?
Multiple services?Single zone?
Multi-zone?
Multi-region?Sub-millisecond?
Milliseconds?
Seconds?
Batch?This last question is often overlooked.
Suppose:
100M users
High read traffic
Users follow many accounts
Posts are frequent
Feed reads must be fastOne possible architecture:
Post creation
|
v
Event / Queue
|
v
Fan-out workers
|
v
Timeline store
|
v
User timelineThe timeline may be stored in a distributed key-value or wide-column system.
Why?
Because the critical access pattern is:
Get recent posts for user XThe data model can be optimized around this query.
Requirement:
Get recent messages for conversation X.A suitable distributed model might be:
partition key = conversation_id
sort key = message timestampConceptually:
Conversation 123
|
+-- 10:01 message
+-- 10:02 message
+-- 10:03 message
+-- 10:04 messageThis makes:
latest 50 messagesa natural access pattern.
Requirement:
Get session by session IDA key-value model:
session:abc123
->
session datais simple.
Potential architecture:
Application
|
v
Session Store
|
v
SessionIf sessions are ephemeral, TTL can automatically remove expired data.
Suppose product attributes vary significantly.
Phone:
camera
battery
screen
Laptop:
processor
ram
storage
Shoes:
size
material
colorA document database can naturally represent variable product attributes.
But search requirements may justify a separate search engine.
This is another example of polyglot persistence.
Distributed NoSQL systems can fail in many ways:
Node failure
Network partition
Hot partition
Replication lag
Write conflicts
Unavailable quorum
Clock issues
Storage failure
Uneven data distributionA system-design interview should not stop at:
It scales horizontally.
You should ask:
How does it behave when a node or network link fails?
Suppose:
1 billion usersbut traffic is highly concentrated on:
user_id = 123If:
hash(user_id)maps all requests for user 123 to one partition, that partition can become a bottleneck.
Adding more partitions does not automatically solve the hot key.
This is a major distributed database design lesson.
A partition can be balanced by data size but still be unbalanced by traffic.
Example:
Partition A -> 100 GB -> 1,000 req/sec
Partition B -> 100 GB -> 100,000 req/secData is balanced.
Traffic is not.
Therefore:
Partition-key design must consider both data distribution and access distribution.
Suppose replicas are geographically distributed:
India
Europe
USA write may need confirmation from multiple regions for stronger guarantees.
This can increase latency.
Alternatively, the system may acknowledge locally and replicate asynchronously.
That may reduce write latency but increase the window for stale reads or conflict handling.
This is a classic distributed-system trade-off.
A globally distributed application might look like:
Global Traffic
|
+--------+--------+
| | |
v v v
Region A Region B Region C
| | |
+--------+--------+
|
Replicated DataDesign questions include:
These questions become central in later chapters.
Wrong.
NoSQL often means more flexible schema enforcement, not absence of data modeling.
Wrong.
Performance depends on workload and access patterns.
Not universally.
Different systems provide different consistency options.
Wrong.
Many systems support some form of transactional or atomic operations.
Not necessarily.
Bad partition keys can create:
hot partitionsand uneven load.
More partitions can increase:
The system must be designed appropriately.
No.
You trade:
read efficiencyfor:
write complexity
consistency management
storageSometimes a single database is exactly the right choice.
But different workloads may justify specialized systems.
A family of non-relational database systems with different data models and operational characteristics.
Document
Key-value
Wide-column
GraphA database that stores records as document-like structures, commonly JSON-like.
A system based primarily on:
key -> valuelookups.
A database optimized around nodes, edges, and relationships.
Because many distributed NoSQL systems optimize predictable queries and partition-based access rather than arbitrary joins.
A value used to determine where data is distributed.
A partition receiving disproportionately high traffic or data.
Replicas may temporarily differ but can converge after updates propagate.
Duplicating data to make important access patterns faster or simpler.
Consider:
Cardinality
Distribution
Traffic distribution
Query patterns
Data size
Growth
Hot-key riskPotential approaches:
Caching
Key spreading
Sharded key
Replication
Request coalescing
Access-pattern redesignThe correct choice depends on workload.
Do not answer:
SQL is better.
or:
NoSQL is better.
Instead explain:
Data model
Queries
Transactions
Consistency
Scale
Availability
Latency
Operational requirementsThen select a technology that fits those requirements.
Because it can eliminate expensive joins and make common queries direct partition lookups.
Using multiple storage technologies in one system, each selected for a specific workload.
Design storage for a social media application.
Requirements:
Users can follow users.
Users create posts.
Users view their timeline.
Users like posts.
Users comment on posts.Define:
Get a user's recent timeline.
Get recent posts by a user.
Get comments for a post.
Check whether a user liked a post.
Get followers of a user.
For each query, determine:
Key
Partition key
Sort key
Potential database model
Potential index
Potential caching strategyDesign a NoSQL data model for:
Chat applicationRequirements:
Users can create conversations.
A conversation contains messages.
Users can send messages.
Users can retrieve recent messages.
Messages have timestamps.
Users may belong to many conversations.Design the data model around these access patterns:
Get conversation
Get latest 50 messages
Get messages after timestamp X
Get conversations for user
Check membershipFor each query identify:
Partition key
Sort key
Expected result size
Hot-key risk
Consistency requirementSuppose:
10 million active sessions
500,000 requests/sec
Users distributed globally
Sessions expire after 30 minutesDesign:
Key format
Value format
TTL
Partition strategy
Replication
Read strategy
Failure handlingThink about:
session:user:123versus:
session:<random-id>Which key better distributes traffic?
The answer depends on the actual access pattern and distribution.
Before deploying a NoSQL database, ask:
Think of NoSQL architecture like this:
Application
|
v
Access Patterns
|
v
Data Model
|
v
Partition Key
|
v
Partition
|
+-----------+-----------+
| | |
v v v
Replica Replica Replica
| | |
+-----------+-----------+
|
Consistency
|
v
Failure ModelThis is the key mindset for distributed database design.
| Requirement | Often Consider |
|---|---|
| Complex relational queries | SQL |
| Strong multi-record transactions | SQL |
| Flexible document structure | Document DB |
| Simple key lookup | Key-value |
| Huge distributed write workload | Wide-column |
| Relationship traversal | Graph |
| Cache/session workload | Key-value/in-memory |
| Highly predictable access patterns | Distributed NoSQL can fit well |
| Ad-hoc joins | SQL |
| Multiple specialized workloads | Polyglot persistence |
These are starting points, not universal rules.
The biggest lesson from this chapter is:
NoSQL is a collection of different database approaches, not a single technology.
The major families are:
Document
Key-Value
Wide-Column
GraphAnd the core distributed concepts are:
Access patterns
↓
Data modeling
↓
Partitioning
↓
Replication
↓
Consistency
↓
Failure handlingAt scale:
Data
|
+-- partition
|
+-- replicate
|
+-- distribute
|
+-- optimize access
|
+-- handle failuresThe most important system-design rule is:
Design the database around the workload, access patterns, consistency requirements, scale, and failure model—not around the popularity of a database technology.
You should now be able to explain:
Next we will go deep into database indexing.
We will learn:
Why indexes exist
↓
How indexes work internally
↓
B-Tree indexes
↓
Hash indexes
↓
Composite indexes
↓
Covering indexes
↓
Clustered vs non-clustered indexes
↓
Cardinality and selectivity
↓
Index scan vs sequential scan
↓
Query planner
↓
EXPLAIN
↓
Index design
↓
Index anti-patterns
↓
Write amplification
↓
Large-table indexing
↓
Production indexing strategiesThis chapter will go much deeper into how indexes actually make database queries fast—and when an index can make a system slower instead.
Mark it as complete to track your mastery.