Sunday, 9 August 2026

🚀 Redis Scaling — Ramesh Style

 Absolutely. This chapter is essentially “How do we take Redis from one machine to a production-scale distributed system?” I’ll explain it in your Ramesh style: concept → why → architecture → example → interview point → real-world trade-off.

The chapter covers Persistence, Replication, Partitioning, Hash Partitioning, Presharding, Consistent Hashing, Tagging, and Twemproxy.


1. First understand the BIG picture

Imagine you have:

                Application
                     |
                     v
              +-------------+
              |    Redis    |
              |   1 Server  |
              +-------------+

Initially this is very fast.

But production grows:

  • 100 GB → 500 GB → 2 TB data

  • 10K requests/sec → 100K → 1M

  • One server becomes a bottleneck

  • Server failure becomes a major problem

So Redis scaling has 3 fundamental dimensions:

                  Redis Scaling
                       |
       +---------------+---------------+
       |               |               |
       v               v               v
 Persistence       Replication     Partitioning
       |               |               |
   Don't lose      Don't depend     Spread data
     data           on one node      across nodes

Ramesh formula:

Persistence = Don't lose data
Replication = Don't lose availability
Partitioning = Don't overload one machine


2. Persistence — “Redis memory is temporary”

Redis primarily stores data in memory.

Suppose:

Redis RAM

user:101 → Ramesh
user:102 → Sudha
user:103 → Yagna

Suddenly:

Redis crashes 💥

RAM disappears.

Therefore:

RAM = Fast
Disk = Durable

Redis provides two major persistence mechanisms:

             Persistence
                 |
        +--------+--------+
        |                 |
        v                 v
       RDB               AOF
   Snapshot           Write Log

The file explicitly describes RDB and AOF as the two Redis persistence mechanisms, which can also be enabled together.


3. RDB — “Take a photograph 📸”

Think of RDB as taking a snapshot of Redis.

At 10:00:

Redis:

A = 100
B = 200
C = 300

Redis creates:

dump.rdb

That file represents the dataset at that point in time.

Example

10:00 → Snapshot
10:05 → Snapshot
10:10 → Snapshot

If Redis crashes at:

10:12

You may restore from:

10:10 snapshot

But:

10:10 → 10:12

changes may be lost.

Ramesh rule

RDB = Fast recovery + possible recent-data loss


4. SAVE vs BGSAVE

This is an important interview question.

SAVE

Redis Main Process
      |
      | SAVE
      v
Disk

Redis blocks while creating the snapshot.

Therefore:

❌ Don't use SAVE casually in production.

BGSAVE

             Redis
               |
            fork()
           /      \
          /        \
   Main Process   Child
      |             |
  Serve users     RDB
                  Disk

BGSAVE creates the snapshot in a child process so the main process can continue serving requests.

But there is a hidden cost:

Copy-on-Write

Suppose:

Parent
100 GB

Child is creating RDB.

Meanwhile application modifies memory.

Redis may need additional memory for changed pages.

So:

Memory usage
     ↑
     |
100GB|        + changed pages
     |       /
     |______/

Interview point:

BGSAVE is non-blocking from the application's perspective, but fork + copy-on-write can increase memory pressure.


5. Redis Snapshot Configuration

The file gives examples such as:

save 900 1
save 300 10
save 60 10000

Meaning:

ConditionSnapshot
1 change within 15 minRDB
10 changes within 5 minRDB
10,000 changes within 1 minRDB

Ramesh interpretation

This is not:

“Save every 60 seconds.”

It is:

“If enough writes happen within the specified interval, take a snapshot.”


6. AOF — “Write down every transaction 📝”

RDB:

Take photograph

AOF:

Record the commands

Suppose application executes:

SET user:1 Ramesh
SET user:2 Sudha
INCR pageview
INCR pageview

AOF records the write commands.

After restart:

Redis
  |
  v
Read AOF
  |
  v
Replay commands
  |
  v
Rebuild dataset

The file describes AOF as an append-only command log that can rebuild Redis state by replaying commands in order.


7. AOF fsync — VERY important

AOF has three important policies:

appendfsync

       |
 +-----+------+ 
 |            |
no         everysec       always

no

Redis → OS → Disk

OS decides when to flush.

✅ Fast
❌ More potential data loss

everysec

Redis
  |
  +---- flush every second

✅ Good balance
⭐ Common default in the source material

always

Every write
     |
     v
fsync
     |
     v
Disk

✅ Strongest durability
❌ Slowest

The source explicitly describes no as fastest, always as safest but slowest, and everysec as a performance/durability balance.

Interview answer

RDB optimizes snapshot-based recovery; AOF prioritizes durability by recording writes.


8. RDB vs AOF — Ramesh Table

FeatureRDBAOF
ConceptSnapshotCommand log
FileBinaryAppend-only log
RecoveryFasterUsually slower
Data lossPossible between snapshotsDepends on fsync policy
File sizeSmallerCan become larger
Main useBackup / DRDurability
PerformanceGenerally betterMore overhead
Can coexist?YesYes

An important point from the source: when both exist, AOF takes precedence during startup because of its durability characteristics.


9. Replication — “One Redis is not enough”

Now imagine:

              Application
                   |
                   v
               MASTER
              Redis-1

Redis-1 crashes.

Game over.

So create replicas:

                 MASTER
                Redis-1
               /       \
              /         \
             v           v
        Replica-1     Replica-2

Writes:

Application
     |
     v
 MASTER
  |
  +--------> Replica 1
  |
  +--------> Replica 2

Redis replication allows one master to have multiple replicas.


10. Why Replication?

Three major reasons:

① Read scaling

                MASTER
             writes only
                  |
          +-------+-------+
          |               |
          v               v
       Replica          Replica
        reads            reads

Instead of:

1 Redis → 100K reads

you can distribute:

Master → writes
Replica1 → reads
Replica2 → reads
Replica3 → reads

The source specifically identifies replicas as a way to handle read operations separately from writes.

② High availability

If master dies:

MASTER 💥

Replica
   |
   v
Promote → MASTER

③ Data redundancy

Multiple copies exist.


11. But replication has a BIG problem

Replication alone does not automatically mean failover in the single-instance setup described by the chapter.

Example:

Master A
   |
   +---- Replica B
   +---- Replica C

Master A dies.

You manually need:

B → Master
C → replicate B
Clients → connect B

The source notes that automatic failover is the role of Redis Sentinel, covered in the following chapter.

Ramesh rule

Replication gives copies. Sentinel gives automatic failover.

And later:

Redis Cluster gives distributed data + cluster management.


12. Partitioning — THE BIG SCALING CONCEPT

Suppose:

Redis Server = 128 GB RAM

Your dataset becomes:

500 GB

Replication won't solve the capacity problem.

Why?

Because:

Master = 128 GB
Replica = copy of 128 GB
Replica = copy of 128 GB

You still need a machine capable of holding the entire dataset.

