Packages
journey
0.10.57
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/archive.livemd
<!-- livebook:{"persist_outputs":true} -->
# "archive" nodes
```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)
# Configure more frequent background sweeper runs (the default is 60 seconds).
# The precision of the timer is determined by the granularity of the sweeper.
Application.put_env(:journey, :background_sweeper, period_seconds: 5)
# 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_archive_nodes",
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 service. 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
This tutorial focuses on the `archive` node type.
An `archive` node marks its execution as, well, archived. This is the graph equivalent of calling `Journey.archive/1` on the execution.
Once archived, the execution is, in effect, soft-deleted, and removed from normal use:
* excluded from `Journey.load/2` by default (unless you explicitly ask for it with `include_archived: true`).
* excluded from `Journey.list_executions/1` / `Journey.count_executions/1`, unless explicitly requested with `include_archived: true`.
* excluded from sweeper processing: its timers won't fire.
We'll build an execution that archives itself 30 seconds after the customer's name is provided, using a `tick_once` to schedule the cleanup.
An archived execution can be unarchived, with `Journey.unarchive/1`.
## Define the Graph
```elixir
import Journey.Node
archive_after_seconds = 30
graph = Journey.new_graph(
"Customer Order",
"v1",
[
input(:customer_name),
tick_once(:schedule_cleanup, [:customer_name],
fn %{customer_name: name} ->
IO.puts("schedule_cleanup: archiving order for #{name} in #{archive_after_seconds} seconds")
{:ok, System.os_time(:second) + archive_after_seconds}
end
),
archive(:archive_order, [:schedule_cleanup])
]
); :ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
Three nodes:
* `:customer_name` is an input node,
* `:schedule_cleanup` fires as soon as `:customer_name` is set, and returns the time (unix epoch seconds) at which the order should be archived, and
* `:archive_order` fires when the time returned by `:schedule_cleanup` has arrived, and archives the execution.
<!-- livebook:{"break_markdown":true} -->
Visualize the graph:
```elixir
graph
|> Journey.Tools.generate_mermaid_graph()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Customer Order', version v1"]
execution_id[execution_id]
last_updated_at[last_updated_at]
customer_name[customer_name]
schedule_cleanup[["schedule_cleanup<br/>(anonymous fn)<br/>tick_once node"]]
archive_order[["archive_order<br/>archive node"]]
customer_name --> schedule_cleanup
schedule_cleanup --> archive_order
end
%% Styling
classDef defaultNode fill:#f8f9fa,stroke:#495057,stroke-width:2px,color:#000000
%% Apply styles to nodes
class execution_id,last_updated_at,customer_name,schedule_cleanup,archive_order defaultNode
```
## Start an Execution
```elixir
execution =
graph
|> Journey.start()
|> Journey.set(:customer_name, "Luigi");
:ok
```
<!-- livebook:{"output":true} -->
```
:ok
```
## Schedule the Cleanup
The tick fires immediately, scheduling the archive for 30 seconds from now:
```elixir
{:ok, scheduled_time, _revision} = Journey.get(execution, :schedule_cleanup, wait: :any)
now = System.os_time(:second)
scheduled_at_string = scheduled_time |> DateTime.from_unix!() |> Calendar.strftime("%H:%M:%S UTC")
"scheduled_time: #{scheduled_at_string}, in #{scheduled_time - now} seconds"
```
<!-- livebook:{"output":true} -->
```
schedule_cleanup: archiving order for Luigi in 30 seconds
```
<!-- livebook:{"output":true} -->
```
"scheduled_time: 04:52:03 UTC, in 29 seconds"
```
## The Execution Is Still Here
The archive hasn't fired yet. The execution is alive and loadable:
```elixir
loaded = Journey.load(execution.id)
loaded != nil
```
<!-- livebook:{"output":true} -->
```
true
```
```elixir
Journey.values(execution)
```
<!-- livebook:{"output":true} -->
```
%{
last_updated_at: 1776919893,
execution_id: "EXECL65EG5T93XGL1JXJB3J5",
customer_name: "Luigi",
schedule_cleanup: 1776919923
}
```
```elixir
execution.id
|> Journey.Tools.generate_mermaid_execution()
|> Kino.Mermaid.new()
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD
%% Graph
subgraph Graph["π§© 'Customer Order', version v1, EXECL65EG5T93XGL1JXJB3J5"]
execution_id["β
execution_id"]
last_updated_at["β
last_updated_at"]
customer_name["β
customer_name"]
schedule_cleanup[["β
schedule_cleanup<br/>(anonymous fn)<br/>tick_once node"]]
archive_order[["π« archive_order<br/>archive node"]]
customer_name --> schedule_cleanup
schedule_cleanup --> archive_order
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 schedule_cleanup,customer_name,last_updated_at,execution_id setNode
class archive_order neutralNode
```
## Wait for Cleanup
```elixir
"Waiting for #{scheduled_at_string}..."
```
<!-- livebook:{"output":true} -->
```
"Waiting for 04:52:03 UTC..."
```
This will block until the archive fires:
```elixir
{:ok, archived_at, _revision} = Journey.get(execution, :archive_order, wait: :any, timeout: 60_000)
now = archived_at |> DateTime.from_unix!() |> Calendar.strftime("%H:%M:%S UTC")
"#{now}: order archived"
```
<!-- livebook:{"output":true} -->
```
"04:52:05 UTC: order archived"
```
## The Execution Has Been Archived
```elixir
Journey.load(execution.id)
```
<!-- livebook:{"output":true} -->
```
nil
```
`nil` β the execution is no longer returned by `Journey.load/2`. It has been archived.
## It's Still There If You Need It
Archived executions aren't deleted. Pass `include_archived: true` to retrieve them:
```elixir
execution = Journey.load(execution.id, include_archived: true)
execution != nil
```
<!-- livebook:{"output":true} -->
```
true
```
```elixir
execution.archived_at |> DateTime.from_unix!() |> Calendar.strftime("%H:%M:%S UTC")
```
<!-- livebook:{"output":true} -->
```
"04:52:05 UTC"
```
```elixir
Journey.Tools.introspect(execution.id) |> IO.puts()
```
<!-- livebook:{"output":true} -->
```
Execution summary:
- ID: 'EXECL65EG5T93XGL1JXJB3J5'
- Graph: 'Customer Order' | 'v1'
- Archived at: 2026-04-23 04:52:05Z
- Created at: 2026-04-23 04:51:33Z UTC | 34 seconds ago
- Last updated at: 2026-04-23 04:52:05Z UTC | 2 seconds ago
- Duration: 32 seconds
- Revision: 6
- # of Values: 5 (set) / 5 (total)
- # of Computations: 2
Values:
- Set:
- archive_order: '1776919925' | :archive
computed at 2026-04-23 04:52:05Z | rev: 6
- last_updated_at: '1776919925' | :input
set at 2026-04-23 04:52:05Z | rev: 6
- schedule_cleanup: '1776919923' | :tick_once
computed at 2026-04-23 04:51:33Z | rev: 3
- customer_name: '"Luigi"' | :input
set at 2026-04-23 04:51:33Z | rev: 1
- execution_id: 'EXECL65EG5T93XGL1JXJB3J5' | :input
set at 2026-04-23 04:51:33Z | rev: 0
- Not set:
Computations:
- Completed:
- :archive_order (CMPXG21EB0B2AGV7415EMHJ): β
:success | :archive | rev 6
started: 2026-04-23 04:52:05Z | completed: 2026-04-23 04:52:05Z (0s)
inputs used:
:schedule_cleanup (rev 3)
- :schedule_cleanup (CMPR0Y5R3LA1DMXYVD909E2): β
:success | :tick_once | rev 3
started: 2026-04-23 04:51:33Z | completed: 2026-04-23 04:51:33Z (0s)
inputs used:
:customer_name (rev 1)
- Outstanding:
```
<!-- livebook:{"output":true} -->
```
:ok
```
## Summary
In this Livebook, we saw how an `archive` node soft-deleted the execution, removing it from default queries.
We built a simple graph with three nodes β an `input`, a `tick_once`, and an `archive` β and watched the execution disappear from `Journey.load/2` after the scheduled cleanup time.
Key takeaways:
* An `archive` node, when unblocked, marks the execution as "archived," effectively soft-deleting the execution.
* Archived executions are excluded from normal processing by default β they're still in the database, just out of sight and not consuming Journey's background processing resources.
* Archived executions can still be accessed by various functions (`Journey.load/2`, `Journey.list_executions/1` / `Journey.count_executions/1`), by giving them an explicit flag, `include_archived: true`.
* An execution can also be archived by passing it to `Journey.archive/1`.
* An archived execution can be unarchived by passing its id to `Journey.unarchive/1`.
* Use `archive` when you want to remove an execution from circulation (a credit card application, a pizza delivery order, etc.)