Message Idempotency
debt(d9/e7/b7/t5)
Closest to 'silent in production until users hit it' (d9). The detection_hints flag automated:no and the code_pattern hint (message_id|idempotency) is just a grep for presence of a keyword — it cannot detect absence of idempotency logic. Duplicate processing silently corrupts state or sends duplicate emails; users discover it only when they receive duplicate emails or see double-charged orders in production.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix describes a multi-step solution: introduce unique UUIDs in every message producer, add a deduplication table with transactional checks in every consumer, and implement a key-expiry mechanism. This touches message producers, all consumer workers, and the data layer — spanning multiple components and requiring coordinated rollout rather than a single-file fix.
Closest to 'strong gravitational pull' (b7). Applies_to covers web, cli, and queue-worker contexts — every consumer in the system must be designed with idempotency in mind. The deduplication table becomes a shared dependency, expiry logic must be maintained, and every new message type or consumer added in future must conform to the idempotency contract, shaping ongoing development across the board.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field captures the canonical trap: developers believe idempotency means returning the same response, but it actually means leaving the same system state — an idempotent operation may legitimately return 409 on a duplicate. Common mistakes (using timestamps as keys, forgetting key expiry) reinforce that while this trap is documented and learnable, it consistently catches intermediate developers.
TL;DR
Explanation
Idempotency strategies: (1) Natural idempotency: UPDATE SET status = 'sent' WHERE status = 'pending' — safe to run twice. (2) Idempotency key: store message ID, skip if already processed. (3) Conditional insert: INSERT IGNORE / ON CONFLICT DO NOTHING. (4) Check-and-set: check if result already exists before creating. Design events to carry their own idempotency: 'set balance to X' (idempotent) vs 'add X to balance' (not idempotent). Include a unique message ID (UUID) in every message. Deduplication window: SQS FIFO has 5-minute dedup window by content hash.
Common Misconception
Why It Matters
Common Mistakes
- Non-idempotent email sending — sends duplicate emails on redeliver.
- Using timestamp as idempotency key — not unique enough.
- Forgetting that idempotency keys need to expire (storage grows infinitely otherwise).
Code Examples
// Not idempotent — sends email twice on redeliver:
function handleWelcomeEmail($msg) {
sendEmail($msg['user_email'], 'Welcome!'); // Sent twice if redelivered
ack($msg);
}
function handleWelcomeEmail($msg) {
$alreadySent = DB::table('sent_emails')
->where('message_id', $msg['id'])->exists();
if ($alreadySent) return ack($msg);
DB::transaction(function() use ($msg) {
sendEmail($msg['user_email'], 'Welcome!');
DB::table('sent_emails')->insert([
'message_id' => $msg['id'],
'sent_at' => now(),
]);
});
ack($msg);
}