System Design — Từ Monolith Đến Distributed System
Các khái niệm system design cơ bản: load balancing, caching, database sharding, CAP theorem và hơn thế nữa.
System design là kỹ năng thiết kế hệ thống đáp ứng được hàng triệu user. Không chỉ viết code — mà là cách các thành phần giao tiếp, scale, chịu lỗi.
CAP Theorem#
Một distributed system chỉ có thể đáp ứng 2 trong 3:
| Tính chất | Ý nghĩa |
|---|---|
| Consistency | Mọi node thấy cùng dữ liệu tại một thời điểm |
| Availability | Mọi request đều nhận được response (không nhất thiết là data mới nhất) |
| Partition Tolerance | Hệ thống vẫn hoạt động khi mạng bị chia cắt |
Thực tế: Partition luôn xảy ra → bạn chọn CP hoặc AP:
- CP (Consistency + Partition): Bank, payment — ưu tiên đúng hơn có
- AP (Availability + Partition): Social media, CDN — ưu tiên có hơn đúng
Load Balancer#
Phân phối request đến nhiều server:
User → Load Balancer → [App Server 1]
[App Server 2]
[App Server 3]plaintextCác thuật toán:#
- Round Robin — tuần tự
- Least Connections — server ít kết nối nhất
- IP Hash — cùng IP đến cùng server (session persistence)
Công cụ: Nginx, HAProxy, AWS ALB, Cloudflare Load Balancer#
Caching#
Lưu dữ liệu hay dùng ở nơi truy xuất nhanh:
Client → CDN (static)
↓
Load Balancer → App Server → Cache (Redis) → DatabasetextCache Strategy#
Cache Aside (phổ biến nhất):
async function getUser(id: number) {
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;
}typescriptWrite Through — ghi DB xong, ghi luôn cache Write Behind — ghi cache trước, async ghi DB sau
Cache Eviction#
- TTL — tự động xoá sau thời gian
- LRU — xoá cái ít dùng nhất
- LFU — xoá cái dùng ít nhất
Database Scaling#
Read Replicas#
Write → Primary DB
Read → [Replica 1] [Replica 2] [Replica 3]textSharding — Chia Dữ Liệu Ngang#
Shard 1: user_id 1-10000
Shard 2: user_id 10001-20000
Shard 3: user_id 20001-30000textCách shard:
- Hash-based —
shard = hash(user_id) % N - Range-based — theo khoảng giá trị
- Directory-based — lookup table
Sharding Key — Quan Trọng Nhất#
Chọn key sai → một shard quá tải (hotspot), các shard khác rảnh.
Message Queue#
Xử lý bất đồng bộ, giảm tải cho server:
App → Queue (RabbitMQ/Kafka) → [Worker 1]
[Worker 2]
[Worker 3]textLợi ích:
- Decoupling — producer không cần biết consumer
- Buffering — chịu được traffic spike
- Retry — nếu worker fail, message quay lại queue
Rate Limiting#
Bảo vệ hệ thống khỏi abuse:
// Token Bucket với Redis
async function checkRateLimit(userId: string, limit: number, windowSec: number) {
const key = `ratelimit:${userId}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, windowSec);
}
if (current > limit) {
throw new Error('Rate limit exceeded');
}
}typescriptMonitoring & Observability#
Metrics (Prometheus) → Grafana
Logs (ELK/Loki)
Tracing (Jaeger/Tempo)
Alerts (PagerDuty/Slack)textRED Method (Microservices)#
- Rate — requests/second
- Errors — số lỗi
- Duration — thời gian xử lý
Ví Dụ — Thiết Kế URL Shortener#
Yêu cầu: Tạo short link, redirect, analytics
Traffic: 100M URLs/month, 1000 writes/sec, 10K reads/sec
Design:
1. Load Balancer → Nginx
2. App Server → Stateless, auto-scale
3. Cache → Redis (hot URLs), TTL 24h
4. DB → PostgreSQL, shard by hash(short_code)
5. Queue → Kafka cho analytics (async)
6. ID Generation → Snowflake (Twitter) cho unique ID
Storage:
- 100M URLs × 1KB = 100GB → 3 shard, mỗi shard 35GBplaintextChecklist Phỏng Vấn System Design#
- Hiểu yêu cầu — functional vs non-functional
- Tính toán — traffic, storage, bandwidth
- Data model — schema, database
- High-level design — components, diagram
- Deep dive — từng component chi tiết
- Bottleneck — single point of failure, scale
Kết Luận#
System design không phải “đáp án đúng” — nó là trade-off. Cache hay không cache? Queue hay sync? SQL hay NoSQL? Câu trả lời luôn là: “It depends”. Học cách phân tích trade-off là kỹ năng quan trọng nhất.