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

Apply new consumption defaults in AzureStorageDurabilityProvider #1706

Merged
merged 25 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d725310
WIP: Apply new consumption defaults in AzureStorageDurabilityProvider
davidmrdavid Mar 3, 2021
506a158
Refactor PlatformInformation utility to use DI
davidmrdavid Mar 3, 2021
f41e671
Fix linter errors
davidmrdavid Mar 3, 2021
6a78b8f
Added testing abstractions platformprovider
davidmrdavid Mar 3, 2021
23c8654
Fixed missing DurableTask constructor
davidmrdavid Mar 3, 2021
65a7406
Fixed more missing DurableTask constructor
davidmrdavid Mar 4, 2021
a5f6e65
Fixed more missing DurableTask constructors
davidmrdavid Mar 4, 2021
10f2b62
Apply further test modifications
davidmrdavid Mar 4, 2021
7a22217
Fix CustomHelperTypeDependencyInjection test
davidmrdavid Mar 4, 2021
a38a0ce
Fail fast when platformInformation service is not provided
davidmrdavid Mar 5, 2021
c35c4b8
Make batch size into 1/4 of the buffer threshold
davidmrdavid Mar 9, 2021
815043c
Set MaxConcurrentActivities to 10
davidmrdavid Mar 9, 2021
bb4d185
Remove accidental version change
davidmrdavid Mar 9, 2021
01cb6b8
Comment new defaults, validate controlQueueBatchSize
davidmrdavid Mar 10, 2021
a1f5760
Merge branch 'dev' of https://github.com/Azure/azure-functions-durabl…
davidmrdavid Mar 10, 2021
dbc3ff5
Documented changes in release notes
davidmrdavid Mar 10, 2021
87de988
Add pending docs
davidmrdavid Mar 10, 2021
733bc7d
Added simple test
davidmrdavid Mar 10, 2021
72ea9dc
Provide complete test set, detected bug
davidmrdavid Mar 10, 2021
50d34dd
Add nullable options
davidmrdavid Mar 11, 2021
d801423
Respect customer defaults
davidmrdavid Mar 11, 2021
330893a
revert version cchange
davidmrdavid Mar 11, 2021
a71d813
fix if-statement swap
davidmrdavid Mar 11, 2021
92e4042
Satisfy all tests
davidmrdavid Mar 11, 2021
7697565
Apply PR feedback
davidmrdavid Mar 11, 2021
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
3 changes: 2 additions & 1 deletion pending_docs.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<-- 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)).
<!-- 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)).
Your code PR should not be merged until your docs PR has been signed off. -->
https://github.com/MicrosoftDocs/azure-docs-pr/pull/149980
2 changes: 1 addition & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@

