For users and operators wiring a Fief instance into a supervision tree. Assumes you have followed getting started and want the full option reference: every instance option, its default, and whether it is fixed cluster-wide or tuned per node.

Fief has no application environment. Everything is instance options passed in your own supervision tree — the child spec {Fief, opts} you place beside your repo. Nothing runs under the :fief application itself, and where the option values come from (a literal, runtime.exs, secrets) is entirely yours.

{Fief,
 name: MyApp.Fief,
 authority: {Fief.Authority.Postgres, repo: MyApp.Repo},
 vnode_impl: {Fief.Key.VnodeImpl, []},
 partitions: 1024,
 lease_ttl: 5_000}

The vnode_impl above selects the Fief.Key surface; swapping in {Fief.Cache.VnodeImpl, []} configures a Fief.Cache instance instead, with the rest of these options unchanged.

Options fall into two classes, and the distinction is operational, not cosmetic:

  • Immutable cluster-wide options are written to the namespace's authoritative config by the first node ever to start it, and every later joiner is checked against that record. A mismatch is fatal at startup, before the joining node touches any shared state — the instance refuses to start. You cannot change one without a full stop of every node in the namespace.
  • Per-node tuning options are read only by the node that sets them. They may differ from node to node and change on a rolling restart. Getting one wrong costs latency or churn, never correctness.

A short third group — name, authority, vnode_impl, leadership — selects what the instance is rather than tuning it; those come first.

Structural options

These define the instance's identity and its pluggable layers.

OptionTypeDefaultPurpose
nameatom(required)The namespace: the identity of the coordination domain. Scopes every row in the Fief tables, so instances coexist on one database and in one BEAM. Must be identical on every node participating in this instance.
authority{module, opts} or module{Fief.Authority.Local, []}The authority adapter (StateStore + Leadership + Presence). {Fief.Authority.Postgres, repo: MyRepo} in production.
vnode_impl{module, opts} or modulenilThe Fief.Vnode implementation. Exactly two first-class values, mutually exclusive per instance: {Fief.Key.VnodeImpl, impl_opts} for the Fief.Key layer, {Fief.Cache.VnodeImpl, impl_opts} for Fief.Cache. When absent, the instance owns nothing and runs no vnode, planner, or leadership subtree.
leadership:from_authority or false:from_authority:from_authority elects a planner leader through the StateStore. false disables election and the planner. Only :from_authority and false are accepted; any other value is fatal at startup.

name is a namespace key, not a validated field — it selects which authoritative record applies rather than being compared against one. Two nodes with different names simply belong to different namespaces.

authority is a bare {module, opts} tuple, so its own options nest inside it. For the Postgres adapter:

  • repo(required) your Ecto repo module.
  • bootstrap — boolean, default true. Whether the adapter self-checks the coordination tables exist at startup.

Design notes: docs/implementation.md §9 (all-instance-opts, no app env), §1 (the instance model). The authority contracts themselves: the authority concepts page.

leadership: :from_authority requires the StateStore adapter to implement the Fief.Leadership.Store primitives (try_lead/3 and leader/1); an adapter that does not is rejected at startup with a naming error. Fief.Authority.Local and Fief.Authority.Postgres both qualify.

Immutable cluster-wide options

Fixed for the life of the namespace. The first node writes them; joiners are validated and refuse to start on mismatch.

OptionTypeDefaultPurpose
partitionspositive integer1024Number of virtual nodes the keyspace is split into by the immutable hash.
hasher (impl)moduleFief.Hasher.DefaultThe Fief.Hasher module mapping a key to a vnode (rem(hasher.hash_key(key), partitions)). The default (:erlang.phash2/2) accepts binaries only; a custom module (never a fun — it is fingerprinted, so it must be a stable, cross-node-comparable term) hashes other terms.

