Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b5e72b2
Add hub region header for read consistency strategy
aavasthy Apr 28, 2026
57e2e02
Only setting hub region header for LastCommittedWriteRegion ReadConsi…
aavasthy Apr 29, 2026
cddf8f1
Merge branch 'master' into users/aavasthy/ReadConsistencyWithHubRegio…
aavasthy Apr 29, 2026
b009087
Update tests
aavasthy Apr 29, 2026
a24b765
merge with master
aavasthy Apr 29, 2026
8db9406
Merge branch 'master' into users/aavasthy/ReadConsistencyWithHubRegio…
aavasthy Apr 29, 2026
5bcbbaa
Update contracts and fix tests
aavasthy Apr 30, 2026
cba8c03
Merge remote-tracking branch 'origin/main' into users/aavasthy/ReadCo…
aavasthy May 4, 2026
ba78a6f
Add hub region header for hedge requests as well
aavasthy May 4, 2026
2143590
Fixes made based on review comments
aavasthy May 5, 2026
186b351
Code clean up
aavasthy May 5, 2026
d4c4820
Update LastCommittedWriteRegion to LastCommitedSingleWriteRegion
aavasthy May 5, 2026
15e1644
Merge branch 'main' into users/aavasthy/ReadConsistencyWithHubRegionh…
aavasthy May 5, 2026
1f7d9e9
Code clean up
aavasthy May 6, 2026
ee4ca31
Remove read consistency header for LastCommitedSingleWriteRegion
aavasthy May 6, 2026
dac78b4
Merge with master
aavasthy May 6, 2026
d7454ca
Merge branch 'main' into users/aavasthy/ReadConsistencyWithHubRegionh…
aavasthy May 6, 2026
67b58bc
Add latestCommit as RCS for LatestCmmitSingleWriteRegion
aavasthy May 6, 2026
fc16fd5
correct naming LastCommittedSingleWriteRegion
aavasthy May 7, 2026
abc6b83
Update direct package version 3.43.1 and fix bad request exception fo…
aavasthy May 11, 2026
cd565c7
Fix tests
aavasthy May 11, 2026
9ac50cd
Resolve merge conflicts with master
aavasthy May 11, 2026
5c6fbae
Merge branch 'main' into users/aavasthy/ReadConsistencyWithHubRegionh…
aavasthy May 11, 2026
7d0ac16
Add resourcetype check
aavasthy May 12, 2026
67fe4c9
Merge branch 'main' into users/aavasthy/ReadConsistencyWithHubRegionh…
aavasthy May 13, 2026
69b12ad
update changelog
aavasthy May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ClientOfficialVersion>3.59.0</ClientOfficialVersion>
<ClientPreviewVersion>3.60.0</ClientPreviewVersion>
<ClientPreviewSuffixVersion>preview.0</ClientPreviewSuffixVersion>
<DirectVersion>3.43.0</DirectVersion>
<DirectVersion>3.43.1</DirectVersion>
Comment thread
aavasthy marked this conversation as resolved.
<FaultInjectionVersion>1.0.0</FaultInjectionVersion>
<FaultInjectionSuffixVersion>beta.1</FaultInjectionSuffixVersion>
<EncryptionOfficialVersion>2.0.5</EncryptionOfficialVersion>
Expand Down
39 changes: 31 additions & 8 deletions Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal sealed class ClientRetryPolicy : IDocumentClientRetryPolicy
private DocumentServiceRequest documentServiceRequest;
#if !INTERNAL
private volatile bool addHubRegionProcessingOnlyHeader;
private CrossRegionAvailabilityContext crossRegionAvailabilityContext;
#endif

