Skip to content

Fallback to Host ID if WEBSITE_SITE_NAME isn't defined #1178

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions docs/BindingsOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [Sql\_Trigger\_MaxBatchSize](#sql_trigger_maxbatchsize)
- [Sql\_Trigger\_PollingIntervalMs](#sql_trigger_pollingintervalms)
- [Sql\_Trigger\_MaxChangesPerWorker](#sql_trigger_maxchangesperworker)
- [WEBSITE\_SITE\_NAME](#website_site_name)
- [Scaling for Trigger Bindings](#scaling-for-trigger-bindings)
- [Retry support for Trigger Bindings](#retry-support-for-trigger-bindings)
- [Startup retries](#startup-retries)
Expand Down Expand Up @@ -154,11 +155,10 @@ The upper limit on the number of pending changes in the user table that are allo

#### WEBSITE_SITE_NAME

The unique name used in creating the lease tables. The local apps depend on this setting for creating unique leases tables, please give a unique name for each app.
If this setting exists, it will be used to generate a unique identifier for the function that is used for tracking function state. If not specified, this unique identifier will be generated from the [IHostIdProvider.GetHostIdAsync](https://github.com/Azure/azure-webjobs-sdk/blob/dev/src/Microsoft.Azure.WebJobs.Host/Executors/IHostIdProvider.cs#L14).

> **NOTE:**
> * If the setting is re-used across apps, having the same function name could cause the functions to use the same lease tables and the function runs to not work as expected.
> * If you have 2 different SQL trigger functions with same functionName locally, not having WEBSITE_SITE_NAME would mean that the same leasees table would be used for both triggers resulting in only one of the functions being triggered.
> * This is a read-only variable that is provided by the Azure App service for deployed functions and the user provided value will be overridden. Refer to [Environment variables](https://learn.microsoft.com/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#app-environment) for apps.

### Scaling for Trigger Bindings
Expand Down
9 changes: 5 additions & 4 deletions docs/TriggerBinding.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ To find the name of the leases table associated with your function, look in the

This log message is at the `Information` level, so make sure your log level is set correctly.

NOTE: `FunctionId` is generated from a couple of inputs:
- The [WEBSITE_SITE_NAME](https://github.com/Azure/azure-functions-sql-extension/blob/main/docs/BindingsOverview.md#website_site_name) setting
- The name of the function
NOTE: `FunctionId` is generated from the name of the function and either

If either of these values are changed then a new FunctionId will be generated and result in the function starting over from the beginning, including creating a new Leases table.
* The [WEBSITE_SITE_NAME](https://github.com/Azure/azure-functions-sql-extension/blob/main/docs/BindingsOverview.md#website_site_name) setting
* [IHostIdProvider.GetHostIdAsync](https://github.com/Azure/azure-webjobs-sdk/blob/dev/src/Microsoft.Azure.WebJobs.Host/Executors/IHostIdProvider.cs#L14) as a fallback if the WEBSITE_SITE_NAME setting doesn't exist

Copy link
Preview

Copilot AI May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Clarify in the documentation that while falling back to the host ID supports local development, it may lead to differences in consumption plan scaling behavior compared to when WEBSITE_SITE_NAME is used.

Suggested change
**Note:** While falling back to the host ID (`IHostIdProvider.GetHostIdAsync`) supports local development, it may lead to differences in scaling behavior in a consumption plan compared to when the `WEBSITE_SITE_NAME` setting is used. This is because the host ID is generated differently in local and production environments, which can affect how instances are scaled.

Copilot uses AI. Check for mistakes.

If either the name of the function or the ID value are changed then a new FunctionId will be generated and result in the function starting over from the beginning, including creating a new Leases table.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's also add what ID means here.


This table is used to ensure that all changes are processed and that no change is processed more than once. This table consists of two groups of columns:

Expand Down
1 change: 0 additions & 1 deletion samples/samples-csharp/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesCSharp",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
1 change: 0 additions & 1 deletion samples/samples-csx/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesCsx",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
1 change: 0 additions & 1 deletion samples/samples-java/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "java",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesJava",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
3 changes: 1 addition & 2 deletions samples/samples-js-v4/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesNodeV4"
"SqlConnectionString": ""
}
}
1 change: 0 additions & 1 deletion samples/samples-js/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesJavascript",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
1 change: 0 additions & 1 deletion samples/samples-outofproc/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesOOP",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
1 change: 0 additions & 1 deletion samples/samples-powershell/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"FUNCTIONS_WORKER_RUNTIME": "powershell",
"FUNCTIONS_WORKER_RUNTIME_VERSION" : "~7.2",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesPowershell",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
1 change: 0 additions & 1 deletion samples/samples-python-v2/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesPythonV2",
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
}
}
1 change: 0 additions & 1 deletion samples/samples-python/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"SqlConnectionString": "",
"WEBSITE_SITE_NAME": "SamplesPython",
"Sp_SelectCost": "SelectProductsCost",
"ProductCost": 100
}
Expand Down
15 changes: 0 additions & 15 deletions src/SqlBindingUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,6 @@ public static string GetConnectionString(string connectionStringSetting, IConfig
return connectionString;
}

public static string GetWebSiteName(IConfiguration configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
string websitename = configuration.GetConnectionStringOrSetting(SqlBindingConstants.WEBSITENAME);
// We require a WEBSITE_SITE_NAME for avoiding duplicates if users use the same function name accross apps.
if (string.IsNullOrEmpty(websitename))
{
throw new ArgumentException($"WEBSITE_SITE_NAME cannot be null or empty in your function app settings, please update the setting with a string value. Please refer to https://github.com/Azure/azure-functions-sql-extension/blob/main/docs/BindingsOverview.md#website_site_name for more information.");
}
return websitename;
}

/// <summary>
/// Parses the parameter string into a list of parameters, where each parameter is separated by "," and has the form
/// "@param1=param2". "@param1" is the parameter name to be used in the query or stored procedure, and param1 is the
Expand Down
23 changes: 14 additions & 9 deletions src/TriggerBinding/SqlTriggerBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ public async Task<IListener> CreateListenerAsync(ListenerFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context), "Missing listener context");

string userFunctionId = this.GetUserFunctionId();
string oldUserFunctionId = await this.GetOldUserFunctionIdAsync();
return new SqlTriggerListener<T>(this._connectionString, this._tableName, this._leasesTableName, userFunctionId, oldUserFunctionId, context.Executor, this._sqlOptions, this._logger, this._configuration);
string websiteSiteNameFunctionId = this.GetWebsiteSiteNameFunctionId();
string hostIdFunctionId = await this.GetHostIdFunctionIdAsync();
return new SqlTriggerListener<T>(this._connectionString, this._tableName, this._leasesTableName, websiteSiteNameFunctionId, hostIdFunctionId, context.Executor, this._sqlOptions, this._logger, this._configuration);
}

public ParameterDescriptor ToParameterDescriptor()
Expand All @@ -94,18 +94,23 @@ public ParameterDescriptor ToParameterDescriptor()
}

/// <summary>
/// Returns an ID that uniquely identifies the user function.
/// Returns an ID that uniquely identifies the user function, based on the WEBSITE_SITE_NAME configuration value.
///
/// We call the WEBSITE_SITE_NAME from the configuration and use that to create the hash of the
/// user function id. Appending another hash of class+method in here ensures that if there
/// are multiple user functions within the same process and tracking the same SQL table, then each one of them
/// gets a separate view of the table changes.
/// </summary>
private string GetUserFunctionId()
/// <returns>The function ID, or NULL if there isn't a config value for WEBSITE_SITE_NAME</returns>
private string GetWebsiteSiteNameFunctionId()
{
// Using read-only App name for the hash https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#app-environment
string websiteName = SqlBindingUtilities.GetWebSiteName(this._configuration);

string websiteName = this._configuration.GetConnectionStringOrSetting(SqlBindingConstants.WEBSITENAME);
Copy link
Preview

Copilot AI May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the XML documentation to explicitly note that this method may return null if WEBSITE_SITE_NAME is not set, so that consumers are aware of the fallback to host ID.

Copilot uses AI. Check for mistakes.

if (string.IsNullOrEmpty(websiteName))
{
this._logger.LogWarning("WEBSITE_SITE_NAME configuration is not set, will fall back to using function ID based on the host ID. This will mean consumption plan scaling will not work as intended.");
return null;
}
var methodInfo = (MethodInfo)this._parameter.Member;
// Get the function name from FunctionName attribute for .NET functions and methodInfo.Name for non .Net
string functionName = ((FunctionNameAttribute)methodInfo.GetCustomAttribute(typeof(FunctionNameAttribute)))?.Name ?? $"{methodInfo.Name}";
Expand All @@ -118,7 +123,7 @@ private string GetUserFunctionId()
}

/// <summary>
/// Returns the deprecated ID that was used to identify the user function.
/// Returns an ID that uniquely identifies the user function, based on the host ID.
///
/// We call the WebJobs SDK library method to generate the host ID. The host ID is essentially a hash of the
/// assembly name containing the user function(s). This ensures that if the user ever updates their application,
Expand All @@ -127,7 +132,7 @@ private string GetUserFunctionId()
/// are multiple user functions within the same process and tracking the same SQL table, then each one of them
/// gets a separate view of the table changes.
/// </summary>
private async Task<string> GetOldUserFunctionIdAsync()
private async Task<string> GetHostIdFunctionIdAsync()
{
string hostId = await this._hostIdProvider.GetHostIdAsync(CancellationToken.None);

Expand Down
Loading