At-Least-Once Delivery
debt(d9/e7/b7/t7)
Closest to 'silent in production until users hit it' (d9). detection_hints.automated is no; duplicate processing only surfaces when users see double charges/emails. No tool flags a non-idempotent consumer.
Closest to 'cross-cutting refactor across the codebase' (e7). quick_fix requires making all consumers idempotent, adding message IDs everywhere, building a dedup table, and wrapping process+record in transactions — touches every consumer, not a single-line fix.
Closest to 'strong gravitational pull' (b7). Per applies_to (queue-workers) and tags (idempotency, reliability), the at-least-once contract shapes how every consumer and message schema is designed; idempotency keys propagate through the system.
Closest to 'serious trap' (t7). The misconception (assuming exactly-once when broker is at-least-once) directly contradicts naive intuition that a delivered message means processed once; common_mistakes confirm the 'obvious' non-idempotent consumer is wrong.
TL;DR
Explanation
Three delivery guarantees: at-most-once (may lose), at-least-once (may duplicate), exactly-once (ideal, expensive). At-least-once: broker retries unacknowledged messages. Consumer crashes after processing but before ack → redeliver → process twice. Handling duplicates (idempotency): deduplication table (message ID + status), natural idempotency (UPDATE SET status = 'sent' WHERE status = 'pending'), INSERT IGNORE. Kafka: at-least-once by default (manual offset commit after processing). RabbitMQ: at-least-once with manual ack. SQS: at-least-once by design. Message IDs: always include unique ID to detect duplicates.
Common Misconception
Why It Matters
Common Mistakes
- Non-idempotent consumer with at-least-once broker — double charges, double emails.
- Not including message ID for deduplication.
- Using exactly-once where at-least-once + idempotency suffices — unnecessary complexity.
Code Examples
// Non-idempotent consumer — charges customer twice on redeliver:
function handlePayment($msg) {
charge($msg['amount']); // No idempotency check
ack($msg);
}
// Idempotent with deduplication:
function handlePayment($msg) {
$processed = DB::table('processed_payments')
->where('message_id', $msg['id'])->exists();
if ($processed) return ack($msg); // Already done — skip
DB::transaction(function() use ($msg) {
charge($msg['amount']);
DB::table('processed_payments')->insert(['message_id' => $msg['id']]);
});
ack($msg);
}