Understanding Azure Durable Functions

In this post I would like to share a simple Azure Durable Function which will perform Simple Return Value along with some Timer Delays.

Function Replays

As you are aware Functions undergo multiple Replays which may require aligning your code as well. 

context.IsReplaying property says whether it is a replay.

The Code

The code ensures there is a 2 minute delay between sections.

namespace DurableFunctionTest
{
     public static class Function1
     {
         [FunctionName(“Function1_HttpStart”)]
         public static async Task<HttpResponseMessage> HttpStart(
             [HttpTrigger(AuthorizationLevel.Anonymous, “get”, “post”)]HttpRequestMessage req,
             [OrchestrationClient]DurableOrchestrationClient starter,
             ILogger log)
         {
             // Function input comes from the request content.
             string instanceId = await starter.StartNewAsync(“Function1”, null);

            log.LogInformation($”Started orchestration with ID = ‘{instanceId}’.”);

            return starter.CreateCheckStatusResponse(req, instanceId);
         }

        [FunctionName(“Function1”)]
         public static async Task<List<string>> RunOrchestrator(
             [OrchestrationTrigger] DurableOrchestrationContext context)
         {
             var outputs = new List<string>();

            outputs.Add(await context.CallActivityAsync<string>(“Function1_Hello”, “London”));

            return outputs;
         }

        [FunctionName(“Function1_Hello”)]
         public static string SayHello([ActivityTrigger] string name, ILogger log)
         {
             log.LogInformation($”step 1 {DateTime.Now.ToLongTimeString()}”);
             Thread.Sleep(2 * 60 * 1000); // 2min

            log.LogInformation($”step 2 {DateTime.Now.ToLongTimeString()}”);
            Thread.Sleep(2 * 60 * 1000); // 2min

            log.LogInformation($”step 3 {DateTime.Now.ToLongTimeString()}”);
             Thread.Sleep(2 * 60 * 1000); // 2min

            log.LogInformation($”step 4 {DateTime.Now.ToLongTimeString()}”);
             Thread.Sleep(2 * 60 * 1000); // 2min

            return $”Hello {name} !”;
         }
     }
}

The Output

2/10/2020 6:38:10 PM] 352ff6a310684f6eba4274bb81a54220: Function ‘Function1_Hello (Activity)’ started. IsReplay: False. Input: (40 bytes). State: Started. HubName: DurableFunctionsHub. AppName: . SlotName: . ExtensionVersion: 1.8.2. SequenceNumber: 9.
[2/10/2020 6:38:10 PM] Executing ‘Function1_Hello’ (Reason=”, Id=7f616c57-f299-4f9c-91ec-75feaa180893)
[2/10/2020 6:38:10 PM] step 1 1:38:10 PM
[2/10/2020 6:38:13 PM] Host lock lease acquired by instance ID ‘000000000000000000000000FE3C5631’.
[2/10/2020 6:40:08 PM] step 2 1:40:08 PM
[2/10/2020 6:40:10 PM] step 2 1:40:10 PM
[2/10/2020 6:40:10 PM] step 2 1:40:10 PM
[2/10/2020 6:42:08 PM] step 3 1:42:08 PM
[2/10/2020 6:42:10 PM] step 3 1:42:10 PM
[2/10/2020 6:42:10 PM] step 3 1:42:10 PM
[2/10/2020 6:44:08 PM] step 4 1:44:08 PM
[2/10/2020 6:44:10 PM] step 4 1:44:10 PM
[2/10/2020 6:44:10 PM] step 4 1:44:10 PM
[2/10/2020 6:46:08 PM] Executed ‘Function1_Hello’ (Succeeded, Id=02c78620-a573-4ffa-9f62-0aac2333ceaf)

The Inference

As you can see, although the Function replayed multiple times, but the restart was not loosing any time, it was accurate enough to replay from the past exit point.

Notes

Break your code into multiple chunks

Ensure transactions are protecting the Atomicity.

SQL Server–Compare Performance of INT, GUID, Sequential GUID

In this post we can compare the performance of ID as INT, GUID & Sequential GUID

Advantages of GUID Columns as Primary Key

  • Makes them Globally Unique
  • Can backup/restore/sync to another DB without breaking Primary Keys & Foreign Keys
  • No Performance Impact (proven here)

Note

Sequential GUID preferred over GUID to avoid pagination issues

PROOF – No Performance Issues on GUID

Create Table – INT Primary AutoIncrement

CREATE TABLE [dbo].[IDTest_INT](
     [ID] [int] IDENTITY(1,1) NOT NULL,
     [Name] [varchar](100) NOT NULL,
  CONSTRAINT [PK_IDTest_INT] PRIMARY KEY CLUSTERED
(
     [ID] ASC
)
)

Create Table – GUID Primary –

CREATE TABLE [dbo].[IDTest_GUID](
     [ID] uniqueidentifier DEFAULT NEWID(),
     [Name] [varchar](100) NOT NULL,
  CONSTRAINT [PK_IDTest_GUID] PRIMARY KEY CLUSTERED
(
     [ID] ASC
)
)

Create Table – GUID Primary – Sequential GUID

CREATE TABLE [dbo].[IDTest_SequentialGUID](
     [ID] uniqueidentifier DEFAULT NEWSEQUENTIALID(),
     [Name] [varchar](100) NOT NULL,
  CONSTRAINT [PK_IDTest_SequentialGUID] PRIMARY KEY CLUSTERED
(
     [ID] ASC
)
)

INSERT TEST – 1 LAKH RECORDS

INT 13 seconds

GUID 12 seconds

Sequential GUID  11 seconds

INFERENCE No Impact on Performance for Insert

Here are the queries for same:

declare @count int
select @count = 1
while  @count <= 100000
begin
     insert into IDTest_INT (name) values(‘Name’ + STR(@count))
     insert into IDTest_GUID (name) values(‘Name’ + STR(@count))
     insert into IDTest_SequentialGUID (name) values(‘Name’ + STR(@count))
     select @count = @count + 1
end

SELECT TEST – ON ID

INT 0seconds

GUID 0seconds

Sequential GUID  0seconds

Here are the queries for same:

  SELECT * FROM IDTest_INT WHERE ID = 1000

  SELECT * FROM IDTest_GUID WHERE ID = ‘1EDE341C-7692-4D7C-A99D-000C387337DE’

  SELECT * FROM IDTest_SequentialGUID WHERE ID = ’84a4d912-5448-ea11-8740-48f17ffd0966′

INFERENCE No Impact on Performance for Select