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

Allow RaiseEventAsync to create an orchestration instance if one doesn't already exist #21

Open
mikhailshilkov opened this Issue Jul 21, 2017 · 6 comments

Comments

Projects
None yet
3 participants
@mikhailshilkov
Contributor

mikhailshilkov commented Jul 21, 2017

I have the following scenario (simplified):

  • Function A runs every minute and retrieves a metric value, then it sends it to Function B
  • Function B is an Actor from Durable Function example. It gets an event (metric) and maintains its state (list of metric values over time)

I want 1 instance of Function B per metric type. My question is - how does the first function know/manage the instance ID and instance lifecycle. Should it instantiate new instances of Function B? I did something like this for now in Function A:

var status = await starter.GetStatusAsync(resource);
if (status == null)
{
   await starter.StartNewAsync(nameof(FunctionB), resource, new FunctionBState());
}
await starter.RaiseEventAsync(resource, "new-value", metric);

Is that a proper way? Should it also check if that status is not faulty, and restart otherwise? Isn't it a waste to GetStatus every time? Is it OK that I assigned instance ID myself or do I need to store auto-generated one somewhere?

Missing some guidance on that part of function lifecycle management.

@cgillum cgillum added the question label Jul 21, 2017

@cgillum

This comment has been minimized.

Show comment
Hide comment
@cgillum

cgillum Jul 21, 2017

Collaborator

One part that's missing from the "actor" capability is the ability to have one instance create other instances and send messages to them. I need to open an issue to make this more clear, but we're working on a design for this pattern.

Your example above looks like the right way to go. There are some known timing issues, however, so you may want to add an await starter.CreateTimer(starter.CurrentUtcDateTime.AddSeconds(5), CancellationToken.None) immediately after your StartNewAsync call to avoid the timing issues. Let me know if this ends up working for you.

Collaborator

cgillum commented Jul 21, 2017

One part that's missing from the "actor" capability is the ability to have one instance create other instances and send messages to them. I need to open an issue to make this more clear, but we're working on a design for this pattern.

Your example above looks like the right way to go. There are some known timing issues, however, so you may want to add an await starter.CreateTimer(starter.CurrentUtcDateTime.AddSeconds(5), CancellationToken.None) immediately after your StartNewAsync call to avoid the timing issues. Let me know if this ends up working for you.

@mikhailshilkov

This comment has been minimized.

Show comment
Hide comment
@mikhailshilkov

mikhailshilkov Jul 21, 2017

Contributor

Thanks for the answer.

My starter is actually DurableOrchestrationClient, so it doesn't seem to have timer API (as opposed to DurableOrchestrationContext). I ended up skipping the send part in the first iteration of Function A when StartNewAsync is called, so the first RaiseEventAsync call is on the second timer occurrence (if else block).

Ideally, for my kind of scenario I would expect something like RaiseEventAsync(functionName, instanceId, eventName, data) overload, and the runtime would make sure to create/restart an actor if it doesn't exist yet. A bit ServiceFabric-style virtual actors.

Another use case I was thinking of was stateful stream processing. So, I got an event hub, and each event belongs to an aggregate ID. It could be natural, if I had an eventhub-triggered "normal" function, then sending event to an actor for specific aggregate ID, who would carry state in it. Calling GetStatusAsync from eventhub-function seems wasteful and maybe error-prone (e.g. in case of actor fault). So to avoid that, it would have to keep initialization status per instance ID, which makes the function itself stateful...

Or is there another (not actor based) pattern for such stream processing scenario?

Contributor

mikhailshilkov commented Jul 21, 2017

Thanks for the answer.

My starter is actually DurableOrchestrationClient, so it doesn't seem to have timer API (as opposed to DurableOrchestrationContext). I ended up skipping the send part in the first iteration of Function A when StartNewAsync is called, so the first RaiseEventAsync call is on the second timer occurrence (if else block).

Ideally, for my kind of scenario I would expect something like RaiseEventAsync(functionName, instanceId, eventName, data) overload, and the runtime would make sure to create/restart an actor if it doesn't exist yet. A bit ServiceFabric-style virtual actors.

