Skip to content

Commit

Permalink
Apply new consumption defaults in AzureStorageDurabilityProvider (#1706)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmrdavid authored Mar 12, 2021
1 parent 6fd65a8 commit 85d0642
Show file tree
Hide file tree
Showing 19 changed files with 467 additions and 54 deletions.
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();

// 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)
{
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
#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
{
/// <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

0 comments on commit 85d0642

Please sign in to comment.