Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Internal] Per Partition Automatic Failover: Fixes Gateway 503 Cold Start Issue #4073

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
53ef5f3
Code changes to add retry logic for GW returned 503.9002.
kundadebdatta Sep 7, 2023
ef78559
Revert "Code changes to add retry logic for GW returned 503.9002."
kundadebdatta Sep 7, 2023
fdffdd8
Code changes to clean up the PPAF retry logic fix.
kundadebdatta Sep 7, 2023
9cd3d97
Code changes to add retry logic for GW returned 503.9002.
kundadebdatta Sep 7, 2023
9165885
Revert "Code changes to add retry logic for GW returned 503.9002."
kundadebdatta Sep 7, 2023
9d61eae
Code changes to clean up the PPAF retry logic fix.
kundadebdatta Sep 7, 2023
d05bc19
Code changes to revert location cache changes.
kundadebdatta Sep 8, 2023
4c68e31
Merge branch 'users/kundadebdatta/fix_gateway_503_cold_start_issue' o…
kundadebdatta Sep 8, 2023
cea2b17
Code changes ro revert location cache changes.
kundadebdatta Sep 8, 2023
5355d42
Merge branch 'master' into users/kundadebdatta/fix_gateway_503_cold_s…
kundadebdatta Sep 8, 2023
e23c50c
Code changes to fix some of the failing tests.
kundadebdatta Sep 15, 2023
12d1193
Code changes to fix unit tests.
kundadebdatta Sep 18, 2023
757ca01
Code changes to add unit tests for client options.
kundadebdatta Sep 19, 2023
20b3d21
Code changes to draft docs for PPAF design approach.
kundadebdatta Sep 20, 2023
5f86260
Code changes to add SDK side design docs for PPAF.
kundadebdatta Sep 26, 2023
f740c9d
Code changes to modify the PPAF design.
kundadebdatta Sep 26, 2023
237acc0
Code changes to fix unit test.
kundadebdatta Sep 27, 2023
3dd5678
Code changes to rename test name.
kundadebdatta Sep 27, 2023
045f337
Merge branch 'master' into users/kundadebdatta/fix_gateway_503_cold_s…
kundadebdatta Sep 27, 2023
0d59eb8
Code changes to add some cosmetic changes.
kundadebdatta Sep 27, 2023
07c644a
Code changes to enable retry on write for all regions in single maste…
kundadebdatta Oct 9, 2023
48a1348
Code changes to add code comments.
kundadebdatta Oct 11, 2023
232a950
Code changes to clean up and handle endpoints in location cache.
kundadebdatta Oct 12, 2023
c32809b
Code changes to fix unit tests. Added detailed code comments.
kundadebdatta Oct 12, 2023
3e2aba2
Code changes to clean up the account read endpoints generation logic.
kundadebdatta Oct 13, 2023
466545c
Code changes to fix unit tests.
kundadebdatta Oct 16, 2023
b8b8b0b
Merge branch 'master' into users/kundadebdatta/fix_gateway_503_cold_s…
kundadebdatta Oct 16, 2023
aabbe5e
Code changes to disable retry when ppaf is not enabled. Also validate…
kundadebdatta Oct 20, 2023
a7c17fe
Code changes to fix unit tests.
kundadebdatta Oct 23, 2023
9215f0c
Code changes to update md file.
kundadebdatta Oct 23, 2023
7760f22
Merge branch 'master' into users/kundadebdatta/fix_gateway_503_cold_s…
kundadebdatta Oct 23, 2023
813ea2d
Code changes to remove chache expiry check for account read endpoints.
kundadebdatta Oct 23, 2023
3cb1053
Code changes to fix unit test.
kundadebdatta Oct 24, 2023
8f782fc
Code changes to fix more tests.
kundadebdatta Oct 24, 2023
1e94fc1
Code changes to address review comments.
kundadebdatta Oct 24, 2023
4180263
Code changes to fix verbaige in design document.
kundadebdatta Oct 26, 2023
c6ee981
Merge branch 'master' into users/kundadebdatta/fix_gateway_503_cold_s…
kundadebdatta Oct 26, 2023
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
22 changes: 10 additions & 12 deletions Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal sealed class ClientRetryPolicy : IDocumentClientRetryPolicy
private readonly GlobalEndpointManager globalEndpointManager;
private readonly GlobalPartitionEndpointManager partitionKeyRangeLocationCache;
private readonly bool enableEndpointDiscovery;
private readonly bool isPertitionLevelFailoverEnabled;
private int failoverRetryCount;

