System Design
Cache invalidation strategiyalari
Cache’ning eng qiyin muammosi: qachon yangilash yoki o’chirish?
E-Tag (Entity Tag)
E-Tag — contentning “fingerprint”i (hash).
Client-side caching
GET /api/users/123
Response:
200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
{ "name": "Ali", "email": "..." }
Keyingi request:
GET /api/users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Response:
304 Not Modified (content o'zgarmagan)
Bandwidth tejash
Server’ga request kerak (latency)
Implementation
const crypto = require('crypto');
app.get('/api/users/:id', async (req, res) => {
const user = await db.getUser(req.params.id);
const etag = crypto
.createHash('md5')
.update(JSON.stringify(user))
.digest('hex');
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.setHeader('ETag', etag);
res.json(user);
});
TTL (Time-To-Live)
TTL — cache qancha vaqt yashaydi.
Adaptive TTL
// Ko'p o'zgaradigan data - qisqa TTL
cache.set('trending', data, { ttl: 60 }); // 1 min
// O'zgarmas data - uzoq TTL
cache.set('user:profile', data, { ttl: 3600 }); // 1 hour
// Static assets - juda uzoq
cache.set('logo.png', data, { ttl: 86400 * 365 }); // 1 year
Probabilistic Early Expiration
async function get(key, ttl) {
const { value, expiry } = await cache.get(key);
if (!value) {
// Cache miss
return await fetchFromDB();
}
// Probabilistic refresh
const timeLeft = expiry - Date.now();
const delta = Math.random() * timeLeft;
if (delta < 1000) { // 1 sekund qolganda
// Background refresh
refreshInBackground(key);
}
return value;
}
Foyda: Cache stampede oldini olish.
TTL Jitter
Muammo: Thundering herd
1000 users request → Cache miss (same time)
→ 1000 DB queries!
Yechim: Random TTL
// Bad: Barcha bir vaqtda expire
cache.set('popular-post', data, { ttl: 3600 });
// Good: Random jitter
const baseTime = 3600;
const jitter = Math.random() * 600; // ±5 min
cache.set('popular-post', data, { ttl: baseTime + jitter });
Cache Stampede Prevention
1. Cache Locking
const locks = new Map();
async function get(key) {
const cached = await cache.get(key);
if (cached) return cached;
// Check if someone is already fetching
if (locks.has(key)) {
await locks.get(key); // Wait
return cache.get(key);
}
// Acquire lock
const promise = fetchFromDB(key);
locks.set(key, promise);
try {
const data = await promise;
await cache.set(key, data);
return data;
} finally {
locks.delete(key);
}
}
2. Stale-While-Revalidate
// Serve stale, refresh background
async function get(key) {
const { value, isStale } = await cache.get(key);
if (value) {
if (isStale) {
// Return stale, refresh async
refreshInBackground(key);
}
return value;
}
return await fetchFromDB(key);
}
Cache Warming
Cold cache — birinchi requestlar sekin.
// Startup'da cache'ni to'ldirish
async function warmCache() {
const popularPosts = await db.query('SELECT * FROM posts ORDER BY views DESC LIMIT 100');
for (const post of popularPosts) {
await cache.set(`post:${post.id}`, post);
}
console.log('Cache warmed');
}
// Server start
await warmCache();
app.listen(3000);
Event-driven Invalidation
Database o’zgarganda cache invalidate qilish:
// Database trigger yoki message queue
db.on('user.updated', async (userId) => {
await cache.del(`user:${userId}`);
await cache.del(`user:${userId}:posts`);
});
// Update function
async function updateUser(id, data) {
await db.update(id, data);
await cache.del(`user:${id}`); // Invalidate
}
Cache Tagging
// Tag-based invalidation
await cache.set('post:1', data, { tags: ['user:123', 'category:tech'] });
await cache.set('post:2', data, { tags: ['user:123', 'category:design'] });
// Invalidate by tag
await cache.invalidateByTag('user:123'); // Ikkala post ham o'chiriladi
Multi-layer Cache
// L1: Memory (fast, small)
// L2: Redis (medium, large)
// L3: Database (slow, huge)
async function get(key) {
// L1
if (memoryCache.has(key)) return memoryCache.get(key);
// L2
const redisData = await redis.get(key);
if (redisData) {
memoryCache.set(key, redisData);
return redisData;
}
// L3
const dbData = await db.query(key);
redis.set(key, dbData, { ttl: 3600 });
memoryCache.set(key, dbData);
return dbData;
}
Best Practices
1. Monitor staleness
const age = Date.now() - cache.getTimestamp(key);
if (age > 60000) {
console.warn('Cache older than 1 min');
}
2. Version keys
const version = 'v2';
cache.set(`${version}:user:${id}`, data);
// Version o'zgarsa, eski cache'lar ignore
3. Namespace by deployment
const namespace = process.env.DEPLOYMENT_ID;
cache.set(`${namespace}:user:${id}`, data);
// Yangi deploy → yangi namespace
4. Graceful degradation
async function getUser(id) {
try {
return await cache.get(`user:${id}`);
} catch (err) {
console.error('Cache error, falling back to DB');
return await db.getUser(id);
}
}
Xulosa
E-Tag:
- Content fingerprint
- 304 Not Modified
- Bandwidth tejash
TTL:
- Adaptive (har xil data uchun)
- Jitter (stampede oldini olish)
- Probabilistic refresh
Invalidation:
- Event-driven (DB update → cache delete)
- Tagging (group invalidation)
- Multi-layer
Keyingi dars: Distributed Cache (Redis, Memcached).