System DesignMastery
--Core Components — মূল বিল্ডিং ব্লক

Caching Strategy

Duration৪৫-৬০ মিনিট
LevelIntermediate
FocusPerformance Optimization
001Core Concept

Cache কী এবং কেন দরকার?

ধরুন আপনি প্রতিদিন অফিসে যান। রাস্তায় একটা দোকান আছে যেখান থেকে চা কিনো। আপনি কি প্রতিদিন দোকানদারকে নতুন করে বলুন "আমি চা খাই, চিনি কম দিও"? না — সে মনে রাখে। এটাই Cache — frequently used data কে database এর বদলে memory তে রেখে দ্রুত serve করা।

Technical ভাবে বলতে গেলে: Database থেকে data আনতে 100-500ms লাগে। Cache থেকে আনতে লাগে 1msএরও কম। পার্থক্যটা ৫০০ গুণ পর্যন্ত।

DEFINITION

Cache হলো একটি high-speed data storage layer যেখানে frequently accessed data temporarily store করা হয়, যাতে পরবর্তীবার সেই data এর request আসলে মূল database বা server এ না গিয়ে সরাসরি cache থেকে serve করা যায়।

Cache ছাড়া কী হয়?

ধরুন একটা news website। প্রতি second এ ১০,০০০ user "আজকের top news" দেখতে চাইছে। Cache ছাড়া = ১০,০০০ database queries প্রতি second। Database crash। Site down।

WITHOUT CACHE

User → App Server → Database

User → App Server → Database

User → App Server → Database

১০,০০০ queries/sec → DB CRASH 💥

WITH CACHE

User → Cache HIT ✅ (1ms)

User → Cache HIT ✅ (1ms)

User → Cache MISS → DB (1st time)

DB পায় মাত্র ১টা query 🚀

002Why It Matters

কেন Cache জানা দরকার?

PERFORMANCE

Database query: 100–500ms
Cache hit: <1ms
পার্থক্য ৫০০ গুণ। User দ্রুত response পায়, app fast মনে হয়।

COST SAVING

Facebook cache ব্যবহার করে লক্ষ কোটি টাকা বাঁচায়। Database server অনেক বেশি expensive। Cache server সস্তা এবং fast।

SCALABILITY

DB scale করা কঠিন এবং ব্যয়বহুল। Cache layer add করলেন DB এর load ৯০%+ কমে যায়। এতে কম DB server দিয়েই বেশি traffic handle করা যায়।

AVAILABILITY

Database down হলেও cache থেকে serve করা যায়। User হয়তো একটু পুরনো data দেখবেন, কিন্তু site up থাকবেন

Real Numbers — বিশ্বাস না হলে এগুলো দেখুন

  • Twitter: প্রতি second এ ৬,০০০+ tweets। Cache ছাড়া সম্ভবই না।
  • Amazon: ১০০ms delay = ১% sales drop। Cache মানে সরাসরি revenue।
  • Google: ৫০০ms slow = ২০% কম searches। Speed = user retention।
  • Facebook Memcached: ট্রিলিয়ন object cache করে প্রতিদিন। তাদের entire infrastructure cache-first।
003How It Works

Cache কীভাবে কাজ করে?

CACHE HIT ✅

Request আসলো → Cache check করুন → Data পানয়া গেলো → সাথে সাথে return করুন। Database touch করার দরকারই নেই।

Response time: <1ms

CACHE MISS ❌

Request আসলো → Cache check করুন → Data নেই → Database থেকে আনো → Cache এ রাখুন → Return করুন।

Response time: 100-500ms (DB query)

প্রথম request এ সবসময় cache miss হবে। কিন্তু তারপর থেকে same data এর request আসলে cache থেকে serve হবে। এই pattern টাই caching এর মূল শক্তি।

Cache Eviction Policies — Cache পূর্ণ হলে কোনটা বাদ দেবে?

Policyনামকীভাবে কাজ করেকখন ব্যবহার করুন
LRULeast Recently Usedসবচেয়ে পুরনো সময়ে access হওয়া item বাদ দিনসবচেয়ে common — general purpose
LFULeast Frequently Usedসবচেয়ে কম বার access হওয়া item বাদ দিনযখন popularity matter করে
TTLTime To Liveনির্দিষ্ট সময় পর automatically expire করুনNews, session, rate limiting
FIFOFirst In First Outযেটা আগে এসেছে সেটা আগে বাদ দিনSimple queue-like caching
004Cache Strategies

৪টি প্রধান Caching Pattern

01Cache-Aside (Lazy Loading)

Application নিজেই cache manage করে। Read করার সময়: cache check → miss হলে DB থেকে আনো → cache এ রাখুন → return করুন। এটাই সবচেয়ে popular pattern। Redis সাথে এই pattern সবচেয়ে বেশি দেখা যায়।

1. GET /user/123 → cache.get("user:123")

2. Cache MISS → db.query("SELECT * FROM users WHERE id=123")

3. cache.set("user:123", data, TTL=300)

4. Return data to user ✅

02Write-Through

Data write করার সময় cache এবং database দুটোতেই একসাথে write করুন। Cache সবসময় DB এর সাথে sync থাকে। Write একটু slow কিন্তু data always consistent। Banking, payment system এর জন্য ideal।

03Write-Behind (Write-Back)

Cache এ write করুন, DB তে পরে (asynchronously) write করুন। Write অনেক fast। কিন্তু cache crash হলে data loss হতে পারে। High-write throughput apps এর জন্য — gaming scores, analytics counters।

04Read-Through

Cache-Aside এর মতোই, কিন্তু এখানে cache নিজেই DB থেকে data load করে। Application শুধু cache কে জিজ্ঞেস করে। Cache miss হলে cache নিজেই DB থেকে আনে। Application code clean থাকে।

কোনটা কখন ব্যবহার করবেন?

Interview এ বলুন: Cache-Aside সবচেয়ে flexible — read-heavy apps এর জন্য best। Write consistency দরকার হলে Write-Through। Speed critical হলে Write-Behind (data loss risk মাথায় রেখে)।
PatternRead SpeedWrite SpeedConsistencyBest For
Cache-AsideFastNormalEventualGeneral purpose, read-heavy
Write-ThroughFastSlowStrongBanking, payment systems
Write-BehindFastVery FastWeakGaming scores, analytics
Read-ThroughFastNormalEventualClean code, ORM-like usage
005Redis Deep Dive

Redis — এর ভেতরে কী আছে?

Redis (Remote Dictionary Server) হলো world এর most popular in-memory data store। এটা single-threaded হওয়া সত্ত্বেও 1 million+ operations/second handle করতে পারে। কারণ সব data RAM এ থাকে — disk I/O নেই।

Redis কেন Special?

  • In-Memory: সব data RAM এ — disk read নেই, তাই lightning fast
  • Rich Data Structures: শুধু key-value না — String, Hash, List, Set, Sorted Set, Stream
  • Persistence: RAM এ থাকলেও disk এ backup রাখতে পারে (RDB/AOF)
  • Atomic Operations: Race condition ছাড়াই counter বাড়াও, কমাও
  • Pub/Sub: Message broker হিসেবেও কাজ করে
  • Lua Scripting: Complex atomic operations লেখা যায়

Redis Data Structures — প্রতিটার নিজস্ব use case আছে

Data StructureKey CommandUse Case
StringGET, SET, INCR, DECRSimple cache, counters, session tokens
HashHSET, HGET, HGETALLUser profile, product details (object store)
ListLPUSH, RPUSH, LRANGEQueue, recent activity feed, job queue
SetSADD, SMEMBERS, SINTERUnique visitors, tags, friend lists
Sorted SetZADD, ZRANGE, ZRANKLeaderboard, priority queue, rate limiting

Redis Persistence — Data হারাবে না

RDB — Snapshot

