mule/engines/sqlite
The SQLite engine — the port of Oban.Engines.Lite, built on sqlight
(the Erlang esqlite NIF).
Concurrency model
SQLite gives the engine a single connection, and Mule’s runtime calls the
engine from many processes (producers, the Stager, plugins, the peer). So
new spawns a runner process that owns every use of the connection:
each engine operation ships its whole body to the runner as a closure and
waits for the reply. That serialization is also the engine’s mutual
exclusion — the multi-statement operations (unique insert’s
check-then-act, the bulk transitions’ select-then-mutate, leader
election’s reap-then-upsert) are atomic because nothing else can touch
the connection between their statements. It replaces both Postgres
devices wholesale: no advisory locks (so insert_job never returns
ConflictLocked) and no FOR UPDATE SKIP LOCKED (so update_job’s
JobLockedOrNotFound only ever means not-found).
The runner is linked to the process that called new and the reply wait
is a monitored process.call — if the runner dies, callers panic rather
than hang.
Representation
The schema (see mule/engines/sqlite/migration) stores timestamps as
INTEGER epoch microseconds — unixepoch('subsec') is the database clock,
so due-times and lease expiry never depend on the BEAM clock — and
args/meta/tags/errors/attempted_by as TEXT holding JSON,
queried with the JSON1 functions. Unique containment matching uses the
json_each scan from lite.ex’s json_contains fragment; there are no
GIN-index analogs, so those matches scan (acceptable at SQLite scale).
Pair the engine with the isolated notifier (SQLite has no
LISTEN/NOTIFY) — within one BEAM node it relays every notification the
runtime needs. reindex is a no-op: SQLite indexes do not accrue
Postgres-style bloat, and REINDEX locks the whole database for no
benefit here.
Usage
let assert Ok(connection) = sqlight.open(“mule.db”) let assert Ok(_) = migration.migrate(connection) let engine = sqlite.new(connection)
Values
pub fn new(
connection connection: sqlight.Connection,
) -> engine.Engine
Build the SQLite engine over an open sqlight connection.
Fixes SQLite’s legacy per-connection settings first — busy_timeout = 5000 (wait for a competing writer on another connection instead of
failing SQLITE_BUSY immediately), journal_mode = WAL and synchronous = NORMAL (writers don’t block readers; a no-op on :memory:),
foreign_keys = ON — then spawns the runner process (see the module doc)
that serializes every operation. The runner is linked to the calling
process, so build the engine from a process that outlives the Mule
instance.