System Design
Stateful vs Stateless arxitektura
Horizontal scaling uchun stateless bo’lish muhim. Lekin bu nima demak?
Stateful vs Stateless
Stateful
Server o’zida state (holat) saqlaydi:
Request 1: User login
Server 1: Session saqladi (memory/disk)
Request 2: Get profile
Server 1: Session'dan user_id oldi
Server 2: Session yo'q!
Muammo: User bitta serverga “bog’langan” (sticky).
Stateless
Server hech narsa saqlamaydi:
Request 1: User login
Server: JWT token qaytardi
Client: Token saqladi
Request 2: Get profile (+ token)
Server 1: Token'dan user_id oldi
Server 2: Token'dan user_id oldi
Foyda: Istalgan serverga yuborish mumkin.
Stateful tizim muammolari
1. Horizontal scaling qiyin
┌──────────────┐
│ Load Balancer│
└───────┬──────┘
│
┌────┴─────┐
▼ ▼
┌──────┐ ┌──────┐
│Srv 1 │ │Srv 2 │
├──────┤ ├──────┤
│User A│ │User B│ ← Har bir user bitta serverga bog'langan
│sess │ │sess │
└──────┘ └──────┘
Agar User A → Server 2 yuborilsa:
- Session yo’qoladi
- Logout bo’ladi
- Yomon UX
2. Server failure = data loss
┌──────┐
│Srv 1 │ ← Crash!
├──────┤
│User A│
│User B│ ← Ikkala user session yo'qoldi!
│User C│
└──────┘
3. Deployment qiyin
Server 1 yangilash kerak:
1. Server to'xtatish
2. Active userlar logout bo'ladi
3. Code deploy
4. Server qayta ishga tushirish
Zero-downtime deployment qiyin.
4. Auto-scaling ishlamaydi
CPU low → Scale down
Server 3 o'chirish kerak
Lekin Server 3'da 1000 active session!
Serverni bemalol o’chira olmaysiz.
Stateless tizimga o’tish
Yechim 1: Client-side storage
State client’da saqlash:
// Login
const token = jwt.sign({ userId: 123 }, SECRET);
res.json({ token });
// Client localStorage'da saqlaydi
localStorage.setItem('token', token);
// Keyingi requestlarda yuboradi
fetch('/api/profile', {
headers: { Authorization: `Bearer ${token}` }
});
JWT (JSON Web Token):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOjEyMywiaWF0IjoxNjQwOTk1MjAwfQ.
4Hb-tH_3zVx9Gx_XfHl8Pz_yxQ1wF_g8p3C
Server:
// Har qanday server verify qila oladi
const decoded = jwt.verify(token, SECRET);
console.log(decoded.userId); // 123
Stateless: Server hech narsa saqlamaydi
Yechim 2: Distributed cache
State external store’da:
┌──────────────┐
│ Load Balancer│
└───────┬──────┘
│
┌────┴─────┐
▼ ▼
┌──────┐ ┌──────┐
│Srv 1 │ │Srv 2 │ ← Stateless serverlar
└──┬───┘ └───┬──┘
└───┐ ┌───┘
▼ ▼
┌────────┐
│ Redis │ ← Session store
└────────┘
Code:
// Login - session Redis'da
app.post('/login', async (req, res) => {
const sessionId = uuid();
await redis.set(
`session:${sessionId}`,
JSON.stringify({ userId: 123 }),
'EX', 3600 // 1 hour
);
res.cookie('sessionId', sessionId);
});
// Profile - istalgan server
app.get('/profile', async (req, res) => {
const sessionId = req.cookies.sessionId;
const session = await redis.get(`session:${sessionId}`);
const { userId } = JSON.parse(session);
// ...
});
Stateless server + Stateful Redis
Yechim 3: Database session
Session database’da saqlash:
CREATE TABLE sessions (
id UUID PRIMARY KEY,
user_id INTEGER,
data JSONB,
expires_at TIMESTAMP
);
Kamchilik:
- Har requestda database query
- Sekin (50-100ms)
- Database yuklama
Redis yaxshiroq: 1-2ms, memory-based
JWT vs Session: Taqqoslash
JWT (Stateless)
// Server
const token = jwt.sign(
{ userId: 123, role: 'admin' },
SECRET,
{ expiresIn: '1h' }
);
// Client
localStorage.setItem('token', token);
// Har requestda
headers: { Authorization: `Bearer ${token}` }
Afzalliklari:
- To’liq stateless
- Server hech narsa saqlamaydi
- Horizontal scaling oson
- Microservices uchun ideal
Kamchiliklari:
- Token’ni bekor qilish qiyin
- Token o’g’irlansa himoyasiz (until expiry)
- Token katta bo’lsa har requestda overhead
Session (Stateful)
// Server
const sessionId = generateId();
await redis.set(`sess:${sessionId}`, { userId: 123 });
// Client
cookie: sessionId=abc123
// Har requestda
const session = await redis.get(`sess:${sessionId}`);
Afzalliklari:
- Bekor qilish oson (Redis’dan o’chirish)
- Logout instant
- Kichik cookie
Kamchiliklari:
- Redis kerak
- Har requestda Redis query
- Redis crash = barcha sessionlar yo’qoladi
Stateless Best Practices
1. Request’da barcha kerakli ma’lumot
// Bad: Server state'ga bog'liq
let uploadProgress = {};
app.post('/upload', (req, res) => {
uploadProgress[req.sessionId] = 0;
// ...
});
// Good: Har request mustaqil
app.post('/upload', (req, res) => {
const { fileName, chunkIndex, totalChunks } = req.body;
const progress = chunkIndex / totalChunks;
// ...
});
2. External state storage
Stateless:
- Serverlar (no state)
Stateful:
- Redis (session, cache)
- Database (permanent data)
- S3 (files)
3. Idempotent operations
Bir xil requestni qayta yuborish xavfsiz bo’lishi kerak:
// Bad: counter serverda
let counter = 0;
app.post('/increment', (req, res) => {
counter++;
res.json({ counter });
});
// Good: Database'da atomic
app.post('/increment', async (req, res) => {
const result = await db.query(
'UPDATE counters SET value = value + 1 WHERE id = $1 RETURNING value',
[1]
);
res.json({ counter: result.rows[0].value });
});
4. Sticky session’dan qochish
upstream backend {
ip_hash;
server srv1;
server srv2;
}
upstream backend {
server srv1;
server srv2;
}
Real-world misollar
Instagram (Stateless)
- Django serverlar: Stateless
- Memcached: Session storage
- PostgreSQL: User data
- Result: 1000+ server instances
Netflix (Stateless)
- Spring Boot microservices: Stateless
- EVCache (Memcached): Cache
- Cassandra: Permanent data
- Result: 100,000+ container instances
WhatsApp (Mostly Stateless)
- Erlang servers: Connection state (unavoidable)
- Mnesia: Distributed database
- FreeBSD servers: Message routing
Izoh: Real-time chat uchun WebSocket connection state kerak, lekin minimal.
Qachon Stateful qabul qilish mumkin?
Ba’zan stateful unavoidable:
1. WebSocket/Long polling
// WebSocket connection = stateful
const connections = new Map();
wss.on('connection', (ws, req) => {
const userId = getUserId(req);
connections.set(userId, ws);
});
// Message yuborish
function sendToUser(userId, message) {
const ws = connections.get(userId);
ws.send(message);
}
Yechim:
- Sticky session ishlatish
- Redis Pub/Sub bilan broadcast
2. File upload (multipart)
// Upload progress = stateful
const uploads = new Map();
app.post('/upload', upload.single('file'), (req, res) => {
// Temporary state
});
Yechim:
- Presigned URL (S3 direct upload)
- Chunked upload with resumable token
3. Real-time gaming
Game server:
- Player positions
- Game state
- Physics engine
← Bu state server'da bo'lishi shart
Yechim:
- Dedicated game servers
- Sticky routing
- Session migration on server crash
Xulosa
Stateful:
- Server o’zida state saqlaydi
- Horizontal scaling qiyin
- High availability qiyin
- Deployment qiyin
Stateless:
- Server hech narsa saqlamaydi
- Horizontal scaling oson
- High availability
- Zero-downtime deployment
Strategiya:
- Default: Stateless qiling
- State kerak bo’lsa: External store (Redis)
- Auth uchun: JWT yoki Redis session
- Real-time uchun: Sticky session + backup plan
Keyingi dars: CDN (Content Delivery Network) - global tezlik.