private int sessionTokenRetryCount;
Expand All @@ -41,8 +42,9 @@ internal sealed class ClientRetryPolicy : IDocumentClientRetryPolicy
public ClientRetryPolicy(
GlobalEndpointManager globalEndpointManager,
GlobalPartitionEndpointManager partitionKeyRangeLocationCache,
RetryOptions retryOptions,
bool enableEndpointDiscovery,
RetryOptions retryOptions)
bool isPertitionLevelFailoverEnabled)
{
this.throttlingRetry = new ResourceThrottleRetryPolicy(
retryOptions.MaxRetryAttemptsOnThrottledRequests,
Expand All @@ -55,6 +57,7 @@ public ClientRetryPolicy(
this.sessionTokenRetryCount = 0;
this.serviceUnavailableRetryCount = 0;
this.canUseMultipleWriteLocations = false;
this.isPertitionLevelFailoverEnabled = isPertitionLevelFailoverEnabled;
}

/// <summary>
Expand Down Expand Up @@ -247,8 +250,7 @@ private async Task<ShouldRetryResult> ShouldRetryInternalAsync(
}

// Received 503 due to client connect timeout or Gateway
if (statusCode == HttpStatusCode.ServiceUnavailable
FabianMeiswinkel marked this conversation as resolved.
Show resolved Hide resolved
&& ClientRetryPolicy.IsRetriableServiceUnavailable(subStatusCode))
if (statusCode == HttpStatusCode.ServiceUnavailable)
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
DefaultTrace.TraceWarning("ClientRetryPolicy: ServiceUnavailable. Refresh cache and retry. Failed Location: {0}; ResourceAddress: {1}",
this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty,
Expand All @@ -265,12 +267,6 @@ private async Task<ShouldRetryResult> ShouldRetryInternalAsync(
return null;
}

private static bool IsRetriableServiceUnavailable(SubStatusCodes? subStatusCode)
{
return subStatusCode == SubStatusCodes.Unknown ||
(subStatusCode.HasValue && subStatusCode.Value.IsSDKGeneratedSubStatus());
}

private async Task<ShouldRetryResult> ShouldRetryOnEndpointFailureAsync(
bool isReadRequest,
bool markBothReadAndWriteAsUnavailable,
Expand Down Expand Up @@ -390,7 +386,7 @@ private ShouldRetryResult ShouldRetryOnSessionNotAvailable()

/// <summary>
/// For a ServiceUnavailable (503.0) we could be having a timeout from Direct/TCP locally or a request to Gateway request with a similar response due to an endpoint not yet available.
/// We try and retry the request only if there are other regions available.
/// We try and retry the request only if there are other regions available. The retry logic is applicable for single master write accounts as well.
/// </summary>
private ShouldRetryResult ShouldRetryOnServiceUnavailable()
{
Expand All @@ -401,9 +397,11 @@ private ShouldRetryResult ShouldRetryOnServiceUnavailable()
}

if (!this.canUseMultipleWriteLocations
ealsur marked this conversation as resolved.
Show resolved Hide resolved
&& !this.isReadRequest)
&& !this.isReadRequest
&& !this.isPertitionLevelFailoverEnabled)
{
// Write requests on single master cannot be retried, no other regions available
// Write requests on single master cannot be retried if partition level failover is disabled.
// This means there are no other regions available to serve the writes.
return ShouldRetryResult.NoRetry();
}

Expand Down
12 changes: 11 additions & 1 deletion Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ public Func<HttpClient> HttpClientFactory
/// <summary>
/// Enable partition key level failover
/// </summary>
internal bool EnablePartitionLevelFailover { get; set; } = false;
internal bool EnablePartitionLevelFailover { get; set; } = ConfigurationManager.IsPartitionLevelFailoverEnabled(defaultValue: false);

/// <summary>
/// Quorum Read allowed with eventual consistency account or consistent prefix account.
Expand Down Expand Up @@ -752,6 +752,7 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId)
{
this.ValidateDirectTCPSettings();
this.ValidateLimitToEndpointSettings();
this.ValidatePartitionLevelFailoverSettings();

ConnectionPolicy connectionPolicy = new ConnectionPolicy()
{
Expand Down Expand Up @@ -888,6 +889,15 @@ private void ValidateLimitToEndpointSettings()
}
}

private void ValidatePartitionLevelFailoverSettings()
{
if (this.EnablePartitionLevelFailover
&& (this.ApplicationPreferredRegions == null || this.ApplicationPreferredRegions.Count == 0))
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentException($"{nameof(this.ApplicationPreferredRegions)} is required when {nameof(this.EnablePartitionLevelFailover)} is enabled.");
}
}

private void ValidateDirectTCPSettings()
{
string settingName = string.Empty;
Expand Down
9 changes: 6 additions & 3 deletions Microsoft.Azure.Cosmos/src/RetryPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ internal sealed class RetryPolicy : IRetryPolicyFactory
private readonly GlobalPartitionEndpointManager partitionKeyRangeLocationCache;
private readonly GlobalEndpointManager globalEndpointManager;
private readonly bool enableEndpointDiscovery;
private readonly bool isPertitionLevelFailoverEnabled;
private readonly RetryOptions retryOptions;

/// <summary>
/// Initialize the instance of the RetryPolicy class
/// </summary>
public RetryPolicy(
GlobalEndpointManager globalEndpointManager,
GlobalEndpointManager globalEndpointManager,
ConnectionPolicy connectionPolicy,
GlobalPartitionEndpointManager partitionKeyRangeLocationCache)
{
this.enableEndpointDiscovery = connectionPolicy.EnableEndpointDiscovery;
this.isPertitionLevelFailoverEnabled = connectionPolicy.EnablePartitionLevelFailover;
this.globalEndpointManager = globalEndpointManager;
this.retryOptions = connectionPolicy.RetryOptions;
this.partitionKeyRangeLocationCache = partitionKeyRangeLocationCache;
Expand All @@ -37,10 +39,11 @@ public IDocumentClientRetryPolicy GetRequestPolicy()
ClientRetryPolicy clientRetryPolicy = new ClientRetryPolicy(
this.globalEndpointManager,
this.partitionKeyRangeLocationCache,
this.retryOptions,
this.enableEndpointDiscovery,
this.retryOptions);
this.isPertitionLevelFailoverEnabled);

return clientRetryPolicy;
}
}
}
}
6 changes: 4 additions & 2 deletions Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,11 @@ public GlobalEndpointManager(IDocumentClientInternal owner, ConnectionPolicy con
}
}

