Tổng kết case flash sale: thiết kế hoàn chỉnh và các cái giá
Ráp bảng ràng buộc, phương án ngây thơ và ba quyết định thành một thiết kế hoàn chỉnh, kèm bảng cái giá bạn đã chấp nhận ở từng bước.
TL;DR: Thiết kế hoàn chỉnh cho đợt bán 1.000 sản phẩm gồm ba lớp chồng lên nhau: chặn cửa và cờ hết hàng để giữ phần lớn người mua ở ngoài DB, giữ chỗ có hạn 10 phút để tồn kho bị bỏ quay về kho, và một câu UPDATE có điều kiện trên một row Postgres để chốt từng đơn vị. Không lớp nào miễn phí. Bài này gom cái giá của từng lớp vào một bảng, kèm số đo bạn đã tự lấy, để bạn nhìn được toàn bộ những gì mình đã chấp nhận.
Bạn bắt đầu từ một câu UPDATE và một nỗi lo mơ hồ. Qua sáu bài, nỗi lo đó thành các con số bạn tự đo. Bài này chỉ ráp các mảnh lại để bạn thử giải thích từng mảnh.
1. Ráp thành một thiết kế
Đi theo đường đi của một lượt mua, từ ngoài vào trong.
Người dùng đến cổng trước. Ở đó có phòng chờ hoặc hàng đợi, kèm cờ "hết hàng" trong bộ nhớ ứng dụng, để 50.000 lượt mỗi giây không biến thành 100 đến 1.000 kết nối bận cùng lúc, chạm hoặc vượt max_connections 100 (quyết định ở bài 05). Lượt nào qua cổng thì tạo một chỗ giữ có hạn 10 phút và trừ tồn kho trong cùng một transaction, với unique partial index trên (user_id, item_id) cho các chỗ còn hiệu lực (pending, paid) để mỗi người một chỗ (bài 04). Việc trừ là một câu UPDATE ... WHERE stock > 0, trả 0 row thì hết (bài 03). Chỗ nào quá hạn mà chưa trả tiền thì worker thu hồi trả hàng về kho, còn người trả tiền sau hạn là một quyết định riêng (hoàn tiền, thử trừ lại, hoặc gia hạn chỗ giữ) ở bài 04. Điều giữ cho cả chuỗi đúng là bất biến stock + chỗ pending + chỗ paid luôn bằng tồn kho ban đầu.
Phương án ngây thơ của bài 02 không có mặt trong thiết kế này, vì bất biến "tối đa 1.000 đơn" của nó nằm trong một phép đếm, không nằm trên row nào để khoá.

Mọi quyết định ở trên đều đứng trên bảng ràng buộc của bài 01: 1.000 sản phẩm, tải đỉnh khoảng 50.000 lượt mỗi giây, gấp chừng 40 lần trần một row (cỡ 1.200 lượt mỗi giây ở 50 kết nối). Đổi bảng đó thì phải xét lại thiết kế, đừng giữ thiết kế khi ràng buộc đã khác.
2. Bảng cái giá bạn đã chấp nhận
Cột "lựa chọn" chỉ đúng dưới ràng buộc chuẩn của case, không phải khuyến nghị chung.
| Bước | Lựa chọn | Cái giá | Số đo bằng chứng |
|---|---|---|---|
| Ràng buộc (bài 01) | Chốt bảng số, tải đỉnh là chuẩn | Tự đặt số, sai thì sai cả thiết kế | Tải đỉnh 50.000 lượt mỗi giây, gấp khoảng 40 lần trần một row |
| Ngây thơ (bài 02) | Bỏ: đếm đơn rồi ghi | Bán quá dù có transaction; Serializable giữ được nhưng huỷ nhiều | Bán quá +38 đến +49 ở Read Committed, +28 đến +45 ở Repeatable Read; Serializable 0 bán quá nhưng failed 2% đến 19% (mỗi lần chạy một số khác) |
| Cơ chế trên một row (bài 03) | UPDATE có điều kiện, một câu | Ghi vẫn xếp hàng sau khoá; thêm kết nối không thêm thông lượng | Ở 50 kết nối: UPDATE có điều kiện và FOR UPDATE xấp xỉ nhau (tỉ lệ 0,88 đến 1,00, trong nhiễu); optimistic version có 83% đến 91% lượt ghi trả 0 row |
| Thời gian giữ hàng (bài 04) | Giữ chỗ có hạn 10 phút | Phải nuôi worker thu hồi; trả tiền sau hạn thì chọn hoàn tiền, thử trừ lại hoặc gia hạn chỗ giữ (tồn kho bị giữ lâu hơn); thêm đợt tranh chấp hàng nóng | Trừ không hạn thì 30% × 1.000 = 300 sản phẩm bị giam vĩnh viễn |
| Tải đổ vào (bài 05) | Chặn cửa cộng cờ hết hàng | Người dùng chờ hoặc nhận phản hồi bất đồng bộ; cờ chỉ được sai theo hướng từ chối oan | Chia 64 shard chỉ nâng khoảng 8,3 lần trên máy 10 nhân của tác giả, chưa bằng 40 lần cần; kết nối cạn trước khi khoá row kịp thành nút cổ chai |
Đọc cột cuối theo hàng ngang, bạn thấy mỗi cái giá đều có một con số đứng sau. Khi ai đó hỏi "sao không dùng cách X", câu trả lời tốt là chỉ vào dòng tương ứng, không phải nói "vì nó phức tạp hơn".
Giữ chỗ là một lease: có hạn, hết hạn thì sản phẩm quay về kho. Cái giá đi kèm là worker thu hồi và người trả tiền sau hạn. Hai thứ này dễ bị quên khi tóm tắt thiết kế thành một sơ đồ.

3. Đổi ràng buộc, quyết định nào đổi?
Đổi một ràng buộc thường chỉ lật một quyết định, trừ trục giữ hàng, vì hết hạn giữ chỗ đẩy hàng về kho và làm hàng nóng nóng lại. Tự thử với ba tình huống dưới đây, rồi so với đáp án mẫu.
- Q1Nếu 30% người bỏ giữa chừng thành 80%, quyết định nào đổi?
- Q2Nếu thời hạn giữ chỗ 10 phút thành 30 giây, cái giá nào nặng thêm?
- Q3Nếu cho phép mỗi người mua 5 sản phẩm, bất biến nào phải viết lại?
Liên hệ các bài khác
- Bài 06: Đối chiếu thực tế nhắc rằng hệ thống thật chọn khác bạn vì ràng buộc của họ khác, không vì họ giỏi hơn. Bảng cái giá ở trên là công cụ để làm phép so sánh đó.
- Bài 01 là nơi mọi con số trong bảng bắt đầu.
Tóm tắt
- Bất biến
stock + chỗ pending + chỗ paidbằng tồn kho ban đầu là thứ giữ cả thiết kế đúng; hãy chạy lại nó trên Postgres của bạn. - Việc cần làm sau bài này: đổi một dòng trong bảng ràng buộc, rồi xem quyết định nào lật và quyết định nào kéo theo.
✅ Self-assessment
- Ước lượng tải đỉnh của một đợt bán giới hạn và trần thông lượng của một row tồn kho nóng bằng số đo trên máy mình
- Nếu chưa: đọc lại bài 01, mục tải đỉnh và phần tự đo bằng
pgbench.
- Nếu chưa: đọc lại bài 01, mục tải đỉnh và phần tự đo bằng
- Chẩn đoán vì sao tính tồn kho bằng đếm đơn rồi ghi vẫn bán quá số lượng dù đã bật transaction
- Nếu chưa: đọc lại bài 02, mục ba mức isolation.
- Chọn cơ chế trừ tồn kho trên một row Postgres theo thời gian giữ khoá và tỉ lệ thử lại dưới ràng buộc đã đặt
- Nếu chưa: đọc lại bài 03, phần
<DesignChoice>và số đo ba cách.
- Nếu chưa: đọc lại bài 03, phần
- Thiết kế luồng giữ chỗ có hạn trả lại tồn kho bị giữ mà không thanh toán và không trừ trùng
- Nếu chưa: đọc lại bài 04, mục worker thu hồi và người trả tiền sau hạn.
- Chọn giữa chặn tải ở cửa và nâng trần của hàng nóng khi tải vượt trần hàng chục lần
- Nếu chưa: đọc lại bài 05, mục "chọn theo cái gì đang vỡ trước".
- Biện luận vì sao một hệ thống thật chọn khác thiết kế của bạn dựa trên ràng buộc của họ
- Nếu chưa: đọc lại bài 06, cách tách ràng buộc, lựa chọn và trục bị tác động.
Bài tiếp theo
Case flash sale khép lại ở đây. Muốn đào sâu một mảnh bạn vừa chạm, các khoá khác của OLHub bàn về chia dữ liệu và hàng đợi bền, những thứ bài 05 chỉ nhắc tới. Đọc thêm, không phải tiên quyết: Key range vs hash trong data-phan-tan và Log-based broker trong data-phai-sinh.
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Đối chiếu thực tế: hệ thống bán vé và sàn TMĐT đã làm gì?
Khoá tiếp theoNews feed: trả giá lúc ghi hay lúc đọc
Case thứ hai của tuyến Thiết kế Hệ thống. Bạn tự đặt ràng buộc đọc/ghi lệch, đo chi phí dựng feed trên Postgres của mình, thấy phương án gộp lúc đọc vỡ p99 ở đâu, rồi tự chọn chỗ trả giá fan-out, cách xử lý tài khoản hàng triệu người theo dõi và điều người dùng thấy khi fan-out chạy trễ — mỗi lựa chọn kèm cái giá — trước khi đối chiếu cách các mạng xã hội thật đã làm.
8 bài · ~1.5 giờThảo luận & góp ý
Hỏi đáp về bài này