So we need:

SHARDING

The source defines partitioning as breaking data up and distributing it across hosts; in Redis, horizontal partitioning means distributing keys across instances.


13. Horizontal Partitioning

Imagine:

Redis Cluster

Server 1
user:1
user:2
user:3

Server 2
user:4
user:5
user:6

Server 3
user:7
user:8
user:9

Data is split by keys.

This is:

Horizontal partitioning = Sharding


14. Vertical Partitioning

Different idea:

User data

Profile
Orders
Payments
Activity

Could distribute different parts across servers.

Conceptually:

Redis-1 → Profile
Redis-2 → Orders
Redis-3 → Payments

The source distinguishes horizontal partitioning by keys from vertical partitioning by key values.


15. Range Partitioning

Very simple.

Suppose:

user:1
user:2
...
user:5000

Divide:

Redis-1 → 1–1000
Redis-2 → 1001–2000
Redis-3 → 2001–3000
Redis-4 → 3001–5000

The source uses exactly this type of incremental-ID example.

Problem #1 — Hotspot / uneven distribution

Suppose:

Redis-1 → 1 million keys
Redis-2 → 10,000 keys
Redis-3 → 10,000 keys

Bad distribution.

Problem #2 — Adding a server

Originally:

1 → Redis A
2 → Redis B
3 → Redis C

Add Redis D.

Ranges may need significant restructuring.

Therefore:

Range partitioning = simple, but difficult to rebalance.


16. Hash Partitioning

Now we become smarter.

Instead of:

key range → server

we do:

hash(key) % numberOfServers

Example:

hash("user:101") = 15

15 % 3 = 0

→ Redis-0

The source shows this exact basic formula.

Architecture:

             user:101
                 |
                 v
             hash()
                 |
                 v
               15
                 |
              % 3
                 |
        +--------+--------+
        |        |        |
       0         1        2
        |        |        |
       R1        R2       R3

Usually distribution becomes much more balanced than naive ranges.


17. BUT Hash Partitioning has a killer problem

Suppose:

3 Redis servers

Then:

hash(key) % 3

Now add:

Redis-4

It becomes:

hash(key) % 4

For many keys:

old server ≠ new server

So keys move.

For a cache:

Old:
user:100 → Redis-2

New:
user:100 → Redis-4

Redis-4 doesn't have it.

Result:

CACHE MISS 💥

The source reports that changing the number of instances can invalidate a large portion of data; its example saw 75% invalidated after adding two servers.


18. Presharding — clever workaround

Idea:

Don't wait until you need more nodes. Create many logical partitions upfront.

For example:

Server 1
 ├── Redis 6379
 ├── Redis 6380
 ├── Redis 6381
 ├── Redis 6382
 └── Redis 6383

Server 2
 ├── Redis 6379
 ├── Redis 6380
 ...

Instead of:

3 partitions

create:

15 partitions

Then later:

Small Server → Big Server

instead of changing:

15 partitions → 20 partitions

The source calls this presharding and explains that multiple Redis instances can be run per physical server.

Advantage

Hash mapping stays stable.

Disadvantage

More instances
     ↓
More monitoring
     ↓
More operational complexity

And it isn't truly elastic.


19. ⭐ Consistent Hashing — THE MOST IMPORTANT CONCEPT

This is the concept you were asking about earlier.

Normal hashing:

hash(key) % N

Problem:

N changes
↓
mapping changes massively

Consistent hashing says:

When nodes change, move only a small amount of data.

The source explains the idealized remapping as roughly K/n keys, where K is the number of keys and n is the number of servers.


20. Hash Ring — Think Like a Clock 🕐

Instead of:

0
1
2
3

imagine a circle:

             Server B
                ●
          .-------------.
       .                   .
      .                     .
Server A ●                 ● Server C
      .                     .
       .                   .
          '-------------'

This is the:

HASH RING

Both:

Servers
Keys

are hashed onto the ring.


21. Consistent Hashing Example

Suppose:

Server-1 → hash 3
Server-2 → hash 7
Server-3 → hash 11

Keys:

key1 → 3
key2 → 4
key3 → 8
key4 → 12

Now clockwise:

0 ---- 3 ---- 7 ---- 11 ---- 15
      S1      S2      S3

key1 = 3

Exactly at S1:

key1 → S1

key2 = 4

Next server clockwise:

4 → 7 → S2

key3 = 8

Next server:

8 → 11 → S3

key4 = 12

No server after 12.

So wrap around:

12 → 3 → S1

This is the same routing logic described in the source.


22. Why Adding a Server Is Better

Current:

       S1       S2       S3
-------●--------●--------●------

Add:

             S4
              ●

Only keys in the region immediately affected by S4 need to move.

Not everything.

That's the BIG WIN

Normal Hashing

Node added
    ↓
Many mappings change
    ↓
Many cache misses


Consistent Hashing

Node added
    ↓
Small portion moves
    ↓
Most cache mappings remain

23. Virtual Nodes — Very Important

Suppose only one point per server:

S1 ●

S2                 ●

S3                              ●

Distribution may be poor.

So create multiple points:

S1 → S1-1 S1-2 S1-3 S1-4 ...
S2 → S2-1 S2-2 S2-3 S2-4 ...
S3 → S3-1 S3-2 S3-3 S3-4 ...

These are:

Virtual Nodes / VNodes

The source's implementation defaults to 256 virtual nodes per client in its example.

Why?

To make distribution more uniform.

Physical Server
       |
       +-- vnode1
       +-- vnode2
       +-- vnode3
       ...

This is extremely important in distributed systems.


24. Consistent Hashing — Java Architect View

Think:

server = ring.getNextNode(hash(key));

Not:

server = servers.get(hash(key) % servers.size());

The difference:

Modulo hashing
      ↓
Node count is critical


Consistent hashing
      ↓
Ring position is critical

25. Tagging — Redis Multi-Key Problem

This is another very important Redis Cluster concept.

Suppose:

user:1
user:2

Hashing may send them to different nodes.

But what if you execute:

SINTER user:1 user:2

Redis needs both keys on the same Redis instance for this kind of operation.

Solution:

Hash Tags

Use:

user:1{users}
user:2{users}
user:3{users}

Redis hashes:

{users}

instead of the entire key.

Therefore:

user:1{users} ──┐
user:2{users} ──┼──> same Redis node
user:3{users} ──┘

The source specifically describes curly-brace tags as a way to force related keys onto the same instance.

Ramesh rule

If multiple keys must participate in one Redis operation, give them the same hash tag.


26. Cache vs Data Store — VERY IMPORTANT DESIGN DECISION

This is probably the most architect-level section.

Redis as CACHE

Database
   ↑
   |
Redis Cache

If cache data disappears:

Cache miss
   ↓
Database
   ↓
Reload cache

Therefore:

Cache can tolerate remapping.

Recommended:

Redis Cache
     ↓
Consistent Hashing