partitions is a top-level option. hasher lives inside the vnode_impl tuple's options — it is the fact both first-class impls fold into their impl fingerprint, Fief.Key.VnodeImpl and Fief.Cache.VnodeImpl alike (the cache half is grounded by test/fief/cache_test.exsdescribe "Fief.Cache.VnodeImpl.config_fingerprint/1 ({:hasher, module})"), and the fingerprint is what the join check compares. Everything else in the impl tuple (sweep pacing, pend bounds, blob caps — Fief.Key.VnodeImpl options) is deliberately excluded from the fingerprint and is per-node tuning.

partitions is the one irreversible decision: changing it remaps every key. 1024 is the default and the right answer for most clusters. Read tuning before choosing otherwise. The mismatch check names the offending field in the error, and the joining node never registers or acquires a lease when it fails.

Design notes: docs/implementation.md §4 (join phase one, the fatal-before-shared-state gate); the immutable set in code is partitions, the wire protocol version, and the impl fingerprint — the library version is recorded for observability but deliberately not compared, so upgrades are never forbidden by it.

Per-node tuning options

Read only by the node that sets them; safe to differ across nodes and to change on a rolling restart.

Lease and fencing

OptionTypeDefaultPurpose
lease_ttlpositive integer (ms)5_000Lease lifetime. The failover floor: a dead node takes at least this long to be replaced.
lease_margin:auto or positive integer (ms):autoHow far before nominal expiry a node self-fences. :auto = renew_rtt_budget + 2 × clock_drift_bound.
renew_rtt_budgetnon-negative integer (ms)100Renewal round-trip allowance feeding the :auto margin.
clock_drift_boundnon-negative integer (ms)50Per-node clock-drift bound feeding the :auto margin (doubled).
renew_intervalpositive integer (ms)lease_ttl ÷ 5 (1_000 at the default TTL)How often a node renews its lease.
poll_intervalpositive integer (ms)5_000Routing-table cache refresh cadence. Hints accelerate this; they never replace it.
handle_fence_deadlinenon-negative integer (ms)50Advisory budget before the unconditional kill, forwarded as deadline_ms to the fence advisories (the vnode-level handle_fence/2, and Fief.Key's for on_fence: :callback modules). Applies only when a vnode_impl is configured.
leave_drain_intervalpositive integer (ms)100How often a leaving node re-checks that it is no longer owner or donor anywhere.

Shutdown

OptionTypeDefaultPurpose
leave_on_shutdownpos_integer() (ms), :infinity, or false20_000Whether tearing the instance's supervision tree down — System.stop/0, SIGTERM, application shutdown, or the parent terminating {Fief, opts} — runs a graceful Fief.Node.leave/1 first and blocks the tree's fall until the node drains (to :stopped, or :fenced if the failure path takes over mid-drain) or this deadline expires. The value is the drain's shutdown deadline; there is no second timer. false disables the drain outright.

The drain runs through a tail sentinel child (Fief.Node.ShutdownDrain) appended after the instance's live machinery, so it is the first thing terminated when the tree falls — against a still-fully-alive node, with agents to hand off and an authority to write through. The sentinel exists only when all hold: leave_on_shutdown is not false; a vnode_impl is configured (with none, nothing is owned, so there is nothing to drain); leadership is not false (with no planner, nothing would ever drain the node, so a wait could only time out); and the instance is not sim-joined (a simulation steps its own shutdown by hand). Outside that shape the option is silently inert.

On timeout the supervisor kills the sentinel and the tree falls exactly as it would on a crash: the cluster sees an ordinary node failure and recovers through the already-verified lease-expiry path (failure) — no new failure mode, no partial-drain state. Default 20 s deliberately sits under Kubernetes's default 30 s terminationGracePeriodSeconds; keep the grace period comfortably above leave_on_shutdown if you tune either. leave_drain_interval above is the poll cadence both the sentinel and any explicit leave/1 caller use while waiting for a drain to finish.

Design notes: docs/leave-on-shutdown.md is the decision record.

Planner and leadership

Read only when a vnode_impl is configured and leadership: :from_authority.

OptionTypeDefaultPurpose
on_arbiter_loss:freeze or :serve_last_epoch:freezePosture when the arbiter is unreachable. Any other value is fatal at startup.
max_concurrent_transferspositive integer8Cap on simultaneously open transfers (prev_owner rows), enforced by the planner.
rebalance_intervalpositive integer (ms)1_000Planner reassignment cadence.
rebalance_jitternon-negative integer (ms)rebalance_interval ÷ 10 (100 at the default)Random spread added to the rebalance cadence.
leadership_ttlpositive integer (ms)5_000Leadership lease lifetime. Expiry costs a leaderless interval, never safety.
campaign_intervalpositive integer (ms)leadership_ttl (5_000)How often a follower campaigns for the empty seat.
campaign_jitternon-negative integer (ms)leadership_ttl ÷ 10 (500)Random spread on the campaign cadence.
settle_retry_intervalpositive integer (ms)200How often the settle relay re-forwards an unsettled transfer report.

Routing

OptionTypeDefaultPurpose
max_moved_hopspositive integer3How many {:moved, ...} redirects a caller follows before giving up.
fallback_jitternon-negative integer (ms)50Random spread before a caller re-reads authoritative routing after a miss.

Vnode-impl tuning (Fief.Key.VnodeImpl)

These live inside the vnode_impl tuple's options and are excluded from the fingerprint — per-node, freely varied.

OptionTypeDefaultPurpose
sweep_ratepositive integer100Keys migrated per sweep tick.
sweep_intervalpositive integer (ms)1_000Sweep tick cadence. Together with sweep_rate this sets the drain speed on handover.
pull_retry_intervalpositive integer (ms)1_000Retry cadence for a stalled lazy pull.
max_pending_per_keypositive integer128Cap on messages buffered per key while it is being pulled.
max_blob_sizepositive integer (bytes)65_536Cap on a single extracted-state blob (a freeze-time bundle counts against it).
extract_deadlinepositive integer (ms)5_000Donor-side budget for keys to extract at freeze. A key that hasn't extracted when it expires is killed and rebuilt from durable truth on the new owner — what bounds a transfer against a flooded or stuck key. See the key lifecycle.
channelmoduleFief.Transfer.Channel.InBandTransport for key-state transfer payloads.

Fief.Cache.VnodeImpl has no per-node tuning options today — get/put/ delete only; see cache.md's roadmap for the TTL/eviction knobs planned as fast-follows.

Fail-fast: mounting {Fief, opts}

Fief.Supervisor — the process this child spec starts — does not hide a sick node behind an internal reboot. It is a thin outer supervisor over the instance's live machinery (Fief.Kernel, mounted significant: true, restart: :temporary) and the shutdown-leave sentinel above. The outer never retries a crashed kernel: auto_shutdown: :any_significant means a kernel exit — its own rest_for_one restart budget already exhausted, local recovery already tried and failed — brings the whole instance down, and Fief.Supervisor itself exits with reason :shutdown.

That makes the child spec you write for {Fief, opts} the place recovery policy actually lives, exactly as with any other :permanent / :transient / :temporary child:

  • :permanent — the default (Fief's child_spec/1 leaves :restart unset, so Supervisor applies it) — your tree always restarts the instance, including after this fail-fast :shutdown.
  • :transient — restarts only on an abnormal exit. Because a kernel exit is reported as :shutdown, a :transient instance is not restarted by this path; choose it if a kernel that has exhausted its own retries should stay down until something else intervenes.
  • :temporary — never restarted by your supervisor at all.

Override the default with Supervisor.child_spec({Fief, opts}, restart: :transient) (or :temporary) in your children list.

This is a deliberate contract, not a bug: the cluster's own failure path — lease expiry, takeover by survivors — is the verified fallback for a node that can no longer serve (failure), and a persistently sick node should become visibly dead quickly rather than flap through a nested restart loop generating lease and membership churn the rest of the cluster can see. Design notes: docs/leave-on-shutdown.md §2.6, §2.6.1.

Caveats on the load-bearing options

lease_ttl is your failover floor. A node that dies is not replaced until its lease expires at the arbiter, so recovery of its keys takes at least lease_ttl, and no hint channel or configuration can beat it. Shorter TTLs recover faster but renew more often and tolerate less clock drift and Postgres latency; 3–5 seconds is comfortable against a healthy arbiter. See guarantee 3, downtime over inconsistency.

lease_margin must be smaller than lease_ttl. The margin is the window in which a node self-fences before its lease could expire at the arbiter, so a margin at or above the TTL would fence the node before its lease could ever be live — this is validated and fatal at startup. The :auto value doubles the clock-drift bound deliberately: drift can swing between extremes across a measurement interval, and the 1× margin has a model-checked counterexample. Design notes: docs/implementation.md §2; specs/FiefLease.tla.

handle_fence_deadline must fit strictly inside the margin. The advisory handle_fence/2 callback runs before the unconditional kill, and the whole kill must complete inside lease_ttl − lease_margin so it finishes before the lease can expire at the arbiter. A deadline at or above the margin is validated and fatal at startup. The advisory budget is advisory; the kill that follows it is not — see fencing modes.

on_arbiter_loss is consulted only with leadership enabled. The planner-side posture is always to freeze — an unreachable arbiter accepts no CAS writes anyway. :serve_last_epoch names the node-side behavior, which is already the de-facto default: nodes serve their last-observed routing table until the fence clock decides otherwise. Both accepted values produce the same freeze-writes / serve-until-fence behavior today; a stricter node-side freeze would be a future refinement. Design notes: docs/implementation.md §9.

on_fence is not an instance option. The fence mode (:terminate / :continue / :callback) is set per key module with use Fief.Key, not in the instance opts — see fencing modes.

What is deliberately not configurable

The sources of nondeterminism — time, randomness, cross-node transport, and the simulation progress marks — are compile-time seams, not instance options. They exist so a test harness can take over a deterministic simulation; no user ever configures them, and they sit on hot paths where an indirection would cost.

A handful of instance options exist purely as test seams and are not part of the supported surface: sim, node_id, fence_action, impl_fingerprint, and authority: {:external, module, handle}. They let one BEAM impersonate a cluster under simulation. Do not use them in production. Design notes: docs/implementation.md §10 (the determinism seams), §11 (the testing shape).

Verified by

  • test/fief/node_test.exs describe "join" — first-writer-wins config; a partitions mismatch and a protocol_version / impl_fingerprint mismatch are each fatal before shared state, naming the field.
  • test/fief/node_test.exs describe "margin arithmetic" — the :auto margin is renew_rtt_budget + 2 × clock_drift_bound; resolve_margin defaults and overrides; a lease_margin ≥ lease_ttl and a handle_fence_deadline ≥ margin each refuse to start.
  • test/fief/key/vnode_impl_test.exs describe "join validation" — a hasher mismatch is fatal at join, before shared state.
  • test/fief/key_test.exs describe "config_fingerprint/1 ({:hasher, module})" — the fingerprint is {:hasher, Fief.Hasher.Default} by default; a fun hasher and a hasher without hash_key/1 are each fatal.
  • test/guides/configuration_test.exs — the option set this page documents and the defaults it states for the load-bearing options are asserted against the validator code, so this page cannot drift from it.
  • test/fief/node/shutdown_drain_test.exsleave_on_shutdown default 20_000 and validation; describe "shape gating" — absent under false, under leadership: false, with no vnode_impl, and for a sim-joined instance; describe "auto_shutdown" — a kernel exit brings Fief.Supervisor down with reason :shutdown, the fail-fast contract above.