Đặt vé sự kiện
Hiểu đề
Yêu cầu chức năng
- Người dùng có thể tìm và xem sự kiện (tên, nghệ sĩ, địa điểm, ngày)
- Người dùng có thể xem sơ đồ ghế và trạng thái ghế gần như real-time
- Người dùng có thể giữ tối đa 4 ghế trong 10 phút để thanh toán
- Người dùng có thể thanh toán và nhận vé điện tử (QR) qua email/app
Yêu cầu phi chức năng
- Không bán một ghế cho 2 người: 0 vé trùng, tính nhất quán mạnh ở bước giữ ghế
- Chịu được đột biến ×100 lúc mở bán, hệ thống không sập khi 2 triệu người chờ
- Giữ ghế p95 < 300 ms với người đã được vào trang chọn ghế
- Trạng thái ghế trên sơ đồ trễ tối đa 2-3 s
- Tìm kiếm sự kiện p95 < 200 ms, độ sẵn sàng 99,9%
Ngoài phạm vi: Bán lại vé / chợ vé thứ cấp; Công cụ cho ban tổ chức thiết kế sơ đồ ghế; Giá động theo nhu cầu; Chống phe vé bằng xác minh danh tính (chỉ nhắc ở mức giới hạn số vé).
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| GET | /v1/events?q=&city=&date= | Tìm sự kiện. Kết quả cache ngắn ở CDN. |
| GET | /v1/events/{id}/seats?since={version} | Sơ đồ ghế. Có since thì chỉ trả các ghế đổi trạng thái. |
| POST | /v1/events/{id}/queue | Vào phòng chờ, nhận QueueToken. Trạng thái vị trí đọc qua CDN. |
| POST | /v1/bookings | Giữ ghế: body {eventId, seatIds[]}, header Idempotency-Key và token đã được cho vào. Trả 409 nếu ghế đã bị giữ. |
| POST | /v1/bookings/{id}/pay | Tạo phiên thanh toán ở cổng (VNPay, MoMo...), trả URL chuyển hướng. |
| POST | /v1/payments/webhook | Cổng thanh toán gọi về. Xử lý idempotent theo mã giao dịch. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Người dùng có thể tìm và xem sự kiện
Trang sự kiện gần như tĩnh và được đọc rất nhiều, nhất là ngay trước giờ mở bán. Render sẵn và đẩy lên CDN, API chỉ trả dữ liệu động như số ghế còn lại.
- 1Client gọi GET /v1/events?q=... qua CDN
- 2CDN trả cache nếu còn hạn (TTL 30-60 s), hết hạn thì gọi Event API
- 3Event API truy vấn Postgres (full-text) hoặc search index, trả danh sách
- 4Trang chi tiết sự kiện là HTML/JSON tĩnh, cập nhật khi ban tổ chức sửa
Nguồn: Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · Content Consistency in CDN · mục "Time-to-live (TTL)" ; Grokking Modern System Design · ch.11 CDN ↗ · mục "Pull CDN" ; Seven Databases in Seven Weeks ↗ (.zip) · ch.2 PostgreSQL · §2.4 Day 3: Full-Text and Multidimensions (tr. 35) ; Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Inverted index" (Build a searchable index)
2) Người dùng có thể xem sơ đồ ghế gần real-time
Sơ đồ đầy đủ (vị trí ghế, giá) gần như không đổi nên cache ở CDN. Chỉ trạng thái ghế thay đổi, và gửi phần thay đổi đó qua SSE hoặc polling theo version.
- 1Client tải layout ghế tĩnh từ CDN
- 2Client gọi GET /seats để lấy trạng thái hiện tại kèm version
- 3Client mở kết nối SSE, nhận các sự kiện seat_held / seat_released / seat_sold
- 4Mất kết nối thì gọi lại /seats?since={version} để bù phần thiếu
Nguồn: Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · Content Consistency in CDN · mục "Time-to-live (TTL)" ; Grokking Modern System Design · ch.18 Pub-sub ↗ · mục "High-level design" ; Grokking Modern System Design · ch.36 WhatsApp ↗ · mục "Connection with a WebSocket server" ; Chưa có nguồn trong thư viện: SSE, CDN TTL của file trạng thái và hàng đợi ảo không có sách trong thư viện
3) Người dùng có thể giữ ghế trong 10 phút
Bước giữ ghế là chỗ tranh chấp duy nhất cần nhất quán mạnh. Dùng một câu UPDATE có điều kiện trên Postgres để đổi AVAILABLE sang HELD kèm hạn giữ, một ghế chỉ một người thắng.
- 1Client gửi POST /v1/bookings với seatIds và Idempotency-Key
- 2Booking API kiểm tra token phòng chờ và giới hạn 4 ghế/người
- 3UPDATE event_seats SET status='HELD', held_by=?, hold_expires_at=now()+10 phút WHERE id IN (...) AND (status='AVAILABLE' OR (status='HELD' AND hold_expires_at < now()))
- 4Số hàng cập nhật khác số ghế yêu cầu thì rollback, trả 409
- 5Thành công thì tạo Booking PENDING và phát sự kiện seat_held
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.3 Handling the lack of isolation · "Countermeasures for handling the lack of isolation" (semantic lock, tr. 128) ; Redis in Action ↗ · ch.4 · §4.4 Redis transactions (tr. 78) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95) ; Chưa có nguồn trong thư viện: câu UPDATE có điều kiện với hold_expires_at là thiết kế của khoá học
4) Người dùng có thể thanh toán và nhận vé
Thanh toán đi qua cổng bên ngoài nên chậm và có thể gọi webhook nhiều lần. Webhook xác nhận đơn trong một transaction, kèm bản ghi outbox để worker phát hành vé và gửi email.
- 1Client gọi /pay, nhận URL cổng thanh toán và chuyển hướng
- 2Cổng gọi POST /payments/webhook với mã giao dịch
- 3Trong 1 transaction: kiểm tra ghế vẫn HELD bởi đúng người, đổi sang SOLD, Booking sang PAID, ghi outbox
- 4Worker đọc outbox, sinh vé QR, gửi email/app
- 5Nếu hold đã hết hạn và ghế đã bán cho người khác thì hoàn tiền tự động
Nguồn: Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100) ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.6 Handling duplicate messages (tr. 95) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.2 Coordinating sagas (tr. 117–126) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability)
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
Trang bán vé của một ban tổ chức nhỏ. Hai instance Node sau CDN và một Postgres managed là đủ; bước giữ ghế dùng UPDATE có điều kiện ngay trên Postgres.
Ước lượng
- Bình thường: 10.000 DAU × 15 req / 86.400 ≈ 1,7 req/s
- Mở bán show 2.000 ghế: 5.000 người × 6 req trong 60 s = 30.000 / 60 = 500 req/s
- API: ceil(500 / (1.000 × 0,6)) = 1 → lấy tối thiểu 2 instance để dự phòng
- Giữ ghế: 2.000 vé bán trong 10 phút ≈ 2.000 / 600 ≈ 3,3 vé/s, tính cả lần thử thất bại ×5 ≈ 17 ghi/s
- Lưu trữ: 50 sự kiện/năm × 2.000 vé × 1 KB = 100.000 KB ≈ 100 MB/năm
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Examples of Resource Estimation" (Number of servers required, Storage requirements, Bandwidth requirements) ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3) ; Chưa có nguồn trong thư viện: hệ số đột biến mở bán (×100), 20.000 kết nối SSE mỗi instance là giả định của khoá học, cần load test
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| CDN | Cloudflare (gói free/pro) | 1 | Cache trang sự kiện, layout ghế, JS/CSS | Đỡ phần lớn lượt xem trước giờ mở bán, gần như miễn phí. |
| API | Node.js (NestJS), Docker trên Cloud Run | 2 | 2 vCPU · 4 GB RAM | 500 req/s chỉ cần 1 instance, chạy 2 để không chết khi 1 cái lỗi. |
| Database | PostgreSQL managed | 1 | 2 vCPU · 8 GB · 50 GB SSD | 17 ghi/s và dữ liệu 100 MB/năm là rất nhỏ; UPDATE có điều kiện đảm bảo không bán trùng. |
| Dọn ghế hết hạn | Cron job (Cloud Scheduler) mỗi phút | 1 | Chạy trong chính API | Đổi HELD hết hạn về AVAILABLE cho sơ đồ gọn; query đã tự coi hold hết hạn là trống. |
| Thanh toán | VNPay / MoMo | 1 | Dịch vụ bên ngoài | Không tự xử lý thẻ, chỉ nhận webhook. |
| Email vé | SendGrid / Amazon SES | 1 | Dịch vụ bên ngoài | Vài nghìn email mỗi sự kiện, gửi trực tiếp từ API là đủ. |
- Điểm xuất phát: một API, một Postgres, CDN phía trước
- Giữ ghế bằng status + hold_expires_at, chưa cần Redis
- Sơ đồ ghế cập nhật bằng polling 5 s là đủ cho 2.000 ghế
Khi một show hot kéo 100.000+ người vào cùng phút, API và Postgres nhận toàn bộ tải trực tiếp và sập. Cần phòng chờ để điều tiết lượng người vào luồng mua.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 10.000 DAU, sự kiện ≤ 2.000 ghế | 1 triệu DAU, show lớn 20.000 ghế | 5 triệu DAU; 2 triệu người cùng chờ một show 40.000 ghế |
| Chi phí | ≈ 50–150 USD/tháng (ước lượng) | ≈ 2.000–5.000 USD/tháng (ước lượng, tăng trong ngày mở bán) | ≈ 15.000–40.000 USD/tháng (ước lượng, chưa tính chi phí scale trong ngày mở bán) |
| CDN | Cloudflare (gói free/pro) 1 × Cache trang sự kiện, layout ghế, JS/CSS | Cloudflare / CloudFront 1 × Cache trang sự kiện, layout ghế, /seats 1 s | Chưa cần |
| API | Node.js (NestJS), Docker trên Cloud Run 2 × 2 vCPU · 4 GB RAM | Chưa cần | Chưa cần |
| Database | PostgreSQL managed 1 × 2 vCPU · 8 GB · 50 GB SSD | PostgreSQL managed (HA) 1 primary + 1 read replica × 4 vCPU · 16 GB · 200 GB SSD | PostgreSQL HA (Aurora / Cloud SQL), multi-AZ 1 primary + 2 replica × 8 vCPU · 32 GB · 500 GB SSD |
| Dọn ghế hết hạn | Cron job (Cloud Scheduler) mỗi phút 1 × Chạy trong chính API | Chưa cần | Chưa cần |
| Thanh toán | VNPay / MoMo 1 × Dịch vụ bên ngoài | Chưa cần | Chưa cần |
| Email vé | SendGrid / Amazon SES 1 × Dịch vụ bên ngoài | Chưa cần | Chưa cần |
| Phòng chờ | Chưa cần | Node.js + Redis sorted set 2–12 (autoscale) × 2 vCPU · 4 GB RAM | Chưa cần |
| Booking API | Chưa cần | Node.js (NestJS), container autoscale 2–9 (autoscale) × 2 vCPU · 4 GB RAM | Node.js trên Kubernetes, 3 AZ 17 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| SSE gateway | Chưa cần | Node.js SSE + Redis pub/sub 2 × 2 vCPU · 4 GB RAM | Node.js SSE, 3 AZ 3 pod × 2 vCPU · 4 GB mỗi pod |
| Redis | Chưa cần | ElastiCache / Memorystore 1 primary + 1 replica × 2 vCPU · 4 GB | Chưa cần |
| Queue | Chưa cần | Amazon SQS / Google Pub/Sub 1 × Managed, có dead-letter queue | Chưa cần |
| Worker | Chưa cần | Node.js consumer 2 × 1 vCPU · 2 GB RAM | Node.js consumer trên Kubernetes 6 pod × 1 vCPU · 2 GB mỗi pod |
| CDN + WAF | Chưa cần | Chưa cần | Cloudflare / CloudFront + bot management 1 × Cache trang, layout ghế, file vị trí hàng đợi |
| Queue service | Chưa cần | Chưa cần | Node.js trên Kubernetes 14 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| Redis Cluster | Chưa cần | Chưa cần | ElastiCache Redis Cluster 3 shard × (1 primary + 1 replica) × 2 vCPU · 8 GB mỗi node |
| Kafka | Chưa cần | Chưa cần | MSK / Confluent 3 broker × Topic bookings 6 partition, RF 3 |
| Search | Chưa cần | Chưa cần | OpenSearch 3 node × 8 vCPU · 32 GB mỗi node |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Giữ ghế trong lúc khách đang thanh toán (5-10 phút) thế nào để không bán trùng?
Khóa hàng ghế trong DB rồi mở transaction suốt thời gian khách ở trang cổng thanh toán. Đúng về mặt nhất quán nhưng mỗi khách chiếm một connection và một khóa trong nhiều phút.
Đánh đổi: Vài trăm người cùng thanh toán là cạn connection pool, khách bỏ ngang thì khóa treo đến khi timeout.
Nguồn: Redis in Action ↗ · ch.6 · §6.2 Distributed locking (khóa giữ lâu làm tắc nghẽn các client khác, tr. 116) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3 Handling the lack of isolation · "Countermeasures for handling the lack of isolation" (semantic lock, tr. 128)
Ai SET NX thành công thì giữ ghế, TTL 10 phút tự nhả khi khách bỏ đi. Nhanh, không chiếm connection DB, đây là mẫu khóa có timeout trong Redis in Action §6.2.5.
Đánh đổi: Redis failover có thể mất key vừa ghi nên hai người cùng giữ một ghế, và trạng thái ghế nằm ở hai nơi (Redis và DB) dễ lệch nhau.
Nguồn: Redis in Action ↗ · ch.6 · §6.2 Distributed locking · §6.2.5 Locks with timeouts (tr. 126) ; Grokking Modern System Design · ch.9 Databases ↗ · Data Replication · mục "Single leader/primary-secondary replication" và "Synchronous versus asynchronous replication" (failover bất đồng bộ có thể mất bản ghi cuối)
Nguồn sự thật là cột status và hold_expires_at trên hàng ghế. Một câu UPDATE ... WHERE status='AVAILABLE' OR hold đã hết hạn là nguyên tử, khóa hàng chỉ vài ms, ghế hết hạn được coi là trống ngay khi đọc nên không phụ thuộc job dọn. Đây là semantic lock (Microservices Patterns §4.3). Khi tranh chấp rất cao thì thêm SET NX trên Redis phía trước để loại bớt request thua cuộc trước khi chạm DB.
Đánh đổi: Phải kiểm tra lại hạn giữ ở bước xác nhận thanh toán và có luồng hoàn tiền cho trường hợp tiền về sau khi hold đã hết.
Nguồn: Microservices Patterns ↗ (.zip) · ch.4 · §4.3 Handling the lack of isolation · "Countermeasures for handling the lack of isolation" (semantic lock, tr. 128) ; Microservices Patterns ↗ (.zip) · ch.4 · §4.3 (tr. 126–132) ; Redis in Action ↗ · ch.6 · §6.2 Distributed locking · §6.2.5 Locks with timeouts (tr. 126) ; Chưa có nguồn trong thư viện: UPDATE ... WHERE status='AVAILABLE' OR hold hết hạn là thiết kế của khoá học
2) Lúc mở bán, 2 triệu người bấm F5 cùng lúc. Làm sao hệ thống sống sót?
Cho API tự scale theo CPU. Đột biến xảy ra trong vài giây còn instance mới cần 1-3 phút để lên, và DB thì không scale ngang kịp.
Đánh đổi: Hệ thống sập đúng lúc quan trọng nhất, người dùng bấm thử lại khiến tải còn tăng thêm.
Nguồn: Grokking Modern System Design · ch.4 Non-functional System Characteristics ↗ · Scalability · mục "Vertical scalability--scaling up" và "Horizontal scalability--scaling out" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3) (autoscaling cần 1-3 phút để bật máy mới)
Trang sự kiện, sơ đồ ghế, JS/CSS nằm hết trên CDN. Rate limit theo IP và user (token bucket, Grokking ch.19) ở gateway để chặn bot. Scale sẵn số instance 30 phút trước giờ G vì đã biết lịch.
Đánh đổi: Rate limit chỉ cắt bớt tải, người thật vẫn nhận lỗi 429 ngẫu nhiên và trải nghiệm không công bằng.
Nguồn: Grokking Modern System Design · ch.19 Rate Limiter ↗ · mục "Algorithms for rate limiting" (Token bucket algorithm) ; Grokking Modern System Design · ch.19 Rate Limiter ↗ · mục "Where to place the rate limiter" ; Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · Content Consistency in CDN · mục "Time-to-live (TTL)"
Mọi người vào phòng chờ, nhận QueueToken có số thứ tự (ai vào trước giờ mở bán thì được xáo trộn ngẫu nhiên cho công bằng). Hệ thống cho vào theo tốc độ cố định, ví dụ 2.000 người/phút, tính từ năng lực của Booking API. Vị trí hiện tại được công bố thành một file JSON nhỏ trên CDN, client đọc file đó nên origin gần như không chịu tải. Khi ghế đã hết thì báo cho cả hàng đợi ngay.
Đánh đổi: Thêm một thành phần phải vận hành và phải ký token chống làm giả; người ở cuối hàng đợi chờ lâu nhưng biết rõ vị trí.
Nguồn: Chưa có nguồn trong thư viện: SSE, CDN TTL của file trạng thái và hàng đợi ảo không có sách trong thư viện ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability) · mục "Rate limiting" (queue hấp thụ đột biến tải)
3) Sơ đồ ghế cập nhật real-time cho hàng chục nghìn người xem thế nào?
40.000 ghế × khoảng 30 B là 1,2 MB mỗi lần, nhân với hàng chục nghìn người mỗi giây. Băng thông và DB đều không chịu nổi.
Đánh đổi: Đơn giản nhất nhưng tốn kém nhất, chỉ ổn cho sự kiện vài trăm ghế.
Nguồn: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Bandwidth requirements" (cách tính băng thông) ; Chưa có nguồn trong thư viện: 30 B mỗi ghế là giả định của khoá học
Mỗi lần ghế đổi trạng thái thì tăng version của sự kiện. Client gọi /seats?since=v mỗi 3 s và chỉ nhận vài chục ghế vừa đổi. Response cache 1 s ở CDN nên nhiều người dùng chung một kết quả.
Đánh đổi: Trễ 1-3 s và vẫn có request rỗng khi không có gì đổi.
Nguồn: Grokking Modern System Design · ch.11 Content Delivery Network (CDN) ↗ · Content Consistency in CDN · mục "Time-to-live (TTL)" ; Chưa có nguồn trong thư viện: polling theo version là thiết kế của khoá học
Booking API publish seat_held/seat_sold lên kênh của sự kiện (pub/sub, Grokking ch.18). SSE gateway subscribe và đẩy xuống client. SSE là một chiều, chạy trên HTTP thường nên đi qua proxy dễ hơn WebSocket. Client mất kết nối thì gọi lại /seats?since=v.
Đánh đổi: Phải giữ hàng chục nghìn kết nối mở; nếu gateway quá tải thì giảm cấp về polling.
Nguồn: Grokking Modern System Design · ch.18 Pub-sub ↗ · mục "High-level design" ; Grokking Modern System Design · ch.36 WhatsApp ↗ · mục "Connection with a WebSocket server" ; Chưa có nguồn trong thư viện: SSE (Server-Sent Events) không có sách trong thư viện
4) Tìm kiếm sự kiện theo tên, nghệ sĩ, thành phố, ngày nên làm thế nào?
Không dùng được index nên quét cả bảng, không xử lý được tiếng Việt có dấu/không dấu (gõ 'son tung' vẫn phải ra 'Sơn Tùng').
Đánh đổi: Chạy được với vài nghìn sự kiện, chậm dần và kết quả kém.
Nguồn: Seven Databases in Seven Weeks ↗ (.zip) · ch.2 PostgreSQL · §2.4 Day 3: Full-Text and Multidimensions (tr. 35) (LIKE không tận dụng được chỉ mục toàn văn) ; Grokking Modern System Design · ch.9 Databases (mục "Types of NoSQL databases" đối chiếu) ↗ ; Chưa có nguồn trong thư viện: hành vi LIKE '%x%' quét toàn bảng là kiến thức chung
Tạo cột tsvector đã bỏ dấu, index GIN; pg_trgm cho gõ sai chính tả. Lọc city, date bằng index thường. Số sự kiện đang mở bán chỉ vài chục nghìn nên đủ nhanh.
Đánh đổi: Không cần thêm hệ thống; facet, xếp hạng theo độ phổ biến và gợi ý khi gõ thì làm khó.
Nguồn: Seven Databases in Seven Weeks ↗ (.zip) · ch.2 PostgreSQL · §2.4 Day 3: Full-Text and Multidimensions (tr. 35) ; Chưa có nguồn trong thư viện: pg_trgm, unaccent, GIN index là tiện ích riêng của PostgreSQL
Index sự kiện với analyzer asciifolding cho tiếng Việt, facet theo thành phố/thể loại/giá, geo theo khoảng cách, boost theo lượt xem. Thay đổi từ Postgres đi qua outbox/Kafka vào index.
Đánh đổi: Dữ liệu tìm kiếm trễ vài giây và thêm một cụm phải vận hành; chỉ đáng khi lượng tìm kiếm và yêu cầu lọc đủ lớn.
Nguồn: Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Inverted index" (Build a searchable index) ; Grokking Modern System Design · ch.21 Distributed Search ↗ · mục "Indexing" ; Microservices Patterns ↗ (.zip) · ch.3 · §3.3.7 Transactional messaging (tr. 97–100) ; Chưa có nguồn trong thư viện: analyzer asciifolding của Elasticsearch/OpenSearch (thư viện không có sách về Elasticsearch)
Trong phỏng vấn
- “Chỉ bước giữ ghế cần nhất quán mạnh; xem sự kiện, tìm kiếm, sơ đồ ghế đều chấp nhận trễ vài giây.”
- “Tôi giữ ghế bằng UPDATE có điều kiện trên status và hold_expires_at, ghế hết hạn tự được coi là trống.”
- “Tải của ngày mở bán do số người chờ quyết định, nên tôi đặt phòng chờ ảo và cho vào theo tốc độ cố định.”
- “Webhook thanh toán phải idempotent và kiểm tra lại hạn giữ ghế, có luồng hoàn tiền khi tiền về muộn.”
- Giữ transaction DB mở suốt lúc khách thanh toán.
- Dùng khóa Redis làm nguồn sự thật duy nhất cho ghế.
- Tin rằng autoscaling kịp đỡ đột biến xảy ra trong vài giây.
- Đẩy toàn bộ sơ đồ ghế mỗi giây cho mọi client.
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.