The source explicitly recommends consistent hashing for Redis cache workloads to minimize cache misses.


27. Redis as PRIMARY DATA STORE

Now imagine Redis contains:

Customer account balance
Payment information
Order state

You cannot casually move:

user:101

from Redis-1 to Redis-2.

You need:

Data ownership
Replication
Failover
Routing
Consistency
Recovery

That's where:

Redis Cluster

becomes much more appropriate.

The source recommends Redis Cluster or an equivalent replicated routing solution when Redis is used as a data store.


28. Client vs Proxy vs Query Router

There are three places where sharding logic can live:

              Sharding
                 |
       +---------+---------+
       |         |         |
       v         v         v
    Client     Proxy    Query Router

① Client-side

Application decides:

key → Redis node
Application
     |
     +--> Redis-1
     +--> Redis-2
     +--> Redis-3

② Proxy

Application thinks there is one Redis:

Application
     |
     v
  Proxy
 /  |  \
R1  R2  R3

Proxy decides where the key goes.

③ Query Router

Redis cluster itself handles routing.

Application
     |
     v
Redis Cluster
     |
 +---+---+---+
 R1  R2  R3

The source describes these three layers and notes that Redis Cluster acts as the query-routing layer.


29. Twemproxy

Twemproxy is essentially:

A proxy sitting between application and Redis servers to perform sharding.

Architecture:

                 Application
                      |
                      v
                 Twemproxy
                /    |    \
               /     |     \
             R1      R2      R3

Application doesn't need to know:

Which key → which Redis

Twemproxy handles it.

The source describes twemproxy as a lightweight Redis/Memcached proxy supporting multiple hashing modes including consistent hashing.


30. BUT Twemproxy has a SPOF

Imagine:

Application
     |
     v
 Twemproxy 💥
     |
  X  X  X
 Redis Redis Redis

Redis servers are healthy.

But application can't reach them.

Therefore:

Proxy itself must be highly available.

Better:

                 Load Balancer
                 /            \
                v              v
           Twemproxy-1    Twemproxy-2
              |  |  |        |  |  |
             R1 R2 R3       R1 R2 R3

The source explicitly identifies a single twemproxy process as a single point of failure and proposes a load balancer in front of multiple proxy instances.


🧠 Ramesh Master Architecture

Now put everything together.

                         CLIENTS
                            |
                            v
                     Load Balancer
                            |
              +-------------+-------------+
              |                           |
              v                           v
         Redis Proxy 1              Redis Proxy 2
              |                           |
              +-------------+-------------+
                            |
                     Redis Cluster
                            |
          +-----------------+-----------------+
          |                 |                 |
          v                 v                 v
       Shard-1           Shard-2           Shard-3
          |                 |                 |
        Master             Master            Master
        /    \             /    \            /    \
       R1     R2           R1     R2          R1     R2

And persistence:

Redis Master/Replica
       |
   +---+---+
   |       |
  RDB     AOF
   |       |
 Backup   Durable log

🔥 Ramesh Interview Cheat Sheet

ConceptOne-line meaning
RDBSnapshot of Redis
AOFRecord Redis write commands
ReplicationCopy data to other Redis nodes
ReplicaRead scaling + redundancy
FailoverPromote replica to master
PartitioningSplit data across machines
ShardingDistribute keys across nodes
Range partitioningKey range → server
Hash partitioninghash(key) % N
PreshardingCreate many partitions upfront
Consistent hashingAdd/remove nodes with minimal remapping
Hash ringCircular hash space
Virtual nodeMultiple logical positions for one server
TaggingForce related keys onto same shard
ProxyRouting layer outside Redis
TwemproxyRedis/Memcached sharding proxy
SentinelAutomatic failover/monitoring
Redis ClusterDistributed Redis with sharding + cluster management

⭐ The Most Important Mental Model

Remember this sequence:

             REDIS SCALING
                  |
       +----------+----------+
       |          |          |
       v          v          v
   PERSISTENCE  REPLICA   SHARDING
       |          |          |
       v          v          v
    RDB/AOF    Read/HA    Split data
                             |
                  +----------+----------+
                  |                     |
                  v                     v
              Hashing             Consistent
              Hash % N              Hashing
                                       |
                                       v
                                  Hash Ring
                                       |
                                       v
                                    VNodes
                                       |
                                       v
                                    Tags

And the Ramesh Golden Rule:

RDB/AOF solves DATA LOSS.
Replication solves READ SCALE + REDUNDANCY.
Sentinel solves FAILOVER.
Sharding solves DATA SIZE + WRITE SCALE.
Consistent Hashing solves NODE CHANGE + CACHE MISS.
Hash Tags solve MULTI-KEY OPERATIONS.
Redis Cluster brings distributed Redis together.

This chapter concludes with exactly these major themes—persistence, replication, partitioning, presharding, consistent hashing, and twemproxy—before moving into Redis Sentinel and Redis Cluster.

Saturday, 1 August 2026

Gossip - Protocol - System Design Fundamentals

 

Gossip Protocol in Dynamo Explained (Ramesh Style)

Think like this:

"If there is no Team Lead in a company, how does everyone know who is on leave?"

This is exactly the problem Dynamo solves.


Problem Statement

Suppose we have 100 Dynamo nodes.

Node A
Node B
Node C
...
Node Z

There is NO Master Server.

No Leader.

No Coordinator.

Every node is equal.

Now imagine

Node 37 crashes.

Question:

How do the remaining 99 nodes know Node37 is dead?


First Idea (Bad Solution)

Every node checks every other node.

A → B

A → C

A → D

....

A → Z

Similarly

B → A
B → C
B → D

Every node sends heartbeat to every other node.

Heartbeat

"I'm alive."

every second.


Total Messages

Suppose

100 nodes

Every node sends

99 messages

Total

100 × 99

≈ 9900 messages

Every second!

Huge network traffic.

This is

O(N²)

Very expensive.


Dynamo's Brilliant Solution

Instead of talking to everyone...

Every node talks to ONE RANDOM NODE.

That is Gossip.


Real Life Example

Imagine

100 friends in WhatsApp.

Instead of sending

"I got married"

to all 100 people...

You tell

Friend A

Friend A tells

Friend B

Friend B tells

Friend C

Friend C tells

Friend D

Soon...

Everyone knows.

Nobody informed everyone directly.

This is exactly Gossip Protocol.


Example

Cluster

A
B
C
D
E
F

Initially

Only

A

knows

Node X crashed

Round 1

A randomly picks

D
A ---> D

Node X crashed

Now

A knows
D knows

Round 2

A randomly picks

B
A ---> B

D randomly picks

F
D ---> F

Now

A
B
D
F

know.


Round 3

B tells

E

F tells

C

Now

Everyone knows.

No central server.

No broadcasting.

Only random communication.


Visualization

Initially

      A

Round 1

A ------> D

Round 2

A ---> B

D ---> F

Round 3

B ---> E

