NoNoncense.MachineId (NoNoncense v2.0.0)

Copy Markdown View Source

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

StrategyInfrastructureNotes
HostIdentifiersNoneDerives IDs from node names/IPs; requires identical config on all nodes; no coordination
EnvironmentVariableKubernetes StatefulSetReads pod ordinal env var; no coordination
SqlLeasePostgreSQL or MySQLDynamically coordinates across nodes; requires a migration (see module doc)
RedisLeaseRedis 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 (default NoNoncense.MachineId)
  • :instances (required) - list of per-instance option keywords, each accepting the same options as NoNoncense.init/1 (:base_key, :epoch, etc.). Note that names must be unique for each factory, the default name is NoNoncense.
  • :enable_conflict_guard? - whether to start a NoNoncense.MachineId.ConflictGuard as a supervised child (default true)
  • :strategy (required) - a module implementing the NoNoncense.MachineId.Strategy behaviour
  • :strategy_opts - passed as-is to the strategy's callbacks (default [])
  • :lease_duration - the duration, in ms, to request from the strategy on every acquire/renew call (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 failing start_link/1 (default 60_000; pass :infinity to 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

opt()

@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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts)

@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 (default NoNoncense.MachineId)
  • :instances (required) - list of per-instance option keywords, each accepting the same options as NoNoncense.init/1 (:base_key, :epoch, etc.). Note that names must be unique for each factory, the default name is NoNoncense.
  • :enable_conflict_guard? - whether to start a NoNoncense.MachineId.ConflictGuard as a supervised child (default true)
  • :strategy (required) - a module implementing the NoNoncense.MachineId.Strategy behaviour
  • :strategy_opts - passed as-is to the strategy's callbacks (default [])
  • :lease_duration - the duration, in ms, to request from the strategy on every acquire/renew call (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 failing start_link/1 (default 60_000; pass :infinity to 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.