Supervised machine ID lease management for NoNoncense.
Every node generating nonces needs a unique machine ID (0–511). NoNoncense.MachineId
acquires and holds that ID through a pluggable strategy, automatically renewing it in the
background. LeaseManager directly initializes the configured NoNoncense factories after
acquiring a lease, disables them when that lease is lost, and initializes them again after a
successful reacquisition.
Usage
Add it to your supervision tree in the appropriate place (in this case, after your Repo):
children = [
...
MyApp.Repo,
{NoNoncense.MachineId,
strategy: NoNoncense.MachineId.Strategy.SqlLease,
strategy_opts: [repo: MyApp.Repo],
instances: [[base_key: System.fetch_env!("BASE_KEY")]]}
]Startup blocks until the initial lease is acquired. Once started, the lease renews
automatically. If a lease is confirmed lost or reaches its local expiry deadline, the factories
are disabled, :on_lease_lost is called with the reason, and the manager keeps trying to
acquire a lease. The callback may notify, halt the node, or take any other application-specific
action.
Strategies
| Strategy | Infrastructure | Notes |
|---|---|---|
HostIdentifiers | None | Derives IDs from node names/IPs; requires identical config on all nodes; no coordination |
EnvironmentVariable | Kubernetes StatefulSet | Reads pod ordinal env var; no coordination |
SqlLease | PostgreSQL or MySQL | Dynamically coordinates across nodes; requires a migration (see module doc) |
RedisLease | Redis 8+ / Valkey 9+ | Dynamically coordinates across nodes; requires HSETEX support |
Conflict Guard
A NoNoncense.MachineId.ConflictGuard is started by default. Set
enable_conflict_guard?: false to disable it. The guard uses Erlang node networking to detect
machine ID conflicts. It works with every strategy but is most useful with strategies that don't
have a central lease coordinator. It requires distributed Erlang; without connected nodes, it
has no peers to compare.
Options
:name- registered name of this supervisor (defaultNoNoncense.MachineId):instances(required) - list of per-instance option keywords, each accepting the same options asNoNoncense.init/1(:base_key,:epoch, etc.). Note that names must be unique for each factory, the default name isNoNoncense.:enable_conflict_guard?- whether to start aNoNoncense.MachineId.ConflictGuardas a supervised child (defaulttrue):strategy(required) - a module implementing theNoNoncense.MachineId.Strategybehaviour:strategy_opts- passed as-is to the strategy's callbacks (default[]):lease_duration- the duration, in ms, to request from the strategy on everyacquire/renewcall (the strategy may grant a different actual duration; default 8 hours):renew_interval- how often to renew, in ms; should be comfortably shorter than the lease's actual TTL to allow for retries (default 30 minutes):acquire_timeout- max total time, in ms, to keep retrying acquisition at boot before giving up and failingstart_link/1(default60_000; pass:infinityto retry forever):on_lease_lost- called with the reason for the lease loss. Can be used to halt the entire node, for example. Note that on lease loss, all NoNoncense factories are always disabled to preserve uniqueness guarantees.
Lease duration and renew interval
The defaults leave a wide recovery window for a temporary connection loss to your coordinator (e.g. your database), while still renewing well ahead of the lease's expiration. If you override these, keep that balance intact - most practical uses of nonce generation, such as ID generation, will grind to a halt if generation stops, so favor conservative, generous durations over aggressive, short ones. Machine IDs are meant to stay static for the lifetime of the node, so there's little to be gained from renewing more aggressively.
Summary
Functions
Returns a specification to start this module under a supervisor.
Starts the machine ID supervisor, blocking until the initial lease is acquired.
Types
@type opt() :: {:name, module()} | {:strategy, module()} | {:strategy_opts, keyword()} | {:lease_duration, pos_integer()} | {:renew_interval, pos_integer()} | {:acquire_timeout, pos_integer() | :infinity} | {:on_lease_lost, (term() -> any())} | {:instances, [NoNoncense.init_opt()]} | {:enable_conflict_guard?, boolean()}
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec start_link([opt()]) :: Supervisor.on_start()
Starts the machine ID supervisor, blocking until the initial lease is acquired.
Options
:name- registered name of this supervisor (defaultNoNoncense.MachineId):instances(required) - list of per-instance option keywords, each accepting the same options asNoNoncense.init/1(:base_key,:epoch, etc.). Note that names must be unique for each factory, the default name isNoNoncense.:enable_conflict_guard?- whether to start aNoNoncense.MachineId.ConflictGuardas a supervised child (defaulttrue):strategy(required) - a module implementing theNoNoncense.MachineId.Strategybehaviour:strategy_opts- passed as-is to the strategy's callbacks (default[]):lease_duration- the duration, in ms, to request from the strategy on everyacquire/renewcall (the strategy may grant a different actual duration; default 8 hours):renew_interval- how often to renew, in ms; should be comfortably shorter than the lease's actual TTL to allow for retries (default 30 minutes):acquire_timeout- max total time, in ms, to keep retrying acquisition at boot before giving up and failingstart_link/1(default60_000; pass:infinityto retry forever):on_lease_lost- called with the reason for the lease loss. Can be used to halt the entire node, for example. Note that on lease loss, all NoNoncense factories are always disabled to preserve uniqueness guarantees.
Lease duration and renew interval
The defaults leave a wide recovery window for a temporary connection loss to your coordinator (e.g. your database), while still renewing well ahead of the lease's expiration. If you override these, keep that balance intact - most practical uses of nonce generation, such as ID generation, will grind to a halt if generation stops, so favor conservative, generous durations over aggressive, short ones. Machine IDs are meant to stay static for the lifetime of the node, so there's little to be gained from renewing more aggressively.