Skip to content

Commit 9c1e6ec

Browse files
authored
docs: durable execution + update self-hosted defaults to use v1 (#1484)
* docs: update self-hosted defaults to use v1 * docs: durable execution * rm dep * lint: run black * redundant readme * more wording * other small things * isort
1 parent 747fa28 commit 9c1e6ec

File tree

19 files changed

+421
-457
lines changed

19 files changed

+421
-457
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Hatchet is a platform for running background tasks, built on top of Postgres. In
3636

3737
Background tasks are critical for offloading work from your main web application. Usually background tasks are sent through a FIFO (first-in-first-out) queue, which helps guard against traffic spikes (queues can absorb a lot of load) and ensures that tasks are retried when your task handlers error out. Most stacks begin with a library-based queue backed by Redis or RabbitMQ (like Celery or BullMQ). But as your tasks become more complex, these queues become difficult to debug, monitor and start to fail in unexpected ways.
3838

39-
This is where Hatchet comes in. Hatchet is a full-featured background task management platform, with built-in support for chaining complex tasks together into workflows, alerting on failures, and building complex workflows which would otherwise take months of engineering effort.
39+
This is where Hatchet comes in. Hatchet is a full-featured background task management platform, with built-in support for chaining complex tasks together into workflows, alerting on failures, making tasks more durable, and viewing tasks in a real-time web dashboard.
4040

4141
### Features
4242

@@ -730,7 +730,7 @@ Most AI frameworks are built to run in-memory, with horizontal scaling and durab
730730

731731
### Issues
732732

733-
Please submit any bugs that you encounter via Github issues. However, please reach out on [Discord](https://hatchet.run/discord) before submitting a feature request - as the project is very early, we'd like to build a solid foundation before adding more complex features.
733+
Please submit any bugs that you encounter via Github issues.
734734

735735
### I'd Like to Contribute
736736

examples/v1/workflows/durable-event.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ type DurableEventOutput struct {
2121
}
2222

2323
func DurableEvent(hatchet v1.HatchetClient) workflow.WorkflowDeclaration[DurableEventInput, DurableEventOutput] {
24-
durableEventWorkflow := factory.NewDurableTask(
24+
// ❓ Durable Event
25+
durableEventTask := factory.NewDurableTask(
2526
create.StandaloneTask{
26-
Name: "durable-sleep",
27+
Name: "durable-event",
2728
},
2829
func(ctx worker.DurableHatchetContext, input DurableEventInput) (*DurableEventOutput, error) {
2930
eventData, err := ctx.WaitForEvent("user:update", "")
@@ -45,6 +46,34 @@ func DurableEvent(hatchet v1.HatchetClient) workflow.WorkflowDeclaration[Durable
4546
},
4647
hatchet,
4748
)
49+
// !!
4850

49-
return durableEventWorkflow
51+
factory.NewDurableTask(
52+
create.StandaloneTask{
53+
Name: "durable-event",
54+
},
55+
func(ctx worker.DurableHatchetContext, input DurableEventInput) (*DurableEventOutput, error) {
56+
// ❓ Durable Event With Filter
57+
eventData, err := ctx.WaitForEvent("user:update", "input.user_id == '1234'")
58+
// !!
59+
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
v := EventData{}
65+
err = eventData.Unmarshal(&v)
66+
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return &DurableEventOutput{
72+
Data: v,
73+
}, nil
74+
},
75+
hatchet,
76+
)
77+
78+
return durableEventTask
5079
}

examples/v1/workflows/durable-sleep.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type DurableSleepOutput struct {
2020
}
2121

2222
func DurableSleep(hatchet v1.HatchetClient) workflow.WorkflowDeclaration[DurableSleepInput, DurableSleepOutput] {
23-
// ctx as first param of NewTask
23+
// ❓ Durable Sleep
2424
simple := factory.NewDurableTask(
2525
create.StandaloneTask{
2626
Name: "durable-sleep",
@@ -38,6 +38,7 @@ func DurableSleep(hatchet v1.HatchetClient) workflow.WorkflowDeclaration[Durable
3838
},
3939
hatchet,
4040
)
41+
// !!
4142

4243
return simple
4344
}

frontend/docs/pages/home/_meta.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,28 @@ export default {
4949
},
5050
"conditional-workflows": "Conditional Workflows",
5151
"on-failure-tasks": "On Failure Tasks",
52-
"durable-execution": {
53-
"title": "Durable Execution"
54-
},
5552
"child-spawning": {
5653
"title": "Child Spawning"
5754
},
5855
"additional-metadata": {
5956
"title": "Additional Metadata"
6057
},
58+
"--durable-execution": {
59+
"title": "Durable Execution",
60+
"type": "separator"
61+
},
62+
"durable-execution": {
63+
"title": "Durable Execution"
64+
},
65+
"durable-events": {
66+
"title": "Durable Events"
67+
},
68+
"durable-sleep": {
69+
"title": "Durable Sleep"
70+
},
71+
"durable-best-practices": {
72+
"title": "Best Practices"
73+
},
6174
"--error-handling": {
6275
"title": "Error Handling",
6376
"type": "separator"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Durable Execution Best Practices
2+
3+
Durable tasks require a bit of extra work to ensure that they are not misused. An important concept in running a durable task is that the task should be **deterministic**. This means that the task should always perform the same sequence of operations in between retries.
4+
5+
The deterministic nature of durable tasks is what allows Hatchet to replay the task from the last checkpoint. If a task is not deterministic, it may produce different results on each retry, which can lead to unexpected behavior.
6+
7+
## Maintaining Determinism
8+
9+
By following a few simple rules, you can ensure that your durable tasks are deterministic:
10+
11+
1. **Only call methods available on the `DurableContext`**: a very common way to introduce non-determinism is to call methods within your application code which produces side effects. If you need to call a method in your application code which fetches data from a database, calls any sort of i/o operation, or otherwise interacts with other systems, you should spawn those tasks as a **child task** or **child workflow** using `RunChild`.
12+
13+
2. **When updating durable tasks, always guarantee backwards compatibility**: if you change the order of operations in a durable task, you may break determinism. For example, if you call `SleepFor` followed by `WaitFor`, and then change the order of those calls, Hatchet will not be able to replay the task correctly. This is because the task may have already been checkpointed at the first call to `SleepFor`, and if you change the order of operations, the checkpoint is meaningless.
14+
15+
## Using DAGs instead of durable tasks
16+
17+
[DAGs](./dags) are generally a much easier, more intuitive way to run a durable, deterministic workflow. DAGs are inherently deterministic, as their shape of work is predefined, and they cache intermediate results. If you are running simple workflows that can be represented as a DAG, you should use DAGs instead of durable tasks. DAGs also have conditional execution primitives which match the behavior of `SleepFor` and `WaitFor` in durable tasks.
18+
19+
Durable tasks are useful if you need to run a workflow that is not easily represented as a DAG.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Callout, Card, Cards, Steps, Tabs } from "nextra/components";
2+
import UniversalTabs from "@/components/UniversalTabs";
3+
import { GithubSnippet, getSnippets } from "@/components/code";
4+
5+
export const DurableGo = {
6+
path: "examples/v1/workflows/durable-event.go",
7+
};
8+
9+
export const DurableTs = {
10+
path: "src/v1/examples/durable-event/workflow.ts",
11+
};
12+
13+
export const DurablePy = {
14+
path: "examples/durable_event/worker.py",
15+
};
16+
17+
export const getStaticProps = ({}) =>
18+
getSnippets([DurableGo, DurableTs, DurablePy]);
19+
20+
## Durable Events
21+
22+
Durable events are a feature of **durable tasks** which allow tasks to wait for an event to occur before continuing. This is useful in cases where a task needs to wait for a long time for an external action. Durable events are useful, because even if your task is interrupted and requeued while waiting for an event, the event will still be processed. When the task is resumed, it will read the event from the durable event log and continue processing.
23+
24+
## Declaring durable events
25+
26+
Durable events are declared using the context method `WaitFor` (or utility method `WaitForEvent`) on the `DurableContext` object.
27+
28+
<UniversalTabs items={["Python", "Typescript", "Go"]}>
29+
<Tabs.Tab title="Python">
30+
31+
<GithubSnippet src={DurablePy} target="Durable Event" />
32+
33+
</Tabs.Tab>
34+
<Tabs.Tab title="Typescript">
35+
36+
<GithubSnippet src={DurableTs} target="Durable Event" />
37+
38+
</Tabs.Tab>
39+
<Tabs.Tab title="Go">
40+
41+
<GithubSnippet src={DurableGo} target="Durable Event" />
42+
43+
</Tabs.Tab>
44+
</UniversalTabs>
45+
46+
## Durable event filters
47+
48+
Durable events can be filtered using [CEL](https://github.com/google/cel-spec) expressions. For example, to only receive `user:update` events for a specific user, you can use the following filter:
49+
50+
<UniversalTabs items={["Python", "Typescript", "Go"]}>
51+
<Tabs.Tab title="Python">
52+
53+
<GithubSnippet src={DurablePy} target="Durable Event With Filter" />
54+
55+
</Tabs.Tab>
56+
<Tabs.Tab title="Typescript">
57+
58+
<GithubSnippet src={DurableTs} target="Durable Event With Filter" />
59+
60+
</Tabs.Tab>
61+
<Tabs.Tab title="Go">
62+
63+
<GithubSnippet src={DurableGo} target="Durable Event With Filter" />
64+
65+
</Tabs.Tab>
66+
</UniversalTabs>

frontend/docs/pages/home/durable-execution.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ This is especially useful in cases such as:
2121

2222
## How Hatchet Runs Durable Tasks
2323

24-
When you register a durable task, Hatchet marks the entire task as durable. Then, when you start your worker, Hatchet will start a second worker in the background for running durable tasks.
25-
26-
If you don't register any durable tasks, the durable worker will not be started. Similarly, if you start a worker with _only_ durable tasks, the "main" worker will not start, and _only_ the durable worker will run. The durable worker will show up as a second worker in the Hatchet Dashboard.
24+
When you register a durable task, Hatchet will start a second worker in the background for running durable tasks. If you don't register any durable workflows, the durable worker will not be started. Similarly, if you start a worker with _only_ durable workflows, the "main" worker will not start, and _only_ the durable worker will run. The durable worker will show up as a second worker in the Hatchet Dashboard.
2725

2826
Tasks that are declared as being durable (using `durable_task` instead of `task`), will receive a `DurableContext` object instead of a normal `Context,` which extends the `Context` by providing some additional tools for working with durable execution features.
2927

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Callout, Card, Cards, Steps, Tabs } from "nextra/components";
2+
import UniversalTabs from "@/components/UniversalTabs";
3+
import { GithubSnippet, getSnippets } from "@/components/code";
4+
5+
export const DurableGo = {
6+
path: "examples/v1/workflows/durable-sleep.go",
7+
};
8+
9+
export const DurableTs = {
10+
path: "src/v1/examples/durable-sleep/workflow.ts",
11+
};
12+
13+
export const DurablePy = {
14+
path: "examples/durable_sleep/worker.py",
15+
};
16+
17+
export const getStaticProps = ({}) =>
18+
getSnippets([DurableGo, DurableTs, DurablePy]);
19+
20+
## Durable Sleep
21+
22+
Durable sleep is a feature of **durable tasks** which allow tasks to pause execution for a specified amount of time. Instead of a regular `sleep` call in your task, durable sleep is guaranteed to only sleep for the specified amount of time after the first time it was called.
23+
24+
For example, say you'd like to send a notification to a user after 24 hours. With a regular `sleep`, if the task is interrupted after 23 hours, it will restart and call `sleep` for 24 hours again. This means that the task will sleep for 47 hours in total, which is not what you want. With durable sleep, the task will respect the original sleep duration on restart -- that is, if the task calls `ctx.aio_sleep_for` for 24 hours and is interrupted after 23 hours, it will only sleep for 1 more hour on restart.
25+
26+
## Using durable sleep
27+
28+
Durable sleep can be used by calling the `SleepFor` method on the `DurableContext` object. This method takes a duration as an argument and will sleep for that duration.
29+
30+
<UniversalTabs items={["Python", "Typescript", "Go"]}>
31+
<Tabs.Tab title="Python">
32+
33+
<GithubSnippet src={DurablePy} target="Durable Sleep" />
34+
35+
</Tabs.Tab>
36+
<Tabs.Tab title="Typescript">
37+
38+
<GithubSnippet src={DurableTs} target="Durable Sleep" />
39+
40+
</Tabs.Tab>
41+
<Tabs.Tab title="Go">
42+
43+
<GithubSnippet src={DurableGo} target="Durable Sleep" />
44+
45+
</Tabs.Tab>
46+
</UniversalTabs>

0 commit comments

Comments
 (0)