Improved concurrency defaults for the App Service Consumption plan (https://github.com/Azure/azure-functions-durable-extension/pull/1706)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactor
private readonly string defaultConnectionName;
private readonly INameResolver nameResolver;
private readonly ILoggerFactory loggerFactory;
private readonly bool inConsumption; // If true, optimize defaults for consumption
private AzureStorageDurabilityProvider defaultStorageProvider;

// Must wait to get settings until we have validated taskhub name.
Expand All @@ -27,15 +28,32 @@ public AzureStorageDurabilityProviderFactory(
IOptions<DurableTaskOptions> options,
IConnectionStringResolver connectionStringResolver,
INameResolver nameResolver,
ILoggerFactory loggerFactory)
ILoggerFactory loggerFactory,
#pragma warning disable CS0612 // Type or member is obsolete
IPlatformInformationService platformInfo)
#pragma warning restore CS0612 // Type or member is obsolete
{
this.options = options.Value;
this.nameResolver = nameResolver;
this.loggerFactory = loggerFactory;
this.azureStorageOptions = new AzureStorageOptions();
this.inConsumption = platformInfo.InConsumption();
ConnorMcMahon marked this conversation as resolved.
Show resolved Hide resolved

// The consumption plan has different performance characteristics so we provide
// different defaults for key configuration values.
int maxConcurrentOrchestratorsDefault = this.inConsumption ? 5 : 10 * Environment.ProcessorCount;
int maxConcurrentActivitiesDefault = this.inConsumption ? 10 : 10 * Environment.ProcessorCount;
this.azureStorageOptions.ControlQueueBufferThreshold = this.inConsumption ? 32 : this.azureStorageOptions.ControlQueueBufferThreshold;

// The following defaults are only applied if the customer did not explicitely set them on `host.json`
this.options.MaxConcurrentOrchestratorFunctions = this.options.MaxConcurrentOrchestratorFunctions ?? maxConcurrentOrchestratorsDefault;
this.options.MaxConcurrentActivityFunctions = this.options.MaxConcurrentActivityFunctions ?? maxConcurrentActivitiesDefault;

// Override the configuration defaults with user-provided values in host.json, if any.
JsonConvert.PopulateObject(JsonConvert.SerializeObject(this.options.StorageProvider), this.azureStorageOptions);

this.azureStorageOptions.Validate();
var logger = loggerFactory.CreateLogger(nameof(this.azureStorageOptions));
this.azureStorageOptions.Validate(logger);

this.connectionStringResolver = connectionStringResolver ?? throw new ArgumentNullException(nameof(connectionStringResolver));
this.defaultConnectionName = this.azureStorageOptions.ConnectionStringName ?? ConnectionStringNames.Storage;
Expand Down Expand Up @@ -138,8 +156,8 @@ internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationSe
ControlQueueBufferThreshold = this.azureStorageOptions.ControlQueueBufferThreshold,
ControlQueueVisibilityTimeout = this.azureStorageOptions.ControlQueueVisibilityTimeout,
WorkItemQueueVisibilityTimeout = this.azureStorageOptions.WorkItemQueueVisibilityTimeout,
MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions,
MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions,
MaxConcurrentTaskOrchestrationWorkItems = this.options.MaxConcurrentOrchestratorFunctions ?? throw new InvalidOperationException($"{nameof(this.options.MaxConcurrentOrchestratorFunctions)} needs a default value"),
MaxConcurrentTaskActivityWorkItems = this.options.MaxConcurrentActivityFunctions ?? throw new InvalidOperationException($"{nameof(this.options.MaxConcurrentOrchestratorFunctions)} needs a default value"),
ExtendedSessionsEnabled = this.options.ExtendedSessionsEnabled,
ExtendedSessionIdleTimeout = extendedSessionTimeout,
MaxQueuePollingInterval = this.azureStorageOptions.MaxQueuePollingInterval,
Expand All @@ -155,6 +173,11 @@ internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationSe
UseLegacyPartitionManagement = this.azureStorageOptions.UseLegacyPartitionManagement,
};

if (this.inConsumption)
ConnorMcMahon marked this conversation as resolved.
Show resolved Hide resolved
{
settings.MaxStorageOperationConcurrency = 25;
}

// When running on App Service VMSS stamps, these environment variables are the best way
// to enure unqique worker names
string stamp = this.nameResolver.Resolve("WEBSITE_CURRENT_STAMPNAME");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;

namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
{
/// <summary>
/// Provides information about the enviroment (OS, app service plan, user-facing PL)
/// using the DI-injected INameResolver.
/// </summary>
#pragma warning disable CS0612 // Type or member is obsolete
internal class DefaultPlatformInformationProvider : IPlatformInformationService
davidmrdavid marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning restore CS0612 // Type or member is obsolete
{
private readonly INameResolver nameResolver;

public DefaultPlatformInformationProvider(INameResolver nameResolver)
{
this.nameResolver = nameResolver;
}

public bool InConsumption()
{
return this.InLinuxConsumption() | this.InWindowsConsumption();
}

public bool InWindowsConsumption()
{
string value = this.nameResolver.Resolve("WEBSITE_SKU");
return string.Equals(value, "Dynamic", StringComparison.OrdinalIgnoreCase);
}

public bool InLinuxConsumption()
{
string containerName = this.GetContainerName();
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
bool inLinuxConsumption = !inAppService && !string.IsNullOrEmpty(containerName);
return inLinuxConsumption;
}

public bool InLinuxAppService()
{
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
string functionsLogsMountPath = this.nameResolver.Resolve("FUNCTIONS_LOGS_MOUNT_PATH");
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
bool inLinuxDedicated = inAppService && !string.IsNullOrEmpty(functionsLogsMountPath);
return inLinuxDedicated;
}

public string GetLinuxTenant()
{
return this.nameResolver.Resolve("WEBSITE_STAMP_DEPLOYMENT_ID");
}

public string GetLinuxStampName()
{
return this.nameResolver.Resolve("WEBSITE_HOME_STAMPNAME");
}

public string GetContainerName()
{
return this.nameResolver.Resolve("CONTAINER_NAME");
}
}
}
36 changes: 22 additions & 14 deletions src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ public class DurableTaskExtension :
#endif
private readonly bool isOptionsConfigured;
private readonly IApplicationLifetimeWrapper hostLifetimeService = HostLifecycleService.NoOp;

#pragma warning disable CS0612 // Type or member is obsolete
private readonly IPlatformInformationService platformInformationService;
#pragma warning restore CS0612 // Type or member is obsolete
private IDurabilityProviderFactory durabilityProviderFactory;
private INameResolver nameResolver;
private ILoggerFactory loggerFactory;
Expand Down Expand Up @@ -108,6 +110,7 @@ public DurableTaskExtension()
/// <param name="messageSerializerSettingsFactory">The factory used to create <see cref="JsonSerializerSettings"/> for message settings.</param>
/// <param name="errorSerializerSettingsFactory">The factory used to create <see cref="JsonSerializerSettings"/> for error settings.</param>
/// <param name="telemetryActivator">The activator of DistributedTracing. .netstandard2.0 only.</param>
/// <param name="platformInformationService">The platform information provider to inspect the OS, app service plan, and other enviroment information.</param>
#pragma warning restore CS1572
public DurableTaskExtension(
IOptions<DurableTaskOptions> options,
Expand All @@ -118,11 +121,15 @@ public DurableTaskExtension(
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory = null,
ILifeCycleNotificationHelper lifeCycleNotificationHelper = null,
IMessageSerializerSettingsFactory messageSerializerSettingsFactory = null,
#pragma warning disable CS0612 // Type or member is obsolete
IPlatformInformationService platformInformationService = null,
#pragma warning restore CS0612 // Type or member is obsolete
#if !FUNCTIONS_V1
IErrorSerializerSettingsFactory errorSerializerSettingsFactory = null,
#pragma warning disable SA1113, SA1001, SA1115
ITelemetryActivator telemetryActivator = null)
#pragma warning restore SA1113, SA1001, SA1115

#else
IErrorSerializerSettingsFactory errorSerializerSettingsFactory = null)
#endif
Expand All @@ -131,6 +138,7 @@ public DurableTaskExtension(
this.Options = options?.Value ?? new DurableTaskOptions();
this.nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.platformInformationService = platformInformationService ?? throw new ArgumentNullException(nameof(platformInformationService));
this.ResolveAppSettingOptions();

ILogger logger = loggerFactory.CreateLogger(LoggerCategoryName);
Expand Down Expand Up @@ -173,10 +181,15 @@ internal DurableTaskExtension(
IDurabilityProviderFactory orchestrationServiceFactory,
IConnectionStringResolver connectionStringResolver,
IApplicationLifetimeWrapper shutdownNotification,
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory)
IDurableHttpMessageHandlerFactory durableHttpMessageHandlerFactory,
#pragma warning disable CS0612 // Type or member is obsolete
IPlatformInformationService platformInformationService)
#pragma warning restore CS0612 // Type or member is obsolete

: this(options, loggerFactory, nameResolver, orchestrationServiceFactory, shutdownNotification, durableHttpMessageHandlerFactory)
{
this.connectionStringResolver = connectionStringResolver;
this.platformInformationService = platformInformationService;
}

/// <summary>
Expand Down Expand Up @@ -341,19 +354,13 @@ void IExtensionConfigProvider.Initialize(ExtensionConfigContext context)
/// </summary>
private void InitializeLinuxLogging()
{
// Read enviroment variables to determine host platform
string containerName = this.nameResolver.Resolve("CONTAINER_NAME");
string azureWebsiteInstanceId = this.nameResolver.Resolve("WEBSITE_INSTANCE_ID");
string functionsLogsMountPath = this.nameResolver.Resolve("FUNCTIONS_LOGS_MOUNT_PATH");

// Determine host platform
bool inAppService = !string.IsNullOrEmpty(azureWebsiteInstanceId);
bool inLinuxDedicated = inAppService && !string.IsNullOrEmpty(functionsLogsMountPath);
bool inLinuxConsumption = !inAppService && !string.IsNullOrEmpty(containerName);
bool inLinuxDedicated = this.platformInformationService.InLinuxAppService();
bool inLinuxConsumption = this.platformInformationService.InLinuxConsumption();

// Reading other enviroment variables for intializing the logger
string tenant = this.nameResolver.Resolve("WEBSITE_STAMP_DEPLOYMENT_ID");
string stampName = this.nameResolver.Resolve("WEBSITE_HOME_STAMPNAME");
string tenant = this.platformInformationService.GetLinuxTenant();
string stampName = this.platformInformationService.GetLinuxStampName();
string containerName = this.platformInformationService.GetContainerName();

// If running in linux, initialize the EventSource listener with the appropiate logger.
LinuxAppServiceLogger linuxLogger = null;
Expand Down Expand Up @@ -447,7 +454,8 @@ private void InitializeForFunctionsV1(ExtensionConfigContext context)
new OptionsWrapper<DurableTaskOptions>(this.Options),
this.connectionStringResolver,
this.nameResolver,
this.loggerFactory);
this.loggerFactory,
this.platformInformationService);
this.defaultDurabilityProvider = this.durabilityProviderFactory.GetDurabilityProvider();
this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper();
var messageSerializerSettingsFactory = new MessageSerializerSettingsFactory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public static IWebJobsBuilder AddDurableTask(this IWebJobsBuilder builder)
serviceCollection.TryAddSingleton<IApplicationLifetimeWrapper, HostLifecycleService>();
serviceCollection.AddSingleton<ITelemetryActivator, TelemetryActivator>();
serviceCollection.TryAddSingleton<IDurableClientFactory, DurableClientFactory>();
#pragma warning disable CS0612 // Type or member is obsolete
serviceCollection.AddSingleton<IPlatformInformationService, DefaultPlatformInformationProvider>();
#pragma warning restore CS0612 // Type or member is obsolete

return builder;
}
Expand Down
61 changes: 61 additions & 0 deletions src/WebJobs.Extensions.DurableTask/IPlatformInformationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;

namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
{
/// <summary>
/// Interface for accessing the AppService plan information,
/// the OS, and user-facing PL.
///
/// Note: The functionality is currently limited, but will grow
/// along with the pursuit of more platform-specific defaults.
/// </summary>
[Obsolete]
public interface IPlatformInformationService
ConnorMcMahon marked this conversation as resolved.
Show resolved Hide resolved
ConnorMcMahon marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Determines if the application is running on a Consumption plan,
/// irrespective of OS.
/// </summary>
/// <returns>True if running in Consumption. Otherwise, False.</returns>
bool InConsumption();

/// <summary>
/// Determines if the application is running in a Linux Consumption plan.
/// </summary>
/// <returns>True if running in Linux Consumption. Otherwise, False.</returns>
bool InLinuxConsumption();

/// <summary>
/// Determines if the application is running in a Windows Consumption plan.
/// </summary>
/// <returns>True if running in Linux Consumption. Otherwise, False.</returns>
bool InWindowsConsumption();

/// <summary>
/// Determines if the application is running in a Linux AppService plan.
/// </summary>
/// <returns>True if running in Linux AppService. Otherwise, False.</returns>
bool InLinuxAppService();

/// <summary>
/// Returns the application tenant when running on linux.
/// </summary>
/// <returns>The application tenant.</returns>
string GetLinuxTenant();

/// <summary>
/// Returns the application stamp name when running on linux.
/// </summary>
/// <returns>The application stamp name.</returns>
string GetLinuxStampName();

/// <summary>
/// Returns the application container name when running on linux.
/// </summary>
/// <returns>The application container name.</returns>
string GetContainerName();
}
}
Loading