F ---> C

Finally

A B C D E F

Information spreads like a virus.


Why is it called Gossip?

Because humans gossip exactly like this.

A says

"Did you hear?"

↓

B says

"I heard..."

↓

C says

"Really?"

↓

Soon

Whole office knows.

What information is exchanged?

Each node sends

Node Status

Alive

Dead

Joining

Leaving

Also

Hash Ring Information

Token Ranges

Replication Info

Version Number

Basically,

Cluster Metadata

Example

Node A stores

Node1 Alive

Node2 Alive

Node3 Dead

Node4 Alive

Node B stores

Node1 Alive

Node2 Alive

Node3 Alive

Node4 Alive

When

A gossips with B

they compare.

B

"Oh...

Node3 is dead?"

Update completed.


Java Design

Let's design it.


Node Class

class Node {

    String nodeId;

    Map<String, NodeStatus> clusterInfo = new HashMap<>();

}

NodeStatus

class NodeStatus {

    String nodeId;

    boolean alive;

    long heartbeatVersion;

}

Gossip Message

class GossipMessage {

    Map<String, NodeStatus> clusterInfo;

}

Gossip Algorithm

Every second

Pick Random Node

↓

Send Cluster Metadata

↓

Receiver merges

↓

Done

Java Example

public class GossipNode {

    private final String nodeId;

    private final Map<String, NodeStatus> state = new HashMap<>();

    private final List<GossipNode> cluster;

    public GossipNode(String nodeId, List<GossipNode> cluster) {
        this.nodeId = nodeId;
        this.cluster = cluster;
    }

    public void gossip() {

        Random random = new Random();

        GossipNode target =
                cluster.get(random.nextInt(cluster.size()));

        if (target != this) {

            target.receive(state);

            System.out.println(nodeId +
                    " gossiped with "
                    + target.nodeId);
        }
    }

    public void receive(Map<String, NodeStatus> remoteState) {

        remoteState.forEach((id, remoteStatus) -> {

            NodeStatus local = state.get(id);

            if (local == null ||
                    remoteStatus.version > local.version) {

                state.put(id, remoteStatus);

            }
        });
    }
}

Merge Logic

Suppose

Node A

Node3 Version = 12

Node B

Node3 Version = 15

Obviously

15

is newer.

After gossip

Node A

updates to Version 15

Newest information always wins.


Heartbeat

Every node periodically updates

myStatus.version++;

Example

Node A

Heartbeat

Version

1

2

3

4

5

If it crashes

Version stops increasing.

Other nodes notice

Heartbeat timeout

↓

Dead Node

Scheduler

Every second

ScheduledExecutorService scheduler =
        Executors.newScheduledThreadPool(1);

scheduler.scheduleAtFixedRate(
        node::gossip,
        0,
        1,
        TimeUnit.SECONDS);

Every second

Random node selected.

Information exchanged.


Time Complexity

Without Gossip

Every node talks

to every node

O(N²)

With Gossip

One random node

O(N)

Much cheaper.


Advantages

FeatureBenefit
No Master NodeNo single point of failure
Random CommunicationLow network traffic
Eventually ConsistentEvery node learns the latest state over time
ScalableSuitable for clusters with thousands of nodes
Fault TolerantCluster continues even when nodes fail

Real-World Systems Using Gossip

SystemPurpose
Amazon DynamoMembership and cluster state
Apache CassandraNode discovery and failure detection
RiakCluster synchronization
ScyllaDBMembership management
HashiCorp SerfService discovery
Consul (internally)Membership and health dissemination

Interview Questions

QuestionAnswer
Why doesn't Dynamo use a master node?To eliminate a single point of failure and improve scalability.
Why not use heartbeats between every pair of nodes?It generates O(N²) messages, which doesn't scale.
What does Gossip Protocol exchange?Node status, heartbeat versions, hash ring information, replication metadata, and cluster membership.
Does Gossip guarantee immediate consistency?No. It provides eventual consistency for cluster metadata.
Why choose a random node?Random peer selection spreads information efficiently with minimal network overhead.

🎯 Ramesh Interview One-Liner

"Gossip Protocol is a decentralized peer-to-peer communication mechanism where each node periodically shares its view of the cluster with one randomly selected node. Over successive rounds, membership, heartbeat, and hash-ring information propagate throughout the cluster, enabling scalable and fault-tolerant cluster-state synchronization without requiring a central coordinator."

Friday, 31 July 2026

Rolling Hash in Java (Hash Collision Solving) – Part 2: Polynomial Hashing, Modulo Arithmetic, and Rabin–Karp (Interview Guide)

Author: Ramesh Vankayala


Introduction

In Part 1, we learned a simple rolling hash using the sum of ASCII values.

Hash = ASCII1 + ASCII2 + ASCII3

Although this helps us understand the concept, it is not suitable for real-world applications because many different strings can produce the same hash.

Example:

ABC

65 + 66 + 67 = 198

CAB

67 + 65 + 66 = 198

Different strings, same hash.

This is called a Hash Collision.

To reduce collisions, the Rabin-Karp algorithm uses Polynomial Rolling Hash.


Why Do We Need Polynomial Hashing?

Think of a vehicle registration number.

KA01AB1234

If we only added all digits together, many different registration numbers would produce the same total.

Instead, every position contributes differently.

The same idea is applied in polynomial hashing.

Characters appearing at different positions receive different weights.


Polynomial Hash Formula

The hash of a string is calculated as:

[
Hash = s_0 \times p^{m-1}

  • s_1 \times p^{m-2}

  • ...

  • s_{m-1} \times p^0
    ]

Where

SymbolMeaning
sCharacter value
pBase (31, 53, or 256 are common choices)
mLength of the string

Example

String

ABC

ASCII values

CharacterASCII
A65
B66
C67

Assume

Base = 31

Weights

CharacterWeight
A31²
B31¹
C31⁰

Step-by-Step Calculation

31² = 961

31¹ = 31

31⁰ = 1

CharacterFormulaValue
A65 × 96162,465
B66 × 312,046
C67 × 167

Final Hash

62465 + 2046 + 67

=64578

Unlike the simple ASCII sum, changing the order of the characters now changes the hash.


Why Choose Base 31?

Interviewers often ask:

"Why is the base usually 31?"

Reasons:

  • It is a prime number.

  • It distributes hash values well.

  • It reduces collisions.

  • Multiplication by 31 is computationally efficient.

  • Java's String.hashCode() also uses 31.


What is Modulo Arithmetic?

Imagine calculating the hash for a string with one million characters.

The number becomes extremely large.

Eventually, integer overflow occurs.

To avoid this, we keep the hash within a fixed range.

Instead of storing

98765432123456789

we store

98765432123456789 % 1,000,000,007

Why 1,000,000,007?

This number is frequently used because:

  • It is prime.

  • It fits within 32-bit integer calculations.

  • It minimizes collisions.

  • It is efficient for modular arithmetic.


