Skip to content

Commit d242b85

Browse files
Hedging: Adds Read hedging PREVIEW contracts (#4598)
* Update contracts * removed unneeded changes * made other contracts public * update enable method * comments * contract update * changed eneabled to internal * fixed internal method * Revert "fixed internal method" This reverts commit f7da9f0. * revert + change methods to internal * fixed internal * changed to factory creation * disabledstrat fix * fixed test + contracts * xml changes * updated comments * Fixed Tests * requested changes * added client options check and one region check * fix get * fixed test * fixed set * fix client options * updatecontracts * requested changes * fixed validate method * Delete MultiRegionSetupHelpers.cs removed file * update contracts * fixed merge * fixed check
1 parent 6d603f5 commit d242b85

File tree

12 files changed

+223
-45
lines changed

12 files changed

+223
-45
lines changed

Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,35 @@ public Func<HttpClient> HttpClientFactory
694694
/// <summary>
695695
/// Availability Strategy to be used for periods of high latency
696696
/// </summary>
697-
internal AvailabilityStrategy AvailabilityStrategy { get; set; }
697+
/// /// <example>
698+
/// An example on how to set an availability strategy custom serializer.
699+
/// <code language="c#">
700+
/// <![CDATA[
701+
/// CosmosClient client = new CosmosClientBuilder("connection string")
702+
/// .WithApplicationPreferredRegions(
703+
/// new List<string> { "East US", "Central US", "West US" } )
704+
/// .WithAvailabilityStrategy(
705+
/// AvailabilityStrategy.CrossRegionHedgingStrategy(
706+
/// threshold: TimeSpan.FromMilliseconds(500),
707+
/// thresholdStep: TimeSpan.FromMilliseconds(100)
708+
/// ))
709+
/// .Build();
710+
/// ]]>
711+
/// </code>
712+
/// </example>
713+
/// <remarks>
714+
/// The availability strategy in the example is a Cross Region Hedging Strategy.
715+
/// These strategies take two values, a threshold and a threshold step.When a request that is sent
716+
/// out takes longer than the threshold time, the SDK will hedge to the second region in the application preferred regions list.
717+
/// If a response from either the primary request or the first hedged request is not received
718+
/// after the threshold step time, the SDK will hedge to the third region and so on.
719+
/// </remarks>
720+
#if PREVIEW
721+
public
722+
#else
723+
internal
724+
#endif
725+
AvailabilityStrategy AvailabilityStrategy { get; set; }
698726

699727
/// <summary>
700728
/// Enable partition key level failover
@@ -887,7 +915,8 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId)
887915
{
888916
this.ValidateDirectTCPSettings();
889917
this.ValidateLimitToEndpointSettings();
890-
this.ValidatePartitionLevelFailoverSettings();
918+
this.ValidatePartitionLevelFailoverSettings();
919+
this.ValidateAvailabilityStrategy();
891920

892921
ConnectionPolicy connectionPolicy = new ConnectionPolicy()
893922
{
@@ -1064,6 +1093,15 @@ private void ValidatePartitionLevelFailoverSettings()
10641093
{
10651094
throw new ArgumentException($"{nameof(this.ApplicationPreferredRegions)} is required when {nameof(this.EnablePartitionLevelFailover)} is enabled.");
10661095
}
1096+
}
1097+
1098+
private void ValidateAvailabilityStrategy()
1099+
{
1100+
if (this.AvailabilityStrategy != null
1101+
&& this.ApplicationPreferredRegions == null && this.ApplicationRegion == null)
1102+
{
1103+
throw new ArgumentException($"{nameof(this.ApplicationPreferredRegions)} or {nameof(this.ApplicationRegion)} must be set to use {nameof(this.AvailabilityStrategy)}");
1104+
}
10671105
}
10681106

10691107
private void ValidateDirectTCPSettings()

Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,12 @@ internal CosmosClientBuilder WithApiType(ApiType apiType)
705705
/// </summary>
706706
/// <param name="strategy"></param>
707707
/// <returns>The CosmosClientBuilder</returns>
708-
internal CosmosClientBuilder WithAvailibilityStrategy(AvailabilityStrategy strategy)
708+
#if PREVIEW
709+
public
710+
#else
711+
internal
712+
#endif
713+
CosmosClientBuilder WithAvailibilityStrategy(AvailabilityStrategy strategy)
709714
{
710715
this.clientOptions.AvailabilityStrategy = strategy;
711716
return this;

Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public override async Task<ResponseMessage> SendAsync(
7979
await request.AssertPartitioningDetailsAsync(this.client, cancellationToken, request.Trace);
8080
this.FillMultiMasterContext(request);
8181

82-
AvailabilityStrategy strategy = this.AvailabilityStrategy(request);
82+
AvailabilityStrategyInternal strategy = this.AvailabilityStrategy(request);
8383

8484
ResponseMessage response = strategy != null && strategy.Enabled()
8585
? await strategy.ExecuteAvailabilityStrategyAsync(
@@ -103,10 +103,17 @@ public override async Task<ResponseMessage> SendAsync(
103103
/// </summary>
104104
/// <param name="request"></param>
105105
/// <returns>whether the request should be a parallel hedging request.</returns>
106-
public AvailabilityStrategy AvailabilityStrategy(RequestMessage request)
106+
public AvailabilityStrategyInternal AvailabilityStrategy(RequestMessage request)
107107
{
108-
return request.RequestOptions?.AvailabilityStrategy
108+
AvailabilityStrategy strategy = request.RequestOptions?.AvailabilityStrategy
109109
?? this.client.ClientOptions.AvailabilityStrategy;
110+
111+
if (strategy == null)
112+
{
113+
return null;
114+
}
115+
116+
return strategy as AvailabilityStrategyInternal;
110117
}
111118

112119
public virtual async Task<ResponseMessage> BaseSendAsync(

Microsoft.Azure.Cosmos/src/RequestOptions/RequestOptions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ public class RequestOptions
7676
/// reduce latency and increase availability. Currently there is one type of availability strategy, parallel request hedging.
7777
/// If there is a globally enabled availability strategy, setting one in the request options will override the global one.
7878
/// </summary>
79-
internal AvailabilityStrategy AvailabilityStrategy { get; set; }
79+
#if PREVIEW
80+
public
81+
#else
82+
internal
83+
#endif
84+
AvailabilityStrategy AvailabilityStrategy { get; set; }
8085

8186
/// <summary>
8287
/// Gets or sets the boolean to use effective partition key routing in the cosmos db request.

Microsoft.Azure.Cosmos/src/Routing/AvailabilityStrategy/AvailabilityStrategy.cs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,46 @@
44
namespace Microsoft.Azure.Cosmos
55
{
66
using System;
7-
using System.Threading;
8-
using System.Threading.Tasks;
9-
using Microsoft.Azure.Cosmos.Handlers;
107

118
/// <summary>
129
/// Types of availability strategies supported
1310
/// </summary>
14-
internal abstract class AvailabilityStrategy
11+
#if PREVIEW
12+
public
13+
#else
14+
internal
15+
#endif
16+
abstract class AvailabilityStrategy
1517
{
1618
/// <summary>
17-
/// Execute the availability strategy
19+
/// Default constructor
1820
/// </summary>
19-
/// <param name="sender"></param>
20-
/// <param name="client"></param>
21-
/// <param name="requestMessage"></param>
22-
/// <param name="cancellationToken"></param>
23-
/// <returns>The response from the service after the availability strategy is executed</returns>
24-
public abstract Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
25-
Func<RequestMessage, CancellationToken, Task<ResponseMessage>> sender,
26-
CosmosClient client,
27-
RequestMessage requestMessage,
28-
CancellationToken cancellationToken);
21+
internal AvailabilityStrategy()
22+
{
23+
}
2924

30-
internal abstract bool Enabled();
25+
/// <summary>
26+
/// Used on a per request level to disable a client level AvailabilityStrategy
27+
/// </summary>
28+
/// <returns>something</returns>
29+
internal static AvailabilityStrategy DisabledStrategy()
30+
{
31+
return new DisabledAvailabilityStrategy();
32+
}
33+
34+
/// <summary>
35+
/// After a request's duration passes a threshold, this strategy will send out
36+
/// hedged request to other regions. The first hedge request will be sent after the threshold.
37+
/// After that, the strategy will send out a request every thresholdStep
38+
/// until the request is completed or regions are exausted
39+
/// </summary>
40+
/// <param name="threshold"> how long before SDK begins hedging</param>
41+
/// <param name="thresholdStep">Period of time between first hedge and next hedging attempts</param>
42+
/// <returns>something</returns>
43+
public static AvailabilityStrategy CrossRegionHedgingStrategy(TimeSpan threshold,
44+
TimeSpan? thresholdStep)
45+
{
46+
return new CrossRegionHedgingAvailabilityStrategy(threshold, thresholdStep);
47+
}
3148
}
3249
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// ------------------------------------------------------------
4+
5+
namespace Microsoft.Azure.Cosmos
6+
{
7+
using System;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
internal abstract class AvailabilityStrategyInternal : AvailabilityStrategy
12+
{
13+
/// <summary>
14+
/// Execute the availability strategy
15+
/// </summary>
16+
/// <param name="sender"></param>
17+
/// <param name="client"></param>
18+
/// <param name="requestMessage"></param>
19+
/// <param name="cancellationToken"></param>
20+
/// <returns>The response from the service after the availability strategy is executed</returns>
21+
internal abstract Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
22+
Func<RequestMessage, CancellationToken, Task<ResponseMessage>> sender,
23+
CosmosClient client,
24+
RequestMessage requestMessage,
25+
CancellationToken cancellationToken);
26+
27+
/// <summary>
28+
/// Checks to see if the strategy is enabled
29+
/// </summary>
30+
/// <returns>a bool representing if the strategy is enabled</returns>
31+
internal abstract bool Enabled();
32+
}
33+
}
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ namespace Microsoft.Azure.Cosmos
1616
using Microsoft.Azure.Documents;
1717

1818
/// <summary>
19-
/// Parallel hedging availability strategy. Once threshold time is reached,
19+
/// Hedging availability strategy. Once threshold time is reached,
2020
/// the SDK will send out an additional request to a remote region in parallel
21-
/// if the first parallel request or the original has not returned after the step time,
22-
/// additional parallel requests will be sent out there is a response or all regions are exausted.
21+
/// if the first hedging request or the original has not returned after the step time,
22+
/// additional hedged requests will be sent out there is a response or all regions are exausted.
2323
/// </summary>
24-
internal class CrossRegionParallelHedgingAvailabilityStrategy : AvailabilityStrategy
24+
internal class CrossRegionHedgingAvailabilityStrategy : AvailabilityStrategyInternal
2525
{
2626
private const string HedgeContext = "Hedge Context";
2727
private const string ResponseRegion = "Response Region";
@@ -37,11 +37,11 @@ internal class CrossRegionParallelHedgingAvailabilityStrategy : AvailabilityStra
3737
public TimeSpan ThresholdStep { get; private set; }
3838

3939
/// <summary>
40-
/// Constructor for parallel hedging availability strategy
40+
/// Constructor for hedging availability strategy
4141
/// </summary>
4242
/// <param name="threshold"></param>
4343
/// <param name="thresholdStep"></param>
44-
public CrossRegionParallelHedgingAvailabilityStrategy(
44+
public CrossRegionHedgingAvailabilityStrategy(
4545
TimeSpan threshold,
4646
TimeSpan? thresholdStep)
4747
{
@@ -59,17 +59,18 @@ public CrossRegionParallelHedgingAvailabilityStrategy(
5959
this.ThresholdStep = thresholdStep ?? TimeSpan.FromMilliseconds(-1);
6060
}
6161

62+
/// <inheritdoc/>
6263
internal override bool Enabled()
6364
{
6465
return true;
6566
}
6667

6768
/// <summary>
68-
/// This method determines if the request should be sent with a parallel hedging availability strategy.
69+
/// This method determines if the request should be sent with a hedging availability strategy.
6970
/// This availability strategy can only be used if the request is a read-only request on a document request.
7071
/// </summary>
7172
/// <param name="request"></param>
72-
/// <returns>whether the request should be a parallel hedging request.</returns>
73+
/// <returns>whether the request should be a hedging request.</returns>
7374
internal bool ShouldHedge(RequestMessage request)
7475
{
7576
//Only use availability strategy for document point operations
@@ -88,20 +89,21 @@ internal bool ShouldHedge(RequestMessage request)
8889
}
8990

9091
/// <summary>
91-
/// Execute the parallel hedging availability strategy
92+
/// Execute the hedging availability strategy
9293
/// </summary>
9394
/// <param name="sender"></param>
9495
/// <param name="client"></param>
9596
/// <param name="request"></param>
9697
/// <param name="cancellationToken"></param>
9798
/// <returns>The response after executing cross region hedging</returns>
98-
public override async Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
99+
internal override async Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
99100
Func<RequestMessage, CancellationToken, Task<ResponseMessage>> sender,
100101
CosmosClient client,
101102
RequestMessage request,
102103
CancellationToken cancellationToken)
103104
{
104-
if (!this.ShouldHedge(request))
105+
if (!this.ShouldHedge(request)
106+
|| client.DocumentClient.GlobalEndpointManager.ReadEndpoints.Count == 1)
105107
{
106108
return await sender(request, cancellationToken);
107109
}

Microsoft.Azure.Cosmos/src/Routing/AvailabilityStrategy/DisabledAvailabilityStrategy.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ namespace Microsoft.Azure.Cosmos
1010
/// <summary>
1111
/// A Disabled availability strategy that does not do anything. Used for overriding the default global availability strategy.
1212
/// </summary>
13-
internal class DisabledAvailabilityStrategy : AvailabilityStrategy
13+
internal class DisabledAvailabilityStrategy : AvailabilityStrategyInternal
1414
{
15+
/// <inheritdoc/>
1516
internal override bool Enabled()
1617
{
1718
return false;
@@ -25,7 +26,7 @@ internal override bool Enabled()
2526
/// <param name="requestMessage"></param>
2627
/// <param name="cancellationToken"></param>
2728
/// <returns>nothing, this will throw.</returns>
28-
public override Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
29+
internal override Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
2930
Func<RequestMessage,
3031
CancellationToken,
3132
Task<ResponseMessage>> sender,

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public async Task AvailabilityStrategyNoTriggerTest()
198198
{
199199
ConnectionMode = ConnectionMode.Direct,
200200
ApplicationPreferredRegions = new List<string>() { "Central US", "North Central US" },
201-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
201+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
202202
threshold: TimeSpan.FromMilliseconds(300),
203203
thresholdStep: TimeSpan.FromMilliseconds(50)),
204204
Serializer = this.cosmosSystemTextJsonSerializer
@@ -272,7 +272,7 @@ public async Task AvailabilityStrategyRequestOptionsTriggerTest()
272272

273273
ItemRequestOptions requestOptions = new ItemRequestOptions
274274
{
275-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
275+
AvailabilityStrategy = new CrossRegionHedgingAvailabilityStrategy(
276276
threshold: TimeSpan.FromMilliseconds(100),
277277
thresholdStep: TimeSpan.FromMilliseconds(50))
278278
};
@@ -317,7 +317,7 @@ public async Task AvailabilityStrategyDisableOverideTest()
317317
{
318318
ConnectionMode = ConnectionMode.Direct,
319319
ApplicationPreferredRegions = new List<string>() { "Central US", "North Central US" },
320-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
320+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
321321
threshold: TimeSpan.FromMilliseconds(100),
322322
thresholdStep: TimeSpan.FromMilliseconds(50)),
323323
Serializer = this.cosmosSystemTextJsonSerializer
@@ -416,7 +416,7 @@ public async Task AvailabilityStrategyAllFaultsTests(string operation, string co
416416
{
417417
ConnectionMode = ConnectionMode.Direct,
418418
ApplicationPreferredRegions = new List<string>() { "Central US", "North Central US" },
419-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
419+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
420420
threshold: TimeSpan.FromMilliseconds(100),
421421
thresholdStep: TimeSpan.FromMilliseconds(50)),
422422
Serializer = this.cosmosSystemTextJsonSerializer
@@ -597,7 +597,7 @@ public async Task AvailabilityStrategyStepTests(string operation, string condito
597597
{
598598
ConnectionMode = ConnectionMode.Direct,
599599
ApplicationPreferredRegions = new List<string>() { "Central US", "North Central US", "East US" },
600-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
600+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
601601
threshold: TimeSpan.FromMilliseconds(100),
602602
thresholdStep: TimeSpan.FromMilliseconds(50)),
603603
Serializer = this.cosmosSystemTextJsonSerializer

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosMultiRegionDiagnosticsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public async Task HedgeNestingDiagnosticsTest()
105105

106106
ItemRequestOptions requestOptions = new ItemRequestOptions
107107
{
108-
AvailabilityStrategy = new CrossRegionParallelHedgingAvailabilityStrategy(
108+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
109109
threshold: TimeSpan.FromMilliseconds(100),
110110
thresholdStep: TimeSpan.FromMilliseconds(50))
111111
};

0 commit comments

Comments
 (0)