Status: Current
The Raft core log: record format, group commit, snapshots and failover (shipped in 1.3, 2026-10-01).
The storage core's metadata log¶
2026-09-26. Package io.cresco.gfs.core.log. Covers OUT-08, OUT-13 (core), OUT-04 (core), OUT-14,
OUT-35, OUT-40 and OUT-53.
The storage core no longer journals through the prototype federation index. Every batch of engine deltas is an entry in the core's own log, replicated to the core peers with Raft, and acknowledged to the client only once a majority of peers has it on disk.
On disk (core_log_dir)¶
| File | Contents | Written by |
|---|---|---|
core-log.header |
magic, format, cluster id, key-check value, MAC | once, atomically |
raft-state |
current term, vote, this directory's raft state id, whether the peer votes (N18), MAC | temp, fsync, rename, directory fsync |
seg-<first index>.log |
segment header (first index, term and MAC before it, header MAC), then records | append, one force per group commit |
snap-<last index>.snap |
engine snapshot at that index, its term and chain MAC, payload SHA-384, MAC | temp, fsync, rename, directory fsync |
imported-index.marker |
the one-time legacy import, MAC'd | once |
epoch-<n>.key |
LC-ENC: an at-rest epoch's file key, AES-KWP-wrapped under the current master kid, MAC'd | temp, fsync, rename, directory fsync; destroyed (overwritten, unlinked) once no file needs it |
A record is magic | fmt | len | term | index | batch | hmac16 | payload | mac, with
mac_i = HMAC-SHA-384(k_log, fmt|term|index|mac_(i-1)|payload). The 16-byte header MAC covers the
header fields and mac_(i-1), so a length is authenticated before it is used. batch is the first
index of the record's group commit (one write and one force); everything before it was forced
before the record was written. It is local to the peer and outside the chain, so peers' chain values
stay equal.
k_log = HKDF-SHA-384(master, "gfs/core/log-mac/v1"). Equal chain values at an index mean equal
logs up to that index, on any peer.
Recovery fails closed¶
- The directory's fsync barrier is probed with the storage nodes' own probe (
FsBinding.commission, five unanimous rounds). If it is not proven, the core refuses to start unlesscore_log_durability_attested=true. - A wrong master key is refused by the key-check value before anything is read.
- Every record's framing, MAC, chain and index continuity is verified. An incomplete or
unverifiable record in the last segment is a torn tail unless a verifiable record of a later
group commit follows it. A group commit that was never forced may reach the disk in any order
(later pages written, an earlier one zero-filled), so records of the bad record's own group commit
after it prove nothing; a later group commit was written only after this one was forced, so it
proves corruption. A torn tail is truncated from the bad record, synced, logged and counted
(
core.status→log.torn_*). A record counts as verifiable if its header MAC checks against the bytes before it, against the chain value expected there, or against the value a damaged predecessor's own bytes compute to. A segment header is forced before any record is written, so a bad header with bytes after it is corruption, never a torn creation. - Anything else refuses to start with the file, offset and index: mid-log corruption, a bad MAC followed by valid records, a gap, a chain break, a snapshot that fails its MAC or disagrees with the log, an unknown format number.
- Installing a leader's snapshot that replaces the log deletes the replaced segments newest first, syncing the directory after each, so a crash leaves a prefix of the old log that recovery recognises (it disagrees with the snapshot) and finishes discarding. A gap after a snapshot is therefore always lost data, and refuses.
- An entry the engine cannot apply (an unknown delta from a newer release) halts the core, naming segment, offset and index. A halted core serves nothing.
Master-key rotation (OUT-25)¶
k_log and k_raft are HKDF-SHA-384 from one master key, so each pair is bound to that key's id
(kid). A peer holds the log keys of every master kid it holds (core_master_key_file plus
core_master_key_previous_files).
- The header names the kid.
core-log.headercarries the kid the log is keyed under, that kid's key-check value, andrekeyedAt, the last index written under an earlier kid. A header naming a kid the peer does not hold refuses to start and names it. - Every file is MAC'd under one kid and verified under the held kid that authenticates it. A
segment's records are under its header's kid. Recovery refuses an earlier kid where only the
header's may be: a segment starting after
rekeyedAt, a snapshot pastrekeyedAt; and a change of kid between segments (or inside one) that no journaled switch accounts for. A file under a retired kid that is put back, or forged by someone holding the retired key, is refused rather than replayed. - The chain's kid changes through the log (journaled kid switch). Chain values are MAC'd under
the kid in effect at each index, and peers compare them on every append, so every peer must change
kid at the same index. A
CHAINKIDentry (payload kind 3, naming a kid) says the chain continues under that kid after it. The leader appends one once every member has answered the features probe withckid1andcur:<kid>naming the leader's current kid (within three election timeouts; a member that is down or has not answered blocks it, and an older build never answersckid1, so it never receives the entry it would halt on). Each peer, as it appends the entry, forces it, rewrites the header (new kid,rekeyedAt= the entry's index), rolls a new segment under the new kid and rewrites the raft state and marker. A peer starting with a new current kid changes nothing in its log; the leader'sraft.kid_switch(core.status) says what the switch waits for. A follower that does not hold the kid an entry names does not append it (it logs why and waits; it does not halt). - Crashes and truncations. A switch entry written and forced but not yet in the header is finished at the next open. A truncation below a switch undoes it: the segment that held the first removed index stays (emptied) as the record of the kid before, the header goes back to it, and an open after a crash in between does the same. History is never rewritten: the chain value at the switch is the same, and later records chain from it under the new kid.
- A follower with nothing after its snapshot (a fresh peer, a snapshot install that replaced its log, a truncation to the snapshot) cannot tell from its own files which kid the next record chains under. It takes the held kid under which the leader's entries end at the leader's chain value (the header's kid first). The leader's chain over those entries from the same value at the snapshot is what any choice must match, so this accepts no history the check would otherwise refuse. Until it has learned it, a peer whose snapshot was installed over its log (with more than one kid held) does not stand for election, so it never extends the chain under a guess.
- The next snapshot retires the old kid. While
kids_in_use(core.statuslog) lists a kid other than the header's, the snapshot pass takes a snapshot every 10 s; once one lies pastrekeyedAt, compaction deletes the old kid's segments and snapshot.core.rotatemasterreportslog_kid,log_kids_in_useand aretirable_kidsthat accounts for the log as well as the roots. - Raft messages are MAC'd under the sender's current kid and verified under any held kid, so the rotation rolls through the peers one restart at a time, provided every peer holds the new kid (as a previous key) before any peer makes it current. A peer that does not hold it cannot follow; its raft status shows "MAC fails under every held master kid".
CoreLog.rekeymoves a log to the current kid on its own, at its last index. It is for a log no other peer compares chains with (tools, a copy); the core service no longer calls it. Builds before OUT-25 called it at every start, which is why a peer re-keyed alone during a rolling restart halted with "divergent history"; its re-key point (rekeyedAt) still opens as before.
Tested: CoreLogTest (re-key in place, reopen, old kid retired after the snapshot, a forged old-kid snapshot refused, an interrupted re-key finished), CoreLogKidSwitchTest (a switch at one index whatever the batching, a crash before the header, a truncation below the switch and a crash before its header, a record under the old kid after a switch refused, a switch to an unheld kid refused, learning the kid from the leader's chain and not from another history), KidRotationSimTest (three peers rolled onto a new current kid under write load with no halt and every acknowledged write kept, then onto the new kid alone; a peer that missed the switch catching up by entries and by snapshot; an uncommitted switch undone by truncation; a divergent log and a log re-keyed alone still halt), LifecycleAtRestClusterTest (the rotation rolled one peer at a time; the kid census retires the old kid) and CoreServiceClusterTest (the KEY-CUSTODY runbook end to end with rolling restarts and writes around each: add as previous, make current, rotatemaster, snapshots, remove the old kid; a peer without the new kid is refused with the reason).
Replication (Raft)¶
- Peers:
core_peers, theregion:agent:pluginof every core peer including this one. Absent, the index primary is a cluster of one (quorum 1), which is how existing single-index deployments keep working. An index replica (index_primary=false) withoutcore_peersrefuses to run a core: as a cluster of one it would elect itself and serve an empty core, and later join the primary's cluster with a different history. Storage nodes acceptx.*from every address incore_peersandcore_index_addrs. - Every message carries a digest of the sender's
core_peers(order-free). Peers configured with different peer lists refuse each other's messages rather than count different quorums, until the log decides membership (below). - Online membership (OUT-08c).
core_peersis the configuration until the first change. A change is a raft configuration entry (entry kind 2,memb1): single-server (add a learner, promote a caught-up learner, remove a voter or a learner), one at a time (the next only once the last is committed, and only by a leader whose term's no-op committed), in effect on each peer as soon as it is appended, committed or not, and undone if a new leader truncates it. Quorums, elections, the lease and check-quorum count the configuration's voters; learners receive the log and count for nothing. A leader the voters no longer include leads until that removal commits, then steps down; a peer that is not a voter never campaigns. CoreLog notes configuration entries' indices as it verifies records at open and as it appends, so finding them never reads the log; a snapshot records the configuration in effect at its index as its leadingraftConfdelta (a snapshot that does not is not adopted), and a peer installing one takes it from there. Once a peer's log holds a configuration it sends a fixed dynamic digest and accepts any authenticated sender of the cluster (Raft's rules and leader stickiness decide what it may do); a peer still oncore_peersaccepts the dynamic digest from any sender, so a new member can lead it. An older build refuses the dynamic digest and halts on the entry kind, so a change waits until every member the leader has heard from in two election timeouts has answered FEATURES withmemb1and a majority of the new voters have (a down peer does not hold a replacement back).core.membership(system-admin) drives it: op=status, op=add and op=remove as C2 jobs (OPERATIONS §5.2). - Divergent histories halt. Every append carries the leader's chain values at
prevIndexand at its last entry. A follower whose term matches at an index but whose chain value differs holds a history written by another cluster (a peer that once ran alone, a log copied from elsewhere): it halts, naming segment, offset and index, instead of accepting entries on top of it. - Pre-vote, randomized election timeout in
[core_election_timeout_ms, 2x), heartbeatcore_heartbeat_ms. A peer that has heard from a leader within the timeout ignores vote requests. - The leader writes a proposal to its log and sends it at once, and syncs its log on a thread of its own: the
raft monitor is never held while the medium syncs, so heartbeats, acknowledgements and status go on during a
slow fsync (an ingest's writes on the same disk made one take ~3 s on the HPC cluster, 2026-10-01, and the leader, its
monitor held, sent no heartbeat, counted no acknowledgement and stepped down). Its own copy counts toward a
commit only once synced (
durable_indexin status). A follower does the same (OUT-08a): it writes the entries a leader sends unforced, answers at once with what is already on its medium, and acknowledges the rest once a sync on its sync thread covers it (one sync covers every append that arrived before it began;raft.follower_sync_acks). Its monitor is free during the sync, so heartbeats, votes and the lease keep flowing through a stall of any length, and the leader never counts an entry a follower has not forced. Unforced appends are one group commit until a force returns (each record's group-commit start is the first unforced index), so a power cut that tears an earlier one and keeps a later one, even across a segment roll, is a torn tail, not refused as corruption. A leader that cannot reach a majority within the timeout steps down. - Commit counts only entries of the current term, from a majority's fsync. A new leader serves only after its term's no-op is committed and applied.
- Non-voters (N18). A peer whose log is empty, or whose directory has no
raft-stateof its own (a wiped or replaced disk), starts as a non-voter (raft.voter: False, persisted). It grants no vote, and it says so in every answer, so the leader does not count its copy toward commit: it may have acknowledged entries its lost disk held, and an empty log would grant any candidate. It becomes a voter (persisted,raft.promotions) once an append leaves its log equal to the leader's through the leader's last index, or through a commit point of the leader's own term, and only if that append names the peer's own process incarnation (R1, raft message format 3: a peer refuses another format's messages, so every core peer is upgraded together). The leader names the incarnation it last heard from, and only while it holds its lease: an append sent to the process before a restart or a wipe (a delayed or duplicated copy that the new process's empty replay window cannot recognise) promotes nothing, and neither does a deposed leader. Until it can vouch for a non-voter, the leader sends it heartbeats but no entries: an empty peer stays empty (it may still consent) rather than hold entries it was not promoted by, which in a cluster just formed by consent would leave no peer able to elect a leader once the first one falls. An empty non-voter only consents: a consent counts toward an election that every peer joins (or the bootstrap majority above), never toward a majority.core_raft_voter_override=<its raft.state_id>makes a non-voter vote when an operator has established that nothing committed depends on what it lost. It is armed at start and applied only once no leader has been heard for two election timeouts (OUT-105: a serving leader disarms it and catches the peer up instead;raft.voter_overridereports armed, applied, ignored or forced);core_raft_voter_override_force=trueapplies it at start. - A follower whose log is behind the leader's snapshot (empty, new, wiped) is sent the snapshot. A follower that restarted (a new process incarnation) or rejects entries it had acknowledged (a truncated tail, a wiped directory) stops counting toward the quorum until it confirms them again.
- Lease. The leader serves (reads and writes) only while a majority answered a request it sent
less than ¾ of an election timeout ago. A follower refuses votes for a full election timeout after
hearing from a leader, and for one timeout after it starts, so a leader cut off by a partition or a
long pause stops serving before another can be elected. Timers, heartbeats and the lease run on
each host's monotonic clock (
System.nanoTime), so a stepped wall clock cannot stretch a lease; clock rates are assumed within 25 %. Only message freshness reads the wall clock. Only answers from peers that kept their term count toward the lease (R1): voters, and a non-voter process that granted or consented to this leadership. A wiped peer restarts at term 0 and follows any leader that reaches it, even one a newer term deposed, so its answers cannot show that no newer leader exists: a leader paused through an election and answered only by a wiped peer holds no lease. - Leadership transfer (
xfer1, Raft thesis §3.10). A leader that steps down while it can still send (check-quorum when its followers' answers are lost one way, as in the 2026-10-15 04:23 incident; removal from the voters; a held candidacy; a fenced engine) hands its leadership to its most caught-up voter at once: it picks, among the voters that vote (N18) and whose current process answered FEATURES withxfer1, the one with the highest match index (then the latest answer), steps down (lease 0), then sendsTIMEOUT_NOW(message type 16, format unchanged) to the other capable voters and last to the candidate. The candidate, if it may lead (a voter with a log, candidacy not held, chain kid known), campaigns for the next term at once: no pre-vote, no timer, its vote requests resent each heartbeat to voters that have not granted. A voter told of the hand-over does not refuse that candidate's vote request for the next term as sticky; its log comparison and its one vote per term apply as always, so a candidate whose log is short is simply not elected and safety is Raft's (at most one leader per term, checked in every simulated run). ATIMEOUT_NOWof an older term, or from a peer that is not the leader this peer follows, is ignored. A graceful stop (CoreService.stop, so every plugin stop and rolling upgrade) hands over first: the leader takes no new proposals, waits until the chosen voter holds its whole log and it is committed (at most half an election timeout; then it hands over anyway and the vote decides), steps down, sendsTIMEOUT_NOW, and waits until it hears the new leader (one election timeout in all) before its transport stops. Gate: the leader asks each voter's features once per process and keeps the answering incarnation; it sendsTIMEOUT_NOWonly to a peer whose current process (the incarnation its appends come from) advertisedxfer1, so an older build never receives the type (it would refuse it, counted inrefused_messages, not halt).core_raft_transfer=falseturns it off on a peer: it neither advertises nor sends it. Status:raft.transfers_sent,transfers_taken,transfers_declined,transfer_votes,last_transfer, andhandoffwhile one is in progress. Measured at a tenth of the standing timing (600 ms elections): leaderless after a one-way-loss check-quorum step-down 786 ms mean without transfer, 13 ms (max 34) with it; at a leader stop 868 ms without, 19 ms (max 37) with (RaftSimTest, 20 seeds each). - Destructive maintenance asks the lease. The live leader's engine checks, before each copy it
destroys (gc, withdrawal, trim, scrub, intent cleanup), that it still holds its lease; otherwise
the operation stops. A withdrawal is committed before the copies it frees are destroyed. The
residual window (one reclaim already in flight when the lease lapses) is closed by the storage
nodes: every
x.*carries the term its signer was elected leader in (RaftNode.leaderTerm, not the newer term a deposed leader may have heard of), and a node refuses a term below the highest it has seen, kept in itsx-termfile across restarts (N17). - Read barrier. A read verb (
core.head,core.get,core.list,core.benchread) first waits until every change applied before it is committed, and fails if one is not: a read never shows a change that may yet be lost. - The leader's engine applies deltas to memory before journaling (unchanged); the journal append
blocks until the batch commits,
core_commit_timeout_ms, or lost leadership. A failure fences the engine, the peer steps down, and the engine is rebuilt from snapshot plus committed log. The rebuild (a full replay) runs off the raft lock, so the peer keeps voting and acknowledging; entries committed meanwhile are replayed into the new engine before it is swapped in, the peer serves nothing until then, and sites that joined meanwhile are added to it. - Messages (
core.raft.msg) carry HMAC-SHA-384 underk_raft = HKDF(master, "gfs/core/raft-auth/v1"), the cluster id, sender and recipient, a wall-clock send time checked against a freshness window (core_raft_freshness_ms, 120 s), and a per-sender sequence number: forged, altered, misaddressed and replayed messages are refused. Hosts whose clocks differ by more than the window cannot talk;core.status→raft.stale_refusalsandraft.last_clock_skew_msshow it. - Entry payloads over 64 KiB and every snapshot chunk travel on the dataplane as FrameBus bodies,
with their SHA-384 inside the MAC'd header. An append carries at most
core_raft_batch_bytesof entries (at least one, at most 8192; a larger single entry is split across frames); snapshots move in chunks of that size. Values above the codec's 64 MiB chunk limit are clamped, with a warning. - One bulk message per follower. The leader keeps at most one append-with-entries or snapshot
chunk per follower in the transport and resends nothing to it until the transport reports that
message sent or dropped; the resend timer starts then. A message the transport has held for
20 election timeouts is presumed lost (
raft.bulk_stuck). The transport's bulk lane is also bounded by bytes (max(16 MiB, 2 x core_raft_batch_bytes + 1 MiB)per peer;raft.transport_bulk_dropped), and header-only messages (votes, heartbeats, answers) use their own lane, so a slow body transfer never delays a heartbeat. While a transfer is slow the leader sends plain heartbeats, which keep the follower following and the lease alive. - Only the leader serves
core.*. Every peer answerscore.statusandcore.metahashwith role, term, leader and commit index. A follower answers anything else with status12andcore_leader.eval/gkt.py:call_leaderfollows that redirect, one retry per redirect, bounded.
At-rest encryption (LC-ENC, OUT-07)¶
core_at_rest_encryption (default false; local to each peer) encrypts the log's files at rest:
- Epochs. An at-rest epoch has a random 32-byte file key, AES-KWP-wrapped (SP 800-38F) under
HKDF(master[kid], "gfs/core/at-rest-kek/v1")thenHKDF(·, "gfs/core/at-rest-epoch/v1", "core-log", epoch), inepoch-<n>.key(MAC'd under that kid'sk_log). The epoch rolls at every snapshot (a compaction or an installed snapshot) and at a master-key re-key;adoptrewrites every segment the compaction keeps under the new epoch (in place: temp, fsync, rename over, the replaced file overwritten through its still-open channel) and destroys every epoch key file no file, writer or new write needs.kidsInUsenames the key files' kids, so an old kid retires only once its epochs are gone. - Segments (fmt 2). The header adds the epoch and the segment's own random key, AES-KWP-wrapped under the epoch
key (bound to the segment's first index). Each record's stored payload is
iv ‖ ciphertext ‖ tag: AES-256-GCM under the segment key, the IV generated by the cryptographic module (GfsCrypto.gcmSeal), the record header (before its MAC) as AAD. The record format stays 1 and its chain MAC is over the plaintext, so an encrypting peer and a plain one have equal chain values. A segment key seals at most 2^24 records (the segment rolls). - Snapshots (fmt 2). The header adds the epoch and the file's own key (AES-KWP under the epoch key, bound to the
last index); the payload is 1 MiB plaintext chunks, each
iv ‖ ct ‖ tag(module IV, the chunk index as AAD). The length, SHA-384 and MAC are over the plaintext, so the payload a leader sends a follower is unchanged; each peer stores what it receives in its own format. - Recovery opens every fmt-2 file only with its epoch's key file; a missing key file, a key that does not unwrap, a chunk or record that does not decrypt are refusals (a record in the tail of the last segment that does not decrypt is a torn tail, as a bad MAC is).
- Off again writes new files in the clear and decrypts at the next compaction, after which the build can be downgraded. An older build refuses fmt 2 by its format number.
- Remanence: secure delete stays best effort on SSD and copy-on-write filesystems; a remanent copy of a deleted file needs its epoch key file, and a remanent copy of that needs its master kid: retiring the kid (LIFECYCLE.md, kid census) is what makes the copies unreadable.
Compaction¶
At a consistent cut the engine's snapshot deltas are written as a snapshot and the segments it
covers are deleted. Streamed (OUT-26): the leader takes its cut copy-on-write inside the consistent cut (policy,
versions and holds copied, the block table cut by pages) and streams it after the cut is released: the delta count is
declared first (a sealed cut is walked once dry to count its envelopes), then each delta and each phase's envelopes go
straight into the file through CoreLog.writeSnapshot(PayloadWriter); memory is the sealer's per-partition buffers
(core_meta_snapshot_group_bytes) and one at-rest chunk, never a list. The install reads the file twice, one delta at
a time: a first pass authenticates it (head, count, check, splice, context, MAC, the anti-rollback check) with nothing
applied; the second applies it in order, sealed envelopes opened ahead by core_meta_install_threads workers in a
bounded window, each opening used only while its partition's key is unchanged. The cut holds the engine's lock and the policy and key-ring locks (their changes
are made outside the engine's lock and journaled by hooks), and a cut is discarded if anything
reached the log meanwhile (log.cuts_discarded). A follower does not snapshot its engine under the
raft lock: it replays its snapshot and committed log into a scratch engine off the lock and
snapshots that, so it keeps answering appends and votes while it compacts. Automatic after
core_log_snapshot_bytes of entries (jittered up to +25 % per peer, so peers do not compact
together), on demand with core.snapshot (any peer compacts its own log; the request is signed for
that peer, audience = its peer_audience, so it cannot be replayed on another). core.snapshot goes
through per-verb authorization (only core.status and core.metahash do not) and is rate-limited
to one taken snapshot per core_snapshot_min_interval_ms (status 14, TOO_SOON, otherwise), because a
leader's cut stalls writers. Replay loads the newest snapshot, then the entries
after it. Measured in ReplicatedCoreTest.replayTimeOfALargeLog (one Mac, JDK 21): 2,000 entries
holding 200,000 block deltas, 36.5 MB of log, replay in 0.4-0.5 s; from a 36.3 MB snapshot,
0.35-0.55 s.
Migration from the index journal¶
When the core log is empty and this peer was the prototype index primary (index_primary=true),
the core.batch lines in gfs-index.jsonl are replayed once into an engine and written as the
log's base snapshot (index 1, term 1), and imported-index.marker records it. The import never
repeats. The index keeps the old lines and applies nothing for them.
The import refuses (and the core does not start) when the journal holds a snapshot line: the index
writes one when a replica installs the primary's state, and the core state before it then lived only
in that dump, so the journal holds a partial core. Import from the peer whose journal holds every
core.batch line, or set core_legacy_import=skip to start without importing. It also refuses when
the importing peer is not the first in core_peers.
Bootstrap rule. A peer whose log is empty never campaigns unless it is the first peer listed in
core_peers, so an empty or wiped peer cannot win against peers that hold state. List the old
index primary first when turning a single index into a replicated core. A brand-new cluster (every
peer a non-voter, below) forms once every peer is up: empty peers consent to an election every peer
joins. With core_raft_bootstrap=true on the first listed peer's first start it forms with a
majority; remove the setting once the core has formed.
Config keys¶
core_at_rest_encryption (LC-ENC, above) and core_meta_install_threads (the install's decrypt pool) are in the
README's metadata-crypto table.
| Key | Default | |
|---|---|---|
core_log_dir |
<plugin data>/core-log |
put on node-local storage (OUT-40) |
core_log_durability_attested |
false |
skip the barrier probe; an operator attestation |
core_log_snapshot_bytes |
268435456 (256 MiB) | entries between automatic snapshots |
core_peers |
this peer only (index primary); refused on an index replica | comma list of every core peer, this one included, identical on every peer |
core_cluster_id |
gfs-core |
stored in the header and in every message |
core_commit_timeout_ms |
15000 | a batch not committed by then fences the leader |
core_election_timeout_ms |
6000 | randomized to up to twice this; the leader's check-quorum window; lease = 3/4 of it |
core_heartbeat_ms |
250 | |
core_raft_batch_bytes |
4194304 (4 MiB) | bound on one append and one snapshot chunk |
core_site_probe_ms |
3000 | the leader's own liveness probe of each storage node |
core_site_probe_timeout_ms |
5000 | a site that does not answer by then is quarantined |
core_legacy_import |
auto |
skip starts without the one-time import (auto or skip) |
core_raft_freshness_ms |
120000 | largest tolerated clock skew between core hosts; older messages are refused |
core_raft_bootstrap |
false |
the first listed peer's first start forms a new core with a majority of empty peers (N18) |
core_raft_transfer |
true |
leadership transfer: a stepping-down or stopping leader hands over with TIMEOUT_NOW (xfer1); off: neither advertised nor sent |
core_raft_voter_override |
none | this peer's own raft.state_id: vote although it lost its raft state (N18, OPERATIONS §5.3); armed until no leader is heard for two election timeouts (OUT-105) |
core_raft_voter_override_force |
false | apply core_raft_voter_override at start, not armed (owner sign-off; gaiakeep op raft override --force prints it) |
core_snapshot_min_interval_ms |
60000 | least time between two on-demand snapshots taken |
core_membership_enabled |
true |
core.membership op=add and op=remove may change the configuration (OUT-08c) |
core_membership_catchup_ms |
600000 | how long an add waits for its learner to catch up before it fails, retryable |
core_identity_reanchor |
none | the journaled core identity's public key: this peer starts with that identity parked so an operator can re-anchor it (gfs#23, OPERATIONS §6.7); remove it afterwards |
Evidence¶
JUnit: CoreLogTest (torn tails, refusals, compaction, install), RaftCodecTest (forged,
altered, replayed, stale, misaddressed, oversized, unknown format), RaftSimTest (deterministic
simulation: elections, majority commit, failover, partitioned old leader, catch-up by entries and by
chunked snapshot, and randomized faults over 300 and 200 rounds checking election safety, log
matching, leader completeness, state machine safety and that every acknowledged entry reaches every
peer, with power losses in the middle of writes that tear the unforced bytes of a group commit in any
order; separate-cluster histories halting; a stepped wall clock; a skewed peer; a slow link; leadership transfer
after a one-way-loss check-quorum step-down and at a graceful stop, measured with and without, an older build never
sent TIMEOUT_NOW, and randomized faults with hand-overs, one-way losses and mixed builds),
LeadershipTransferClusterTest (three CoreService peers: a stopped leader hands over),
CrescoRaftTransportTest (lanes, byte bound, done reporting), ReplicatedCoreTest (real engines: failover byte-identical, rejoin, compaction across
restarts, fencing, legacy import on one and three peers, apply refusal, policy and key-ring changes
racing compaction, rebuild off the raft lock, lease and read barrier), CoreServiceTest (serving,
redirects, refusals), CoreServiceClusterTest (three CoreService peers over the production
transport's encoding on an in-process fabric: election, redirect, a 1.5 MB entry in frames, failover
within the randomized election timeout, rejoin, a wiped peer caught up by snapshot frames, forged,
replayed and malformed core.raft.msg). The live harness eval/core_failover_check.py (three
peers, five storage nodes) is not yet run on the fabric; its scenarios run, signed and sealed, against three real core
peers in one JVM (eval/tests/test_core_failover.py, RealClusterServer, in CI's evaltests).