System Design
Distributed Cache (Redis, Memcached)
Distributed cache — bir nechta serverlarda cache, yagona namespace.
Redis vs Memcached
| Feature | Redis | Memcached |
|---|---|---|
| Data structures | Rich (list, set, hash) | Key-value only |
| Persistence | Disk snapshot | Memory only |
| Replication | Master-slave | No |
| Clustering | Built-in | Client-side |
| Performance | Fast | Faster (simpler) |
| Max value size | 512 MB | 1 MB |
| Lua scripting |
Default choice: Redis (ko’proq features)
Redis Clustering
1. Single Instance (Development)
const redis = require('redis');
const client = redis.createClient();
await client.set('key', 'value');
const value = await client.get('key');
Oddiy
Single point of failure
2. Master-Replica (Production)
Master (writes)
/ | \
Replica Replica Replica (reads)
// Master connection (writes)
const master = redis.createClient({ host: 'master-ip' });
// Replica connection (reads)
const replica = redis.createClient({ host: 'replica-ip' });
await master.set('key', 'value'); // Write
const value = await replica.get('key'); // Read
High availability
Read scaling
3. Redis Sentinel (Auto-failover)
Sentinel 1
Sentinel 2
Sentinel 3
↓
Monitor Master
↓
Master fails → Promote replica
const redis = require('redis');
const client = redis.createClient({
sentinels: [
{ host: 'sentinel1', port: 26379 },
{ host: 'sentinel2', port: 26379 },
{ host: 'sentinel3', port: 26379 }
],
name: 'mymaster'
});
Automatic failover
Monitoring
4. Redis Cluster (Sharding)
Hash slot: 0-16383
Shard 1: slots 0-5460
Shard 2: slots 5461-10922
Shard 3: slots 10923-16383
const redis = require('redis');
const cluster = redis.createCluster({
rootNodes: [
{ host: 'node1', port: 6379 },
{ host: 'node2', port: 6379 },
{ host: 'node3', port: 6379 }
]
});
await cluster.set('key', 'value'); // Auto-routed
Horizontal scaling
High capacity
Redis Data Structures
String
await client.set('user:123', JSON.stringify({ name: 'Ali' }));
const user = JSON.parse(await client.get('user:123'));
// Atomic operations
await client.incr('counter'); // 1
await client.incrBy('counter', 5); // 6
Hash
await client.hSet('user:123', {
name: 'Ali',
email: 'ali@example.com',
age: '25'
});
const name = await client.hGet('user:123', 'name');
const user = await client.hGetAll('user:123');
List
// Queue
await client.lPush('queue', 'task1');
await client.lPush('queue', 'task2');
const task = await client.rPop('queue'); // FIFO
// Stack
await client.lPush('stack', 'item');
const item = await client.lPop('stack'); // LIFO
Set
await client.sAdd('tags', 'redis', 'cache', 'database');
const isMember = await client.sIsMember('tags', 'redis'); // true
const tags = await client.sMembers('tags'); // All members
// Set operations
await client.sUnion('tags1', 'tags2'); // Union
await client.sInter('tags1', 'tags2'); // Intersection
Sorted Set
// Leaderboard
await client.zAdd('leaderboard', [
{ score: 100, value: 'user1' },
{ score: 90, value: 'user2' },
{ score: 80, value: 'user3' }
]);
// Top 10
const top10 = await client.zRange('leaderboard', 0, 9, { REV: true });
// User rank
const rank = await client.zRevRank('leaderboard', 'user1'); // 0 (first)
Redis Persistence
RDB (Snapshot)
save 900 1 # 15 min, 1 change
save 300 10 # 5 min, 10 changes
save 60 10000 # 1 min, 10K changes
Compact
Data loss (last N minutes)
AOF (Append-Only File)
appendonly yes
appendfsync everysec
Har bir write command log’ga.
Minimal data loss
Larger files
Hybrid (Best)
save 900 1
appendonly yes
Caching Patterns with Redis
Cache-aside
async function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.getUser(id);
await redis.set(`user:${id}`, JSON.stringify(user), { EX: 3600 });
return user;
}
Session Store
// Express.js session
const session = require('express-session');
const RedisStore = require('connect-redis').default;
app.use(session({
store: new RedisStore({ client: redis }),
secret: 'secret',
resave: false,
saveUninitialized: false
}));
Rate Limiting
async function checkRateLimit(userId) {
const key = `ratelimit:${userId}`;
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, 60); // 1 minute window
}
if (count > 100) {
throw new Error('Rate limit exceeded');
}
}
Pub/Sub
// Publisher
await redis.publish('notifications', JSON.stringify({ message: 'New post' }));
// Subscriber
const subscriber = redis.duplicate();
await subscriber.subscribe('notifications', (message) => {
console.log('Received:', message);
});
Redis Performance Tips
1. Pipeline
// Bad: Multiple round-trips
await redis.set('key1', 'value1');
await redis.set('key2', 'value2');
await redis.set('key3', 'value3');
// Good: Single round-trip
await redis
.multi()
.set('key1', 'value1')
.set('key2', 'value2')
.set('key3', 'value3')
.exec();
2. Avoid large values
// Bad: 10 MB value
await redis.set('huge', hugeObject);
// Good: Split into chunks
await redis.hSet('data', {
chunk1: part1,
chunk2: part2,
chunk3: part3
});
3. Use connection pooling
const { createPool } = require('generic-pool');
const pool = createPool({
create: () => redis.createClient(),
destroy: (client) => client.quit()
}, { min: 2, max: 10 });
const client = await pool.acquire();
await client.set('key', 'value');
await pool.release(client);
Monitoring
redis-cli INFO
// Hit rate monitoring
const info = await redis.info('stats');
const hits = parseInt(info.match(/keyspace_hits:(\d+)/)[1]);
const misses = parseInt(info.match(/keyspace_misses:(\d+)/)[1]);
const hitRate = hits / (hits + misses);
console.log('Hit rate:', hitRate);
Real-world Examples
- Timeline cache: Redis
- 1000+ Redis instances
- Terabytes of cached data
GitHub
- Session store: Redis
- Background jobs: Resque (Redis-backed)
Stack Overflow
- Page cache: Redis
- Tag cache
- Hit rate: 95%+
Xulosa
Redis:
- Rich data structures
- Persistence
- Replication + clustering
- Most popular
Patterns:
- Cache-aside (default)
- Session store
- Rate limiting
- Pub/Sub
Performance:
- Pipeline commands
- Connection pooling
- Monitor hit rate
Keyingi dars: Eventual Consistency va conflict resolution.