BeamTrail: an OTP-native durable step runner using PostgreSQL event logs #3
sherry255
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Hi,
I am building BeamTrail, an Erlang/OTP library for durable business process
execution. I would like design feedback from people who have operated OTP
systems, PostgreSQL-backed queues, workflow engines, or event-sourced systems.
Repository: https://github.com/sherry255/BeamTrail
BeamTrail is not a Temporal clone and not a job queue. The current goal is
smaller: an embedded OTP runtime for durable step/process execution in an
Erlang application that already runs PostgreSQL.
The basic model:
writers;
The split is intentional:
cross-process recovery.
The project started as a linear durable step runner. It now also has an optional
deterministic decider callback for one-command-at-a-time orchestration. The
decider path supports explicit step inputs, durable signals, durable timers, and
{wait, Reason}. There is an executable approval-deadline pattern built fromsignals, timers, and waits.
Current implemented pieces:
gen_statemactive runner;Current limits:
The at-least-once boundary is explicit. BeamTrail provides stable idempotency
keys, but workflow code must use them when calling external systems. If a VM
dies after a callback performs an external side effect but before BeamTrail
records the outcome, the same attempt can be re-entered with the same
idempotency key.
What I would especially like reviewed:
Is the OTP/PostgreSQL split reasonable?
OTP supervision handles live process failure, but durable business progress
is stored in PostgreSQL. Does this boundary feel natural to Erlang/OTP users,
or does it fight the platform?
Is the lease/fencing model strong enough?
Non-creation appends require a valid fencing token. PostgreSQL serializes one
run's append path with a per-run row lock, and stale
expected_seqwrites arerejected. Are there takeover or zombie-writer races I am missing?
Is the active runner/scanner recovery shape idiomatic enough?
A live run has a supervised
gen_statemowner. If it disappears, scannerrecovery acquires a lease and dispatches a new runner. Does that fit OTP
expectations, or should a different process topology be preferred?
Is scanner-driven timer materialization acceptable?
Durable timers are stored as events and projected as
next_wake_at. When due,recovery dispatch materializes
timer.firedbefore the decider continues.Live runners can use local timers for retry, but durable timers still rely on
the scanner path. Is this a reasonable MVP boundary?
Is the public API shaped naturally for Erlang?
Current APIs include
start_workflow/3,await_terminal/2,signal_run/3,cancel_run/2,park_run/2,resume_run/1,requeue_run/2, andbeamtrail_query:describe/1. I would like feedback on naming and expectedreturn semantics.
Is the MVP scope honest enough?
The project intentionally does not claim to be a full workflow platform yet.
The near-term target is a credible embedded durable step/process runtime
before adding richer orchestration.
Useful docs:
https://github.com/sherry255/BeamTrail/blob/main/design-rationale.md
https://github.com/sherry255/BeamTrail/blob/main/ARCHITECTURE.md
https://github.com/sherry255/BeamTrail/blob/main/DECIDER.md
https://github.com/sherry255/BeamTrail/blob/main/TIMER.md
https://github.com/sherry255/BeamTrail/blob/main/APPROVAL.md
https://github.com/sherry255/BeamTrail/blob/main/ROADMAP.md
I am most interested in failure-model criticism, OTP design criticism, and
whether the current scope would be useful to real Erlang/Elixir teams.
Thanks.
Beta Was this translation helpful? Give feedback.
All reactions