1

I have an Azure Functions app where I'm trying to eliminate delays in requests as much as possible. To combat cold start times, we've upgraded our Azure Functions plan to ensure we generally have one or more pre-warmed instances ready to go.

However, even with a pre-warmed instance, the very first HttpTrigger call to a newly launched function has a delay because it needs to establish a connection to the database. It appears a database connections is not established until the DataContext is instantiated, which in turn, doesn't happen until it is needed by an HttpTrigger. After that first request to the database, everything is quite performant.

I'm using Dependency Injection to create a DbContextPool in my FunctionsStartup class:

services.AddDbContextPool<DataContext>(options => { 
    options.UseSqlServer(connectionString);
});

I understand that establishing a database connection is going to naturally take a little bit of time, but is there any way to get Azure Functions to get its connection pool going at startup rather than waiting until the first HttpTrigger to instantiate my DbContext and connect to the database?

JacobJ
  • 3,677
  • 3
  • 28
  • 32
  • I don't think so. Without the first request to pre warm (load things in memory), the execution runtime has no clue what it needs – Thiago Custodio Dec 14 '22 at 17:22

1 Answers1

0

I was able to figure out a solution thank to this answer. The great thing about this solution is that it not only works for pre-warming database/DbContext connections, but can be used to pre-warm all sorts of connections (e.g., Storage Accounts, Key Vault access, etc.).

using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;

[assembly: WebJobsStartup(typeof(MyCompany.MyProduct.MyFunctionAppInitializer), "MyFunctionAppInitializer")]

namespace MyCompany.MyProduct;

public class MyFunctionAppInitializer : IWebJobsStartup
{
    public void Configure(IWebJobsBuilder builder)
    {
        builder.AddExtension<MyFunctionAppInitializerConfigProvider>();
    }
}

internal class MyFunctionAppInitializerConfigProvider : IExtensionConfigProvider
{
    private readonly IServiceScopeFactory scopeFactory;

    public MyFunctionAppInitializerConfigProvider(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public void Initialize(ExtensionConfigContext context)
    {
        using IServiceScope scope = scopeFactory.CreateScope();
        Task preWarmTask = PreWarmConnections(scope.ServiceProvider);
        preWarmTask.Wait();
    }
    
    private static async Task PreWarmConnections(IServiceProvider serviceProvider)
    {
        // Connect to Database
        var dbContext = serviceProvider.GetService<MyDbContext>();
        await dbContext.PingDatabase();
            
        // Connect to Storage Access
        await MyStorageAccess.PingStorageAccess();
            
        // Connect to Signing Key Vault
        MyAuthorization.InitializeCryptoClient();
    }
}

As a bit of background: if you're like me and first tried putting a DbContext instantiation/connection right in the Startup class, you'll quickly discover you get all sorts of crashing errors (usually logging-related) when you try to deploy this to your Function environment. This is because the Function App is not (yet) fully initialized in the Configure() method of that class.

Using a WebJobStartup call, however, seems to occur after everything is neatly initialized, but still upon launch of the instance. In practice, this has allowed me to get all the connections going I needed such that those requests made to my Function app are decently snappy.

JacobJ
  • 3,677
  • 3
  • 28
  • 32