-
Notifications
You must be signed in to change notification settings - Fork 4
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
Design: Async Activities, Timeout, and Heartbeat #60
Comments
Tech Design
|
More details on timeouts for all workflows here: #63 |
What is deciding that the activity is async here? The declaration or the implementation? act1 = activity<{ result: string }>({
heartbeat: { seconds: number },
timeout: { seconds: number }
}, (context: Context): { result: string } | AsyncToken => {
...doSomeWork...
await sendToQueue({ token: context.activity.token });
return makeAsync();
}) |
What's the use case for heartbeat? |
Just spitballing, would a builder pattern make it more ergonomic? act1 = ActivityBuilder({heartbeat: {seconds: 20}, timeout: {seconds: 20}})
.activity(context: Context): { result: string } | AsyncToken => {
...doSomeWork...
await sendToQueue({ token: context.activity.token });
return makeAsync();
}) |
I really don't like builder patterns for constructing a function. Bottom layer should be pure and a builder can always be put on top. Another consideration is how we use the activity/workflow functions for heuristics in the transformer |
The activity decides that it needs to be async and a single activity can support both patterns (return sync when possible and go async when necessary. The workflow decides how long it is willing to wait for the activity to complete. Controls the Workflow Has:
Controls the Activity Has:
An abstraction would be to support activities that are explicitly async from the workflow like Step Functions does, but it would be basically the same under the hood. workflow(() => {
await asyncEventActivity((token) => {}); // create an event which contains a token and waits on the response
});
// or maybe a special activity type?
const myActivity = eventActivity<string>((token, input) => ({ type: "myEvent", token, input })); And then the other way to do it would be like SFN's Activities which provide a queue to poll on from anywhere. Which again could just be a special activity type that is called by the workflow like any other activity. const myActivity = queueActivity<string>(myQueue); // a queue that contains activity requests to resolve using the token they contain. |
Heartbeat is important in durable systems. Let say you have a long running activity that may take up to a week, so you set it's timeout to 2 weeks just in case. That means if something goes wrong and the message is lost, the workflow won't wake up for 2 weeks just to find it failed. Now you could set a hourly or daily heartbeat which allows the activity's system to report back to the workflow to say it is still alive. Yehuda expressed how important that this is in his systems when long running processes are involved. From Temporal's Docs:
|
Use Cases:
|
I could have been clearer, I do know why they are important. Just not sure why it's important right now. |
Yehuda will ask about them and I think we can get the basic impl done quickly. |
So low effort high roi? Sounds good. Let's try and think of some examples when we implement it and add them to the test app? I may be being pedantic, just trying to learn the lesson of functionless and focus on examples and features, not just features. |
Totally agree. From chats with people, timeout and heartbeat are important parts of long running workflows that would make the service look more legit/complete. Because timers are implemented from sleep, it is easy to create timeouts now and the only new part about heartbeat is adding the client operation. Will start to work on this and if it proves to be high effort, will push off. |
Was looking at how to avoid the context argument. Option 1: context methodactivity((...args) => {
const { asyncToken } = getActivityContext(); // fails when called from outside of an activity.
async sqs.send(new SendMessageCommand(... token ... ));
return makeAsync();
}); Option 2: token is provided by the
|
What's wrong with the context parameter? I think we wanted to update activities to only allow a single input argument just like a workflow so that it aligns with a lambda contract. There was another reason I think too, but can't remember. Context argument is preferable because it's discoverable. |
While writing the documentation, I found myself confused about why heartbeat is a global. have we closed on our decision to change activities to be single argument only and then add a context parameter? We could then provide the heartbeat function on the context parameter instead. |
How do you decide what is a context method and what is an intrinsic? Is the difference that heartbeat is specific to activities (and systems acting on behalf of an async activity)? Would we apply the same logic to workflow only things, sleep, Promise.*, signal, etc? Heartbeat for an activity can be done by anything with access to the token. I see it as the same as Options:
|
Was building something today and found myself really wanting a |
Problem Statement:
As a developer/workflow author, I want to create activities that run for indefinite amounts of time, involve human interaction, invoke other services, or wait for the result of outside actions. I should be able to ensure inconsistencies and fault are recoverable from. I should be able to use the service to support idempotency of partial failures.
Stories:
Strawman
with heartbeat
with heartbeat checkpoint - FUTURE
The text was updated successfully, but these errors were encountered: