Bedrock.ControlPlane.Config.CoreState (bedrock v0.7.0)
View SourceThe durable record a recovery recovers FROM — FDB's DBCoreState,
held during recovery as cstate.prevDBState.
This is the other half of a divide FDB keeps in two structures and Bedrock, until now, kept in one type:
CoreState— DURABLE. Persisted (for us, in the object-storage cluster bootstrap the coordinator loads at cold start), it names what the next recovery must find in order to recover at all. FDB'sDBCoreState(DBCoreState.h:132): the tLog sets, old generations,recoveryCount.TransactionSystemLayout— TRANSIENT. Rebuilt every recovery and broadcast so workers can reach each other; it carries pids, which are meaningless the moment the epoch ends. FDB'sServerDBInfo, whose own comment reads: "This structure contains transient information which is broadcast to all workers for a database, permitting them to communicate with each other."
The rule the split makes structural: what must SURVIVE goes here;
what must be REACHED goes in the layout. That is why the layout may
never carry anything O(workers) (see its moduledoc) while this
record may — a thing you must recover is worth persisting, a thing
you must merely contact is not worth broadcasting.
Why only logs
Recovery consumes exactly one fact from its prior state: which logs the last epoch ran, so it can lock them and copy from them. The durable bootstrap record also carries the epoch, the cluster id and the coordinator set — all read by the coordinator directly, none consumed as recovery's prior state — so projecting them here would add fields with no reader.
Log LOCATIONS are likewise absent: the bootstrap schema HAS an
otp_ref per log, but the writer always sets it to nil
(persistence_phase.ex), because recovery discovers live services
through foreman registration rather than trusting a durable address.
The record says WHICH logs, never where they were last seen.
Materializer membership joins this record in bedrock-q67.21.12, for the one shard recovery cannot do without: the system shard, whose keyspace holds the metadata every later phase reads.
Summary
Functions
Whether this cluster has never completed a recovery.
Projects the durable cluster-bootstrap record into the prior state recovery consumes.
Projects a completed recovery's layout into the record the NEXT
recovery reads as its prior state — FDB's logSystem->toCoreState,
which likewise distills a live log system down to what the coordinated
state must hold.
The log ids the prior epoch ran — the services recovery must lock and copy from. A fresh cluster names none.
The system shard's materializer members — the record that says WHERE the cluster's metadata lives.
Types
@type t() :: %{ logs: %{ required(Bedrock.DataPlane.Log.id()) => Bedrock.ControlPlane.Config.LogDescriptor.t() }, system_materializers: %{ required(Bedrock.Service.Worker.id()) => node_name :: String.t() } }
Functions
Whether this cluster has never completed a recovery.
FDB makes the same call on the same evidence, in
TagPartitionedLogSystem::recoverAndEndEpoch
(TagPartitionedLogSystem.actor.cpp:2416): if (!prevState.tLogs.size()) { // This is a brand new database — the branch that MANUFACTURES a
log system rather than recovering one, keyed on the prior core state
naming no logs.
A missing record and a record naming no logs mean the same thing: there is no prior epoch's data to recover, so recovery seeds rather than reads. Absence of the record is not an error — it is the first boot.
Projects the durable cluster-bootstrap record into the prior state recovery consumes.
A log with no recorded tags is carried as [] rather than nil:
downstream takes Map.keys/1 and MapSet.new/1 over these, so a nil
would crash a recovery instead of describing a log that serves no
shard.
@spec from_layout(Bedrock.ControlPlane.Config.TransactionSystemLayout.t(), %{ required(Bedrock.Service.Worker.id()) => String.t() }) :: t()
Projects a completed recovery's layout into the record the NEXT
recovery reads as its prior state — FDB's logSystem->toCoreState,
which likewise distills a live log system down to what the coordinated
state must hold.
Only the durable half crosses the epoch boundary. The layout's pids (sequencer, proxies, resolvers) die with the epoch that made them, so carrying them into a record whose entire purpose is to OUTLIVE the epoch would be a category error — and the reason to keep these two types apart at all.
The system shard's members are passed IN rather than read out of the layout, because the layout deliberately carries no membership at all ("Nothing O(workers) may ever be added to this broadcast"). The director knows them — it just persisted them — so it supplies both halves at once.
@spec log_ids(t() | nil | map()) :: MapSet.t(Bedrock.DataPlane.Log.id())
The log ids the prior epoch ran — the services recovery must lock and copy from. A fresh cluster names none.
@spec system_materializers(t() | nil) :: %{ required(Bedrock.Service.Worker.id()) => String.t() }
The system shard's materializer members — the record that says WHERE the cluster's metadata lives.
Recovery cannot read the shard layout or the materializers family until it knows which workers hold tag 0, because both live IN tag 0. FDB has the same indirection: its coordinated state names the tlogs that hold the txnStateStore, and recovery peeks them to rebuild it.