public ClientRetryPolicy(
Expand Down Expand Up @@ -249,11 +250,24 @@ public void OnBeforeSendRequest(DocumentServiceRequest request)
}
}
#if !INTERNAL
// If previous attempt failed with 404/1002, add the hub-region-processing-only header to all subsequent retry attempts
if (this.addHubRegionProcessingOnlyHeader)
{
request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion] = bool.TrueString;
}
// Initialize CrossRegionAvailabilityContext from Properties if not already set.
// In hedging scenarios, Properties carries the shared context instance injected by
// CrossRegionHedgingAvailabilityStrategy before cloning.
if (this.crossRegionAvailabilityContext == null
&& request.Properties != null
&& request.Properties.TryGetValue(CrossRegionAvailabilityContext.PropertyKey, out object ctxObj)
&& ctxObj is CrossRegionAvailabilityContext sharedCtx)
{
this.crossRegionAvailabilityContext = sharedCtx;
}

// If previous attempt failed with 404/1002, add the hub-region-processing-only header to all subsequent retry attempts.
// Also check the shared context — another hedged request may have already set the flag.
if (this.addHubRegionProcessingOnlyHeader
|| this.crossRegionAvailabilityContext?.ShouldAddHubRegionProcessingOnlyHeader == true)
{
request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion] = bool.TrueString;
}
#endif
// Resolve the endpoint for the request and pin the resolution to the resolved endpoint
// This enables marking the endpoint unavailability on endpoint failover/unreachability
Expand Down Expand Up @@ -484,12 +498,21 @@ private ShouldRetryResult ShouldRetryOnSessionNotAvailable(DocumentServiceReques
else
{
#if !INTERNAL
// Only set the hub region processing header for single master accounts.
// Set header after the second consecutive 404/1002 (count >= 2 means both
// the initial request and the first retry to the write region have failed).
// Hub region discovery: only for single-master accounts.
// In single-master, after 2× 404/1002 (ReadSessionNotAvailable), attach the
// x-ms-cosmos-hub-region-processing-only header so the backend routes the
// next retry to the partition-set level hub (primary) replica in the write region.
if (this.sessionTokenRetryCount >= MaxSessionTokenRetryCount)
{
this.addHubRegionProcessingOnlyHeader = true;

// Propagate to shared context so hedged requests
// (running in parallel with their own ClientRetryPolicy)
// pick up the hub region header immediately.
if (this.crossRegionAvailabilityContext != null)
{
this.crossRegionAvailabilityContext.ShouldAddHubRegionProcessingOnlyHeader = true;
}
}

if (this.sessionTokenRetryCount > MaxSessionTokenRetryCount)
Expand Down
43 changes: 39 additions & 4 deletions Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ public override async Task<ResponseMessage> SendAsync(
request.Headers.Add(HttpConstants.HttpHeaders.SupportedSerializationFormats, RequestInvokerHandler.BinarySerializationFormat);
}

await this.ValidateAndSetReadConsistencyStrategyAsync(request);
await this.ValidateAndSetConsistencyLevelAsync(request);
this.SetPriorityLevel(request);
this.ValidateAndSetThroughputBucket(request);
Expand All @@ -90,6 +89,8 @@ public override async Task<ResponseMessage> SendAsync(
return errorResponse;
}

await this.ValidateAndSetReadConsistencyStrategyAsync(request);

await request.AssertPartitioningDetailsAsync(this.client, cancellationToken, request.Trace);
this.FillMultiMasterContext(request);

Expand Down Expand Up @@ -509,6 +510,9 @@ private async Task ValidateAndSetConsistencyLevelAsync(RequestMessage requestMes

/// <summary>
/// Validate and set the ReadConsistencyStrategy header.
/// When the strategy is LastCommittedSingleWriteRegion and the operation is a read,
/// also set the hub region processing header so the backend routes the request
/// to the hub (write) region.
/// </summary>
private Task ValidateAndSetReadConsistencyStrategyAsync(RequestMessage requestMessage)
{
Expand All @@ -526,9 +530,40 @@ private Task ValidateAndSetReadConsistencyStrategyAsync(RequestMessage requestMe

if (readConsistencyStrategy.HasValue)
{
requestMessage.Headers.Set(
HttpConstants.HttpHeaders.ReadConsistencyStrategy,
readConsistencyStrategy.Value.ToString());
if (requestMessage.ResourceType == ResourceType.Document)
Comment thread
FabianMeiswinkel marked this conversation as resolved.
{
if (readConsistencyStrategy.Value == Cosmos.ReadConsistencyStrategy.LastCommittedSingleWriteRegion)
{
// LastCommittedSingleWriteRegion relies on hub-region routing which only applies
// to single-master accounts. In multi-master accounts every region is a write
// region and there is no partition-set level hub, so reject with a clear error.
if (this.client.DocumentClient.UseMultipleWriteLocations)
{
throw new ArgumentException(
Comment thread
FabianMeiswinkel marked this conversation as resolved.
$"{nameof(Cosmos.ReadConsistencyStrategy)}.{nameof(Cosmos.ReadConsistencyStrategy.LastCommittedSingleWriteRegion)} " +
"is not supported for multi-master (multiple write region) accounts. " +
"In multi-master accounts every region accepts writes and there is no single hub region. " +
"Use a different ReadConsistencyStrategy or configure the account with a single write region.");
}

if (OperationTypeExtensions.IsReadOperation(requestMessage.OperationType))
{
requestMessage.Headers.Set(
HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion,
bool.TrueString);

requestMessage.Headers.Set(
HttpConstants.HttpHeaders.ReadConsistencyStrategy,
Cosmos.ReadConsistencyStrategy.LatestCommitted.ToString());
}
}
else
{
requestMessage.Headers.Set(
HttpConstants.HttpHeaders.ReadConsistencyStrategy,
readConsistencyStrategy.Value.ToString());
}
}
}

return Task.CompletedTask;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ enum ReadConsistencyStrategy
/// Quorum read with GCLSN barrier - returns the latest version across all regions.
/// Only valid for accounts configured with Strong consistency.
/// </summary>
GlobalStrong = 4
GlobalStrong = 4,

/// <summary>
Comment thread
aavasthy marked this conversation as resolved.
/// This strategy is designed for single-master accounts only.
/// In single-master accounts, each physical partition has a designated hub (primary) replica
/// in the write region. When a read encounters a 404/1002 (ReadSessionNotAvailable), the SDK
/// retries by routing the request to the partition-set level hub region using the
/// <c>x-ms-cosmos-hub-region-processing-only</c> header. If the hub replica is in a different
/// region than expected (403/3 WriteForbidden), the SDK discovers and retries on the actual hub.
/// Returns the latest committed version from the hub (write) region, ensuring reads
/// reflect the most recent writes regardless of which region the client is connected to.
Comment thread
FabianMeiswinkel marked this conversation as resolved.
/// </summary>
LastCommittedSingleWriteRegion = 5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ internal override async Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(

HedgingResponse hedgeResponse = null;

// Inject a shared CrossRegionAvailabilityContext into Properties before the clone loop.
// RequestMessage.Clone() shallow-copies Properties, so all hedged clones share the same
// context instance — enabling hub region header propagation across hedged requests.
request.Properties[CrossRegionAvailabilityContext.PropertyKey] = new CrossRegionAvailabilityContext();

//Send out hedged requests
for (int requestNumber = 0; requestNumber < hedgeRegions.Count; requestNumber++)
{
Expand Down Expand Up @@ -407,4 +412,24 @@ public HedgingResponse(bool isNonTransient, ResponseMessage responseMessage, str
}
}
}

/// <summary>
/// Mutable, thread-safe context shared across hedged request clones via the Properties dictionary.
/// When the primary request's ClientRetryPolicy sets the hub region flag after 2x 404/1002,
/// hedged requests (with their own ClientRetryPolicy instances) pick up the flag immediately.
/// </summary>
internal sealed class CrossRegionAvailabilityContext
{
/// <summary>
/// Well-known key used to store/retrieve this context from Properties dictionary.
/// </summary>
internal const string PropertyKey = "CrossRegionAvailabilityContext";

/// <summary>
/// Thread-safe flag indicating that the hub region processing header should be added.
/// Written by the primary request's ClientRetryPolicy after 2x 404/1002,
/// read by hedged request ClientRetryPolicy instances in OnBeforeSendRequest.
/// </summary>
internal volatile bool ShouldAddHubRegionProcessingOnlyHeader;
}
}
Loading
Loading