ADR-0004. Worker idempotency qua lookup InventoryTracking
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Date | 2026-02-12 |
| Deciders | inventory-team |
| Supersedes | — |
Bối cảnh
- Tất cả Kafka topic đều at-least-once. Cùng một message có thể được gửi lại khi consumer crash, rebalance, hoặc replay.
InventoryStockRepository.adjustStockkhông idempotent — áp dụng lại cùng delta sẽ tính kép.- Cần dedup theo
(saleOrderId, stockId)và(purchaseOrderId, stockId)mà tồn tại sau khi restart.
Quyết định
Trước bất kỳ stock mutation nào, worker truy vấn InventoryTracking theo (referenceType, referenceId, inventoryStockId):
- Nếu có row → handler short-circuit, commit Kafka offset, không thay đổi stock.
- Nếu không có row → tiếp tục với
adjustStock+ insert tracking row trong cùng luồng.
InventoryTracking là sổ cái dedup. Phần lớn idempotency của inventory là đọc từ nó trước khi ghi.
Hệ quả
| Pros | Cons |
|---|---|
| Một cơ chế chung cho mọi worker handler | Thêm 1 query mỗi lần stock mutation |
| Tracking row đóng vai trò vừa audit log vừa key dedup | Nếu insert tracking lỗi sau khi adjustStock thành công, lúc retry sẽ trừ kép (hiếm; giảm thiểu bằng transaction) |
| Không cần state store ngoài (Redis, bảng DB lock) | Tracking lookup phải có index để đảm bảo hiệu năng |
| Replay tooling hoạt động với mọi khoảng thời gian quá khứ | Tracking grow vô hạn — cần partition ở quy mô lớn |
Idempotency keys theo topic
| Topic | Key |
|---|---|
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) self-idempotent |
| ProductVariant CDC | ensureInventoryItem(...) self-idempotent |
Phương án thay thế đã cân nhắc
| Phương án | Pros | Cons | Lý do từ chối |
|---|---|---|---|
Bảng IdempotencyKey riêng | Tách biệt khái niệm sạch 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 | Lookup nhanh | Mất khi flush Redis; không có audit trail | Mong manh; tracking vẫn cần thiết |
| Mode 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 workload của ta |
Tham chiếu
inventory/src/services/inventory-worker.service.ts(idempotency check pattern)inventory/src/services/material-worker.service.ts(cùng pattern cho material)core/src/models/schemas/inventory/inventory-tracking/schema.ts(deduplication index)