Yêu cầu hệ thống
| Yêu cầu | Chi tiết |
|---|---|
| Chức năng | Xem sự kiện → Chọn chỗ → Thanh toán → Nhận vé |
| Concurrent users | 500.000 CCU lúc mở bán |
| Inventory | 50.000 vé, mỗi vé chỉ bán được 1 lần |
| Race condition | 100 người bấm cùng 1 vé → chỉ 1 người được |
| Thanh toán | Giữ vé 10 phút, hết hạn tự trả lại |
| SLA | 99.9% uptime, p99 latency < 2 giây |
Architecture overview
CDN → Load Balancer → API Gateway → Event Service + Booking Service + Payment Service. Database: PostgreSQL (booking) + Redis (inventory lock + session). Message queue: RabbitMQ/Kafka cho async events.
// File: architecture.txt
┌─────────────┐
Users ────────► │ CDN/WAF │
└──────┬──────┘
┌──────▼──────┐
│ Load Balancer│
└──────┬──────┘
┌──────▼──────┐
│ API Gateway │ (rate limit, auth)
└──────┬──────┘
┌───────────────┼───────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌──────▼──────┐
│ Event Svc │ │Booking Svc│ │Payment Svc │
└─────┬─────┘ └─────┬─────┘ └──────┬──────┘
┌─────▼─────┐ ┌─────▼─────┐ ┌──────▼──────┐
│ PostgreSQL │ │ Redis │ │ Payment GW │
└───────────┘ └───────────┘ └─────────────┘
Xử lý race condition trên vé
Khi 100 người bấm cùng 1 vé, chỉ 1 người được. Dùng Redis SETNX (set if not exist) để lock vé: người đầu tiên set thành công = được mua. Kết hợp TTL 10 phút để tự giải phóng nếu không thanh toán.
// File: booking.js
// Lock vé bằng Redis SETNX — atomic, chống race condition
async function reserveTicket(ticketId, userId) {
const lockKey = `ticket:${ticketId}:lock`
const HOLD_TIME = 600 // 10 phút
// SET NX: chỉ set nếu key chưa tồn tại
const acquired = await redis.set(lockKey, userId, {
NX: true, // Not eXists: chỉ set khi chưa có
EX: HOLD_TIME // tự xoá sau 10 phút
})
if (!acquired) {
// Vé đã bị người khác giữ
return { success: false, reason: "Vé đã có người đặt" }
}
// Tạo booking record trong PostgreSQL
await db.bookings.create({ ticketId, userId, status: 'pending' })
return { success: true, expiresIn: HOLD_TIME }
}
Trade-offs & decisions
| Quyết định | Lý do | Đánh đổi |
|---|---|---|
| Redis cho inventory lock | Atomic SETNX, nhanh < 1ms | Mất dữ liệu nếu Redis restart (dùng AOF persist) |
| PostgreSQL cho booking | ACID cho giao dịch tiền | Chậm hơn Redis, cần connection pool |
| Message queue cho notification | Email/SMS không cần realtime | Thêm complexity, cần dead letter queue |
| CDN cho trang sự kiện | Giảm 80% load lên origin | Cache invalidation khi hết vé |
Nhà tuyển dụng không cần bạn thiết kế hoàn hảo. Họ muốn thấy: (1) Hỏi đúng câu hỏi clarify, (2) Vẽ được architecture rõ ràng, (3) Giải thích trade-off (vì sao chọn, vì sao không), (4) Biết giới hạn thiết kế của mình.
❓ Dùng Redis SETNX để lock vé thay vì PostgreSQL SELECT FOR UPDATE vì sao?
- Vẽ được architecture diagram cho hệ thống lớn
- Xử lý race condition bằng Redis SETNX
- Thiết kế booking flow có TTL giữ vé
- Trình bày trade-off cho mỗi quyết định
- Sẵn sàng cho phỏng vấn System Design