Rolling Hash Formula

Suppose

ABCDE

Window size

3

Current window

ABC

Next window

BCD

Instead of recalculating the entire polynomial hash, we update it.

Conceptually:

Remove contribution of A

↓

Shift remaining characters

↓

Add D

This is why each window update is O(1).


Java Example

public class PolynomialHash {

    static final int BASE = 31;

    public static long calculateHash(String s) {

        long hash = 0;

        for (int i = 0; i < s.length(); i++) {
            hash = hash * BASE + s.charAt(i);
        }

        return hash;
    }

    public static void main(String[] args) {

        String s = "ABC";

        System.out.println(calculateHash(s));

    }
}

Output

64578

Rabin–Karp Algorithm

The complete algorithm follows these steps:

Pattern

↓

Calculate Pattern Hash

↓

Calculate First Window Hash

↓

Compare Hashes

↓

Different?

↓

Move Window

↓

Update Hash

↓

Compare Again

↓

Hash Match?

↓

Compare Characters

↓

Substring Found

Why Verify Characters?

Even polynomial hashes can collide.

Suppose

Hash(Window)

=

Hash(Pattern)

This does not guarantee the strings are identical.

Therefore, after a hash match, compare each character.

Hash Match

↓

Character Comparison

↓

Equal?

↓

Substring Found

Complexity

OperationComplexity
Pattern HashO(m)
First Window HashO(m)
Sliding Window UpdatesO(n)
Average OverallO(n + m)
Worst Case (many collisions)O(n × m)

Where:

  • n = length of the text

  • m = length of the pattern


Common Interview Questions

1. Why is Rabin–Karp faster than brute force?

Because it compares hash values instead of comparing every character in every window.


2. What is a Hash Collision?

Two different strings generating the same hash value.


3. Why verify characters after hash matching?

Because equal hashes do not always mean equal strings.


4. Why use modulo?

To prevent integer overflow and keep hash values within a manageable range.


5. Why is 31 commonly chosen?

  • Prime number

  • Better distribution

  • Lower collision probability

  • Used by Java's String.hashCode()


6. Where is Rabin–Karp used?

  • Search engines

  • Plagiarism detection

  • DNA sequence matching

  • Virus signature scanning

  • Text editors

  • Log analysis

  • Malware detection

  • Duplicate document detection


Interview Summary

A strong interview answer should include these points:

  1. Start with the brute-force solution and its O(n × m) complexity.

  2. Explain that hashing converts a string into a number.

  3. Introduce polynomial hashing to reduce collisions.

  4. Explain rolling hash for O(1) window updates.

  5. Mention modulo arithmetic to avoid overflow.

  6. Discuss hash collisions and character verification.

  7. Conclude that Rabin–Karp has an average time complexity of O(n + m).


Key Takeaways

  • Polynomial hashing is significantly more reliable than a simple ASCII sum.

  • Rolling hash avoids recomputing every window from scratch.

  • Modulo arithmetic prevents overflow and keeps hashes manageable.

  • Rabin–Karp combines rolling hash with character verification to efficiently solve substring search problems.

  • Understanding these concepts provides a solid foundation for advanced string algorithms such as KMP, suffix arrays, suffix automata, and string indexing.

A great follow-up article would be Part 3: "Rolling Hash Dry Run with Java Debugger", where every iteration is shown in a table with:

  • windowStart

  • windowEnd

  • oldHash

  • newHash

  • outgoing character

  • incoming character

  • hash comparison

  • character verification

  • decision (move/found)

This format is especially effective for interview preparation because it makes the algorithm's execution easy to visualize.

String Array Interview Techniq - Rolling Hash Explained in Java – How Rabin-Karp Solves the Substring Problem


Author: Ramesh Vankayala


Introduction

Finding whether a substring exists inside a larger string is one of the most common interview problems.

For example:

Text    = HELLOWORLD
Pattern = LOW

Expected Output:

Substring Found

The simplest solution is to compare every possible window character by character. However, this becomes slow for large strings.

The Rabin-Karp algorithm improves this by using a technique called Rolling Hash.


Brute Force Approach

We compare every possible window.

HELLOWORLD

HEL
ELL
LLO
LOW
OWO
WOR
ORL
RLD

Every window is compared character by character.

Time Complexity:

O(n × m)

n = Length of Text
m = Length of Pattern

Core Idea of Rolling Hash

Instead of comparing every character in every window,

convert every window into a single integer (Hash Value).

If hash values are different,

the strings are definitely different.

Only when hash values are equal do we compare the actual characters.

This reduces unnecessary comparisons.


Step 1 – Calculate Pattern Hash

Pattern

LOW

ASCII Values

CharacterASCII
L76
O79
W87

Pattern Hash

76 + 79 + 87 = 242

Store this value.


Step 2 – Calculate First Window Hash

Window

HEL

ASCII

CharacterASCII
H72
E69
L76

Hash

72 + 69 + 76 = 217

Compare

217 != 242

Not Found.

Move the window.


Step 3 – Rolling Hash

Previous Window

HEL

New Window

ELL

Instead of recalculating

69 + 76 + 76

Reuse the previous hash.

New Hash

= Previous Hash
- Outgoing Character
+ Incoming Character

=217-72+76

=221

This is called Rolling Hash.


Step-by-Step Execution

WindowHashPattern HashResult
HEL217242No
ELL221242No
LLO231242No
LOW242242Hash Matched

Now compare characters.

L == L

O == O

W == W

Substring Found.


Visual Representation

HELLOWORLD

HEL  -> 217

ELL  -> 221

LLO  -> 231

LOW  -> 242

Pattern

LOW ->242

Hash Match

↓

Compare Characters

↓

Substring Found

Java Program

public class RollingHashSubstring {

    public static void main(String[] args) {

        String text = "HELLOWORLD";
        String pattern = "LOW";

        int windowSize = pattern.length();

        // Pattern Hash
        int patternHash = 0;
        for (int i = 0; i < windowSize; i++) {
            patternHash += pattern.charAt(i);
        }

        // First Window Hash
        int windowHash = 0;
        for (int i = 0; i < windowSize; i++) {
            windowHash += text.charAt(i);
        }

        System.out.println("Pattern Hash : " + patternHash);
        System.out.println();

        for (int i = 0; i <= text.length() - windowSize; i++) {

            String window = text.substring(i, i + windowSize);

            System.out.println(
                    "Window : " + window +
                    "  Hash : " + windowHash);

            if (windowHash == patternHash) {

                boolean match = true;

                for (int j = 0; j < windowSize; j++) {
                    if (text.charAt(i + j) != pattern.charAt(j)) {
                        match = false;
                        break;
                    }
                }

                if (match) {
                    System.out.println();
                    System.out.println("Substring Found at Index : " + i);
                    return;
                }
            }

            // Rolling Hash
            if (i < text.length() - windowSize) {
                windowHash =
                        windowHash
                        - text.charAt(i)
                        + text.charAt(i + windowSize);
            }
        }

        System.out.println("Substring Not Found");
    }
}

