Actors in Serverless
Khaled Hikmat
Software EngineerI started with this documentation page to learn about Azure durable Functions. I wanted to know if I can build a way to implement actors in Azure Functions. Actors Programming Model is pretty interesting and I did some work on it before.
Following the Azure Functions sample instructions mentioned in the above link, I quickly got up and running. However, I wanted to answer the following questions about actors in Azure Functions:
- Create a new actor giving a provided actor id
- Signal an existing actor to perform something
- When do actors get created?
- When do actors get terminated?
- Can we read the actor's internal state?
- What about .NET Core and .NET Standard 2.0 and other stuff?
#
Create a new actor:I created an HTTP trigger that looks like this where I provide a code that can be used as an instance id for the singleton i.e. membership actor. If the membership actor status is null or not running, then I start it with a StartNewAsync
:
#
Signal an existing actor to perform somethingIf the membership actor does exist, we raise a refresh
event to wake up the singleton so it can do work:
The actual membership actor code looks like this:
#
Multiple signalsBut what happens if the actor is signaled frantically via raising an external event from an HTTP trigger, for example? The event signals are actually enqueued to the instance so they should run as many times as they are sginaled.
If you are observing the actor's streaming logs when you try this, it could get very confusing. This is because durable functions manage long-term running processes in short-lived functions is by taking advantage of state retrieved in the context
and replaying the function to resume at the next step (from this article). Effectively what you will see if that functions are started, completed and re-started again to resume state.
#
Code DelaysSingletons should not use Task
functions such as Task.Delay(millis)
to simulate code delays. This will cause run-time errors:
The preferred way for delays or timeouts is:
Where deadline
is defined:
It is very important that we leverage the context
to provide accurate timer information as opposed to TimeSpan
and DateTime.Now
, etc. I have seen very varying (not correct) results when I used TimeSpan.FromMinutes(30)
, for example.
#
Wait on multiple eventsWhat if we want the actor to wait on an external event or on a internal timeout event to perhaps refresh our membership periodically? I created another membership function i.e. E3_MembershipWithTimer
that awaits on either an operation event or a timeout event:
#
When do actors get created?Actors or singletons actually do persist in storage (please see the section about termination)......this is how an Azure Functions knows how to start them when it restarts. So if you create actors with specific instance ids (or actor ids), shut down the functions and restart it, the singleton instances are available. When you want to trigger an instance, you must check its running state and then invoke the proper API:
#
When do actors get terminated?They can be easily terminated using the TerminateAsync
API. So I created a little HTTP trugger that would terminate instances:
The Azure Durable Functions maintain a state of all running instances in a task hub which is basically a storage resource with control queues, qork-item queues, a history table and lease blobs. You can read more about this here.
Effectively, the host.json
durableTask
indicate the hub name:
The run-time environment stores related information about running instances in storage keyed by the hub name.
#
The actor stateEach actor has an internal state! It is initially read by the singleton as an input:
and it is updated using:
But it seems that the internal state is actually not persisted anywhere ...it is transient. When actors are initially created, a state is passed as an input i.e. context.GetInput<dynamic>()
and the actor updates it with a call to ContinueAsNew
which actually restarts itself with a new state.
The internal state can be read by using one of the APIs of the instance management:
Where client
is DurableOrchestrationClient
. The status input is the actor's internal state:
I am not sure if the actor internal state is meant to hold big state though. Perhaps it is better if the actor exposes its state externally so HTTP triggers, for example, can read it directly from the external store.
One way of doing this is to modify the code to look something like this:
and the membership actor:
and the HTTP trigger that retrieves the membership actor state from an extenal source without dealing with the actor:
So unlike regular actor implementation, Azure Functions singletons do not expose any method to be called from the outside! The platform only allows starting/creating, querying and terminating instances.
#
Comments & Anamolies#
.NET Core and .NET Standard 2.0It is work in progress! It is best to use the .NET full framework with Azure Durable Functions. Hopefully this will change soon and we will be able to use .NET Core reliably.
#
Local DebuggingI had a very hard time with this. The symptoms that I experienced are unfortutanely not experienced by other developers who tried this as I could not see similar reported issues. I am using Vs2017 and Azure Functions Extension ...the latest at the time of writing DEC/2017.
Here are my comments:
- If you want to debug locally, make sure you set both the local.setting.json and host.json file to
copy always
. You do this from the properties window. - On both of my developer machines, I hit F5, it prompts me to install the Azure Functions Core tools and things look quite good. I was able to run locally.
- But then subsequent F5, I get very different results ranging from:
- The CLI starts and exits on its own ...I could not find out what the reason is
- The CLI starts and displays the functions URls. But it also complains about some files were changed and the host needs to restart. The URls are not responsive and there is nothing you can do except to terminate and restart.
- The CLI statrs and actually works.....does not happen often ...but I have seen it work
- F5 mostly causes the CLI to start and exit. Control-F5 does not exit ...but the function URLs are not accessible due to this
change detected
message.
- Effectively, local debugging did not work for me at all. It was a very frustrating experience. So I had to deploy everything (see deplyment below) to Azure and debug there....another frustrating experience.
#
Deployment- The only effective way I was able to find out how to deploy a Durable Functions App is via Visual Studio. I have heard some people got it to work with VSTS. But, given that this is a test run, I did not really explore this option.
- However, if you just click the
publish
button in VS, it will auto-create a storage account for you which names things really weird. My recommendation is to create the App Service, Service Plan, Storage and App Insights in portal or via Azure CLI and then use Visual Studio to publish into it. - If you renamed a function and re-deployed, Visual Studio will publish the new functions app with the new function. But the old function will still be there (you can see it from the portal). You can then use
Kudo
, navigate to the directory and actually delete the old function folder. - The local.settings.json entries are not deployed to Azure! This means you have to manually create them in the portal app settings or in Visual Studio deployment window.
#
StorageAs mentioned, an Azure Storage is required to maintain teh durable instances. They are keyed off the hub name you specify in the host. There are entries in blob, tables, files and queues.
#
LoggingUnless you turn on streaming on a function in the portal, you don't get anything (or at least, I could not find a way to do it). But watching the log from the portal is very difficult as it times out and it is not very user friendly. This is one area that requires better UX in the portal. The logs are also stored on the app's file system which you can access from Kudo
. However, I noticed that, unless you request stream logging on a function, these files are not created.
So the story of logging is a little frustrating at best! I had to use App Insights trace to see what is going on.
#
TimersAs mentioned above, it is very important that we leverage the context to provide accurate timer information as opposed to TimeSpan
and DateTime.Now
, etc. Initially I used TimeSpan.FromMinutes(30)
to wait for 30 minutes....but the way to do it is to always use the context
such as DateTime deadline = context.CurrentUtcDateTime.AddMinutes(30);
. After doing that, I started getting conistent timeout periods and things worked normally.
#
Instance TerminationAlthough TerminateAsync
on an instance works, I am not exactly sure if it works the way it is supposed to:
- If I have a running instance and that instance is actually waiting on an external or time out event,
TerminateAsync
does not do anything. I guess because a message is enqueued to the instance but the instance is waiting on other events ....so it did not get theterminate
signal yet. - If the instance is not waiting on anything,
TerminateAsync
replays the instance which runs code that you don't necessarily want to run. For example, I had an instance that triggers a logic app once it receives anend
operation which works. However, if I terminate the instance usingTerminateAync
, the code triggers the logic app again because it was replayed!
Not sure if this behavior is correct and what the terminate
signal actually do.
#
ConclusionReflecting on the little time that I spent with Azure Durable Functions, I think they will play an important part of my future solutions. I think they have what it takes to use as actors especially that the Azure Durable Functions are designed to support 1000's of instances . If we externalize the actor's state, we will be able to query the external store as opposed to query the actors themselves to retrieve their state.
Azure Durable Actors can also employ reminders and other sophisticated techniques found in Service Fabric actors such as long-running, stateful, single-threaded, location-transparent and globally addressable (taken from the overview documentation page). However, as stated above and unlike other actor implementations, Azure Functions singletons do not expose methods that can be called from the outside.
In any case, the Azure Durable Functions are still in preview. So we can expect many great features to be added soon.