How should I design MessageId and PartitionKey for Azure Service Bus duplicate detection to safely retry sends without creating duplicate messages in a partitioned queue?
0 reputation · 24 Apr 2025, 23:54 UTC
0 reputation · 24 Apr 2025, 23:54 UTC
An application sends a message to an Azure Service Bus queue and may experience a transient failure after the send operation, leaving it uncertain whether the message was stored. To recover, the application may resend the same message with a previously used MessageId. How can duplicate detection be configured together with partitioning to guarantee that a resent message is recognized as a duplicate and not stored a second time, while still allowing distinct messages for different business contexts?
To guarantee that a resent message is treated as a duplicate and not stored twice, you must: 1) enable duplicate detection on the queue or topic by setting DuplicateDetectionHistoryTimeWindow to a value that covers your maximum retry interval; 2) assign a deterministic MessageId that uniquely identifies the logical business operation (e.g., a UUID or a hash of the payload plus a correlation key); 3) choose a PartitionKey that is independent of MessageId and is derived from a business key or a hash of the payload to spread load across partitions; 4) reuse the same MessageId on every retry while keeping the PartitionKey unchanged if the message must stay in the same partition, or change it only when the business context changes.
MessageId across the entire queue or topic, regardless of partition.# Azure CLI
az servicebus queue show \
--resource-group MyRG \
--namespace-name MyNS \
--name MyQueue \
--query "{duplicateDetectionHistoryTimeWindow:duplicateDetectionHistoryTimeWindow, isPartitioned:partitioningState}"
var messageId = Guid.NewGuid().ToString(); // for new messages
// For retries, reuse the same messageId stored in your database or correlation header.
string partitionKey = customerId; // or SHA256(payload).Substring(0, 8)
var msg = new ServiceBusMessage(body)
{
MessageId = messageId,
PartitionKey = partitionKey
};
await sender.SendMessageAsync(msg);
MessageId from your idempotency store and resend with the same value and the same PartitionKey if the message must stay in the same partition.DuplicateDetectionHistoryTimeWindow is shorter than your retry window, a retry after the window will be treated as a new message. In that case, increase the window or implement an application‑level idempotency key that persists beyond the Service Bus history.MessageId unless you can guarantee it is identical across retries.MessageId but keep the same PartitionKey if ordering per partition is required.What is the current value of DuplicateDetectionHistoryTimeWindow on your queue or topic? Knowing this will confirm whether the window covers your retry strategy.
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 25 Apr 2025, 09:06 UTC
One operational detail worth adding to the answer above: RequiresDuplicateDetection can only be set when the queue or topic is created. You cannot enable it later on an existing entity, so if the queue already exists without it, the only path is recreating it (or migrating to a new one) — worth confirming before designing the retry logic around it.
A second caveat: deduplication only holds inside the DuplicateDetectionHistoryTimeWindow. If your retry policy can stretch beyond that window (long backoff chains, queued offline retries), a late retry with the same MessageId is stored as a new message. Size the window to your worst-case retry horizon, and keep consumers idempotent regardless — the broker feature narrows the duplicate window, it doesn't eliminate it.
Quick verification: send the same MessageId twice within the window against a test queue and confirm only one message is receivable. Behavior and limits can vary by tier, so check current documentation for your namespace.