Flash sale: chống bán quá số lượng
8/8
Bài 8 / 8~7 phútChống bán quá số lượngMiễn phí… lượt xem

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á.

Đường đi một lượt mua: 200.000 người với đỉnh 50.000 lượt mỗi giây qua lớp chặn cửa, lượt bị cờ hết hàng chặn được báo ngay, khoảng 1.000 lượt mỗi giây tới câu UPDATE có điều kiện trên row Postgres và tạo chỗ giữ 10 phút, rồi thanh toán hoặc quá hạn để worker thu hồi trả hàng về 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ướcLựa chọnCá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ẩnTự đặ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 ghiBán quá dù có transaction; Serializable giữ được nhưng huỷ nhiềuBá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âuGhi 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útPhả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óngTrừ 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àngNgườ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 oanChia 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".

Nhớ lại — giữ chỗ có 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ơ đồ.

Bốn thanh dài bằng nhau cho tổng stock cộng pending cộng paid bằng 1.000: lúc mở bán, sau khoảng 1 giây, sau 10 phút có worker trả 300 về stock, và khi không thu hồi thì 300 kẹt ở pending

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.

Tự kiểm tra
0/3 câu đã trả lời
  1. Q1
    Nếu 30% người bỏ giữa chừng thành 80%, quyết định nào đổi?
  2. Q2
    Nếu thời hạn giữ chỗ 10 phút thành 30 giây, cái giá nào nặng thêm?
  3. Q3
    Nế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ỗ paid bằ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.
  • 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.
  • 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.

Thảo luận & góp ý

Hỏi đáp về bài này