public ReadOnlyCollection<Uri> ReadEndpoints => this.locationCache.ReadEndpoints;
public ReadOnlyCollection<Uri> ReadEndpoints => this.locationCache.ReadEndpoints;

public ReadOnlyCollection<Uri> AccountReadEndpoints => this.locationCache.AccountReadEndpoints;

public ReadOnlyCollection<Uri> WriteEndpoints => this.locationCache.WriteEndpoints;
public ReadOnlyCollection<Uri> WriteEndpoints => this.locationCache.WriteEndpoints;

public int PreferredLocationCount => this.connectionPolicy.PreferredLocations != null ? this.connectionPolicy.PreferredLocations.Count : 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,20 @@ public override bool TryMarkEndpointUnavailableForPartitionKeyRange(

PartitionKeyRangeFailoverInfo partionFailover = this.PartitionKeyRangeToLocation.Value.GetOrAdd(
partitionKeyRange,
(_) => new PartitionKeyRangeFailoverInfo(failedLocation));

(_) => new PartitionKeyRangeFailoverInfo(failedLocation));

// For any single master write accounts, the next locations to fail over will be the read regions configured at the account level.
// For multi master write accounts, since all the regions are treated as write regions, the next locations to fail over
// will be the preferred read regions that are configured in the application preferred regions in the CosmosClientOptions.
bool isSingleMasterWriteAccount = !this.globalEndpointManager.CanUseMultipleWriteLocations(request);

ReadOnlyCollection<Uri> nextLocations = isSingleMasterWriteAccount
? this.globalEndpointManager.AccountReadEndpoints
: this.globalEndpointManager.ReadEndpoints;

