Skip to content

Commit 85d0642

Browse files
authored
Apply new consumption defaults in AzureStorageDurabilityProvider (#1706)
1 parent 6fd65a8 commit 85d0642

19 files changed

+467
-54
lines changed

pending_docs.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
<-- Please include a link to your pending docs PR below. https://docs.microsoft.com/en-us/azure/azure-functions/durable ([private docs repo for Microsoft employees](http://github.com/MicrosoftDocs/azure-docs-pr)).
1+
<!-- Please include a link to your pending docs PR below. https://docs.microsoft.com/en-us/azure/azure-functions/durable ([private docs repo for Microsoft employees](http://github.com/MicrosoftDocs/azure-docs-pr)).
22
Your code PR should not be merged until your docs PR has been signed off. -->
3+
https://github.com/MicrosoftDocs/azure-docs-pr/pull/149980

release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1+
Improved concurrency defaults for the App Service Consumption plan (https://github.com/Azure/azure-functions-durable-extension/pull/1706)

src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactor
1717
private readonly string defaultConnectionName;
1818
private readonly INameResolver nameResolver;
1919
private readonly ILoggerFactory loggerFactory;
20+
private readonly bool inConsumption; // If true, optimize defaults for consumption
2021
private AzureStorageDurabilityProvider defaultStorageProvider;
2122

2223
// Must wait to get settings until we have validated taskhub name.
@@ -27,15 +28,32 @@ public AzureStorageDurabilityProviderFactory(
2728
IOptions<DurableTaskOptions> options,
2829
IConnectionStringResolver connectionStringResolver,
2930
INameResolver nameResolver,
30-
ILoggerFactory loggerFactory)
31+
ILoggerFactory loggerFactory,
32+
#pragma warning disable CS0612 // Type or member is obsolete
33+
IPlatformInformationService platformInfo)
34+
#pragma warning restore CS0612 // Type or member is obsolete
3135
{
3236
this.options = options.Value;
3337
this.nameResolver = nameResolver;
3438
this.loggerFactory = loggerFactory;
3539
this.azureStorageOptions = new AzureStorageOptions();
40+
this.inConsumption = platformInfo.InConsumption();
41+
42+
// The consumption plan has different performance characteristics so we provide
43+
// different defaults for key configuration values.
44+
int maxConcurrentOrchestratorsDefault = this.inConsumption ? 5 : 10 * Environment.ProcessorCount;
45+
int maxConcurrentActivitiesDefault = this.inConsumption ? 10 : 10 * Environment.ProcessorCount;
46+
this.azureStorageOptions.ControlQueueBufferThreshold = this.inConsumption ? 32 : this.azureStorageOptions.ControlQueueBufferThreshold;
47+
48+
// The following defaults are only applied if the customer did not explicitely set them on `host.json`
49+
this.options.MaxConcurrentOrchestratorFunctions = this.options.MaxConcurrentOrchestratorFunctions ?? maxConcurrentOrchestratorsDefault;
50+
this.options.MaxConcurrentActivityFunctions = this.options.MaxConcurrentActivityFunctions ?? maxConcurrentActivitiesDefault;
51+
52+
// Override the configuration defaults with user-provided values in host.json, if any.
3653
JsonConvert.PopulateObject(JsonConvert.SerializeObject(this.options.StorageProvider), this.azureStorageOptions);
3754

38-
this.azureStorageOptions.Validate();
55+
var logger = loggerFactory.CreateLogger(nameof(this.azureStorageOptions));
56+
this.azureStorageOptions.Validate(logger);
3957

4058
this.connectionStringResolver = connectionStringResolver ?? throw new ArgumentNullException(nameof(connectionStringResolver));
4159
this.defaultConnectionName = this.azureStorageOptions.ConnectionStringName ?? ConnectionStringNames.Storage;
@@ -138,8 +156,8 @@ internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationSe
138156
ControlQueueBufferThreshold = this.azureStorageOptions.ControlQueueBufferThreshold,
139157
ControlQueueVisibilityTimeout = this.azureStorageOptions.ControlQueueVisibilityTimeout,
140158
WorkItemQueueVisibilityTimeout = this.azureStorageOptions.WorkItemQueueVisibilityTimeout,
141-
MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions,
142-
MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions,
159+
MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions ?? throw new InvalidOperationException($"{nameof(this.options.MaxConcurrentOrchestratorFunctions)} needs a default value"),
160+
MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions ?? throw new InvalidOperationException($"{nameof(this.options.MaxConcurrentOrchestratorFunctions)} needs a default value"),
143161
ExtendedSessionsEnabled = this.options.ExtendedSessionsEnabled,
144162
ExtendedSessionIdleTimeout = extendedSessionTimeout,
145163
MaxQueuePollingInterval = this.azureStorageOptions.MaxQueuePollingInterval,
@@ -155,6 +173,11 @@ internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationSe
155173
UseLegacyPartitionManagement = this.azureStorageOptions.UseLegacyPartitionManagement,
156174
};
157175

176+
if (this.inConsumption)
177+
{
178+
settings.MaxStorageOperationConcurrency = 25;
179+
}
180+
158181
// When running on App Service VMSS stamps, these environment variables are the best way
159182
// to enure unqique worker names
160183
string stamp = this.nameResolver.Resolve("WEBSITE_CURRENT_STAMPNAME");
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
7+
{
8+
/// <summary>
9+
/// Provides information about the enviroment (OS, app service plan, user-facing PL)
10+
/// using the DI-injected INameResolver.
11+
/// </summary>
12+
#pragma warning disable CS0612 // Type or member is obsolete
13+
internal class DefaultPlatformInformationProvider : IPlatformInformationService
14+
#pragma warning restore CS0612 // Type or member is obsolete
15+
{
16+
private readonly INameResolver nameResolver;
17+
18+
public DefaultPlatformInformationProvider(INameResolver nameResolver)
19+
{
20+
this.nameResolver = nameResolver;
21+
}
22+
23+
public bool InConsumption()
24+
{
25+
return this.InLinuxConsumption() | this.InWindowsConsumption();
26+
}
27+
28+
public bool InWindowsConsumption()
29+
{
30+
string value = this.nameResolver.Resolve("WEBSITE_SKU");
31+
return string.Equals(value, "Dynamic", StringComparison.OrdinalIgnoreCase);
32+
}
33+
34+
public bool InLinuxConsumption()
35+
{
36+
string containerName = this.GetContainerName();
37+
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
38+
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
39+
bool inLinuxConsumption = !inAppService && !string.IsNullOrEmpty(containerName);
40+
return inLinuxConsumption;
41+
}
42+
43+
public bool InLinuxAppService()
44+
{
45+
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
46+
string functionsLogsMountPath = this.nameResolver.Resolve("FUNCTIONS_LOGS_MOUNT_PATH");
47+
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
48+
bool inLinuxDedicated = inAppService && !string.IsNullOrEmpty(functionsLogsMountPath);
49+
return inLinuxDedicated;
50+
}
51+
52+
public string GetLinuxTenant()
53+
{
54+
return this.nameResolver.Resolve("WEBSITE_STAMP_DEPLOYMENT_ID");
55+
}
56+
57+
public string GetLinuxStampName()
58+
{
59+
return this.nameResolver.Resolve("WEBSITE_HOME_STAMPNAME");
60+
}
61+
62+
public string GetContainerName()
63+
{
64+
return this.nameResolver.Resolve("CONTAINER_NAME");
65+
}
66+
}
67+
}

src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ public class DurableTaskExtension :
7070
#endif
7171
private readonly bool isOptionsConfigured;
7272
private readonly IApplicationLifetimeWrapper hostLifetimeService = HostLifecycleService.NoOp;
73-
73+
#pragma warning disable CS0612 // Type or member is obsolete
74+
private readonly IPlatformInformationService platformInformationService;
75+
#pragma warning restore CS0612 // Type or member is obsolete
7476
private IDurabilityProviderFactory durabilityProviderFactory;
7577
private INameResolver nameResolver;
7678
private ILoggerFactory loggerFactory;
@@ -108,6 +110,7 @@ public DurableTaskExtension()
108110
/// <param name="messageSerializerSettingsFactory">The factory used to create <see cref="JsonSerializerSettings"/> for message settings.</param>
109111
/// <param name="errorSerializerSettingsFactory">The factory used to create <see cref="JsonSerializerSettings"/> for error settings.</param>
110112
/// <param name="telemetryActivator">The activator of DistributedTracing. .netstandard2.0 only.</param>
113+
/// <param name="platformInformationService">The platform information provider to inspect the OS, app service plan, and other enviroment information.</param>
111114
#pragma warning restore CS1572
112115
public DurableTaskExtension(
113116
IOptions<DurableTaskOptions> options,
@@ -118,11 +121,15 @@ public DurableTaskExtension(
118121
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory = null,
119122
ILifeCycleNotificationHelper lifeCycleNotificationHelper = null,
120123
IMessageSerializerSettingsFactory messageSerializerSettingsFactory = null,
124+
#pragma warning disable CS0612 // Type or member is obsolete
125+
IPlatformInformationService platformInformationService = null,
126+
#pragma warning restore CS0612 // Type or member is obsolete
121127
#if !FUNCTIONS_V1
122128
IErrorSerializerSettingsFactory errorSerializerSettingsFactory = null,
123129
#pragma warning disable SA1113, SA1001, SA1115
124130
ITelemetryActivator telemetryActivator = null)
125131
#pragma warning restore SA1113, SA1001, SA1115
132+
126133
#else
127134
IErrorSerializerSettingsFactory errorSerializerSettingsFactory = null)
128135
#endif
@@ -131,6 +138,7 @@ public DurableTaskExtension(
131138
this.Options = options?.Value ?? new DurableTaskOptions();
132139
this.nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver));
133140
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
141+
this.platformInformationService = platformInformationService ?? throw new ArgumentNullException(nameof(platformInformationService));
134142
this.ResolveAppSettingOptions();
135143

136144
ILogger logger = loggerFactory.CreateLogger(LoggerCategoryName);
@@ -173,10 +181,15 @@ internal DurableTaskExtension(
173181
IDurabilityProviderFactory orchestrationServiceFactory,
174182
IConnectionStringResolver connectionStringResolver,
175183
IApplicationLifetimeWrapper shutdownNotification,
176-
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory)
184+
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory,
185+
#pragma warning disable CS0612 // Type or member is obsolete
186+
IPlatformInformationService platformInformationService)
187+
#pragma warning restore CS0612 // Type or member is obsolete
188+
177189
: this(options, loggerFactory, nameResolver, orchestrationServiceFactory, shutdownNotification, durableHttpMessageHandlerFactory)
178190
{
179191
this.connectionStringResolver = connectionStringResolver;
192+
this.platformInformationService = platformInformationService;
180193
}
181194

182195
/// <summary>
@@ -341,19 +354,13 @@ void IExtensionConfigProvider.Initialize(ExtensionConfigContext context)
341354
/// </summary>
342355
private void InitializeLinuxLogging()
343356
{
344-
// Read enviroment variables to determine host platform
345-
string containerName = this.nameResolver.Resolve("CONTAINER_NAME");
346-
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
347-
string functionsLogsMountPath = this.nameResolver.Resolve("FUNCTIONS_LOGS_MOUNT_PATH");
348-
349357
// Determine host platform
350-
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
351-
bool inLinuxDedicated = inAppService && !string.IsNullOrEmpty(functionsLogsMountPath);
352-
bool inLinuxConsumption = !inAppService && !string.IsNullOrEmpty(containerName);
358+
bool inLinuxDedicated = this.platformInformationService.InLinuxAppService();
359+
bool inLinuxConsumption = this.platformInformationService.InLinuxConsumption();
353360

354-
// Reading other enviroment variables for intializing the logger
355-
string tenant = this.nameResolver.Resolve("WEBSITE_STAMP_DEPLOYMENT_ID");
356-
string stampName = this.nameResolver.Resolve("WEBSITE_HOME_STAMPNAME");
361+
string tenant = this.platformInformationService.GetLinuxTenant();
362+
string stampName = this.platformInformationService.GetLinuxStampName();
363+
string containerName = this.platformInformationService.GetContainerName();
357364

358365
// If running in linux, initialize the EventSource listener with the appropiate logger.
359366
LinuxAppServiceLogger linuxLogger = null;
@@ -447,7 +454,8 @@ private void InitializeForFunctionsV1(ExtensionConfigContext context)
447454
new OptionsWrapper<DurableTaskOptions>(this.Options),
448455
this.connectionStringResolver,
449456
this.nameResolver,
450-
this.loggerFactory);
457+
this.loggerFactory,
458+
this.platformInformationService);
451459
this.defaultDurabilityProvider = this.durabilityProviderFactory.GetDurabilityProvider();
452460
this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper();
453461
var messageSerializerSettingsFactory = new MessageSerializerSettingsFactory();