নির্দিষ্ট interval এ (যেমন প্রতি ৫ মিনিটে) পুরো memory এর snapshot disk এ save করুন। Fast restart। কিন্তু last snapshot এর পর এর data হারাতে পারে। Default Redis behavior।

AOF — Append Only File

প্রতিটা write operation log এ রাখুন। Crash হলে log replay করে data recover করুন। More durable কিন্তু file বড় হয়। Critical data এর জন্য best।

Redis vs Memcached

বিষয়RedisMemcached
Data StructuresString, Hash, List, Set, Sorted Set, Streamশুধু String (key-value)
Persistenceআছে (RDB + AOF)নেই
Replicationআছে (Master-Replica)নেই
Pub/Subআছেনেই
Multi-threadingSingle-threaded (I/O multi-threaded v6+)Multi-threaded
কখন ব্যবহার করুনAlmost always — more featuresSimple cache, multi-thread দরকার হলে
006Code Examples

হাতে-কলমে Code

Redis এর basic operations থেকে শুরু করে production-ready Cache-Aside pattern এবং Rate Limiting পর্যন্ত — এই তিনটা code snippet আপনার interview এবং real project দুটোতেই কাজে আসবেন।

redis_basics.py
import redis
import json

# Redis connection
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

# ──────────────────────────────────────────────
# 1. STRING — Simple cache
# ──────────────────────────────────────────────
# Set with TTL (expires in 5 minutes)
r.set('user:123:name', 'Ripon Ahmed', ex=300)
name = r.get('user:123:name')   # 'Ripon Ahmed'

# ──────────────────────────────────────────────
# 2. HASH — Object store (User profile)
# ──────────────────────────────────────────────
r.hset('user:123', mapping={
    'name': 'Ripon Ahmed',
    'email': 'ripon@example.com',
    'age': '28'
})
user = r.hgetall('user:123')
# {'name': 'Ripon Ahmed', 'email': 'ripon@example.com', 'age': '28'}

# Single field পড়ো
email = r.hget('user:123', 'email')

# ──────────────────────────────────────────────
# 3. COUNTER — Atomic increment (race-condition safe)
# ──────────────────────────────────────────────
r.set('page:home:views', 0)
r.incr('page:home:views')   # 1
r.incr('page:home:views')   # 2
r.incrby('page:home:views', 10)  # 12

# ──────────────────────────────────────────────
# 4. SORTED SET — Leaderboard
# ──────────────────────────────────────────────
# Game leaderboard — player: score
r.zadd('leaderboard:game1', {
    'alice': 9500,
    'bob': 8700,
    'charlie': 9800,
    'diana': 7200
})

# Top 3 players (highest score first)
top3 = r.zrevrange('leaderboard:game1', 0, 2, withscores=True)
# [('charlie', 9800.0), ('alice', 9500.0), ('bob', 8700.0)]

# Alice এর rank (0-indexed)
rank = r.zrevrank('leaderboard:game1', 'alice')  # 1 (2nd place)
cacheService.js
const redis = require('redis');

// Redis client setup
const client = redis.createClient({
  url: process.env.REDIS_URL || 'redis://localhost:6379'
});
client.connect();

// ──────────────────────────────────────────────────────────────
// Cache-Aside Pattern — Production-ready implementation
// ──────────────────────────────────────────────────────────────
class CacheService {
  constructor(defaultTTL = 300) {  // Default 5 minutes TTL
    this.defaultTTL = defaultTTL;
  }

  // Generic cache-aside method
  async getOrSet(key, fetchFn, ttl = this.defaultTTL) {
    try {
      // 1. Cache check করুন
      const cached = await client.get(key);
      if (cached) {
        console.log(`Cache HIT: ${key}`);
        return JSON.parse(cached);
      }

      // 2. Cache miss — DB থেকে আনো
      console.log(`Cache MISS: ${key} — fetching from DB`);
      const data = await fetchFn();

      // 3. Cache এ রাখুন
      await client.setEx(key, ttl, JSON.stringify(data));

      return data;
    } catch (err) {
      // Cache fail হলেও app চলবে — DB থেকে directly serve করুন
      console.error('Cache error:', err);
      return fetchFn();
    }
  }

  // Cache invalidation
  async invalidate(key) {
    await client.del(key);
    console.log(`Cache invalidated: ${key}`);
  }

  // Pattern-based invalidation (e.g., all user:* keys)
  async invalidatePattern(pattern) {
    const keys = await client.keys(pattern);
    if (keys.length > 0) {
      await client.del(keys);
      console.log(`Invalidated ${keys.length} keys matching: ${pattern}`);
    }
  }
}

// ──────────────────────────────────────────────────────────────
// Express.js route তে ব্যবহার
// ──────────────────────────────────────────────────────────────
const cache = new CacheService();

app.get('/api/users/:id', async (req, res) => {
  const { id } = req.params;

  const user = await cache.getOrSet(
    `user:${id}`,
    () => db.query('SELECT * FROM users WHERE id = ?', [id]),
    600  // 10 minutes TTL
  );

  res.json(user);
});

// Write হলে cache invalidate করুন
app.put('/api/users/:id', async (req, res) => {
  const { id } = req.params;
  await db.query('UPDATE users SET ? WHERE id = ?', [req.body, id]);
  await cache.invalidate(`user:${id}`);  // Stale cache মুছে দিন
  res.json({ success: true });
});
rateLimiter.js
// ──────────────────────────────────────────────────────────────
// Rate Limiting with Redis — Sliding Window Algorithm
// ──────────────────────────────────────────────────────────────
// Redis এর atomic INCR + EXPIRE ব্যবহার করে race-condition ছাড়াই
// rate limiting implement করা যায়

async function rateLimiter(userId, limit = 100, windowSeconds = 60) {
  const key = `ratelimit:${userId}:${Math.floor(Date.now() / 1000 / windowSeconds)}`;

  // Atomic increment + TTL set (Lua script দিয়ে atomic করা)
  const script = `
    local current = redis.call('INCR', KEYS[1])
    if current == 1 then
      redis.call('EXPIRE', KEYS[1], ARGV[1])
    end
    return current
  `;

  const count = await client.eval(script, {
    keys: [key],
    arguments: [windowSeconds.toString()]
  });

  const remaining = Math.max(0, limit - count);
  const allowed = count <= limit;

  return { allowed, count, remaining, limit };
}

// Express middleware হিসেবে ব্যবহার
const rateLimitMiddleware = (limit = 100, window = 60) => {
  return async (req, res, next) => {
    const userId = req.user?.id || req.ip;
    const { allowed, remaining } = await rateLimiter(userId, limit, window);

    // Standard rate limit headers set করুন
    res.setHeader('X-RateLimit-Limit', limit);
    res.setHeader('X-RateLimit-Remaining', remaining);

    if (!allowed) {
      return res.status(429).json({
        error: 'Too Many Requests',
        message: `Rate limit exceeded. Try again in ${window} seconds.`
      });
    }

    next();
  };
};

// Route এ apply করুন
app.use('/api/', rateLimitMiddleware(100, 60));  // 100 req/min
app.use('/api/auth/', rateLimitMiddleware(5, 60));  // Login: 5 req/min
007Real World

Real World Use Cases

TWITTER — Timeline Cache

প্রতিটা user এর home timeline Redis এ cache করা আছে। যখন কেউ tweet করে, তখন তার followers এর cached timeline update হয়। এই "fan-out on write" approach ব্যবহার করে millisecond এ timeline serve করা হয়।

AMAZON — Product Cache

Popular product details, prices, inventory সব cache এ থাকে। ১০০ms delay = ১% sales drop — তাই Amazon এর entire product catalog heavily cached। Cache = direct revenue।

GAMING — Leaderboard

Redis Sorted Set দিয়ে real-time leaderboard। লক্ষ player এর score ZADD দিয়ে update, ZREVRANGE দিয়ে top players। O(log N) complexity — ১ মিলিয়ন players হলেও fast।