// Will return true if it was able to update to a new region
if (partionFailover.TryMoveNextLocation(
locations: this.globalEndpointManager.ReadEndpoints,
locations: nextLocations,
failedLocation: failedLocation))
{
DefaultTrace.TraceInformation("Partition level override added to new location. PartitionKeyRange: {0}, failedLocation: {1}, new location: {2}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ internal interface IGlobalEndpointManager : IDisposable
{
ReadOnlyCollection<Uri> ReadEndpoints { get; }

ReadOnlyCollection<Uri> AccountReadEndpoints { get; }

ReadOnlyCollection<Uri> WriteEndpoints { get; }

int PreferredLocationCount { get; }
Expand Down
41 changes: 31 additions & 10 deletions Microsoft.Azure.Cosmos/src/Routing/LocationCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public ReadOnlyCollection<Uri> ReadEndpoints
}
}

/// <summary>
/// Gets list of account level read endpoints.
/// </summary>
public ReadOnlyCollection<Uri> AccountReadEndpoints => this.locationInfo.AccountReadEndpoints;

/// <summary>
/// Gets list of write endpoints ordered by
/// 1. Preferred location
Expand Down Expand Up @@ -491,20 +496,35 @@ private void UpdateLocationCache(

if (readLocations != null)
{
ReadOnlyCollection<string> availableReadLocations;
nextLocationInfo.AvailableReadEndpointByLocation = this.GetEndpointByLocation(readLocations, out availableReadLocations);
nextLocationInfo.AvailableReadEndpointByLocation = this.GetEndpointByLocation(
readLocations,
out ReadOnlyCollection<string> availableReadLocations);

nextLocationInfo.AvailableReadLocations = availableReadLocations;
nextLocationInfo.AccountReadEndpoints = nextLocationInfo.AvailableReadEndpointByLocation.Select(x => x.Value).ToList().AsReadOnly();
}

if (writeLocations != null)
{
ReadOnlyCollection<string> availableWriteLocations;
nextLocationInfo.AvailableWriteEndpointByLocation = this.GetEndpointByLocation(writeLocations, out availableWriteLocations);
nextLocationInfo.AvailableWriteEndpointByLocation = this.GetEndpointByLocation(
writeLocations,
out ReadOnlyCollection<string> availableWriteLocations);

nextLocationInfo.AvailableWriteLocations = availableWriteLocations;
}

nextLocationInfo.WriteEndpoints = this.GetPreferredAvailableEndpoints(nextLocationInfo.AvailableWriteEndpointByLocation, nextLocationInfo.AvailableWriteLocations, OperationType.Write, this.defaultEndpoint);
nextLocationInfo.ReadEndpoints = this.GetPreferredAvailableEndpoints(nextLocationInfo.AvailableReadEndpointByLocation, nextLocationInfo.AvailableReadLocations, OperationType.Read, nextLocationInfo.WriteEndpoints[0]);
nextLocationInfo.WriteEndpoints = this.GetPreferredAvailableEndpoints(
endpointsByLocation: nextLocationInfo.AvailableWriteEndpointByLocation,
orderedLocations: nextLocationInfo.AvailableWriteLocations,
expectedAvailableOperation: OperationType.Write,
fallbackEndpoint: this.defaultEndpoint);

nextLocationInfo.ReadEndpoints = this.GetPreferredAvailableEndpoints(
endpointsByLocation: nextLocationInfo.AvailableReadEndpointByLocation,
orderedLocations: nextLocationInfo.AvailableReadLocations,
expectedAvailableOperation: OperationType.Read,
fallbackEndpoint: nextLocationInfo.WriteEndpoints[0]);

this.lastCacheUpdateTimestamp = DateTime.UtcNow;

DefaultTrace.TraceInformation("Current WriteEndpoints = ({0}) ReadEndpoints = ({1})",
Expand Down Expand Up @@ -534,8 +554,7 @@ private ReadOnlyCollection<Uri> GetPreferredAvailableEndpoints(ReadOnlyDictionar

foreach (string location in currentLocationInfo.PreferredLocations)
{
Uri endpoint;
if (endpointsByLocation.TryGetValue(location, out endpoint))
if (endpointsByLocation.TryGetValue(location, out Uri endpoint))
{
if (this.IsEndpointUnavailable(endpoint, expectedAvailableOperation))
{
Expand All @@ -560,9 +579,8 @@ private ReadOnlyCollection<Uri> GetPreferredAvailableEndpoints(ReadOnlyDictionar
{
foreach (string location in orderedLocations)
{
Uri endpoint;
if (!string.IsNullOrEmpty(location) && // location is empty during manual failover
endpointsByLocation.TryGetValue(location, out endpoint))
endpointsByLocation.TryGetValue(location, out Uri endpoint))
{
endpoints.Add(endpoint);
}
Expand Down Expand Up @@ -634,6 +652,7 @@ public DatabaseAccountLocationsInfo(ReadOnlyCollection<string> preferredLocation
this.AvailableWriteEndpointByLocation = new ReadOnlyDictionary<string, Uri>(new Dictionary<string, Uri>(StringComparer.OrdinalIgnoreCase));
this.AvailableReadEndpointByLocation = new ReadOnlyDictionary<string, Uri>(new Dictionary<string, Uri>(StringComparer.OrdinalIgnoreCase));
this.WriteEndpoints = new List<Uri>() { defaultEndpoint }.AsReadOnly();
this.AccountReadEndpoints = new List<Uri>() { defaultEndpoint }.AsReadOnly();
this.ReadEndpoints = new List<Uri>() { defaultEndpoint }.AsReadOnly();
}

Expand All @@ -645,6 +664,7 @@ public DatabaseAccountLocationsInfo(DatabaseAccountLocationsInfo other)
this.AvailableWriteEndpointByLocation = other.AvailableWriteEndpointByLocation;
this.AvailableReadEndpointByLocation = other.AvailableReadEndpointByLocation;
this.WriteEndpoints = other.WriteEndpoints;
this.AccountReadEndpoints = other.AccountReadEndpoints;
this.ReadEndpoints = other.ReadEndpoints;
}

Expand All @@ -655,6 +675,7 @@ public DatabaseAccountLocationsInfo(DatabaseAccountLocationsInfo other)
public ReadOnlyDictionary<string, Uri> AvailableReadEndpointByLocation { get; set; }
public ReadOnlyCollection<Uri> WriteEndpoints { get; set; }
public ReadOnlyCollection<Uri> ReadEndpoints { get; set; }
public ReadOnlyCollection<Uri> AccountReadEndpoints { get; set; }
}

[Flags]
Expand Down
28 changes: 26 additions & 2 deletions Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ namespace Microsoft.Azure.Cosmos
internal static class ConfigurationManager
{
/// <summary>
/// A read-only string containing the environment variablename for enabling replica validation.
/// This will eventually be removed oncereplica valdiatin is enabled by default for both preview
/// A read-only string containing the environment variable name for enabling replica validation.
/// This will eventually be removed once replica valdiatin is enabled by default for both preview
/// and GA.
/// </summary>
internal static readonly string ReplicaConnectivityValidationEnabled = "AZURE_COSMOS_REPLICA_VALIDATION_ENABLED";

/// <summary>
/// A read-only string containing the environment variable name for enabling per partition automatic failover.
/// This will eventually be removed once per partition automatic failover is enabled by default for both preview
/// and GA.
/// </summary>
internal static readonly string PartitionLevelFailoverEnabled = "AZURE_COSMOS_PARTITION_LEVEL_FAILOVER_ENABLED";

public static T GetEnvironmentVariable<T>(string variable, T defaultValue)
{
string value = Environment.GetEnvironmentVariable(variable);
Expand Down Expand Up @@ -50,5 +57,22 @@ public static bool IsReplicaAddressValidationEnabled(
variable: ConfigurationManager.ReplicaConnectivityValidationEnabled,
defaultValue: replicaValidationDefaultValue);
}

/// <summary>
/// Gets the boolean value of the partition level failover environment variable. Note that, partition level failover
/// is disabled by default for both preview and GA releases. The user can set the respective environment variable
/// 'AZURE_COSMOS_PARTITION_LEVEL_FAILOVER_ENABLED' to override the value for both preview and GA. The method will
/// eventually be removed, once partition level failover is enabled by default for both preview and GA.
/// </summary>
/// <param name="defaultValue">A boolean field containing the default value for partition level failover.</param>
/// <returns>A boolean flag indicating if partition level failover is enabled.</returns>
public static bool IsPartitionLevelFailoverEnabled(
bool defaultValue)
{
return ConfigurationManager
.GetEnvironmentVariable(
variable: ConfigurationManager.PartitionLevelFailoverEnabled,
defaultValue: defaultValue);
}
}
}
Loading
Loading