Another use case I was thinking of was stateful stream processing. So, I got an event hub, and each event belongs to an aggregate ID. It could be natural, if I had an eventhub-triggered "normal" function, then sending event to an actor for specific aggregate ID, who would carry state in it. Calling GetStatusAsync from eventhub-function seems wasteful and maybe error-prone (e.g. in case of actor fault). So to avoid that, it would have to keep initialization status per instance ID, which makes the function itself stateful...

Or is there another (not actor based) pattern for such stream processing scenario?

@cgillum

This comment has been minimized.

Show comment
Hide comment
@cgillum

cgillum Jul 21, 2017

Collaborator

This is great feedback, thanks!

Yes, sorry about my suggestion about CreateTimer - as you mentioned that's not available. Instead you could simply use Task.Delay.

Your stream processing scenario is a good one and will require a bit more thought on my part. I agree that keeping track of which instances have been created is possible (even using a simple, static in-memory cache) but not ideal. Regular Azure Functions does not support stateful stream processing so there aren't any good alternatives at this point (in the realm of Functions, anyways - so folks look to Service Fabric to help with this).

FYI, I'm tracking improving support for actors here: #22. I'll add some notes about stateful stream processing so we can make sure we enable that scenario.

Collaborator

cgillum commented Jul 21, 2017

This is great feedback, thanks!

Yes, sorry about my suggestion about CreateTimer - as you mentioned that's not available. Instead you could simply use Task.Delay.

Your stream processing scenario is a good one and will require a bit more thought on my part. I agree that keeping track of which instances have been created is possible (even using a simple, static in-memory cache) but not ideal. Regular Azure Functions does not support stateful stream processing so there aren't any good alternatives at this point (in the realm of Functions, anyways - so folks look to Service Fabric to help with this).

FYI, I'm tracking improving support for actors here: #22. I'll add some notes about stateful stream processing so we can make sure we enable that scenario.

@cgillum

This comment has been minimized.

Show comment
Hide comment
@cgillum

cgillum Apr 16, 2018

Collaborator

Updating the title and changing the labels. I think this is closely related to the Aggregator Pattern, which we plan to document and provide samples for: #166.

But I think allowing RaiseEventAsync to create the instance if it doesn't already exist would be a great feature for supporting actor-like patterns (including aggregation) more naturally.

Collaborator

cgillum commented Apr 16, 2018

Updating the title and changing the labels. I think this is closely related to the Aggregator Pattern, which we plan to document and provide samples for: #166.

But I think allowing RaiseEventAsync to create the instance if it doesn't already exist would be a great feature for supporting actor-like patterns (including aggregation) more naturally.

@cgillum cgillum added enhancement dtfx and removed question labels Apr 16, 2018

@cgillum cgillum changed the title from What is the proper approach to instance management? to Allow RaiseEventAsync to create an orchestration instance if one doesn't already exist Apr 16, 2018

@SimonLuckenuik

This comment has been minimized.

Show comment
Hide comment
@SimonLuckenuik

SimonLuckenuik Apr 16, 2018

I have the exact same scenario in place, currently I am forced to calls StartNew in advance to make sure everything is ready prior starting to receive events. When doing the StartNew and raising the event just after, I sometime get an error that the StartNew is not active yet (GetStatus would be returning null).

SimonLuckenuik commented Apr 16, 2018

I have the exact same scenario in place, currently I am forced to calls StartNew in advance to make sure everything is ready prior starting to receive events. When doing the StartNew and raising the event just after, I sometime get an error that the StartNew is not active yet (GetStatus would be returning null).

@cgillum

This comment has been minimized.

Show comment
Hide comment
@cgillum

cgillum Apr 16, 2018

Collaborator

BTW, we've fixed things so that StartNew/GetStatus won't return null anymore. You'll see that in tomorrow's update release.

Collaborator

cgillum commented Apr 16, 2018

BTW, we've fixed things so that StartNew/GetStatus won't return null anymore. You'll see that in tomorrow's update release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment