Caching Strategy
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 🚀
কেন 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।
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 | নাম | কীভাবে কাজ করে | কখন ব্যবহার করুন |
|---|---|---|---|
| LRU | Least Recently Used | সবচেয়ে পুরনো সময়ে access হওয়া item বাদ দিন | সবচেয়ে common — general purpose |
| LFU | Least Frequently Used | সবচেয়ে কম বার access হওয়া item বাদ দিন | যখন popularity matter করে |
| TTL | Time To Live | নির্দিষ্ট সময় পর automatically expire করুন | News, session, rate limiting |
| FIFO | First In First Out | যেটা আগে এসেছে সেটা আগে বাদ দিন | Simple queue-like caching |
৪টি প্রধান 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 থাকে।
কোনটা কখন ব্যবহার করবেন?
| Pattern | Read Speed | Write Speed | Consistency | Best For |
|---|---|---|---|---|
| Cache-Aside | Fast | Normal | Eventual | General purpose, read-heavy |
| Write-Through | Fast | Slow | Strong | Banking, payment systems |
| Write-Behind | Fast | Very Fast | Weak | Gaming scores, analytics |
| Read-Through | Fast | Normal | Eventual | Clean code, ORM-like usage |
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 Structure | Key Command | Use Case |
|---|---|---|
| String | GET, SET, INCR, DECR | Simple cache, counters, session tokens |
| Hash | HSET, HGET, HGETALL | User profile, product details (object store) |
| List | LPUSH, RPUSH, LRANGE | Queue, recent activity feed, job queue |
| Set | SADD, SMEMBERS, SINTER | Unique visitors, tags, friend lists |
| Sorted Set | ZADD, ZRANGE, ZRANK | Leaderboard, 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
| বিষয় | Redis | Memcached |
|---|---|---|
| Data Structures | String, Hash, List, Set, Sorted Set, Stream | শুধু String (key-value) |
| Persistence | আছে (RDB + AOF) | নেই |
| Replication | আছে (Master-Replica) | নেই |
| Pub/Sub | আছে | নেই |
| Multi-threading | Single-threaded (I/O multi-threaded v6+) | Multi-threaded |
| কখন ব্যবহার করুন | Almost always — more features | Simple cache, multi-thread দরকার হলে |
হাতে-কলমে Code
Redis এর basic operations থেকে শুরু করে production-ready Cache-Aside pattern এবং Rate Limiting পর্যন্ত — এই তিনটা code snippet আপনার interview এবং real project দুটোতেই কাজে আসবেন।
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)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 });
});// ──────────────────────────────────────────────────────────────
// 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/minReal 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 করুন।
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 এ।
SUMMARY — আজকে যা শিখলাম
| Concept | এক লাইনে |
|---|---|
| Cache | Frequently accessed data memory তে রেখে দ্রুত serve করার layer |
| Cache Hit | Cache এ data পানয়া গেছে — DB তে যেতে হয়নি |
| LRU | সবচেয়ে পুরনো সময়ে ব্যবহার করা item evict করে — most common policy |
| Cache-Aside | Application নিজে cache manage করে — miss হলে DB থেকে আনে |
| Write-Through | Cache ও DB একসাথে update — consistent কিন্তু write slow |
| Redis | In-memory data store — 1M+ ops/sec, multiple data structures |
| TTL | Time-To-Live — নির্দিষ্ট সময় পর cache auto-delete হয় |
| Cache Stampede | Popular key expire হলে একসাথে অনেক request DB তে যায় — dangerous |