Absolutely. I’ll keep the same approach as the previous notes: easy language, ASCII diagrams, interview-focused, and without unnecessary length. Based only on the uploaded material.

NoSQL Databases — Easy Notes

1. NoSQL — The Right Mental Model

In very simple words: A NoSQL database is not simply “a faster database”. When you send a request, the system has to figure out where the data lives, which node owns it, which copies exist, and how fresh and durable the result needs to be. NoSQL is mainly about making these distributed choices work at scale.

Don't think:

App → Database → Put/Get

Think:

App
 ↓
Coordinator
 ↓
Which partition?
 ↓
Which node?
 ↓
Which replicas?
 ↓
Disk

A NoSQL cluster is mainly about:

WHERE does the data live?
HOW many copies exist?
HOW fresh is the data?
HOW durable is the data?

Is NoSQL faster than SQL?

Not automatically.

NoSQL gives different trade-offs:

NoSQL
 ├── Horizontal scaling
 ├── Flexible schema
 ├── High write throughput
 └── Often relaxed consistency

The reason to choose NoSQL should be:

A specific access pattern or scale requirement — not simply "NoSQL is faster."
2. Four NoSQL Families

In very simple words: The four families are useful because they organize data in different ways. Key-value is like looking up a complete object by its key; document stores related information together; wide-column is useful for very large ordered datasets; and graph databases are built for following relationships. The right choice depends on how the application asks for the data.

                    NoSQL
                      |
       +--------------+--------------+
       |              |              |
   Key-Value      Document      Wide-Column
       |              |              |
  lookup by key   related fields   huge/time
                                  ordered data
                      |
                    Graph
                      |
                relationships

Key-Value

user:1001 → {entire object}

Best when:

I always read/write the entire object using its key.

Document

user:1001
   ↓
{name, city, orders, ...}

Best when:

Data is semi-structured and related fields are read together.

Wide-Column

customer_id = 1001

10:00 → transaction
10:05 → transaction
10:10 → transaction

Best for:

Huge datasets + time/range-based queries.

Graph

Account A
   |
   ├── owns → Account B
   |
   └── linked → Customer C
                    |
                    └── owns → Account D

Best when:

Relationships and traversal are the query.

Most important NoSQL question

Before designing:

"What are my access patterns?"
             ↓
"At what scale?"
             ↓
"How should I partition the data?"

Schemaless ≠ No schema.

The schema moves into:

Application
    +
Access patterns
    +
Partition design
3. Partitioning

In very simple words: Partitioning decides which machine stores a piece of data. Range partitioning keeps nearby values together and helps range searches, while hash partitioning spreads values across machines and helps distribute load. A bad partition key can make one machine extremely busy even when many other machines are idle.

Partitioning answers:

WHERE does this record live?

Range Partitioning

User 1–10000     → Node A
User 10001–20000 → Node B
User 20001–30000 → Node C

Good for:

Range queries

But can create hotspots.

Hash Partitioning

hash(customer_id) % N
          ↓
      Partition

Example:

Customer A → Node 2
Customer B → Node 7
Customer C → Node 1
Customer D → Node 5

Good for:

Even distribution

But range queries become harder.

Hot partition

Suppose an IPO has:

scheme_id = ABC123

Thousands of users hit it simultaneously:

ABC123
  ↓
Partition X
  ↓
🔥🔥🔥🔥🔥🔥
HOT PARTITION

Adding nodes may not solve this if the hot key itself remains on one partition.

Hash vs Range

RANGE
Adjacent values stay together
       ↓
Great range scans
       ↓
Possible hotspots


HASH
Values spread out
       ↓
Great load distribution
       ↓
Range scans become scatter-gather

Best of both

Use a composite design:

customer_id
     ↓
HASH → chooses partition

transaction_time
     ↓
SORT/RANGE within partition

Result:

Customer A partition
 ├── Jan 1
 ├── Jan 2
 ├── Jan 3
 └── Jan 4
4. Partitioning vs Replication

In very simple words: Partitioning and replication solve different problems. Partitioning decides where one copy of the data lives across machines; replication decides how many copies exist so the system can survive failures. You normally need to think about both separately.