Program Output

Pattern Hash : 242

Window : HEL  Hash : 217

Window : ELL  Hash : 221

Window : LLO  Hash : 231

Window : LOW  Hash : 242

Substring Found at Index : 3

Why Rolling Hash is Faster

Without Rolling Hash

HEL

Calculate Again

ELL

Calculate Again

LLO

Calculate Again

LOW

Every window is calculated from scratch.

With Rolling Hash

Previous Hash

↓

Remove Left Character

↓

Add Right Character

↓

New Hash

Only two arithmetic operations are needed.


Important Interview Question

Q: If two different strings have the same hash, what happens?

Example

ABC

Hash =198

CAB

Hash =198

Both hashes are equal, but the strings are different.

This is called a Hash Collision.

Therefore, whenever hashes match, we must verify the actual characters before declaring success.


Time Complexity

ApproachTime Complexity
Brute ForceO(n × m)
Rolling Hash (Average)O(n)
Character VerificationOnly when hashes match

Interview Tips

  • Explain the brute-force solution first.

  • Mention its O(n × m) complexity.

  • Introduce hashing as a way to compare integers instead of characters.

  • Explain the rolling hash formula:

NewHash = OldHash - OutgoingCharacter + IncomingCharacter
  • Mention hash collisions and why character verification is required.

  • Finally, discuss how Rabin-Karp uses polynomial rolling hashes in real implementations to reduce collisions.


Key Takeaways

  • A string can be processed just like an array.

  • Rolling Hash reuses the previous computation instead of recalculating every window.

  • Rabin-Karp combines rolling hash with character verification.

  • The technique dramatically reduces repeated work and is a common interview topic at companies like Amazon, Microsoft, Google, Oracle, Walmart, and Wells Fargo.


Sunday, 26 July 2026

Unknown fields should be preserved, not destroyed - encoding json,avro,protobuff etc

 Excellent question. This is one of the most frequently asked Architect interview questions from DDIA.

The statement:

"Unknown fields should be preserved, not destroyed."

is true for Protocol Buffers (modern implementations) but not generally true for Avro. This is a key difference that interviewers often test.


First understand the problem

Suppose we have three services:

Order Service (V2)
        |
        |  orderId, amount, couponCode
        |
        V
Kafka
        |
        V
Inventory Service (V1)

V2 sends

{
  "orderId":101,
  "amount":1000,
  "couponCode":"NEW100"
}

Inventory Service V1 knows only

orderId
amount

It doesn't know couponCode.


What happens in Avro?

Assume the schemas are:

Producer Schema (V2)

{
  "type":"record",
  "name":"Order",
  "fields":[
    {"name":"orderId","type":"int"},
    {"name":"amount","type":"double"},
    {"name":"couponCode","type":["null","string"],"default":null}
  ]
}

Consumer Schema (V1)

{
  "type":"record",
  "name":"Order",
  "fields":[
    {"name":"orderId","type":"int"},
    {"name":"amount","type":"double"}
  ]
}

During deserialization,

Avro matches fields by name.

It reads

orderId ✔

amount ✔

couponCode ❌ Unknown

Unknown field is simply ignored.

The consumer object becomes

Order

orderId = 101

amount = 1000

couponCode never exists inside the Java object.


If Inventory republishes the message

Suppose

Producer

↓

Kafka

↓

Inventory

↓

Kafka Again

Inventory reads

orderId

amount

and writes again.

The new message becomes

{
  "orderId":101,
  "amount":1000
}

The field

couponCode

is lost.

This is because Avro does not automatically preserve unknown fields during normal deserialize → serialize cycles.


Why?

Avro is schema resolution based.

It creates an object using only fields present in the reader schema.

Unknown fields are discarded during deserialization.

So they cannot be written back later unless the application explicitly carries them along.


Protocol Buffers behave differently

Protocol Buffers internally stores unknown fields.

Imagine

Producer

↓

Protobuf Binary

↓

Consumer

Consumer understands

orderId

amount

Unknown

couponCode

is kept inside an internal UnknownFieldSet.

The object looks conceptually like:

Order

orderId

amount

UnknownFieldSet

     |

couponCode

If the consumer forwards the message,

serialize()

includes

couponCode

again.

Nothing is lost.


Visual Comparison

Avro

Producer V2

↓

orderId
amount
couponCode

↓

Consumer V1

↓

Reads

orderId

amount

↓

couponCode discarded ❌

↓

Writes again

↓

orderId

amount

Protocol Buffers

Producer V2

↓

orderId

amount

couponCode

↓

Consumer V1

↓

Reads

orderId

amount

↓

UnknownFieldSet

↓

couponCode preserved

↓

Serialize again

↓

orderId

amount

couponCode

How does Avro achieve compatibility then?

Avro's strength is schema evolution, not unknown field preservation.

It achieves compatibility using writer schema + reader schema resolution:

  1. The writer's schema (or its ID via a Schema Registry) is available.

  2. The reader uses its own schema.

  3. Avro resolves differences:

    • Matching fields are read.

    • New writer fields missing from the reader are ignored.

    • Reader fields missing from the writer use default values (if defined).

  4. The application works without errors, but unknown fields are not retained after deserialization.


Real-world Kafka Example

Order Service V2

↓

Kafka

↓

Payment Service V1

↓

Notification Service V2

If Payment Service only validates payment and republishes the event:

  • With Avro: If it deserializes into a V1 object and reserializes, couponCode is lost unless the application preserves it explicitly.

  • With Protocol Buffers: The unknown field is retained automatically and forwarded.


Architect Interview Answer ⭐

Question: How does Avro preserve unknown fields?

Answer:

Avro generally does not preserve unknown fields during a deserialize–serialize cycle. It resolves schema differences using the writer and reader schemas, ignoring fields that the reader doesn't know. This enables schema compatibility, but unknown fields are discarded unless the application explicitly carries them forward. In contrast, Protocol Buffers stores unknown fields internally (using an UnknownFieldSet in many implementations) so they can be reserialized without loss.

This distinction between schema compatibility (Avro) and unknown field preservation (Protocol Buffers) is an important concept for distributed systems and frequently comes up in Senior Java Architect interviews.

SOLID principles with Design Patterns

 

SOLID + Design Patterns (Java Examples)

One of the most common Java interview questions is:

Which Design Patterns follow which SOLID Principle?

The answer is that design patterns are practical implementations of SOLID principles.


SOLID vs Design Patterns

SOLID PrincipleDesign PatternsWhy?
SRPFacade, DAO, Repository, Service LayerOne class = One responsibility
OCPStrategy, Decorator, Template MethodAdd new behavior without changing existing code
LSPFactory Method, Template MethodChild classes can replace parent classes
ISPAdapter, BridgeSmall focused interfaces
DIPFactory, Abstract Factory, Dependency Injection, BuilderDepend on interfaces, not implementations

1. SRP + DAO Pattern

❌ Bad Design

class Employee {

    void calculateSalary(){}

    void saveEmployee(){}

    void sendEmail(){}
}

Three responsibilities.


✅ Good Design

class EmployeeService {

    void calculateSalary(){}
}
class EmployeeRepository {

    void save(Employee e){}
}
class EmailService {

    void sendMail(){}
}

Pattern Used

Controller
      |
      V
Service
      |
      V
Repository (DAO)

Every class has one responsibility.


2. OCP + Strategy Pattern

Suppose Flipkart offers different payment methods.

Instead of:

if(payment.equals("UPI"))

if(payment.equals("CARD"))

if(payment.equals("NETBANKING"))

Use Strategy.


Strategy Interface

interface PaymentStrategy {

    void pay(double amount);

}

UPI Strategy

class UpiPayment implements PaymentStrategy {

    public void pay(double amount){

        System.out.println("UPI Payment");

    }

}

Card Strategy

class CardPayment implements PaymentStrategy {

    public void pay(double amount){

        System.out.println("Card Payment");

    }

}

Client

class PaymentService {

    private PaymentStrategy payment;

    PaymentService(PaymentStrategy payment){

        this.payment = payment;

    }

    void checkout(){

        payment.pay(1000);

    }

}

Adding Wallet Payment?

Just create

class WalletPayment implements PaymentStrategy{}

No existing code changes.

OCP achieved.


3. OCP + Decorator Pattern

Imagine Coffee Shop.

Base Coffee

interface Coffee {

    String getDescription();

}

Simple Coffee

class SimpleCoffee implements Coffee {

    public String getDescription(){

        return "Coffee";

    }

}

Decorator

class MilkDecorator implements Coffee{

    private Coffee coffee;

    MilkDecorator(Coffee coffee){

        this.coffee=coffee;

    }

    public String getDescription(){

        return coffee.getDescription()+" + Milk";

    }

}

Usage

Coffee coffee = new MilkDecorator(
                    new SimpleCoffee());

System.out.println(coffee.getDescription());

Output

Coffee + Milk

Want Sugar?

Create

SugarDecorator

No modification.

Only extension.


4. LSP + Factory Method

Vehicle example

abstract class Vehicle{

    abstract void start();

}
class Car extends Vehicle{

    void start(){

        System.out.println("Car Started");

    }

}
class Bike extends Vehicle{

    void start(){

        System.out.println("Bike Started");

    }

}

Client

Vehicle vehicle = VehicleFactory.create("CAR");

vehicle.start();

Client never worries whether it is Car or Bike.

Any subclass works.

LSP satisfied.


5. ISP + Adapter Pattern

Suppose Printer

Bad Interface

interface Printer{

    print();

    scan();

    fax();

}

Old Printer

Needs only print.

Forced to implement scan().

Violation.


Split Interfaces

interface Printable{

    void print();

}
interface Scannable{

    void scan();

}

Adapter

class OldPrinterAdapter implements Printable{

    OldPrinter printer;

    public void print(){

        printer.print();

    }

}

Only required methods implemented.


6. DIP + Factory Pattern

Bad

class NotificationService{

    EmailSender sender=new EmailSender();

}

Tightly coupled.


Better

interface MessageSender{

    void send();

}
class EmailSender implements MessageSender{}
class SmsSender implements MessageSender{}

Factory

class SenderFactory{

    static MessageSender getSender(String type){

        if(type.equals("EMAIL"))
            return new EmailSender();

        return new SmsSender();

    }

}

Client

MessageSender sender =
        SenderFactory.getSender("EMAIL");

sender.send();

NotificationService depends only on

MessageSender

DIP achieved.


7. DIP + Spring Dependency Injection ⭐

Without Spring

class OrderService{

    MySqlRepository repo =
            new MySqlRepository();

}

Coupled.


With Spring

interface OrderRepository{

    void save();

}
@Repository
class MySqlRepository
implements OrderRepository{

}
@Service
class OrderService{

    private final OrderRepository repo;

    OrderService(OrderRepository repo){

        this.repo=repo;

    }

}

Spring injects implementation automatically.

This is Dependency Inversion in action.


8. SRP + Builder Pattern

Instead of

Employee e = new Employee(
    "Ramesh",
    30,
    "Architect",
    "Bangalore",
    true,
    150000,
    "Java");

Use Builder

Employee employee = Employee.builder()
        .name("Ramesh")
        .age(30)
        .city("Bangalore")
        .designation("Architect")
        .salary(150000)
        .build();

Builder focuses only on object creation.

Employee focuses only on business data.

SRP improved.


SOLID + Spring Boot Mapping

Spring Boot FeatureSOLID PrinciplePattern
@ServiceSRPService Layer
@RepositorySRPRepository
Dependency InjectionDIPIoC
@Autowired ConstructorDIPDependency Injection
Strategy Bean SelectionOCPStrategy
JpaRepositoryISPRepository Interface
Bean PolymorphismLSPFactory
RestTemplateBuilderSRPBuilder
ResponseEntityOCPBuilder

Complete Mapping

Design PatternSOLID Principle
StrategyOCP
DecoratorOCP
Factory MethodDIP + LSP
Abstract FactoryDIP
BuilderSRP
AdapterISP
BridgeISP + DIP
ObserverOCP
CommandOCP + DIP
Template MethodOCP + LSP
ProxyOCP
FacadeSRP
RepositorySRP
Singleton(Not directly tied to SOLID; often overused and can violate SRP/DIP if misapplied)

Interview Cheat Sheet

Interview QuestionBest Answer
Which pattern best demonstrates OCP?Strategy and Decorator
Which pattern demonstrates DIP?Factory, Abstract Factory, Dependency Injection
Which pattern uses LSP?Factory Method, Template Method
Which pattern follows ISP?Adapter, Bridge
Which pattern improves SRP?Repository, Facade, Builder, Service Layer

Tip for Senior Java Architect Interviews

Rather than memorizing mappings, explain the reasoning:

  • Strategy supports OCP because you add new algorithms without modifying existing client code.

  • Decorator supports OCP by extending behavior dynamically instead of changing the original class.

  • Factory and Dependency Injection support DIP by ensuring clients depend on interfaces rather than concrete implementations.

  • Repository and Service Layer encourage SRP by separating persistence, business logic, and presentation concerns.

  • Adapter supports ISP by exposing only the operations a client actually needs.

  • Template Method supports LSP because subclasses provide specialized behavior while preserving the base class contract.

This explanation demonstrates a deeper understanding than simply listing patterns against SOLID principles, which is often what interviewers for Senior Architect roles look for.

Saturday, 20 June 2026

Blocking IO vs Non-Blocking IO Concepts

1. CPU Internal Model & Thread Context Switching

How CPU Executes Instructions

A CPU continuously performs:

