Packages
journey
0.10.58
0.10.58
0.10.57
0.10.56
0.10.55
0.10.54
0.10.53
0.10.52
0.10.51
0.10.50
0.10.49
0.10.48
0.10.47
0.10.46
0.10.45
0.10.44
0.10.43
0.10.41
0.10.40
0.10.39
0.10.38
0.10.37
0.10.36
0.10.35
0.10.34
0.10.33
0.10.32
0.10.31
0.10.30
0.10.29
0.10.28
0.10.27
0.10.26
0.10.25
0.10.24
0.10.23
0.10.22
0.0.9
retired
0.0.8
0.0.7
0.0.6
0.0.5
0.0.3
0.0.2
Journey is a library for defining and running durable workflows with persistence, reliability, and scalability.
Current section
Files
Jump to
Current section
Files
lib/examples/compute_retries.livemd
<!-- livebook:{"persist_outputs":true} -->
# "compute" retries
```elixir
# [Optional] Setting Build Key, see https://gojourney.dev/your_keys
# (Using "Journey Livebook Demo" build key)
System.put_env("JOURNEY_BUILD_KEY", "B27AXHMERm2Z6ehZhL49v")
Mix.install(
[
{:ecto_sql, "~> 3.13"},
{:postgrex, "~> 0.22"},
{:jason, "~> 1.4"},
{:journey, "~> 0.10"},
{:kino, "~> 0.19"}
],
start_applications: false
)
Application.put_env(:journey, :log_level, :warning)
# This livebook requires a PostgreSQL database.
# If you don't have one running, you can start one with Docker:
# docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres:16
# Update this configuration to point to your database server
Application.put_env(:journey, Journey.Repo,
database: "journey_compute_retries",
username: "postgres",
password: "postgres",
hostname: "localhost",
log: false,
port: 5432
)
Application.put_env(:journey, :ecto_repos, [Journey.Repo])
Journey.Repo.__adapter__().storage_up(Journey.Repo.config())
Application.loaded_applications()
|> Enum.map(fn {app, _, _} -> app end)
|> Enum.each(&Application.ensure_all_started/1)
```
## DB Setup
This livebook requires a PostgreSQL database. If you don't have one running, you can start one with Docker:
```bash
docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres:16
```
## What We'll Cover
In this example, we'll look into how Journey handles compute failures. What happens if a compute node's function tries to send an email but the email service is down?
Spoiler alert: Journey will try a few times, and give up. Once the email service is back up, you can kick off another computation using a helper function.
In this livebook, we will create a simple graph with a `compute` node whose computation function returns an error, and observe Journey's retry behavior:
1. the failing computation will be attempted by journey, up to `max_retries` times, which we set to 4 (default: 3),
2. once attempts are exhausted, the computation will fail,
3. once you fixed the underlying error (or think you fixed the underlying error;), you can kick the computation to try again, with `Journey.Tools.retry_computation/2`,
4. introspection tools (mermaid diagram - `Journey.Tools.generate_mermaid_execution/1`, execution textual introspection βΒ `Journey.Tools.introspect/1`) show you the status,
5. execution itself has more metadata on computations, if you need more insight.
## Define the Graph
```elixir
import Journey.Node
graph = Journey.new_graph(
"Welcome, but failing",
"v1",
[
input(:name),
compute(
:greeting,
[:name],
fn values ->
now = DateTime.utc_now() |> Calendar.strftime("%H:%M:%S UTC")
welcome = "Hello, #{values.name}, at #{now}, π€!"
IO.puts(welcome)
{:error, "oh no, failed, #{now}"}
end,
# Overriding the default of 3 attempts.
max_retries: 4
)
]
); :ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
Visualize the graph:
```elixir
graph
|> Journey.Tools.generate_mermaid_graph()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Welcome, but failing', version v1"]
execution_id[execution_id]
last_updated_at[last_updated_at]
name[name]
greeting[["greeting<br/>(anonymous fn)"]]
name --> greeting
end
%% Styling
classDef defaultNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000
%% Apply styles to nodes
class execution_id,last_updated_at,name,greeting defaultNode
```
## Start an Execution
```elixir
execution = Journey.start(graph); :ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
In the new execution the `:greeting` computation is waiting for `:name` to be set.
<!-- livebook:{"break_markdown":true} -->
As seen on the diagram:
```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Welcome, but failing', version v1, EXECG5217Z92XXJA1BM8R3LG"]
execution_id["β
execution_id"]
last_updated_at["β
last_updated_at"]
name["β¬ name"]
greeting[["π« greeting<br/>(anonymous fn)"]]
name --> greeting
end
%% Styling
classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000
%% Apply styles to nodes
class last_updated_at,execution_id setNode
class greeting,name neutralNode
```
As seen in the values:
```elixir
Journey.values_all(execution)
```
<!-- livebook:{"output":true} -->
```
%{
name: :not_set,
last_updated_at: {:set, 1776922650},
execution_id: {:set, "EXECG5217Z92XXJA1BM8R3LG"},
greeting: :not_set
}
```
As seen on the textual introspection:
```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```
<!-- livebook:{"output":true} -->
```
Execution summary:
- ID: 'EXECG5217Z92XXJA1BM8R3LG'
- Graph: 'Welcome, but failing' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:37:30Z UTC | 0 seconds ago
- Last updated at: 2026-04-23 05:37:30Z UTC | 0 seconds ago
- Duration: 0 seconds
- Revision: 0
- # of Values: 2 (set) / 4 (total)
- # of Computations: 1
Values:
- Set:
- execution_id: 'EXECG5217Z92XXJA1BM8R3LG' | :input
set at 2026-04-23 05:37:30Z | rev: 0
- last_updated_at: '1776922650' | :input
set at 2026-04-23 05:37:30Z | rev: 0
- Not set:
- greeting: <unk> | :compute
- name: <unk> | :input
Computations:
- Completed:
- Outstanding:
- greeting: β¬ :not_set (not yet attempted) | :compute
π :name | &provided?/1
```
<!-- livebook:{"output":true} -->
```
:ok
```
## `:name` is Set -> `:greeting` is Computing with Retries
We'll set the value for `:name`, and watch the `:greeting` computation get unblocked, and fail after a few attempts.
```elixir
execution =
execution
|> Journey.set(:name, "Luigi"); :ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
`Journey.get` below waits for the result, and returns an error once the computation's 4 attempts are exhausted:
(A side note: retries happen with a small randomized pause βΒ a few seconds β between attempts. Proper backoff is on the roadmap.)
```elixir
Journey.get(execution, :greeting, wait: :any, timeout: 120_000)
```
<!-- livebook:{"output":true} -->
```
Hello, Luigi, at 05:37:30 UTC, π€!
22:37:30.132 [warning] Worker [EXECG5217Z92XXJA1BM8R3LG.CMPET6LMRBXMXGA771YJX9E.greeting] [Welcome, but failing]: async computation completed with an error
Hello, Luigi, at 05:37:31 UTC, π€!
22:37:31.706 [warning] Worker [EXECG5217Z92XXJA1BM8R3LG.CMP06YLV03DXGEVYLX46BD1.greeting] [Welcome, but failing]: async computation completed with an error
Hello, Luigi, at 05:37:32 UTC, π€!
22:37:32.980 [warning] Worker [EXECG5217Z92XXJA1BM8R3LG.CMP9Y72L34RL30VXH747J21.greeting] [Welcome, but failing]: async computation completed with an error
Hello, Luigi, at 05:37:41 UTC, π€!
22:37:41.175 [warning] Worker [EXECG5217Z92XXJA1BM8R3LG.CMP6RL4RJGG99AEBE4Z53T3.greeting] [Welcome, but failing]: async computation completed with an error
```
<!-- livebook:{"output":true} -->
```
{:error, :computation_failed}
```
The computation is now failed, as seen on the diagram:
```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Welcome, but failing', version v1, EXECG5217Z92XXJA1BM8R3LG"]
execution_id["β
execution_id"]
last_updated_at["β
last_updated_at"]
name["β
name"]
greeting[["β greeting<br/>(anonymous fn)"]]
name --> greeting
end
%% Styling
classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000
%% Apply styles to nodes
class name,last_updated_at,execution_id setNode
class greeting errorNode
```
No `:greeting` value has been set:
```elixir
Journey.values_all(execution)
```
<!-- livebook:{"output":true} -->
```
%{
name: {:set, "Luigi"},
last_updated_at: {:set, 1776922650},
execution_id: {:set, "EXECG5217Z92XXJA1BM8R3LG"},
greeting: :not_set
}
```
And `introspect/1` shows the failed computation attempts, including the errors reported by each of the computations:
```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```
<!-- livebook:{"output":true} -->
```
Execution summary:
- ID: 'EXECG5217Z92XXJA1BM8R3LG'
- Graph: 'Welcome, but failing' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:37:30Z UTC | 14 seconds ago
- Last updated at: 2026-04-23 05:37:41Z UTC | 3 seconds ago
- Duration: 11 seconds
- Revision: 9
- # of Values: 3 (set) / 4 (total)
- # of Computations: 4
Values:
- Set:
- last_updated_at: '1776922650' | :input
set at 2026-04-23 05:37:30Z | rev: 1
- name: '"Luigi"' | :input
set at 2026-04-23 05:37:30Z | rev: 1
- execution_id: 'EXECG5217Z92XXJA1BM8R3LG' | :input
set at 2026-04-23 05:37:30Z | rev: 0
- Not set:
- greeting: <unk> | :compute
Computations:
- Completed:
- :greeting (CMP6RL4RJGG99AEBE4Z53T3): β :failed | :compute | rev 9
started: 2026-04-23 05:37:41Z | completed: 2026-04-23 05:37:41Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:41 UTC"
- :greeting (CMP9Y72L34RL30VXH747J21): β :failed | :compute | rev 7
started: 2026-04-23 05:37:32Z | completed: 2026-04-23 05:37:32Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:32 UTC"
- :greeting (CMP06YLV03DXGEVYLX46BD1): β :failed | :compute | rev 5
started: 2026-04-23 05:37:31Z | completed: 2026-04-23 05:37:31Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:31 UTC"
- :greeting (CMPET6LMRBXMXGA771YJX9E): β :failed | :compute | rev 3
started: 2026-04-23 05:37:30Z | completed: 2026-04-23 05:37:30Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:30 UTC"
- Outstanding:
```
<!-- livebook:{"output":true} -->
```
:ok
```
## Underlying Problem Solved? Invoke Another [re-]Computation (Spoiler: It Wasn't Solved)
Now, let's say you think you fixed the root cause of the failure, and want to retry the computation. `retry_computation/2` to the rescue.
Calling `retry_computation/2` creates another computation attempt:
```elixir
execution = Journey.Tools.retry_computation(execution.id, :greeting); :ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
```elixir
Journey.get(execution, :greeting, wait: {:newer_than, execution.revision}, timeout: 120_000)
```
<!-- livebook:{"output":true} -->
```
Hello, Luigi, at 05:37:44 UTC, π€!
22:37:44.837 [warning] Worker [EXECG5217Z92XXJA1BM8R3LG.CMPBTHXTZ61EG875VG2Y9HB.greeting] [Welcome, but failing]: async computation completed with an error
```
<!-- livebook:{"output":true} -->
```
{:error, :computation_failed}
```
Not surprisingly, the computation is still failing.
```elixir
Journey.values_all(execution)
```
<!-- livebook:{"output":true} -->
```
%{
name: {:set, "Luigi"},
last_updated_at: {:set, 1776922650},
execution_id: {:set, "EXECG5217Z92XXJA1BM8R3LG"},
greeting: :not_set
}
```
```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Welcome, but failing', version v1, EXECG5217Z92XXJA1BM8R3LG"]
execution_id["β
execution_id"]
last_updated_at["β
last_updated_at"]
name["β
name"]
greeting[["β greeting<br/>(anonymous fn)"]]
name --> greeting
end
%% Styling
classDef setNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000000
classDef computingNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px,color:#000000
classDef errorNode fill:#f8bbd0,stroke:#b71c1c,stroke-width:2px,color:#000000
classDef neutralNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000
%% Apply styles to nodes
class name,last_updated_at,execution_id setNode
class greeting errorNode
```
Introspection now includes one more failed computation:
```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```
<!-- livebook:{"output":true} -->
```
Execution summary:
- ID: 'EXECG5217Z92XXJA1BM8R3LG'
- Graph: 'Welcome, but failing' | 'v1'
- Archived at: not archived
- Created at: 2026-04-23 05:37:30Z UTC | 15 seconds ago
- Last updated at: 2026-04-23 05:37:44Z UTC | 1 seconds ago
- Duration: 14 seconds
- Revision: 11
- # of Values: 3 (set) / 4 (total)
- # of Computations: 5
Values:
- Set:
- last_updated_at: '1776922650' | :input
set at 2026-04-23 05:37:30Z | rev: 1
- name: '"Luigi"' | :input
set at 2026-04-23 05:37:30Z | rev: 1
- execution_id: 'EXECG5217Z92XXJA1BM8R3LG' | :input
set at 2026-04-23 05:37:30Z | rev: 0
- Not set:
- greeting: <unk> | :compute
Computations:
- Completed:
- :greeting (CMPBTHXTZ61EG875VG2Y9HB): β :failed | :compute | rev 11
started: 2026-04-23 05:37:44Z | completed: 2026-04-23 05:37:44Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:44 UTC"
- :greeting (CMP6RL4RJGG99AEBE4Z53T3): β :failed | :compute | rev 9
started: 2026-04-23 05:37:41Z | completed: 2026-04-23 05:37:41Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:41 UTC"
- :greeting (CMP9Y72L34RL30VXH747J21): β :failed | :compute | rev 7
started: 2026-04-23 05:37:32Z | completed: 2026-04-23 05:37:32Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:32 UTC"
- :greeting (CMP06YLV03DXGEVYLX46BD1): β :failed | :compute | rev 5
started: 2026-04-23 05:37:31Z | completed: 2026-04-23 05:37:31Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:31 UTC"
- :greeting (CMPET6LMRBXMXGA771YJX9E): β :failed | :compute | rev 3
started: 2026-04-23 05:37:30Z | completed: 2026-04-23 05:37:30Z (0s)
inputs used:
:name (rev 1)
error: "oh no, failed, 05:37:30 UTC"
- Outstanding:
```
<!-- livebook:{"output":true} -->
```
:ok
```
If the information you get via introspection tools is not sufficient, you can load the execution itself, and examine it by hand. Here are a few most recent computations in this execution:
```elixir
execution = Journey.load(execution.id)
execution.computations |> Enum.take(3)
```
<!-- livebook:{"output":true} -->
```
[
%Journey.Persistence.Schema.Execution.Computation{
__meta__: #Ecto.Schema.Metadata<:loaded, "computations">,
id: "CMPBTHXTZ61EG875VG2Y9HB",
execution_id: "EXECG5217Z92XXJA1BM8R3LG",
execution: #Ecto.Association.NotLoaded<association :execution is not loaded>,
node_name: :greeting,
computation_type: :compute,
state: :failed,
ex_revision_at_start: 10,
ex_revision_at_completion: 11,
scheduled_time: nil,
start_time: 1776922664,
completion_time: 1776922664,
deadline: 1776922724,
last_heartbeat_at: nil,
heartbeat_deadline: 1776922904,
error_details: "\"oh no, failed, 05:37:44 UTC\"",
computed_with: %{name: 1},
inserted_at: 1776922664,
updated_at: 1776922664
},
%Journey.Persistence.Schema.Execution.Computation{
__meta__: #Ecto.Schema.Metadata<:loaded, "computations">,
id: "CMP6RL4RJGG99AEBE4Z53T3",
execution_id: "EXECG5217Z92XXJA1BM8R3LG",
execution: #Ecto.Association.NotLoaded<association :execution is not loaded>,
node_name: :greeting,
computation_type: :compute,
state: :failed,
ex_revision_at_start: 8,
ex_revision_at_completion: 9,
scheduled_time: nil,
start_time: 1776922661,
completion_time: 1776922661,
deadline: 1776922721,
last_heartbeat_at: nil,
heartbeat_deadline: 1776922901,
error_details: "\"oh no, failed, 05:37:41 UTC\"",
computed_with: %{name: 1},
inserted_at: 1776922652,
updated_at: 1776922661
},
%Journey.Persistence.Schema.Execution.Computation{
__meta__: #Ecto.Schema.Metadata<:loaded, "computations">,
id: "CMP9Y72L34RL30VXH747J21",
execution_id: "EXECG5217Z92XXJA1BM8R3LG",
execution: #Ecto.Association.NotLoaded<association :execution is not loaded>,
node_name: :greeting,
computation_type: :compute,
state: :failed,
ex_revision_at_start: 6,
ex_revision_at_completion: 7,
scheduled_time: nil,
start_time: 1776922652,
completion_time: 1776922652,
deadline: 1776922712,
last_heartbeat_at: nil,
heartbeat_deadline: 1776922892,
error_details: "\"oh no, failed, 05:37:32 UTC\"",
computed_with: %{name: 1},
inserted_at: 1776922651,
updated_at: 1776922652
}
]
```
## Summary
In this Livebook, we setup a graph whose compute node's function returns an error, and we observed journey retrying the computation, subject to the node's retry policy (the `max_retries: 4` in the graph definition overrode the default value of 3).
We also looked at the state of the execution, by rendering its mermaid graph, looking at its values, and doing in-depth introspection with `Journey.Tools.introspect/1`.
We also kicked off a recomputation on a failed node, with `Journey.Tools.retry_computation/2`, which, given the nature of our failure mode (a hardcoded error;), predictably did not fix the problem.
We also took a glimpse at the `computation` portion of the complete `execution` structure.