Liên hệ

    Thiết kế cơ chế khóa tồn kho tạm thời (Inventory Reservation) chống bán vượt tồn kho khi chạy Flash Sale đa kênh

    Trả lời nhanh

    Chống bán vượt tồn kho (Overselling) khi chạy chiến dịch Flash Sale đa kênh đòi hỏi cơ chế khóa giữ chỗ tồn kho tạm thời (Inventory Reservation) thực thi nguyên tử ngay ở tầng bộ nhớ đệm tốc độ cao. Thay vì kiểm tra và trừ tồn kho bằng truy vấn đọc - sửa - ghi thông thường dễ phát sinh điều kiện tranh chấp (Race Condition) khi hàng nghìn người bấm mua cùng mili giây, hệ thống sử dụng tập lệnh Redis Lua Script kết hợp thời gian sống (TTL) để giữ hàng và hoàn trả tự động nếu quá hạn.

    Trong các đợt mở bán giới hạn hoặc chiến dịch săn giá sốc Flash Sale đúng khung giờ vàng, hàng chục nghìn yêu cầu đặt mua có thể dồn vào một mã sản phẩm chỉ còn vài chục đơn vị tồn kho. Nếu kiến trúc Website không xử lý triệt để bài toán tranh chấp đồng thời, doanh nghiệp sẽ rơi vào khủng hoảng bán vượt tồn kho thực tế và phải hủy đơn.

    Phần 01

    1. Giải phẫu lỗi Điều kiện tranh chấp (Race Condition) khiến Website bán âm tồn kho dịp Flash Sale

    Nguyên nhân gốc rễ khiến phần lớn Website thương mại điện tử truyền thống bị bán lố số lượng tồn kho khi chạy Flash Sale nằm ở mô hình lập trình Đọc trước - Kiểm tra - Ghi sau (Check-Then-Act) không nguyên tử. Cụ thể, khi người mua bấm nút Đặt hàng, mã nguồn ứng dụng thường thực hiện câu lệnh SELECT để kiểm tra xem số lượng tồn kho hiện tại trong cơ sở dữ liệu có lớn hơn hoặc bằng số lượng khách mua hay không; nếu thỏa mãn mới tiến hành tạo đơn hàng và chạy câu lệnh UPDATE trừ tồn kho.

    Trong điều kiện lưu lượng ngày thường, khoảng thời gian vài chục mili giây giữa câu lệnh SELECT và câu lệnh UPDATE hiếm khi xảy ra va chạm. Thế nhưng, đúng vào giây 00:00 của chương trình Flash Sale, khi sản phẩm chỉ còn đúng 1 chiếc cuối cùng trong kho mà có tới 200 yêu cầu HTTP đồng thời đi vào các tiến trình máy chủ ứng dụng, cả 200 luồng cùng chạy câu lệnh SELECT tại một thời điểm và đều nhận được kết quả tồn kho đang bằng 1. Hệ quả là cả 200 luồng đều vượt qua bước kiểm tra điều kiện và lần lượt chạy lệnh trừ kho, khiến hệ thống ghi nhận thành công 200 đơn hàng và đẩy số tồn kho trong bảng dữ liệu về mức âm (-199).

    Nguy cơ thứ hai đến từ độ trễ đồng bộ đa kênh. Khi một doanh nghiệp vừa mở bán trên Website, vừa bán trực tiếp tại chuỗi cửa hàng vật lý (POS) và các kênh trực tuyến khác trên cùng một bể tồn kho dùng chung (Shared Pool), khoảng trễ chỉ từ 5 đến 30 giây trong việc cập nhật số liệu giữa các kênh cũng đủ để hàng trăm sản phẩm đã bán hết tại quầy thu ngân vẫn tiếp tục cho phép đặt mua trên Website.

    So sánh 3 kỹ thuật kiểm soát đồng thời khi trừ tồn kho trong thương mại điện tử
    Phương pháp kỹ thuậtCơ chế hoạt động cốt lõiKhả năng chịu tải (Throughput)Tình huống áp dụng phù hợp
    Khóa bi quan (Pessimistic Locking - SELECT FOR UPDATE)Khóa cứng dòng dữ liệu sản phẩm trong RDBMS ngay khi bắt đầu giao dịch cho đến khi Commit xong.Thấp đến trung bình; dễ gây nghẽn hàng loạt kết nối (Connection Pool Exhaustion) khi nghìn người mua cùng 1 mã SKU.Phù hợp cho đơn hàng bán buôn B2B đa dòng hàng hoặc giao dịch tần suất vừa phải cần tính nhất quán tuyệt đối.
    Khóa lạc quan (Optimistic Locking / Atomic Conditional Update)Không khóa khi đọc; khi ghi sử dụng điều kiện UPDATE inventory SET qty = qty - :buy WHERE sku = :id AND qty >= :buy.Cao hơn khóa bi quan; cơ sở dữ liệu tự động tuần tự hóa ở bước cập nhật dòng và trả về số dòng bị ảnh hưởng.Phù hợp cho Website bán lẻ quy mô vừa, khuyến mại thông thường có vài chục đến vài trăm lượt mua đồng thời.
    Trừ kho nguyên tử trên bộ nhớ đệm (Redis Lua Script + Queue)Đưa tồn kho Flash Sale lên RAM (Redis), thực thi kiểm tra và trừ kho trong 1 lệnh Lua đơn luồng trước khi đẩy xuống DB.Rất cao (hàng chục nghìn yêu cầu/giây trên 1 mã SKU) với độ trễ dưới 2 mili giây, bảo vệ hoàn toàn cơ sở dữ liệu chính.Thiết yếu cho các chiến dịch Mega Sale, Flash Sale khung giờ vàng hoặc mở bán sản phẩm phiên bản giới hạn.
    Phần 02

    2. Thiết kế máy trạng thái giữ chỗ tồn kho (Inventory Reservation State Machine) theo từng bước thanh toán

    Ngay cả khi đã giải quyết xong bài toán tranh chấp ở cấp độ câu lệnh trừ kho, đội ngũ thiết kế hệ thống vẫn phải đối mặt với một bài toán nghiệp vụ hóc búa: nên khóa và trừ số lượng tồn kho tại thời điểm nào trong hành trình mua hàng? Nếu trừ kho ngay khi khách vừa thêm sản phẩm vào giỏ hàng (Add to Cart), những đối tượng xấu hoặc người mua dạo chỉ cần bỏ hết hàng vào giỏ rồi tắt trình duyệt là toàn bộ kho hàng Flash Sale sẽ bị chiếm dụng ảo, khiến khách hàng thực sự muốn mua lại thấy báo hết hàng.

    Ngược lại, nếu chỉ trừ tồn kho sau khi cổng thanh toán trực tuyến gọi Webhook báo thu tiền thành công (Payment Captured), rủi ro bán vượt tồn kho sẽ xảy ra ngay ở bước thanh toán. Giả sử chỉ còn 1 sản phẩm mà có 5 khách hàng cùng bấm chuyển sang màn hình quét mã QR ngân hàng; sau 2 phút cả 5 khách đều quét mã chuyển khoản thành công, lúc này hệ thống đã thu tiền của cả 5 người cho cùng 1 món hàng duy nhất, kéo theo quy trình hoàn tiền và giải trình vô cùng phức tạp.

    Lời giải chuẩn mực cho bài toán này là mô hình **Giữ chỗ tồn kho hai giai đoạn (Two-Phase Inventory Reservation)** gắn liền với vòng đời đơn hàng có giới hạn thời gian sống (Time-To-Live - TTL). Tồn kho của mỗi mã SKU được chia rõ thành 3 trạng thái: Available (Khả dụng cho khách mới đặt), Reserved (Đang tạm khóa chờ khách hoàn tất thanh toán) và Sold/Committed (Đã bán chính thức).

    • Bước 1 (Khởi tạo giữ chỗ - Reserve): Khi khách hàng bấm nút “Đặt hàng / Tiến hành thanh toán” tại trang Checkout, hệ thống lập tức chuyển số lượng mua từ trạng thái Available sang Reserved kèm theo một bản ghi inventory_reservation có thời hạn TTL (ví dụ: 10 phút đối với đơn thanh toán trực tuyến).
    • Bước 2A (Xác nhận giữ chỗ - Commit): Nếu trong vòng 10 phút, cổng thanh toán gửi tín hiệu xác nhận giao dịch thành công (hoặc khách hoàn tất xác nhận đơn COD), bản ghi giữ chỗ chuyển trạng thái sang Committed và tồn kho chính thức được trừ vĩnh viễn.
    • Bước 2B (Giải phóng giữ chỗ - Release/Rollback): Nếu hết 10 phút mà khách hàng đóng tab thanh toán hoặc giao dịch thẻ thất bại, tiến trình quét tự động (Expiration Worker) sẽ hủy bản ghi giữ chỗ và cộng trả lại số lượng từ Reserved về lại Available để khách hàng khác trên trang tiếp tục mua được.
    Phần 03

    3. Thực thi nguyên tử với Redis Lua Script và đồng bộ xuống cơ sở dữ liệu quan hệ (PostgreSQL / MySQL)

    Để chịu được áp lực hàng chục nghìn lượt bấm mua trong cùng một giây mở màn Flash Sale mà không làm sập cơ sở dữ liệu PostgreSQL hay MySQL, toàn bộ thao tác kiểm tra và giữ chỗ tồn kho ở Bước 1 phải được đưa lên tầng bộ nhớ trong (In-Memory Data Store) bằng **Redis**. Vì kiến trúc xử lý lệnh của Redis hoạt động theo mô hình đơn luồng (Single-threaded Event Loop), mọi tập lệnh **Lua Script** chạy qua lệnh EVAL hoặc EVALSHA đều được thực thi nguyên tử (Atomic): không một yêu cầu nào khác có thể xen ngang vào giữa lúc tập lệnh Lua đang kiểm tra và trừ số dư tồn kho.

    Trước giờ mở bán Flash Sale khoảng 15 phút, một tiến trình khởi tạo (Warm-up Job) sẽ nạp số lượng suất bán Flash Sale đã được duyệt của từng mã SKU từ cơ sở dữ liệu lên các khóa Redis (ví dụ: flashsale:stock:{campaign_id}:{sku_id}). Khi người dùng bấm nút mua, máy chủ API gọi tập lệnh Lua trên Redis để thực hiện trọn gói 3 việc trong chưa đầy 1 mili giây: kiểm tra xem tài khoản người dùng (User ID) đã vượt quá giới hạn số lượng mua tối đa mỗi tài khoản hay chưa, kiểm tra số dư tồn kho trên Redis có đủ không, và nếu đủ thì giảm ngay số dư đồng thời ghi nhận mã phiên giữ chỗ.

    Chỉ những yêu cầu nào nhận được kết quả trả về thành công (1) từ Redis Lua Script mới được phép đi tiếp vào hàng đợi Message Queue để khởi tạo bản ghi đơn hàng trong cơ sở dữ liệu PostgreSQL/MySQL. Toàn bộ hàng nghìn yêu cầu đến muộn vài mili giây khi kho đã về 0 sẽ bị chặn lại ngay tại tầng Redis và nhận phản hồi “Sản phẩm vừa hết suất Flash Sale” tức thì mà không hề chạm tới ổ cứng của cơ sở dữ liệu chính.

    • Tách biệt bể tồn kho chiến dịch (Campaign Quota Allocation): khi chạy Flash Sale đa kênh, doanh nghiệp nên trích trước một số lượng cố định (ví dụ 200 chiếc dành riêng cho Flash Sale Website) tách khỏi bể tồn kho bán thường tại cửa hàng POS để tránh xung đột chéo trong khung giờ cao điểm.
    • Xử lý trường hợp biên “Thanh toán trễ sau khi hết hạn TTL” (Late Payment Webhook): nếu khách hàng quét mã chuyển khoản ở phút thứ 9:58 nhưng do mạng ngân hàng trễ nên đến phút 10:05 Webhook mới báo về (lúc này suất giữ chỗ đã bị hủy), hệ thống phải tự động kiểm tra xem kho còn hàng tự do để cấp lại hay không; nếu đã hết sạch hàng thì lập tức kích hoạt luồng hoàn tiền tự động (Auto-Refund) và gửi thông báo ngay cho khách.
    • Cấu hình Redis Persistence (AOF - Append Only File ở chế độ everysec kết hợp Redis Sentinel/Cluster) kèm cơ chế đối chiếu nhanh với bảng inventory_reservations dưới DB để khôi phục chính xác trạng thái giữ chỗ nếu nút Redis gặp sự cố khởi động lại.
    Phần 04

    4. Phòng chống Bot găm hàng (Inventory Hoarding) và thiết kế trải nghiệm chờ thanh toán minh bạch

    Một hệ lụy phổ biến khi áp dụng cơ chế khóa tồn kho tạm thời (Inventory Reservation) tại bước đặt hàng là nguy cơ bị tấn công từ chối dịch vụ tồn kho (**Inventory Denial of Service / Bot Hoarding**). Đối thủ cạnh tranh hoặc các tay săn hàng bằng công cụ tự động (Auto-click Bot) có thể dùng hàng trăm tài khoản ảo đồng loạt bấm đặt hàng ở giây đầu tiên nhưng cố tình không thanh toán, khiến toàn bộ hàng hóa bị khóa cứng trong suốt 10-15 phút đầu của chương trình Flash Sale. Khi thời gian giữ chỗ hết hạn và hàng được nhả ra thì khách hàng thật đã rời bỏ trang vì tưởng chương trình đã bán hết.

    Để bảo vệ nguồn hàng cho người mua thực, kiến trúc khóa tồn kho phải đi kèm với các lớp phòng vệ chống tự động hóa ngay trước bước gọi Redis Lua Script. Hệ thống cần bắt buộc người dùng đăng nhập tài khoản đã xác thực, giới hạn tần suất yêu cầu theo địa chỉ IP và dấu vân tay thiết bị (Device Fingerprint Rate Limiting), đồng thời giới hạn số lượng đơn hàng đang ở trạng thái “Chờ thanh toán” tối đa cho mỗi tài khoản (ví dụ: mỗi khách hàng chỉ được có tối đa 1 đơn hàng Flash Sale đang giữ chỗ chưa thanh toán).

    Về mặt giao diện người dùng (UI/UX), sự minh bạch trong trạng thái tồn kho giúp giữ chân khách hàng ở lại trang ngay cả khi đợt đầu vừa tạm hết suất. Thay vì chỉ hiển thị nhãn “Hết hàng” cứng nhắc khi toàn bộ sản phẩm đang nằm trong trạng thái Reserved, giao diện trang sản phẩm Flash Sale nên phân biệt rõ trạng thái “Tạm hết suất - Đang chờ thanh toán (Có thể mở lại sau ít phút)” so với trạng thái “Đã bán hết hoàn toàn (Sold Out)” khi 100% suất giữ chỗ đã thu tiền thành công.

    • Rút ngắn thời gian giữ chỗ TTL riêng cho các mặt hàng Flash Sale có độ nóng cao xuống còn 3 đến 5 phút (thay vì 15-30 phút như đơn hàng thông thường), kèm đồng hồ đếm ngược hiển thị rõ ràng trên màn hình thanh toán của khách.
    • Áp dụng hàng đợi ảo (Virtual Waiting Room / Token Bucket) cho các đợt mở bán vé hoặc sản phẩm giới hạn cực nóng để làm mượt lưu lượng đi vào API đặt hàng theo đúng công suất xử lý của cổng thanh toán.
    • Ghi nhận nhật ký kiểm toán (Inventory Audit Log) cho mọi biến động cộng/trừ giữa ba cột Available, Reserved và Sold để bộ phận vận hành dễ dàng truy vết từng đơn vị sản phẩm trong suốt chiến dịch.
    FAQ

    Câu hỏi thường gặp

    Giải đáp nhanh những điểm thương hiệu bán lẻ D2C, chuỗi cửa hàng đa chi nhánh, doanh nghiệp sản xuất – phân phối B2B và đơn vị xây dựng sàn giao dịch thương mại điện tử hay hỏi về chủ đề này.

    Nên khóa giữ chỗ tồn kho ở bước Thêm vào giỏ hàng (Add to Cart) hay ở bước Bấm nút Đặt hàng (Place Order)?

    Trong hầu hết các mô hình thương mại điện tử bán lẻ và Flash Sale, nguyên tắc chuẩn là chỉ khóa giữ chỗ tồn kho khi khách hàng bấm nút Đặt hàng / Thanh toán (Place Order) chứ không khóa ở bước Thêm vào giỏ hàng. Việc bỏ hàng vào giỏ chỉ là hành vi tham khảo; nếu khóa kho ngay từ giỏ hàng, tỷ lệ bỏ giỏ cao (thường trên 70%) sẽ làm đóng băng toàn bộ hàng hóa trong kho khiến những người sẵn sàng trả tiền ngay không thể mua được.

    Nếu khách hàng chọn phương thức Thanh toán khi nhận hàng (COD) trong dịp Flash Sale thì xử lý giữ chỗ như thế nào?

    Đối với các sản phẩm Flash Sale giảm giá sâu, doanh nghiệp nên ưu tiên cấu hình chỉ chấp nhận thanh toán trả trước trực tuyến để hạn chế đơn ảo. Trường hợp vẫn mở COD, hệ thống sẽ chuyển tồn kho sang trạng thái Reserved ngay khi đặt đơn và yêu cầu khách xác thực mã OTP qua tin nhắn/Zalo hoặc nhân viên gọi xác nhận trong khung thời gian ngắn trước khi chuyển sang Committed.

    Làm sao giữ số lượng tồn kho hiển thị trên trang sản phẩm cập nhật nhanh mà không cần người dùng tải lại trang?

    Website có thể sử dụng kết nối Server-Sent Events (SSE) hoặc WebSocket đọc từ kênh thông báo của Redis, hoặc kỹ thuật Short Polling qua CDN Edge Cache với thời gian lưu đệm ngắn (1-2 giây) cho riêng API lấy tiến độ thanh trạng thái đã bán (Sold Progress Bar). Nhờ đó thanh phần trăm số lượng bán trên màn hình khách hàng luôn nhảy số mượt mà mà không gây quá tải cơ sở dữ liệu.

    Nhận tư vấn & báo giá

    Nhận tư vấn kiến trúc & Báo giá chi tiết hệ thống Website Thương mại điện tử

    Hãy cho Uptech biết mô hình bán hàng của bạn (B2C, B2B hay Đa kho/Sàn), số lượng mã sản phẩm (SKU) dự kiến và các phần mềm cổng thanh toán, vận chuyển, ERP/POS cần kết nối. Kỹ sư giải pháp của Uptech sẽ phân tích và gửi bản đề xuất kiến trúc kèm dự toán chi phí minh bạch trong 24 giờ làm việc.

    • Phản hồi kèm sơ đồ kiến trúc đề xuất trong vòng 24 giờ làm việc
    • Tư vấn miễn phí lộ trình đồng bộ tồn kho ERP/POS và cấu trúc SEO danh mục lớn
    • Hợp đồng pháp nhân rõ ràng, bàn giao mã nguồn, xuất hóa đơn GTGT (VAT) đầy đủ
    Nhận tư vấn & báo giá

    Miễn phí khảo sát. Phản hồi trong ngày làm việc gần nhất.

    Bạn cần loại website nào?

    Hoặc nhắn Zalo / gọi 0961 163 961