diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/LeaseManagement/DocumentServiceLeaseManagerCosmos.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/LeaseManagement/DocumentServiceLeaseManagerCosmos.cs index 0f343a7732..6ad742cf07 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/LeaseManagement/DocumentServiceLeaseManagerCosmos.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/LeaseManagement/DocumentServiceLeaseManagerCosmos.cs @@ -128,7 +128,11 @@ public override Task CreateLeaseIfNotExistAsync( Mode = this.GetChangeFeedMode() }; - this.requestOptionsFactory.AddPartitionKeyIfNeeded((string pk) => documentServiceLease.LeasePartitionKey = pk, Guid.NewGuid().ToString()); + // Use the lease document id as the partition-key value so that retries / concurrent + // creates for the same lease resolve to the same (id, partitionKey) tuple. This lets the + // Cosmos per-partition-key id-uniqueness check turn duplicates into a 409 Conflict + // instead of silently persisting cross-partition-key duplicates. + this.requestOptionsFactory.AddPartitionKeyIfNeeded((string pk) => documentServiceLease.LeasePartitionKey = pk, leaseDocId); return this.TryCreateDocumentServiceLeaseAsync(documentServiceLease); } @@ -153,7 +157,11 @@ public override Task CreateLeaseIfNotExistAsync( Mode = this.GetChangeFeedMode() }; - this.requestOptionsFactory.AddPartitionKeyIfNeeded((string pk) => documentServiceLease.LeasePartitionKey = pk, Guid.NewGuid().ToString()); + // Use the lease document id as the partition-key value so that retries / concurrent + // creates for the same lease resolve to the same (id, partitionKey) tuple. This lets the + // Cosmos per-partition-key id-uniqueness check turn duplicates into a 409 Conflict + // instead of silently persisting cross-partition-key duplicates. + this.requestOptionsFactory.AddPartitionKeyIfNeeded((string pk) => documentServiceLease.LeasePartitionKey = pk, leaseDocId); return this.TryCreateDocumentServiceLeaseAsync(documentServiceLease); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/DocumentServiceLeaseManagerCosmosTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/DocumentServiceLeaseManagerCosmosTests.cs index d93167e639..86f4a626cb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/DocumentServiceLeaseManagerCosmosTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/DocumentServiceLeaseManagerCosmosTests.cs @@ -628,6 +628,13 @@ private static void ValidateRequestOptionsFactory(RequestOptionsFactory requestO else if (requestOptionsFactory is PartitionedByPartitionKeyCollectionRequestOptionsFactory) { Assert.IsNotNull(lease.PartitionKey); + + // The lease's partition-key value must equal its id so that concurrent/retry creates + // for the same lease resolve to the same (id, partitionKey) tuple and Cosmos's per- + // partition-key id-uniqueness check turns duplicates into a 409 Conflict. Using a + // random Guid here would silently persist cross-partition-key duplicates and stall + // the change feed load balancer. See DocumentServiceLeaseManagerCosmos.CreateLeaseIfNotExistAsync. + Assert.AreEqual(lease.Id, lease.PartitionKey); } else {