Bedrock.ControlPlane.Distributor.Transactions (bedrock v0.7.0)
View SourceThe distributor's fenced system transactions.
Every mutating commit is built the FDB way: read the lock state at a
pinned version, evaluate the Lock fence, and commit the fence's
mutations WITH read conflicts on the lock keys at that version — so a
concurrent take conflicts with this commit inside the pipeline itself.
A commit abort is therefore an authoritative supersession verdict (the
replacement for phase-a's director-side delta rejection); transient
read/commit failures are surfaced as themselves and are not verdicts.
Reads resolve through the same channel clients use: a commit proxy's covering entry names the system-shard materializer, and the versioned read happens there. The keyspace is the channel, for the distributor too.
Summary
Types
The injectable dependency set. deps_for/4 builds the production
wiring; tests script the three seams directly.
Functions
A CHECK-fenced mutating commit, honoring the Lock runner obligations: the owner key is read (and read-conflicted) always; the write key is read only when the owner is not ours — an unconditional write-key read would make every pair of concurrent same-owner distributor transactions mutually conflict and serialize.
Production dependencies: read versions from the epoch's sequencer, reads resolved by-key through a commit proxy to the covering materializer, commits through a random proxy.
The read-only poll-to-die verdict: reads both lock keys at a fresh
version and mirrors Lock.poll/3. A failed read is :unavailable —
not a verdict; the poll loop simply tries again on its next tick.
Reads the distributor's snapshot of the durable mapping families — shard layout and materializer refs — at one pinned version (a single-version multi-page read cannot tear: the families are written transactionally). Read AFTER the lock is taken, under FDB's lock-first-snapshot-second startup order.
Takes the distributor lock: reads both lock keys at a pinned version (FDB's take reads both — the remembered write UID is the unobserved-take evidence), claims the owner key, and commits with read conflicts on both keys at that version.
Types
@type deps() :: %{ epoch: Bedrock.epoch(), proxies: [Bedrock.DataPlane.CommitProxy.ref()], next_read_version_fn: (-> {:ok, Bedrock.version()} | {:error, term()}), get_fn: (Bedrock.key(), Bedrock.version() -> {:ok, binary()} | {:error, :not_found | term()} | {:failure, term(), term()}), commit_fn: (Bedrock.DataPlane.CommitProxy.ref(), Bedrock.epoch(), binary(), keyword() -> {:ok, Bedrock.version(), non_neg_integer()} | {:error, term()}), get_range_fn: (Bedrock.key(), Bedrock.key(), Bedrock.version() -> {:ok, {[{Bedrock.key(), binary()}], boolean()}} | {:error, term()} | {:failure, term(), term()}) }
The injectable dependency set. deps_for/4 builds the production
wiring; tests script the three seams directly.
Functions
@spec commit_checked(Bedrock.ControlPlane.Distributor.Lock.t(), deps(), [ Bedrock.ControlPlane.Distributor.Lock.mutation() | {:set, Bedrock.key(), binary()} | {:clear, Bedrock.key()} ]) :: :ok | {:error, :superseded} | {:error, {:read_version_failed | :lock_read_failed | :lock_commit_failed, term()}}
A CHECK-fenced mutating commit, honoring the Lock runner obligations: the owner key is read (and read-conflicted) always; the write key is read only when the owner is not ours — an unconditional write-key read would make every pair of concurrent same-owner distributor transactions mutually conflict and serialize.
Verdict semantics mirror FDB exactly: supersession is the READ
verdict (Lock.check's refusal — FDB's movekeys_conflict), and it
is authoritative. A commit ABORT is not: it retries with a fresh read
version (re-evaluating the fence, so a genuine usurper is caught by
the read on the retry); exhausted retries surface as a transient
commit failure.
@spec deps_for(module(), Bedrock.epoch(), Bedrock.DataPlane.Sequencer.ref(), [ Bedrock.DataPlane.CommitProxy.ref() ]) :: deps()
Production dependencies: read versions from the epoch's sequencer, reads resolved by-key through a commit proxy to the covering materializer, commits through a random proxy.
@spec poll_verdict(Bedrock.ControlPlane.Distributor.Lock.t(), deps()) :: :ok | :superseded | :unavailable
The read-only poll-to-die verdict: reads both lock keys at a fresh
version and mirrors Lock.poll/3. A failed read is :unavailable —
not a verdict; the poll loop simply tries again on its next tick.
@spec read_snapshot(deps()) :: {:ok, %{ shard_layout: %{ required(Bedrock.key()) => {Bedrock.range_tag(), Bedrock.key()} }, materializer_refs: %{ required(Bedrock.range_tag()) => %{required(String.t()) => String.t()} } }} | {:error, term()}
Reads the distributor's snapshot of the durable mapping families — shard layout and materializer refs — at one pinned version (a single-version multi-page read cannot tear: the families are written transactionally). Read AFTER the lock is taken, under FDB's lock-first-snapshot-second startup order.
@spec take_lock(deps()) :: {:ok, Bedrock.ControlPlane.Distributor.Lock.t()} | {:error, {:read_version_failed | :lock_read_failed | :lock_commit_failed, term()}}
Takes the distributor lock: reads both lock keys at a pinned version (FDB's take reads both — the remembered write UID is the unobserved-take evidence), claims the owner key, and commits with read conflicts on both keys at that version.
An abort at TAKE time is not a verdict — FDB's takeMoveKeysLock
retries not_committed/too_old with a fresh read version, because
an abort here can also mean the read version fell below the
resolver's pruning floor. Take semantics are last-take-wins: the
re-take reads the interleaved winner as the new previous owner and
claims over it; supersession is delivered where it is authoritative —
by the CHECK fence on mutating transactions and the poll loop.
Exhausted retries surface as a transient commit failure for the
director's recruit-retry.