+---------+    +---------+    +---------+
| Fetch   | -> | Decode  | -> | Execute |
+---------+    +---------+    +---------+

Fetch

CPU fetches instructions from memory/cache.

Decode

CPU understands what operation must be performed.

Execute

CPU executes the instruction.


What is a Thread Context Switch?

Assume CPU is executing Thread-A.

CPU
 |
 +--> Thread-A Running

Suddenly a higher-priority thread arrives.

CPU
 |
 +--> Save Thread-A State
 |
 +--> Load Thread-B State
 |
 +--> Execute Thread-B

The CPU must save:

  • Program Counter (PC)

  • Registers

  • Stack Pointer

  • Thread State

Then load another thread's state.

Cost of Context Switching

Context Switch

Save Current Thread
        +
Load New Thread
        +
CPU Cache Disturbance
        +
Scheduler Overhead

Result:

More Threads
      ↓
More Context Switches
      ↓
CPU Wastage
      ↓
Lower Throughput

This is why high-performance systems try to minimize unnecessary threads.


2. Why Redis Uses Single Thread

Redis is famous for using a mostly single-threaded event loop.

Traditional Multi-thread Model

Request-1 --> Thread-1
Request-2 --> Thread-2
Request-3 --> Thread-3
Request-4 --> Thread-4

Problem:

Many Threads
      ↓
Many Context Switches
      ↓
CPU Overhead

Redis Model

Request-1
Request-2
Request-3
Request-4
      |
      v
+----------------+
| Single Event   |
| Loop Thread    |
+----------------+

Benefits:

  • No thread synchronization

  • Minimal context switching

  • Predictable latency

  • Better CPU cache utilization


CPU Cache Locality

Linked List

Node-A --> Node-B --> Node-C --> Node-D

Memory:

A ----- far ----- B ----- far ----- C

CPU keeps jumping in RAM.


Array-Based Structure

[A][B][C][D][E]

Memory is contiguous.

CPU Fetch
     ↓
Cache Line Loaded
     ↓
Multiple Elements Available

Advantages:

  • Better cache hit rate

  • Less RAM access

  • Faster execution

This principle is used heavily in Redis internals. (rameshvanka.blogspot.com)


3. Blocking I/O Architecture

What is Blocking I/O?

A thread waits until data becomes available.

Flow

Client
   |
   v
Socket Created
   |
   v
Dedicated Thread Assigned
   |
   v
Waiting For Data
   |
   v
Thread Blocked
   |
   v
Data Arrives
   |
   v
Process Request
   |
   v
Response

Example

Suppose 10,000 clients connect.

10,000 Clients
      ↓
10,000 Sockets
      ↓
10,000 Threads

Most threads are doing:

Waiting...
Waiting...
Waiting...
Waiting...

CPU is not busy.

Memory is wasted.


Blocking I/O Diagram

Client-1 ---> Thread-1 ---> Waiting
Client-2 ---> Thread-2 ---> Waiting
Client-3 ---> Thread-3 ---> Waiting
Client-4 ---> Thread-4 ---> Waiting

Problems:

  • High memory usage

  • Context switching overhead

  • Limited scalability

Traditional Tomcat thread-per-request model largely follows this pattern. (rameshvanka.blogspot.com)


4. Non-Blocking I/O Architecture

Core Idea

Don't dedicate a thread per socket.

Instead:

One Thread
      ↓
Monitor Many Sockets
      ↓
Process Only Ready Sockets

Event Loop Model

            +----------------+
Socket-1 -->|                |
Socket-2 -->| Event Loop     |
Socket-3 -->| (Poller)       |
Socket-4 -->|                |
            +----------------+
                     |
                     v
          Ready Socket Found
                     |
                     v
             Worker Executes

Detailed Flow

Client Request
       |
       v
Socket Registered
       |
       v
Selector/Poller
       |
       v
Data Available?
   |
   +-- No --> Continue Monitoring
   |
   +-- Yes
          |
          v
    Worker Thread
          |
          v
    Business Logic
          |
          v
      Response

5. Selector Pattern (Java NIO)

Java NIO introduced:

Selector
Channel
Buffer

Architecture:

SocketChannel-1
SocketChannel-2
SocketChannel-3
SocketChannel-4
       |
       v
    Selector
       |
       v
Ready Events
       |
       v
Worker Pool

One selector can monitor thousands of connections.


6. Blocking vs Non-Blocking Comparison

FeatureBlocking IONon-Blocking IO
Thread per socketYesNo
Memory usageHighLow
Context switchingHighLow
ScalabilityLimitedVery High
Idle thread wastageHighVery Low
Suitable forSmall systemsLarge-scale systems
ExampleTraditional Servlet/TomcatNetty, Node.js, Vert.x

7. Where Non-Blocking IO Fails

Your note is correct but can be explained better.

Non-blocking IO is excellent for:

IO Bound Work

Examples:

  • Database calls

  • Network calls

  • API calls

  • Messaging


Problem: CPU Intensive Tasks

Image Processing
Video Encoding
AI Inference
Complex Calculations
Encryption

If a single event-loop thread does this:

Event Loop
     |
     +--> Heavy CPU Task

Then:

Event Loop Blocked
      ↓
Cannot Accept New Requests
      ↓
Performance Collapse

Correct Modern Architecture

            Event Loop
                 |
                 v
          Ready Request
                 |
                 v
        Worker Thread Pool
                 |
                 v
         CPU Intensive Work
                 |
                 v
             Response

This is exactly what frameworks like Netty, Spring WebFlux, Vert.x, and Node.js ecosystems follow.


Interview Summary (One-Line Version)

Blocking IO:
One Socket -> One Thread -> Wait For Data

Non-Blocking IO:
Many Sockets -> One Event Loop -> Process Only Ready Events
Blocking IO optimizes programming simplicity.

Non-Blocking IO optimizes scalability and resource utilization.

This version would be more accurate for senior Java Architect/System Design interviews and aligns with modern Java NIO, Netty, Spring WebFlux, and Redis architecture concepts.

In multi thread env, thread context switch will be take more time for the cpu.

   

Reference:





Instead of multi thread, single thread is best for we wil save time and fast due to saving time of thread context switch

Redis

Redis using internally arraylist, datastructure which will store content side by side, instead of linkedlist, due to this cpu will fetch set of instructions fetch phase, store them those instructions instruction cache, due to this CPU will save cycle times.due to cpu will not goes to RAM instead it will fetch instructiosn from instruction cache only.
------------


Above diagram clearly explain the when request comes, one socket will be created, then for that corresponding socket tomcat will create the thread, thread will wait until the socket will have data, thread is blocked until the socket fulled, due to this - threads wasting the user space due to blocking nature.



In the Single Thread Model with Non-block IO with event loop, it will reads the sockets full, it will handle multple requests, where as tomcat instance can't handle multiple requests.


Note: Single Thread IO - if CPU intension task this single thread model will fail.