DDevArchive
Đăng nhập

Capstone: thiết kế hệ thống đặt vé 500.000 concurrent users

Bài cuối cùng của hành trình Zero → Architect: thiết kế hệ thống đặt vé sự kiện chịu tải 500K CCU. Bạn sẽ vẽ architecture diagram, chọn công nghệ, xử lý race condition trên vé, và trình bày trade-off — giống hệt vòng phỏng vấn System Design.

Yêu cầu hệ thống

Yêu cầuChi tiết
Chức năngXem sự kiện → Chọn chỗ → Thanh toán → Nhận vé
Concurrent users500.000 CCU lúc mở bán
Inventory50.000 vé, mỗi vé chỉ bán được 1 lần
Race condition100 người bấm cùng 1 vé → chỉ 1 người được
Thanh toánGiữ vé 10 phút, hết hạn tự trả lại
SLA99.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 địnhLý doĐánh đổi
Redis cho inventory lockAtomic SETNX, nhanh < 1msMất dữ liệu nếu Redis restart (dùng AOF persist)
PostgreSQL cho bookingACID cho giao dịch tiềnChậm hơn Redis, cần connection pool
Message queue cho notificationEmail/SMS không cần realtimeThêm complexity, cần dead letter queue
CDN cho trang sự kiệnGiảm 80% load lên originCache invalidation khi hết vé
🎯 Phỏng vấn System Design

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