AUTH — Session Store

JWT token blacklist, session data, OTP সব Redis এ। TTL দিয়ে auto-expire। Stateless servers maintain করতে Redis session store অপরিহার্য।

Cache Stampede (Thundering Herd Problem)

কী হয়: একটা popular cache key expire হলো। একই সময়ে ১০,০০০ request আসলো। সবাই cache miss দেখলো। সবাই DB তে গেলো। Database crash।

সমাধান:

  • Mutex/Lock: প্রথম request DB থেকে আনবে, বাকিরা wait করবেন।
  • Probabilistic Early Expiration: Expiry আসার আগেই randomly refresh।
  • Stale-While-Revalidate: Expired data serve করুন, background এ refresh করুন।
008Interview Prep

Common Interview Questions

Q1: Cache Invalidation কীভাবে করবেন?

এটাই caching এর সবচেয়ে কঠিন সমস্যা।

  • TTL (Time-To-Live): সবচেয়ে সহজ — নির্দিষ্ট সময় পর auto-expire। Stale data কিছুক্ষণ থাকতে পারে।
  • Event-driven: Data update হলে সাথে সাথে cache delete করুন। Strong consistency কিন্তু extra code।
  • Write-Through: Write এর সময় cache এবং DB দুটোই update — সবসময় consistent।

Interview তে বলুন: "Use case ভেদে TTL + event-driven combination ব্যবহার করবো।"

Q2: Cache Hit Rate কীভাবে বাড়াবে?

Cache Hit Rate = (Cache Hits / Total Requests) × 100%। ভালো system এ ৯০%+ হওয়া উচিত।

  • ✅ TTL বাড়াও (কিন্তু staleness বাড়বে)
  • ✅ Cache size বাড়াও
  • ✅ Better eviction policy (LRU → LFU)
  • ✅ Cache key design উন্নত করুন — coarse grained keys
  • ✅ Hot data pre-warm করুন (app start এ popular data load করুন)

Q3: Redis single-threaded হয়েও কীভাবে এত fast?

এটা একটা common trap question। Redis slow না কারণ:

  • All in RAM: Disk I/O নেই — memory access nanosecond এ
  • No locking overhead: Single thread মানে mutex/lock এর complexity নেই
  • I/O Multiplexing: epoll/kqueue দিয়ে thousands of connections handle করে
  • Simple operations: GET/SET O(1) — complex computation নেই

Redis v6+ এ I/O threading add হয়েছে — এখন আরও fast।

Q4: Cache কোথায় রাখবে? (Cache Topology)

  • Client-side Cache: Browser cache, local memory। Fastest কিন্তু per-user।
  • CDN Cache: Static assets (images, JS, CSS) globally cache। Geographic latency কমায়।
  • Application Cache: In-process memory (Node.js Map, Python dict)। Ultra fast কিন্তু server restart এ হারায়।
  • Distributed Cache: Redis, Memcached। All servers share করে। Most common production choice।
  • Database Cache: Query result cache (MySQL query cache)। DB level এ।
009Lesson Summary

SUMMARY — আজকে যা শিখলাম

Conceptএক লাইনে
CacheFrequently accessed data memory তে রেখে দ্রুত serve করার layer
Cache HitCache এ data পানয়া গেছে — DB তে যেতে হয়নি
LRUসবচেয়ে পুরনো সময়ে ব্যবহার করা item evict করে — most common policy
Cache-AsideApplication নিজে cache manage করে — miss হলে DB থেকে আনে
Write-ThroughCache ও DB একসাথে update — consistent কিন্তু write slow
RedisIn-memory data store — 1M+ ops/sec, multiple data structures
TTLTime-To-Live — নির্দিষ্ট সময় পর cache auto-delete হয়
Cache StampedePopular key expire হলে একসাথে অনেক request DB তে যায় — dangerous
010Knowledge Check
011Assignments
012Practical Lab