ADR-0004. Worker idempotent nhờ tra cứu InventoryTracking
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-12 |
| Người quyết định | Phat Nguyen |
| Thay thế | - |
Bối cảnh
- Mọi Kafka topic đều theo cơ chế at-least-once. Cùng một message có thể được gửi lại khi consumer crash, cân bằng lại phân vùng, hoặc phát lại.
InventoryStockRepository.adjustStockkhông idempotent - áp dụng lại cùng một delta sẽ tính kép.- Cần khử trùng lặp theo
(saleOrderId, stockId)và(purchaseOrderId, stockId)mà vẫn còn hiệu lực sau khi khởi động lại.
Quyết định
Trước mọi thao tác thay đổi tồn kho, worker truy vấn InventoryTracking theo (referenceType, referenceId, inventoryStockId):
- Nếu đã có dòng → handler dừng sớm, commit Kafka offset, không thay đổi tồn kho.
- Nếu chưa có dòng → tiếp tục với
adjustStock+ chèn dòng tracking trong cùng luồng.
InventoryTracking là sổ cái khử trùng lặp. Phần lớn tính idempotent của inventory nằm ở việc đọc từ nó trước khi ghi.
Hệ quả
| Ưu điểm | Nhược điểm |
|---|---|
| Một cơ chế chung cho mọi worker handler | Thêm 1 truy vấn cho mỗi lần thay đổi tồn kho |
| Dòng tracking đóng vai trò vừa nhật ký kiểm toán vừa khóa khử trùng lặp | Nếu chèn tracking lỗi sau khi adjustStock thành công, lúc thử lại sẽ trừ kép (hiếm; giảm thiểu nhờ transaction) |
| Không cần kho lưu trạng thái bên ngoài (Redis, bảng khóa DB) | Phải đánh index cho tra cứu tracking để đảm bảo hiệu năng |
| Công cụ phát lại hoạt động với mọi khoảng thời gian trong quá khứ | Tracking tăng vô hạn - cần phân vùng khi quy mô lớn |
Khóa idempotency theo từng topic
| Topic | Khóa |
|---|---|
PAYMENT_SUCCESS | (SALE_ORDER, saleOrderId, stockId) |
KITCHEN_TICKET_ITEM_STATUS_CHANGED | (KITCHEN_TICKET_ITEM, kitchenTicketItemId, materialStockId) |
MATERIAL_TRANSFERRED | (transferId, fromStockId) + (transferId, toStockId) |
| Merchant CDC | ensureDefaultLocation(merchantId) tự idempotent |
| ProductVariant CDC | ensureInventoryItem(...) tự idempotent |
Phương án thay thế đã cân nhắc
| Phương án | Ưu điểm | Nhược điểm | Lý do từ chối |
|---|---|---|---|
Bảng IdempotencyKey riêng | Tách biệt khái niệm rõ ràng hơn | Thêm một bảng nữa; cùng dữ liệu đã có ở tracking | Dư thừa |
| Redis SETEX với TTL | Tra cứu nhanh | Mất khi flush Redis; không có dấu vết kiểm toán | Mong manh; tracking vẫn cần thiết |
| Chế độ transactional / exactly-once của Kafka | Phối hợp DB-Kafka | Phức tạp hơn đáng kể; ràng buộc phiên bản broker | Quá mức cho khối lượng công việc của ta |
Tham chiếu
inventory/src/services/inventory-worker.service.ts(pattern kiểm tra idempotency)inventory/src/services/material-worker.service.ts(cùng pattern cho material)core/src/models/schemas/inventory/inventory-tracking/schema.ts(index khử trùng lặp)