Skip to content

ADR-0004. Worker idempotency via InventoryTracking lookup

FieldValue
StatusAccepted
Date2026-02-12
Decidersinventory-team
Supersedes

Context

  • All Kafka topics are at-least-once. Same message can be redelivered on consumer crash, rebalance, or replay.
  • InventoryStockRepository.adjustStock is not idempotent — re-applying the same delta double-counts.
  • We need a per-(saleOrderId, stockId) and per-(purchaseOrderId, stockId) deduplication that survives restarts.

Decision

Before any stock mutation, the worker queries InventoryTracking by (referenceType, referenceId, inventoryStockId):

  • If a row exists → handler short-circuits, commits Kafka offset, no stock change.
  • If no row exists → proceed with adjustStock + insert tracking row in the same flow.

InventoryTracking is the deduplication ledger. The bulk of inventory's idempotency is reading from it before any write.

Consequences

ProsCons
Single mechanism for all worker handlersAdds 1 query per stock mutation
Tracking row doubles as both audit log and dedup keyIf tracking insert fails after adjustStock succeeds, on retry we double-deduct (rare; mitigated by transactions)
No external state store (Redis, DB lock table)Tracking lookup must be indexed for performance
Replay tooling works against any past timeframeTracking grows unbounded — needs partitioning at scale

Idempotency keys per topic

TopicKey
PAYMENT_SUCCESS(SALE_ORDER, saleOrderId, stockId)
KITCHEN_TICKET_ITEM_STATUS_CHANGED(KITCHEN_TICKET_ITEM, kitchenTicketItemId, materialStockId)
MATERIAL_TRANSFERRED(transferId, fromStockId) + (transferId, toStockId)
Merchant CDCensureDefaultLocation(merchantId) self-idempotent
ProductVariant CDCensureInventoryItem(...) self-idempotent

Alternatives Considered

OptionProsConsWhy rejected
Separate IdempotencyKey tableCleaner conceptual separationYet another table; same data already in trackingRedundant
Redis SETEX with TTLFast lookupLost on Redis flush; no audit trailFragile; tracking is needed anyway
Kafka's transactional / exactly-once modeDB-Kafka coordinationSignificantly more complex; broker version constraintsOverkill for our workload

References

  • inventory/src/services/inventory-worker.service.ts (idempotency check pattern)
  • inventory/src/services/material-worker.service.ts (same pattern for materials)
  • core/src/models/schemas/inventory/inventory-tracking/schema.ts (deduplication index)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.