src/WebJobs.Extensions.DurableTask/DurableTaskJobHostConfigurationExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ public static IWebJobsBuilder AddDurableTask(this IWebJobsBuilder builder)
8686
serviceCollection.TryAddSingleton<IApplicationLifetimeWrapper, HostLifecycleService>();
8787
serviceCollection.AddSingleton<ITelemetryActivator, TelemetryActivator>();
8888
serviceCollection.TryAddSingleton<IDurableClientFactory, DurableClientFactory>();
89+
#pragma warning disable CS0612 // Type or member is obsolete
90+
serviceCollection.AddSingleton<IPlatformInformationService, DefaultPlatformInformationProvider>();
91+
#pragma warning restore CS0612 // Type or member is obsolete
8992

9093
return builder;
9194
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
7+
{
8+
/// <summary>
9+
/// Interface for accessing the AppService plan information,
10+
/// the OS, and user-facing PL.
11+
///
12+
/// Note: The functionality is currently limited, but will grow
13+
/// along with the pursuit of more platform-specific defaults.
14+
/// </summary>
15+
[Obsolete]
16+
public interface IPlatformInformationService
17+
{
18+
/// <summary>
19+
/// Determines if the application is running on a Consumption plan,
20+
/// irrespective of OS.
21+
/// </summary>
22+
/// <returns>True if running in Consumption. Otherwise, False.</returns>
23+
bool InConsumption();
24+
25+
/// <summary>
26+
/// Determines if the application is running in a Linux Consumption plan.
27+
/// </summary>
28+
/// <returns>True if running in Linux Consumption. Otherwise, False.</returns>
29+
bool InLinuxConsumption();
30+
31+
/// <summary>
32+
/// Determines if the application is running in a Windows Consumption plan.
33+
/// </summary>
34+
/// <returns>True if running in Linux Consumption. Otherwise, False.</returns>
35+
bool InWindowsConsumption();
36+
37+
/// <summary>
38+
/// Determines if the application is running in a Linux AppService plan.
39+
/// </summary>
40+
/// <returns>True if running in Linux AppService. Otherwise, False.</returns>
41+
bool InLinuxAppService();
42+
43+
/// <summary>
44+
/// Returns the application tenant when running on linux.
45+
/// </summary>
46+
/// <returns>The application tenant.</returns>
47+
string GetLinuxTenant();
48+
49+
/// <summary>
50+
/// Returns the application stamp name when running on linux.
51+
/// </summary>
52+
/// <returns>The application stamp name.</returns>
53+
string GetLinuxStampName();
54+
55+
/// <summary>
56+
/// Returns the application container name when running on linux.
57+
/// </summary>
58+
/// <returns>The application container name.</returns>
59+
string GetContainerName();
60+
}
61+
}

0 commit comments

Comments
 (0)