DEV Community

Chris Gillum
Chris Gillum

Posted on

Resetting your Durable Functions Task Hub state

I was recently asked in our GitHub Discussions board how to delete all storage resources associated with a Durable Functions app. I posted an answer that I was happy with but thought it might also be worth sharing here for broader reach.

Why would I want to delete all my Durable storage resources?

There are several reasons you might want to delete all your Durable state.

  • You want to deploy a new version of your app from scratch and don't intend to preserve any existing orchestrations (check out our Versioning and Zero Downtime Deployments docs if you're interested in this topic).
  • You have an environment that's been sitting around and want to quickly clean up the storage resources to avoid incurring storage costs.
  • You're doing rapid prototyping and don't want the debugger to be triggered by old orchestrations from previous debug sessions.

So while there are a few reasons to do this, Durable Functions doesn't actually provide a first-class API for this.

What's the "official" way to clean up state?

The "official" way to delete your Durable state would be to use our Terminate APIs to terminate all still-running orchestrations, followed by our Purge APIs to delete all state associated with these instances. This works and is fully supported, but can be very time consuming, especially if you have a lot of orchestration instances. It can also be expensive if you're using the default Azure Storage backend since each transaction is billed individually.

If you're curious about why this approach can be slow, consider the following:

  • The Purge APIs will only clear data for orchestration instances that are in a non-running state (Completed, Failed, or Terminated). If you want to purge everything, you need to make sure all your running instances are completed or terminated.
  • Scanning for all running instances can be time consuming. We have APIs that allow you to query for running instances, but this is a very slow API when using Azure Storage because they rely on multi-partition table scans.
  • The Terminate API works by sending a message to a specific orchestration instance. Only after the orchestration receives the message will it transition into the Terminated state. This means any existing queue backlogs will further slow down the process.

Read-on if you need a faster way to clean up your Durable state and you're willing to try something that's "less official".

What's the "fast" way to clean up Durable state?

The fast and less official way to clean up state is to use the underlying Durable Task Framework storage APIs directly. Durable Functions has an internal dependency on the Durable Task Framework, so no new packages need to be added to your app to access them. Here's an example function that demonstrates how to clean up Azure Storage state quickly (disclaimer: use any of my code samples at your own risk).

class ResetDurableStateHelper
{
    readonly INameResolver nameResolver;

    // INameResolver is a service of the Functions host that can
    // be used to look up app settings.
    public ResetDurableStateHelper(INameResolver nameResolver)
    {
        this.nameResolver = nameResolver;
    }

    [FunctionName("ResetDurableState")]
    public async Task<HttpResponseMessage> ResetDurableState(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestMessage req,
        [DurableClient] IDurableClient client,
        ILogger log)
    {
        // Get the connection string from app settings
        string connString = this.nameResolver.Resolve("AzureWebJobsStorage");
        var settings = new AzureStorageOrchestrationServiceSettings
        {
            StorageConnectionString = connString,
            TaskHubName = client.TaskHubName,
        };

        // AzureStorageOrchestrationService is defined in
        // Microsoft.Azure.DurableTask.AzureStorage, which is an
        // implicit dependency of the Durable Functions extension.
        var storageService = new AzureStorageOrchestrationService(settings);

        // Delete all Azure Storage tables, blobs, and queues in the task hub
        log.LogInformation(
            "Deleting all storage resources for task hub {taskHub}...",
            settings.TaskHubName);
        await storageService.DeleteAsync();

        // Wait for a minute since Azure Storage won't let us immediately
        // recreate resources with the same names as before.
        log.LogInformation(
            "The delete operation completed. Waiting one minute before recreating...");
        await Task.Delay(TimeSpan.FromMinutes(1));

        // Optional: Recreate all the Azure Storage resources for this task hub.
        // This happens automatically whenever the function app restarts, so it's
        // not a required step.
        log.LogInformation(
            "Recreating storage resources for task hub {taskHub}...",
            settings.TaskHubName);
        await storageService.CreateIfNotExistsAsync();

        return req.CreateResponse(System.Net.HttpStatusCode.OK);
    }
}
Enter fullscreen mode Exit fullscreen mode

In short, the above HTTP-triggered function uses the Azure Storage-based orchestration service APIs to delete all the task hub state and recreate it. Refer to the inline code comments for more details on each step.

Unfortunately, these APIs are only available in .NET, so users of other language stacks will need to either use the traditional techniques or create a new .NET-based app and exercise their copy/paste skills.

What if I'm using a backend other than Azure Storage?

Great question! For example, you might be using the Netherite or MSSQL backends for storing your Durable Task state instead of Azure Storage. It turns out that the process is exactly the same (or perhaps slightly simpler). Here's an example with the MSSQL provider, since I happened to have some code for this handy (again, use at your own risk).

class ResetDurableStateHelper
{
    readonly INameResolver nameResolver;

    // INameResolver is a service of the Functions host that can be used
    // to look up app settings.
    public ResetDurableStateHelper(INameResolver nameResolver)
    {
        this.nameResolver = nameResolver;
    }

    [FunctionName("ResetDurableState")]
    public async Task<HttpResponseMessage> ResetDurableState(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestMessage req,
        ILogger log)
    {
        // Get the connection string from app settings.
        string cnString = this.nameResolver.Resolve("SQLDB_Connection");
        string dbName = new SqlConnectionStringBuilder(cnString).InitialCatalog;
        var settings = new SqlOrchestrationServiceSettings(cnString);

        // SqlOrchestrationService is defined in the
        // Microsoft.DurableTask.SqlServer nuget package, which is an
        // implicit dependency of Microsoft.DurableTask.SqlServer.AzureFunctions.
        var storageService = new SqlOrchestrationService(settings);

        // This will delete all Durable Task resources associated with
        // the database. It's not scoped to any specific task hub.
        // Non-Durable Task schema objects will not be impacted.
        log.LogInformation(
            "Deleting all Durable Task resources in the {database} database...",
            dbName);
        await storageService.DeleteAsync();

        // Optional: Recreate all the Durable Task SQL resources for this
        // task hub. This is done automatically whenever the function app
        // restarts, so it's not a required step if you plan to restart.
        log.LogInformation(
            "Recreating Durable Task resources in the {database} database...",
            dbName);
        await storageService.CreateIfNotExistsAsync();

        return req.CreateResponse(System.Net.HttpStatusCode.OK);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this post puts a useful tool in your toolbox when using Durable Functions. The above procedure may not be appropriate for some production environments, but it may be useful for those that want a quick way to clean up their storage and don't have strict data retention policies.

Also, if you happen to run this cleanup function in the same Durable function app that you're cleaning up, you may want to restart the app just to make 100% sure that everything gets back into a clean state (running threads may not be happy about having their storage deleted and recreated right from underneath them). 😉

Discussion (0)