Very important distinction:

PARTITIONING
     ↓
Where does the data live?

REPLICATION
     ↓
How does the data survive failure?

They solve different problems.

5. Replication

In very simple words: Replication means keeping copies of data on multiple nodes. In a leader-based design, one node normally handles writes and copies the changes to replicas, which makes consistency simpler but creates a failover concern if the leader dies. In a leaderless design, multiple nodes can participate and correctness is controlled using quorum rules.

Leader-Based

             Leader
               A
             /   \
            ↓     ↓
          B         C
       Replica    Replica

Writes normally go through the leader.

Advantage

One authoritative writer
        ↓
Simpler consistency

Disadvantage

Leader dies
    ↓
Failover
    ↓
Temporary interruption

Leaderless / Quorum

Partition P

Node A ← write
Node B ← write
Node C ← write

All can participate

Correctness is based on quorum.

W = Write quorum
R = Read quorum
N = Number of replicas
6. Replication Factor ≠ Durability

In very simple words: Having three copies does not automatically mean a write is safe. If the client gets an acknowledgement after only one node has received the write and that node dies, the write may be lost. The important question is how many replicas must confirm the write before the system tells the client it succeeded, and where those replicas are physically placed.

Suppose:

N = 3

You have three copies.

But:

Write
 ↓
Node A ACK
 ↓
Client gets success
 ↓
B and C haven't received it

If A crashes:

Write LOST

So:

Durability depends on how many replicas must acknowledge the write, not merely how many copies eventually exist.

Replica Placement

Don't put replicas too close physically:

BAD

Rack 1
 A
 B
 C

A rack failure can kill all three.

Better:

AZ 1 → A
AZ 2 → B
AZ 3 → C

Why?

Failures are often correlated.
7. Write Path

In very simple words: A write normally travels through a coordinator and storage node, gets recorded in a durable log, is placed in memory, and is sent to replicas before the required acknowledgement is returned. The WAL or commit log is important because it gives the system something to use for recovery after a crash. Waiting for more replica confirmations gives stronger safety but adds latency.

Typical flow:

Client
  ↓
Coordinator
  ↓
Leader
  |
  +──→ WAL / Commit Log
  |
  +──→ Memtable
  |
  +──→ Replicas
  |
  ↓
 ACK

Why WAL first?

WAL = recovery source of truth

If:

Memory updated
     ↓
Process crashes
     ↓
WAL wasn't written

The update cannot be recovered.

Therefore:

WAL
 ↓
Memtable
 ↓
Replication

When should ACK happen?

Fast but risky:

Memtable updated
      ↓
ACK
      ↓
Replicate later

Leader crashes:

          LOST WRITE

Safer:

Write
 ↓
Enough replicas confirm
 ↓
ACK

More latency, but stronger durability.

8. Read Path

In very simple words: A write can reach the main node before all replicas have caught up. If the next read goes to a slower replica, the user may briefly see the old value. That delay between the latest write and a replica catching up is called replication lag.

Example:

Customer redeems MF
      ↓
Write accepted
      ↓
Refresh after 200 ms
      ↓
Old balance appears

Why?

Write → Leader
          ↓
       Replicas
          ↓
      propagation delay

The read may hit:

Replica that hasn't caught up yet

This is replication lag.

9. Read-Your-Writes

In very simple words: Read-your-writes means that after a user changes something, their next read should show that change. A simple approach is to route that user’s reads to the leader or to a replica known to be caught up. The cost is that some reads cannot be spread as freely across all replicas.

Means:

After I write something, I should immediately see my own change.

Example:

Redeem MF
   ↓
Refresh
   ↓
Must see updated balance

Possible solution:

After write
    ↓
Route user's reads
    ↓
Leader / caught-up replica

Trade-off:

More consistency
      ↓
Less read scalability
10. Quorum — W + R > N

In very simple words: Quorum is a way to make reads and writes overlap. If there are N copies, and the write needs W confirmations while the read checks R copies, W + R greater than N guarantees that at least one copy belongs to both groups. That overlap helps the read find the latest value.

