System Design

Distributed Cache (Redis, Memcached)

Distributed cache — bir nechta serverlarda cache, yagona namespace.

Redis vs Memcached

FeatureRedisMemcached
Data structuresRich (list, set, hash)Key-value only
PersistenceDisk snapshotMemory only
ReplicationMaster-slaveNo
ClusteringBuilt-inClient-side
PerformanceFastFaster (simpler)
Max value size512 MB1 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

Twitter

- 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:

Patterns:

Performance:

Keyingi dars: Eventual Consistency va conflict resolution.