ADR-0002. Pessimistic Lock SELECT FOR UPDATE trên order khi thay đổi item
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-08 |
| Người quyết định | sale-team |
| Thay thế | — |
Bối cảnh
- Hai thiết bị (POS-A và POS-B) thường thêm cùng một item vào cùng order DRAFT cùng lúc.
- Logic merge tự nhiên — "nếu cùng
(orderId, itemType, itemId)đã tồn tại, tăng số lượng" — có race window: cả hai transaction đều thấy không có row, cả hai cùng insert, kết quả là item bị duplicate. - Đã được quan sát thấy trong production khi khách gọi "burger × 2" trên tablet A và "burger × 1" trên tablet B cùng lúc — kết quả là hai dòng burger riêng thay vì một dòng qty=3.
Quyết định
Trong SaleOrderService.addSaleOrderItem, service mở một transaction và phát SELECT * FROM "SaleOrder" WHERE id = $1 FOR UPDATE trước khi đọc items và insert/merge. Row order đóng vai trò khóa điều phối cho TẤT CẢ thao tác cấp item trên order đó.
Cùng pattern áp dụng cho:
- Bulk update item (
SaleOrderItemService.update) - Clear items (
SaleOrderService.clearOrderItems) - Checkout (
CheckoutService.checkout) - Merge / split
Hệ quả
| Ưu | Nhược |
|---|---|
| Merge race-free: item duplicate gộp deterministic | Lock giữ đến khi commit transaction — thao tác chậm chặn người khác |
| Cơ chế duy nhất cho mọi tranh chấp row order | Thao tác chỉ đọc không chia sẻ lock (chỉ ghi mới serialize) |
| Tương thích với mô hình concurrency của PostgreSQL | Lock waits hiển thị trên metrics DB — có thể có alert false-positive |
| Không cần mutex cấp ứng dụng | Transaction kéo dài gây cascade waits |
Phương án thay thế đã cân nhắc
| Phương án | Ưu | Nhược | Lý do từ chối |
|---|---|---|---|
Optimistic locking (cột version + retry) | DB-portable, không waits | Storm retry khi nhiều thiết bị thêm cùng item; logic client phức tạp | SKU hot trong thực tế làm retry tốn kém |
| Redis lock cấp ứng dụng | Cross-DB | Rủi ro stale lock; SPOF; sync overhead | Tệ hơn primitive DB native |
Unique index trên (orderId, itemType, itemId) + ON CONFLICT INCREMENT | Chỉ DB | Không hoạt động với item CUSTOM (mỗi lần add là dòng unique); không capture được discount cấp dòng | Không phù hợp ngữ nghĩa mode=CUSTOM |
Tham chiếu
sale/src/services/sale.service.ts(lock pattern trongaddSaleOrderItem)sale/src/services/sale-order-item.service.ts(bulk update lock)core/src/models/schemas/sale/sale-item/schema.ts(trườngmode)