Suppose:

N = 3
W = 2
R = 2

Then:

W + R = 4
4 > 3

Therefore read and write sets must overlap.

WRITE
[A][B]

READ
[B][C]

       ↑
   overlap

So the read can find the latest version.

Weak configuration

N = 3
W = 1
R = 1

1 + 1 = 2
2 <= 3

No overlap guarantee.

WRITE → A

READ  → C

A ≠ C

Potential stale read.

11. Strong vs Eventual Consistency

In very simple words: Strong consistency waits for the required coordination before returning success, so the result is fresher but may be slower. Eventual consistency returns quickly and lets other copies catch up afterward. In production, the notes emphasize measuring the actual replication lag instead of simply saying “it will eventually be consistent.”

Strong

Write
 ↓
Wait for required replicas
 ↓
Confirm
 ↓
Return

More:

Consistency ↑
Latency ↑

Eventual

Write
 ↓
Return quickly
 ↓
Replicas catch up later

More:

Speed ↑
Freshness ↓ temporarily

Operationally

Don't just say:

"Eventually it will be consistent."

Measure:

Replication lag

p50 = typical
p99 = worst important case

This gives you a real staleness window.

12. Access-Pattern Design

In very simple words: In NoSQL, you usually decide how people will query the data before deciding exactly how to store it. The query determines the partition key and therefore where the data lives. For example, if you constantly ask for one customer’s newest orders, putting customer_id in the partition and time in the sort order makes that read simple.

This is one of the most important NoSQL concepts.

SQL:

Create table
    ↓
Later
    ↓
CREATE INDEX
    ↓
New query

NoSQL:

Query pattern
      ↓
Partition key
      ↓
Data placement
      ↓
Performance

Therefore:

Design your access patterns before designing the table.

Example:

Requirement:

Get all orders for customer 1001
newest first

Good:

Partition = customer_id
Sort      = created_at DESC

Bad:

Partition = order_id
13. Don't Copy SQL Tables Directly into NoSQL

In very simple words: A relational design can spread related information across several tables because SQL joins it when needed. Copying that structure directly into a distributed NoSQL system can create many network calls and scatter-gather work. The notes therefore recommend designing around the actual query and denormalizing when that makes the read path simpler.

SQL might have:

Customers
   |
   +--- Orders
          |
          +--- Products

If you copy this structure blindly into NoSQL:

Query
 ↓
Customer partition
 ↓
Order partition
 ↓
Product partition
 ↓
Network calls

Potentially:

Scatter
 +
Gather
 +
Multiple lookups

Instead:

Design around query
        ↓
Denormalize where needed
        ↓
Keep read path simple
14. Secondary Indexes in NoSQL

In very simple words: A secondary index gives you another way to find the same data, but in a distributed database it may live somewhere different from the main record. One logical update can therefore require multiple updates, and the index can temporarily lag behind the primary data. Local indexes keep things closer together; global indexes support broader searches but involve more distributed work.

Primary data:

user_id → customer document

But now you want:

Find customer by email

Secondary index:

email → user_id

Now one logical write becomes:

Write customer
     |
     +----→ Primary storage
     |
     +----→ Secondary index

Potential problem:

Two partitions
      ↓
Two writes
      ↓
No natural atomicity
      ↓
Index can temporarily lag

So secondary indexes can introduce consistency challenges.

Local vs Global Secondary Index

Local

Same partition
     ↓
Base data + index
     ↓
Cheap consistency

But usually requires knowing the partition key.

Global

Base data
   ↓
Partition A

Index
   ↓
Different partition

Supports new query patterns, but:

More distributed work
+
Potential async lag
15. LSM Storage Engine

In very simple words: An LSM tree is designed to make writing data cheap. Instead of constantly changing random disk pages, the system records the write, keeps it in memory, flushes it into sorted files, and later merges those files during compaction. This makes it especially attractive for write-heavy workloads.

LSM = Log-Structured Merge Tree

Typical write path:

Write
 ↓
WAL
 ↓
Memtable
 ↓
Flush
 ↓
SSTable
 ↓
Compaction
 ↓
Merged SSTables

Why is LSM good for writes?

Writes are mostly:

Sequential
   ↓
Append
   ↓
Fast

Instead of constantly modifying disk pages in place.

16. LSM Trade-off

In very simple words: LSM gives high write throughput, but the cost appears during reads and compaction. A value may temporarily exist in several files, so a read may need to check multiple places. If writes arrive faster than compaction can keep up, the number of files grows and important read latency, especially p99, can increase.

You gain:

High write throughput

But pay with:

Read amplification
+
Compaction cost

A key may exist in multiple SSTables:

Read key X

SSTable 1 → check
SSTable 2 → check
SSTable 3 → check
SSTable 4 → check

Compaction eventually merges them.

Compaction Storm

During a huge write workload:

Writes
 ↓
SSTables created quickly
 ↓
Compaction can't keep up
 ↓
More SSTables
 ↓
Reads check more files
 ↓
p99 latency ↑

So:

LSM is excellent for write-heavy workloads, but compaction must be managed.
17. Routing & Rebalancing

In very simple words: Clients may remember which node owns each partition. When a new node is added or ownership changes, that information can become stale, causing requests to go to the wrong machine. The client therefore needs a way to detect the new partition map and refresh its routing information.

Clients may cache:

Partition → Node

Example:

Before:

P1 → A
P2 → B
P3 → C

Add Node D:

After:

P1 → A
P2 → D
P3 → C

If the client still thinks:

P2 → B

it sends requests to the wrong node.

Therefore:

Partition map
     ↓
Must detect changes
     ↓
Refresh
18. Why Rebalancing Is Hard

In very simple words: Rebalancing means moving real data from one machine to another while the system is still serving users. That movement consumes network and disk resources and must deal with reads, writes, and changing ownership at the same time. So adding capacity can itself create temporary production pressure.

Rebalancing means moving live data:

Node A
  |
  | move data
  ↓
Node D

while:

Reads continue
Writes continue

Problems:

Data movement
+
Network usage
+
Disk usage
+
Ownership change
+
Concurrent writes

So rebalancing itself can affect production performance.

19. Split-Brain

In very simple words: Split-brain happens when a network problem makes two parts of the cluster believe they are both the leader. Both sides can then accept different writes, creating two conflicting histories. This is especially dangerous for money and balances because the system needs one authoritative history.

One of the most dangerous failures.

          Network partition
             /       \
            /         \
         Node A      Node B
           ↓           ↓
       "I am leader" "I am leader"
           ↓           ↓
        WRITE         WRITE

Both sides accept different writes.

When network heals:

Node A → History A
Node B → History B

        ↓

     CONFLICT

This is split-brain.

Especially dangerous for:

Money
Balances
Ledger

because there must be one authoritative history.

20. Replica Repair

In very simple words: Replicas can become different because updates may arrive asynchronously. Instead of comparing every record, the system can compare compact hash or Merkle-like summaries to find which area differs. It then repairs only the mismatching range, which is much cheaper than comparing everything.

Replicas can drift because replication is often asynchronous.

Instead of comparing millions of rows:

Replica A
vs
Replica B
vs
Replica C

Use hash/Merkle-like summaries:

Entire dataset
      ↓
   Hash tree
   /       \
 Hash       Hash
 /  \       /  \
... ...   ... ...

If hashes differ:

Find differing range
       ↓
Compare only that range
       ↓
Repair

Much cheaper than comparing everything.

21. Multi-Partition Transactions

In very simple words: Updating one record in one partition is relatively simple. Updating related data that lives in two different partitions becomes a distributed transaction, because more than one machine must agree on the change. That can require distributed-transaction mechanisms and adds complexity and cost.

Single record:

Update document
     ↓
Easy atomic operation

Two different partitions:

Account balance
       +
Transaction log
       ↓
Different partitions
       ↓
Distributed transaction

Now you may need:

2PC / database-native
distributed transaction

This is more expensive and complex.

22. TTL — Logical vs Physical Expiry

In very simple words: When a record reaches its TTL, the system can consider it expired immediately for logical purposes even though the physical deletion happens later. A background process eventually removes it. This difference matters when the business or compliance requirement cares about exactly when data must no longer exist.

Suppose:

TTL = 24 hours

After 24 hours:

LOGICALLY EXPIRED

But physical deletion may happen later:

24h
 ↓
No longer logically valid
 ↓
Background cleanup
 ↓
Physical deletion

So:

Expired does not always mean physically deleted immediately.

Important for compliance requirements.

23. Multi-Region Writes

In very simple words: If two regions can write the same key at the same time, there may be no single leader deciding which update happened first. The system can therefore end up with conflicting versions. A multi-writer design needs an explicit conflict-resolution strategy.

Single region:

Region A
   ↓
Leader

Multi-writer:

Region A → WRITE X
Region B → WRITE Y

       ↓

Same key
       ↓
CONFLICT

Because there is no single leader deciding the order.

Therefore multi-writer systems need:

Conflict resolution strategy
24. Example Architecture

In very simple words: Not every piece of data needs the same level of safety. The example deliberately uses stronger settings for important data such as a ledger and faster, weaker settings for a cache. The simple idea is to choose consistency and performance based on the value and risk of the data.

A NoSQL platform might deliberately use different consistency levels:

                 NoSQL Cluster
                      |
          +-----------+-----------+
          |                       |
       Ledger                  Cache
          |                       |
      W=2, R=2                W=1, R=1
          |                       |
    Stronger safety           Faster

This is not inconsistent engineering.

It is:

Consistency level chosen per table based on the value of the data.
25. Real Production Problems

In very simple words: The examples show how theoretical design choices become real incidents: a popular IPO can create a hot partition, a lagging replica can show an old portfolio value, a secondary index can fall behind, heavy writes can overload compaction, and weak read/write settings can produce stale data. The fix is to design partitioning, routing, consistency, and write pressure around the actual workload.

1. Hot partition

Popular IPO
    ↓
Many applicants
    ↓
Same partition
    ↓
HOT

Fix:

Better bucket / virtual-node distribution

2. Stale portfolio

Redeem
 ↓
Leader updated
 ↓
Read hits lagging replica
 ↓
Old balance

Fix:

Read-your-writes routing

3. Secondary index lag

Bulk update
 ↓
Primary updated
 ↓
Secondary index behind

Fix:

Rate-limit index updates
+
Define staleness SLO

4. Compaction storm

EOD settlement
 ↓
Huge writes
 ↓
SSTables ↑
 ↓
Compaction overloaded
 ↓
p99 latency ↑

Fix:

Rate-limit settlement writes

5. Stale cache

W=1/R=1
   ↓
Fast but stale
   ↓
Wrong valuation

Fix:

Critical lien reads
 → W=2/R=2

Display-only widget
 → W=1/R=1
Final Mental Model

In very simple words: The NoSQL design flow is: first understand the query, then choose where the data should live, then decide how many copies you need, when a write is safe, how fresh reads must be, and which storage/indexing approach fits. The key lesson is that NoSQL structure follows access patterns rather than being designed first and queried later.

Remember NoSQL design as this flow:

          ACCESS PATTERN
                ↓
        What do I query?
                ↓
        PARTITION KEY
                ↓
        Where does data live?
                ↓
          REPLICATION
                ↓
        How does it survive?
                ↓
       WRITE / READ QUORUM
                ↓
       How fresh/durable?
                ↓
       STORAGE ENGINE
                ↓
      B-Tree / LSM / etc.
                ↓
      INDEX + DATA MODEL
                ↓
       Does it scale safely?

The 7 questions to ask in every NoSQL design

1. What are my access patterns?
2. What is my partition key?
3. Can this key become HOT?
4. How many replicas do I need?
5. When do I ACK a write?
6. How stale can a read be?
7. What happens when a node/network/AZ dies?

CTO-level takeaway

NoSQL is not "SQL without joins." It is a deliberate trade-off around data placement, scalability, availability, consistency, and access patterns.

And the most important rule:

SQL:
Data → Add index → Query

NoSQL:
Query → Design partition → Design data

Design the access pattern first. The database structure follows from it.