Skip to content

Workers self-detect displacement; the Foreman becomes relay and janitor - #179

Open
jallum wants to merge 2 commits into
feature/q67.37-typed-unlock-seedfrom
feature/q67.38-worker-self-displacement
Open

Workers self-detect displacement; the Foreman becomes relay and janitor#179
jallum wants to merge 2 commits into
feature/q67.37-typed-unlock-seedfrom
feature/q67.38-worker-self-displacement

Conversation

@jallum

@jallum jallum commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Why

The Foreman's reconciliation rule (do_reconcile_workers) answered a membership question on the workers' behalf: on every layout push it decided which hosted workers to retire. FDB has no component that decides another process's retirement — every role self-detects displacement and throws worker_removed on itself: TLogs compute isDisplaced against ServerDBInfo (rejoinClusterController, TLogServer.actor.cpp), storage servers validate their rejoin against a commit proxy's txnStateStore (absence = worker_removed). The hosting process only observes the exit and deletes files.

Course item B, first half (bedrock-q67.38, settled 2026-08-20 after FDB source check). Depends on #178 (the seed had to stop carrying transaction_services first).

What

  • Foreman: keeps its push-relay role (one per node — the natural distribution point from the cluster push to the workers it hosts) and gains a janitor cast: worker_retired disposes the directory, registration, and entry of a worker that decided its own retirement (:transient restart keeps a deliberate shutdown down). do_reconcile_workers / workers_to_retire are deleted — the Foreman never answers a membership question again.
  • Shale (log worker): log topology is epoch-constant, so the pushed log set IS the membership authority. On each relayed push: displaced iff the layout's epoch progressed past ours AND its log set omits us. The epoch guard keeps absence during an in-flight recovery from being a death sentence; a never-locked cold-boot resurrection is judged by any completed layout.
  • Olivine (materializer): membership lives in the committed keyspace (materializers/<tag>), not the wiring push — so rejoin validation is one tag-keyed ask to a pushed commit proxy: new CommitProxy.resolve_materializer/3, answered from RoutingData (the serverList analogue) under the same cadence rule as fetch_routing. Absence or another worker's id is an authoritative verdict; :locked/:unavailable/:timeout are not — the next push revalidates.

How

  • Foreman.Impl.do_relay_tsl/2 sends {:tsl_updated, tsl} to every running hosted worker; do_worker_retired/2 reuses the existing removal path (terminate-if-alive → deregister → rm -rf).
  • RoutingData.resolve_materializer/2 is a tag lookup in the same routing view that serves clients — one authority, two readers.
  • Worker verdicts are handler-level and unit-tested as such: shale/displacement_test.exs (epoch-progression guard matrix) and olivine/displacement_test.exs (verdict vs non-verdict matrix, stub proxy).

Deferred to q67.21 (recorded on the ticket): in-band privatized-mutation delivery for mid-epoch removals — do not build the private-mutation path before removals exist. services leaving the TSL is the second half of q67.38, next PR in the stack.

Full suite (2582 tests), credo --strict, dialyzer green.

jallum added 2 commits August 20, 2026 20:30
…bedrock-q67.38)

FDB has no component that decides another process's retirement: every
role self-detects displacement and throws worker_removed on itself —
TLogs against ServerDBInfo (isDisplaced), storage servers via rejoin
validation through a commit proxy's txnStateStore. The Foreman's
reconciliation rule was Bedrock answering a membership question on the
workers' behalf; it dies here.

- The Foreman relays each newly durable layout push to its running
  hosted workers (do_relay_tsl) and janitors deliberate retirements
  (worker_retired -> dispose directory, registration, entry; :transient
  restart keeps a deliberate shutdown down). do_reconcile_workers and
  workers_to_retire are deleted.
- Shale checks its own id against the pushed log set, guarded by epoch
  progression: only a layout that progressed past our epoch may retire
  us (absence during an in-flight recovery is not a death sentence);
  a never-locked resurrection is judged by any completed layout.
- Olivine rejoin-validates: one tag-keyed ask to a pushed commit proxy
  (CommitProxy.resolve_materializer, answered from RoutingData — the
  serverList analogue). Absence or another worker's id is authoritative
  displacement; locked/unavailable are not verdicts and the next push
  revalidates.
The audit caught the guard defeating the design: the locking phase locks
old-layout logs and every advertised materializer into the judging epoch
BEFORE the push, so a strict pushed > mine guard meant locked-but-
excluded workers were never judged — strays became immortal and
displaced logs deferred cleanup a full recovery generation.

The guard is now pushed >= locked epoch: a layout may judge every worker
it had the chance to include; only a push OLDER than our lock — an
in-flight recovery's past — is off-limits. Test matrices now pin the
real displaced states (own-epoch push retires an excluded log; the
completing push validates a locked stray).

Also from the audit:
- nil layouts (the coordinator's director-start clear broadcast) are not
  relayed, and the liveness bound (a worker that cannot run cannot
  self-detect) is documented on the relay.
- the foreman pulls the Link's cached layout once at spin-up, closing
  the cold-boot push-miss window (the coordinator replays on Link
  subscription, which can precede the foreman).
- stale reconciliation-era comments and guides updated to the
  self-detection rule.
jallum pushed a commit that referenced this pull request Aug 21, 2026
…6-08-20/21 as stacked PRs #178-#182 (each cold-audited, findings applied, CI green): A=q67.37 typed pull-source seed + ShardRouter as the single placement site (#178); B=q67.38 workers self-detect displacement (epoch guard: a layout judges every worker it could include, pushed >= locked — the strict guard was caught by audit making strays immortal), Foreman relay+janitor, resolve_materializer rejoin ask (#179), services leaves the TSL — ServerDBInfo parity {epoch, sequencer, proxies, resolvers, logs} (#180); E=q67.41 epoch-constant log wiring (fold deletion; layout/logs stays for introspection), carried start keys consumed, worker ids ride the assignment (#181); D=q67.40 by-key covering-entry resolution, two-tier partial coalescing cache (#182). Remaining in the arc: q67.21 Distributor port (mapping ownership + metadata-shard availability + in-band retirement + overlap-aware client cache), q67.42 bootstrap read continuation, q67.20.7 legacy fallback removal."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant