diff options
| -rw-r--r-- | src/api/mod.rs | 2 | ||||
| -rw-r--r-- | src/document/replica.rs | 10 | ||||
| -rw-r--r-- | src/main.rs | 8 | ||||
| -rw-r--r-- | src/remake/api.rs (renamed from src/compaction/api.rs) | 65 | ||||
| -rw-r--r-- | src/remake/catchup.rs (renamed from src/compaction/catchup.rs) | 36 | ||||
| -rw-r--r-- | src/remake/coordinator.rs (renamed from src/compaction/coordinator.rs) | 14 | ||||
| -rw-r--r-- | src/remake/delta.rs (renamed from src/compaction/delta.rs) | 14 | ||||
| -rw-r--r-- | src/remake/local.rs (renamed from src/compaction/local.rs) | 30 | ||||
| -rw-r--r-- | src/remake/mod.rs (renamed from src/compaction/mod.rs) | 28 | ||||
| -rw-r--r-- | src/remake/quarantine.rs (renamed from src/compaction/quarantine.rs) | 12 | ||||
| -rw-r--r-- | src/remake/records.rs (renamed from src/compaction/records.rs) | 16 | ||||
| -rw-r--r-- | src/remake/snapshot.rs (renamed from src/compaction/snapshot.rs) | 61 | ||||
| -rw-r--r-- | src/remake/switch.rs (renamed from src/compaction/switch.rs) | 42 | ||||
| -rw-r--r-- | src/state.rs | 12 | ||||
| -rw-r--r-- | src/swarm/bootstrap.rs | 2 | ||||
| -rw-r--r-- | src/swarm/peer.rs | 2 |
16 files changed, 179 insertions, 175 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index ac6f2d0..ea3ad41 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -37,7 +37,7 @@ pub(crate) fn router(state: AppState, auth_state: AuthState, debug_api: bool) -> .route("/stream/{*key}", get(store::stream_store_entry)) .route("/peers", get(peers::list_peers)) .route("/peers/{peer}", delete(peers::delete_peer)) - .merge(crate::compaction::router::<AppState>()); + .merge(crate::remake::router::<AppState>()); if debug_api { warn!("Debug API enabled"); diff --git a/src/document/replica.rs b/src/document/replica.rs index 4579029..ff3af4c 100644 --- a/src/document/replica.rs +++ b/src/document/replica.rs @@ -1,7 +1,7 @@ //! The local replica: the Automerge document together with the epoch it belongs to. //! -//! Compaction (later steps) replaces the document with a snapshot whose history is -//! unrelated to the old one. Syncing two unrelated histories would make Automerge merge +//! A remake replaces the document with a new one (for compaction: a snapshot) whose +//! history is unrelated to the old one. Syncing two unrelated histories would make Automerge merge //! them, turning every key into a conflict with its own copy, so nodes on different //! epochs must never sync. Keeping the epoch and the document under one lock makes it //! impossible to label a sync message from one document with the other's epoch. @@ -13,10 +13,10 @@ use automerge::AutoCommit; /// One generation of the cluster document. /// -/// * `generation` orders epochs: every compaction increments it, so a node can tell +/// * `generation` orders epochs: every remake increments it, so a node can tell /// whether a peer is behind or ahead. /// * `id` is the hash of the epoch's snapshot change. It tells two epochs of the same -/// generation apart, which only happens if two compactions committed concurrently. +/// generation apart, which only happens if two remakes committed concurrently. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Epoch { pub generation: u64, @@ -31,7 +31,7 @@ pub enum EpochRelation { PeerBehind, /// The peer is on a newer generation; we have to migrate. PeerAhead, - /// Same generation, different snapshot: competing compactions. Never sync. + /// Same generation, different snapshot: competing remakes. Never sync. Diverged, } diff --git a/src/main.rs b/src/main.rs index 1793523..ab91490 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,8 +19,8 @@ use tracing::{debug, error, info, trace}; use url::Url; mod api; -mod compaction; mod document; +mod remake; mod state; mod swarm; mod transport; @@ -155,7 +155,7 @@ async fn main() -> Result<()> { initial_sync_done: Arc::new(AtomicBool::new(false)), change_events, timing: Timing::from_keepalive(Duration::from_secs(args.keepalive_secs)), - compaction: Arc::new(compaction::Compaction::default()), + remake: Arc::new(remake::RemakeState::default()), stale: Default::default(), }; @@ -189,8 +189,8 @@ async fn main() -> Result<()> { // Switches to committed epochs and catches up with newer ones. Starts before // bootstrapping: a node joining a compacted cluster can only finish its initial sync // after fetching the current epoch from a peer. - tokio::spawn(compaction::run_worker(state.clone())); - trace!("Compaction worker task spawned"); + tokio::spawn(remake::run_worker(state.clone())); + trace!("Remake worker task spawned"); bootstrap::bootstrap(&state, &seeds) .await diff --git a/src/compaction/api.rs b/src/remake/api.rs index 9fae7c9..b9c0625 100644 --- a/src/compaction/api.rs +++ b/src/remake/api.rs @@ -1,17 +1,18 @@ -//! `/admin/compaction(s)` handlers. +//! `/admin/remakes` handlers. //! -//! * `POST /admin/compaction` proposes a compaction of the current epoch. The receiving +//! * `POST /admin/remakes` proposes a remake of the current epoch (`POST +//! /admin/compaction` is a shortcut, kept from before). The receiving //! node becomes its coordinator. `202` with the report, or with `?wait=true` the final //! report once decided. A dropped request does not abort anything. `?force=true` //! accepts a `settleSecs` below the dormancy threshold (tests, single-site clusters). -//! * `GET /admin/compactions` lists all compactions. -//! * `GET /admin/compactions/{id}` reports one; `?wait=true` long-polls until decided +//! * `GET /admin/remakes` lists all remakes. +//! * `GET /admin/remakes/{id}` reports one; `?wait=true` long-polls until decided //! (bounded by the proposal's own expiry). //! * `GET /admin/epochs` shows the local epoch, the archived previous ones and whether //! the node is catching up with a newer epoch from a peer. -//! * `DELETE /admin/compactions/{id}` asks the coordinator to abort. Only possible while +//! * `DELETE /admin/remakes/{id}` asks the coordinator to abort. Only possible while //! proposed; `409` once committed (a switch can't be undone) or already over. -//! `DELETE /admin/compaction` does the same for the compaction of the local epoch that +//! `DELETE /admin/remakes` does the same for the remake of the local epoch that //! is in progress, without having to know its ID. //! //! Request body for POST (all optional): @@ -72,14 +73,14 @@ struct Report { phase: Phase, #[serde(flatten)] record: Record, - /// What happened to this compaction on the node answering the request. + /// What happened to this remake on the node answering the request. #[serde(skip_serializing_if = "Option::is_none")] local: Option<LocalStatus>, } #[derive(Debug, thiserror::Error)] enum StartError { - #[error("compaction {0} is already open for this epoch")] + #[error("remake {0} is already open for this epoch")] Open(String), #[error(transparent)] Store(#[from] StoreError), @@ -104,7 +105,7 @@ fn grace<H: Host>(host: &H) -> u64 { fn report<H: Host>(host: &H, id: String, record: Record) -> Report { Report { phase: record.phase(unix_millis(), grace(host)), - local: host.compaction().status(&id), + local: host.remake().status(&id), id, record, } @@ -118,7 +119,7 @@ async fn load_report<H: Host>(host: &H, id: &str) -> Option<Report> { Some(report(host, id.to_owned(), record)) } -/// Waits until the compaction leaves `proposed`. Bounded by the proposal's own expiry, +/// Waits until the remake leaves `proposed`. Bounded by the proposal's own expiry, /// at which point it is reported as `expired`. async fn wait_until_final<H: Host>(host: &H, id: &str) -> Option<Report> { // A record that was readable may be briefly unreadable or absent while concurrent @@ -131,7 +132,7 @@ async fn wait_until_final<H: Host>(host: &H, id: &str) -> Option<Report> { loop { let Some(report) = load_report(host, id).await else { misses += 1; - warn!(compaction = %id, seen, misses, "Compaction record not readable while waiting"); + warn!(remake = %id, seen, misses, "Remake record not readable while waiting"); if !seen || misses >= MAX_MISSES { return None; } @@ -209,7 +210,7 @@ pub(crate) async fn start<H: Host>( if !dormant.is_empty() { return error_response( StatusCode::CONFLICT, - "dormant peers block compaction; forget them first (DELETE /peers/{pk})", + "dormant peers block remake; forget them first (DELETE /peers/{pk})", Some(json!({ "dormantPeers": dormant })), ); } @@ -261,12 +262,12 @@ pub(crate) async fn start<H: Host>( Err(StartError::Open(open)) => { return error_response( StatusCode::CONFLICT, - "another compaction of this epoch is still open", - Some(json!({ "compaction": open })), + "another remake of this epoch is still open", + Some(json!({ "remake": open })), ); } Err(StartError::Store(error)) => { - error!(error = %format!("{error:#}"), "Failed to write compaction proposal"); + error!(error = %format!("{error:#}"), "Failed to write remake proposal"); return error_response( StatusCode::INTERNAL_SERVER_ERROR, "failed to write the proposal", @@ -276,19 +277,19 @@ pub(crate) async fn start<H: Host>( }; info!( - compaction = %id, + remake = %id, settle_secs = settle.as_secs(), - "Compaction proposed" + "Remake proposed" ); if query.wait { return match wait_until_final(&host, &id).await { Some(report) => Json(report).into_response(), - None => error_response(StatusCode::NOT_FOUND, "compaction record vanished", None), + None => error_response(StatusCode::NOT_FOUND, "remake record vanished", None), }; } - let location = format!("/admin/compactions/{id}"); + let location = format!("/admin/remakes/{id}"); ( StatusCode::ACCEPTED, [(LOCATION, location)], @@ -356,11 +357,11 @@ pub(crate) async fn get<H: Host>( }; match found { Some(report) => Json(report).into_response(), - None => error_response(StatusCode::NOT_FOUND, "no such compaction", None), + None => error_response(StatusCode::NOT_FOUND, "no such remake", None), } } -/// `DELETE /admin/compactions/{id}`. +/// `DELETE /admin/remakes/{id}`. pub(crate) async fn abort<H: Host>( State(host): State<H>, Path(id): Path<String>, @@ -369,7 +370,7 @@ pub(crate) async fn abort<H: Host>( abort_by_id(host, id, query.wait).await } -/// `DELETE /admin/compaction`: aborts whichever compaction of the local epoch is still +/// `DELETE /admin/remakes`: aborts whichever remake of the local epoch is still /// proposed. There is at most one, unless two coordinators raced (then both abort anyway /// and this picks the oldest). pub(crate) async fn abort_current<H: Host>( @@ -396,7 +397,7 @@ pub(crate) async fn abort_current<H: Host>( Some(id) => abort_by_id(host, id, query.wait).await, None => error_response( StatusCode::NOT_FOUND, - "no compaction of the local epoch is in progress", + "no remake of the local epoch is in progress", None, ), } @@ -404,7 +405,7 @@ pub(crate) async fn abort_current<H: Host>( async fn abort_by_id<H: Host>(host: H, id: String, wait: bool) -> Response { let Some(current) = load_report(&host, &id).await else { - return error_response(StatusCode::NOT_FOUND, "no such compaction", None); + return error_response(StatusCode::NOT_FOUND, "no such remake", None); }; match current.phase { @@ -413,14 +414,14 @@ async fn abort_by_id<H: Host>(host: H, id: String, wait: bool) -> Response { return error_response( StatusCode::CONFLICT, "already committed; the switch cannot be undone", - Some(json!({ "compaction": current })), + Some(json!({ "remake": current })), ) } Phase::Aborted | Phase::Expired => { return error_response( StatusCode::CONFLICT, "already over", - Some(json!({ "compaction": current })), + Some(json!({ "remake": current })), ) } } @@ -437,14 +438,14 @@ async fn abort_by_id<H: Host>(host: H, id: String, wait: bool) -> Response { .commit_doc(move |doc| records::write_abort_request(doc, &record_id, &request)) .await { - error!(compaction = %id, error = %format!("{error:#}"), "Failed to write abort request"); + error!(remake = %id, error = %format!("{error:#}"), "Failed to write abort request"); return error_response( StatusCode::INTERNAL_SERVER_ERROR, "failed to record the abort request", None, ); } - info!(compaction = %id, "Abort requested"); + info!(remake = %id, "Abort requested"); } let report = if wait { @@ -455,7 +456,7 @@ async fn abort_by_id<H: Host>(host: H, id: String, wait: bool) -> Response { match report { Some(report) if report.phase.is_final() => Json(report).into_response(), Some(report) => (StatusCode::ACCEPTED, Json(report)).into_response(), - None => error_response(StatusCode::NOT_FOUND, "no such compaction", None), + None => error_response(StatusCode::NOT_FOUND, "no such remake", None), } } @@ -465,9 +466,9 @@ pub(crate) async fn epochs<H: Host>(State(host): State<H>) -> Response { let current = EpochRecord::from(host.current_epoch()); Json(json!({ "current": current, - "archived": host.compaction().archived_epochs(), - "catchUp": host.compaction().catch_up_report(), - "quarantine": host.compaction().list_quarantine(|entry| entry.id.clone()), + "archived": host.remake().archived_epochs(), + "catchUp": host.remake().catch_up_report(), + "quarantine": host.remake().list_quarantine(|entry| entry.id.clone()), })) .into_response() } diff --git a/src/compaction/catchup.rs b/src/remake/catchup.rs index 5085669..22aa0f8 100644 --- a/src/compaction/catchup.rs +++ b/src/remake/catchup.rs @@ -47,12 +47,12 @@ const MIN_REJOIN_TIMEOUT: Duration = Duration::from_secs(60); /// Called by the switch worker when there is nothing to switch to locally, and on every /// tick while a rejoin is in progress. pub(super) async fn check<H: Host>(host: &H) { - if let Some((target, started)) = host.compaction().rejoin_progress() { + if let Some((target, started)) = host.remake().rejoin_progress() { let timeout = host.dormant_after().max(MIN_REJOIN_TIMEOUT); if host.initial_sync_done() { finish(host).await; } else if started.elapsed() > timeout { - if let Some(rejoin) = host.compaction().take_rejoin() { + if let Some(rejoin) = host.remake().take_rejoin() { let reason = format!( "no peer on {target} completed a sync within {}s", timeout.as_secs() @@ -75,8 +75,8 @@ pub(super) async fn check<H: Host>(host: &H) { ahead.sort_by(|a, b| a.0.cmp(&b.0)); let Some(generation) = ahead.iter().map(|(_, epoch)| epoch.generation).max() else { - if host.compaction().catch_up().is_some() { - host.compaction().set_catch_up(None); + if host.remake().catch_up().is_some() { + host.remake().set_catch_up(None); } return; }; @@ -97,7 +97,7 @@ pub(super) async fn check<H: Host>(host: &H) { // Back off between attempts for the same target. let previous = host - .compaction() + .remake() .catch_up() .filter(|catch_up| catch_up.target == target); if previous @@ -112,7 +112,7 @@ pub(super) async fn check<H: Host>(host: &H) { } async fn start<H: Host>(host: &H, local: Epoch, target: Epoch, attempts: u32) { - host.compaction().set_catch_up(Some(CatchUp { + host.remake().set_catch_up(Some(CatchUp { target, attempts, last_attempt: Instant::now(), @@ -139,7 +139,7 @@ async fn start<H: Host>(host: &H, local: Epoch, target: Epoch, attempts: u32) { host.reset_initial_sync(); info!(from = %local, to = %target, attempt = attempts, "Rejoining newer epoch"); - host.compaction().start_rejoin(Rejoin { + host.remake().start_rejoin(Rejoin { previous: local, target, old, @@ -172,7 +172,7 @@ enum Prepared { } async fn finish<H: Host>(host: &H) { - let Some(rejoin) = host.compaction().take_rejoin() else { + let Some(rejoin) = host.remake().take_rejoin() else { return; }; let Rejoin { @@ -197,7 +197,7 @@ async fn finish<H: Host>(host: &H) { }) => (old, metadata, plan, own_missing, had_history), Ok(Prepared::NotYet(old)) => { host.reset_initial_sync(); - host.compaction().start_rejoin(Rejoin { + host.remake().start_rejoin(Rejoin { previous, target, old, @@ -249,7 +249,7 @@ async fn finish<H: Host>(host: &H) { Plan::Quarantine { paths, reason } => { let id = Uuid::new_v4().to_string(); warn!(quarantine = %id, epoch = %previous, paths = paths.len(), reason = %reason, "Quarantined local changes"); - host.compaction().add_quarantine(Quarantined { + host.remake().add_quarantine(Quarantined { id: id.clone(), epoch: previous, target, @@ -264,7 +264,7 @@ async fn finish<H: Host>(host: &H) { } if let (true, true, Some(document)) = (had_history, keep_archive, old) { - host.compaction().archive( + host.remake().archive( previous, document, metadata.policy.archive_retention.clone(), @@ -272,8 +272,8 @@ async fn finish<H: Host>(host: &H) { ); } - host.compaction().set_status( - &metadata.compaction_id, + host.remake().set_status( + &metadata.remake_id, LocalStatus { switched_at: Some(unix_millis()), replayed_paths: Some(stats.replayed), @@ -282,7 +282,7 @@ async fn finish<H: Host>(host: &H) { error: None, }, ); - host.compaction().set_catch_up(None); + host.remake().set_catch_up(None); if was_ready { host.resume().await; } @@ -405,11 +405,11 @@ async fn abandon<H: Host>(host: &H, rejoin: Rejoin, reason: String) { host.set_stale_view(None); let attempts = host - .compaction() + .remake() .catch_up() .filter(|catch_up| catch_up.target == target) .map_or(1, |catch_up| catch_up.attempts); - host.compaction().set_catch_up(Some(CatchUp { + host.remake().set_catch_up(Some(CatchUp { target, attempts, last_attempt: Instant::now(), @@ -420,7 +420,7 @@ async fn abandon<H: Host>(host: &H, rejoin: Rejoin, reason: String) { #[cfg(test)] mod tests { use super::*; - use crate::compaction::snapshot::{build, SnapshotParams}; + use crate::remake::snapshot::{build, SnapshotParams}; use automerge::{transaction::Transactable, ActorId, ROOT}; use serde_json::{json, Value as JsonValue}; @@ -435,7 +435,7 @@ mod tests { let snapshot = build( &mut old, &SnapshotParams { - compaction_id: "c-1", + remake_id: "c-1", previous: Epoch::GENESIS, base_heads: &base, policy, diff --git a/src/compaction/coordinator.rs b/src/remake/coordinator.rs index 8ce3928..b84b0a9 100644 --- a/src/compaction/coordinator.rs +++ b/src/remake/coordinator.rs @@ -28,7 +28,7 @@ use crate::document::store::StoreError; const TICK: Duration = Duration::from_secs(1); pub(crate) async fn run<H: Host>(host: H, id: String) { - info!(compaction = %id, "Coordinating compaction"); + info!(remake = %id, "Coordinating remake"); loop { tokio::time::sleep(TICK).await; @@ -37,7 +37,7 @@ pub(crate) async fn run<H: Host>(host: H, id: String) { Ok(true) => return, Ok(false) => {} Err(error) => { - warn!(compaction = %id, error = %format!("{error:#}"), "Coordinator step failed; retrying"); + warn!(remake = %id, error = %format!("{error:#}"), "Coordinator step failed; retrying"); } } } @@ -51,7 +51,7 @@ async fn step<H: Host>(host: &H, id: &str) -> Result<bool, StoreError> { .await; let Some(record) = record else { - warn!(compaction = %id, "Compaction record vanished; stopping coordinator"); + warn!(remake = %id, "Remake record vanished; stopping coordinator"); return Ok(true); }; if record.decision.is_some() { @@ -97,7 +97,7 @@ async fn abort_reason<H: Host>( && candidate.proposal.from_epoch == proposal.from_epoch && candidate.phase(now, grace).is_open() }) { - return Some(format!("competing compaction {other} for the same epoch")); + return Some(format!("competing remake {other} for the same epoch")); } // Peers forgotten in the meantime are ignored: an operator decided they are gone. @@ -157,14 +157,14 @@ async fn commit<H: Host>(host: &H, id: &str, record: &Record) -> Result<(), Stor write_decision(host, id, decision).await?; // The local switch reuses it instead of building again. - host.compaction().store_prebuilt(id, snapshot); + host.remake().store_prebuilt(id, snapshot); - info!(compaction = %id, to_epoch = %to_epoch, snapshot_bytes, "Compaction committed"); + info!(remake = %id, to_epoch = %to_epoch, snapshot_bytes, "Remake committed"); Ok(()) } async fn decide_abort<H: Host>(host: &H, id: &str, reason: String) -> Result<(), StoreError> { - warn!(compaction = %id, reason = %reason, "Compaction aborted"); + warn!(remake = %id, reason = %reason, "Remake aborted"); write_decision( host, id, diff --git a/src/compaction/delta.rs b/src/remake/delta.rs index cf556b2..b07c862 100644 --- a/src/compaction/delta.rs +++ b/src/remake/delta.rs @@ -10,11 +10,11 @@ //! * Map entries are replayed per key, so changes to sibling keys made on different //! nodes don't overwrite each other. //! * Lists and text are replayed whole: concurrent edits of the same list or text on two -//! nodes around a compaction resolve as last-writer-wins. +//! nodes around a remake resolve as last-writer-wins. //! * Edits inside a map that lost a merge conflict produce no patch in Automerge and are //! not replayed. Such maps only exist briefly between concurrent creations. //! -//! The protocol's own metadata (`_continuum/compactions`, `_continuum/epochs`) is never +//! The protocol's own metadata (`_continuum/remakes`, `_continuum/epochs`) is never //! replayed: the snapshot owns it. use std::collections::{BTreeMap, HashSet}; @@ -22,7 +22,7 @@ use std::collections::{BTreeMap, HashSet}; use automerge::{ActorId, AutoCommit, ChangeHash, PatchAction, Prop}; use tracing::warn; -use super::snapshot::{COMPACTIONS, EPOCHS, META_ROOT}; +use super::snapshot::{EPOCHS, META_ROOT, REMAKES}; use crate::document::store::{self, StoreError}; #[derive(Debug, Default, Clone, Copy)] @@ -96,7 +96,7 @@ fn log_failure(path: &[String], error: &StoreError) { } fn is_protocol_path(path: &[String]) -> bool { - matches!(path, [root, part, ..] if root == META_ROOT && (part == COMPACTIONS || part == EPOCHS)) + matches!(path, [root, part, ..] if root == META_ROOT && (part == REMAKES || part == EPOCHS)) } /// True for the root and for `_continuum` itself. @@ -362,7 +362,7 @@ mod tests { .unwrap(); new.commit(); - // The old epoch replaces `_continuum` wholesale and writes a compaction record. + // The old epoch replaces `_continuum` wholesale and writes a remake record. store::write_path( &mut old, &[META_ROOT.into()], @@ -371,7 +371,7 @@ mod tests { .unwrap(); store::write_path( &mut old, - &[META_ROOT.into(), COMPACTIONS.into(), "c".into()], + &[META_ROOT.into(), REMAKES.into(), "c".into()], &json!({ "x": 1 }), ) .unwrap(); @@ -382,6 +382,6 @@ mod tests { let meta = &view(&new)[META_ROOT]; assert_eq!(meta[EPOCHS]["1"]["generation"], json!(1)); assert_eq!(meta["forgotten"]["pk"], json!(1)); - assert!(meta.get(COMPACTIONS).is_none()); + assert!(meta.get(REMAKES).is_none()); } } diff --git a/src/compaction/local.rs b/src/remake/local.rs index ec40b06..35a4ff1 100644 --- a/src/compaction/local.rs +++ b/src/remake/local.rs @@ -1,4 +1,4 @@ -//! Per-node, in-memory compaction state: snapshots built ahead of the switch, switch +//! Per-node, in-memory remake state: snapshots built ahead of the switch, switch //! progress for reports, a rejoin in progress, quarantined changes, and archived //! documents of previous epochs. //! @@ -17,7 +17,7 @@ use super::snapshot::Snapshot; use crate::document::replica::Epoch; #[derive(Default)] -pub(crate) struct Compaction { +pub(crate) struct RemakeState { // A std mutex: every access is short and never spans an await. inner: Mutex<Inner>, } @@ -26,7 +26,7 @@ pub(crate) struct Compaction { struct Inner { /// Built by the coordinator at commit time; consumed by the local switch. prebuilt: HashMap<String, Snapshot>, - /// When this node first saw a compaction committed. + /// When this node first saw a remake committed. first_seen: HashMap<String, Instant>, status: HashMap<String, LocalStatus>, archive: Vec<Archived>, @@ -82,7 +82,7 @@ pub(crate) struct CatchUpReport { pub(crate) error: Option<String>, } -/// What happened to a compaction on this node. +/// What happened to a remake on this node. #[derive(Clone, Debug, Default, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct LocalStatus { @@ -118,7 +118,7 @@ pub(crate) struct ArchivedEpoch { pub(crate) archived_at: u64, } -impl Compaction { +impl RemakeState { fn lock(&self) -> MutexGuard<'_, Inner> { self.inner .lock() @@ -133,7 +133,7 @@ impl Compaction { self.lock().prebuilt.remove(id) } - /// When this node first saw the compaction committed (now, on the first call). + /// When this node first saw the remake committed (now, on the first call). pub(crate) fn first_seen(&self, id: &str) -> Instant { *self .lock() @@ -273,14 +273,14 @@ mod tests { #[test] fn archive_retention() { - let compaction = Compaction::default(); + let remake = RemakeState::default(); let epoch = |generation| Epoch { generation, id: [generation as u8; 32], }; // Kept by epoch count only. - compaction.archive( + remake.archive( epoch(0), AutoCommit::new(), Retention { @@ -289,12 +289,12 @@ mod tests { }, 1, ); - assert_eq!(compaction.archived_epochs().len(), 1); - compaction.prune_archive(2); - assert!(compaction.archived_epochs().is_empty()); + assert_eq!(remake.archived_epochs().len(), 1); + remake.prune_archive(2); + assert!(remake.archived_epochs().is_empty()); // Kept by time while the epoch count has passed. - compaction.archive( + remake.archive( epoch(1), AutoCommit::new(), Retention { @@ -303,10 +303,10 @@ mod tests { }, 5, ); - assert_eq!(compaction.archived_epochs().len(), 1); + assert_eq!(remake.archived_epochs().len(), 1); // No limits at all: not kept. - compaction.archive( + remake.archive( epoch(2), AutoCommit::new(), Retention { @@ -315,6 +315,6 @@ mod tests { }, 3, ); - assert_eq!(compaction.archived_epochs().len(), 1); + assert_eq!(remake.archived_epochs().len(), 1); } } diff --git a/src/compaction/mod.rs b/src/remake/mod.rs index 41f011f..8cca57c 100644 --- a/src/compaction/mod.rs +++ b/src/remake/mod.rs @@ -1,7 +1,8 @@ -//! Compaction: replacing the document with a snapshot in a new epoch. +//! Remakes: coordinated moves of the whole cluster to a new document, a new epoch. +//! Compaction is one: the new document has the old one's content without its history. //! -//! * `snapshot`: deterministic snapshot build (step 3). -//! * `records`: the replicated compaction records under `_continuum/compactions/<id>`. +//! * `snapshot`: deterministic build of the next epoch's initial document. +//! * `records`: the replicated remake records under `_continuum/remakes/<id>`. //! * `coordinator`: proposal, settle period and decision on the node that was asked. //! * `switch`: switching this node to a committed epoch (worker). //! * `catchup`: rejoining a newer epoch from peers (late, new or failed nodes), then @@ -9,7 +10,7 @@ //! * `quarantine`: inspecting, replaying or discarding quarantined changes. //! * `delta`: replaying this node's changes since the base into the new epoch. //! * `local`: per-node in-memory state (prebuilt snapshots, switch status, archive). -//! * `api`: `/admin/compaction(s)` handlers. +//! * `api`: `/admin/remakes` handlers. //! //! The rest of the node is reached exclusively through [`Host`], so this module never //! depends on `AppState`, peers or sync internals. @@ -24,7 +25,7 @@ mod records; pub(crate) mod snapshot; mod switch; -pub(crate) use local::Compaction; +pub(crate) use local::RemakeState; pub(crate) use switch::run_worker; use std::future::Future; @@ -41,12 +42,12 @@ use tokio::sync::broadcast; use crate::document::replica::Epoch; use crate::swarm::sync::ChangeSet; -/// What compaction needs from the node. Implemented by `AppState`. +/// What remake needs from the node. Implemented by `AppState`. /// /// Used generically (`H: Host`), never as `dyn Host`, so the generic methods are fine. pub(crate) trait Host: Clone + Send + Sync + 'static { - /// This node's in-memory compaction state. - fn compaction(&self) -> &Compaction; + /// This node's in-memory remake state. + fn remake(&self) -> &RemakeState; /// This node's peer ID. fn node_id(&self) -> String; @@ -129,12 +130,15 @@ pub(crate) struct PeerSummary { pub(crate) fn router<H: Host>() -> Router<H> { Router::new() .route( - "/admin/compaction", - post(api::start::<H>).delete(api::abort_current::<H>), + "/admin/remakes", + get(api::list::<H>) + .post(api::start::<H>) + .delete(api::abort_current::<H>), ) - .route("/admin/compactions", get(api::list::<H>)) + // The original endpoint, kept as a shortcut for a compacting remake. + .route("/admin/compaction", post(api::start::<H>)) .route( - "/admin/compactions/{id}", + "/admin/remakes/{id}", get(api::get::<H>).delete(api::abort::<H>), ) .route("/admin/epochs", get(api::epochs::<H>)) diff --git a/src/compaction/quarantine.rs b/src/remake/quarantine.rs index ad3c616..5643549 100644 --- a/src/compaction/quarantine.rs +++ b/src/remake/quarantine.rs @@ -59,11 +59,11 @@ fn not_found() -> Response { } pub(crate) async fn list<H: Host>(State(host): State<H>) -> Response { - Json(host.compaction().list_quarantine(summary)).into_response() + Json(host.remake().list_quarantine(summary)).into_response() } pub(crate) async fn get<H: Host>(State(host): State<H>, Path(id): Path<String>) -> Response { - let Some((summary, document, paths)) = host.compaction().with_quarantined(&id, |entry| { + let Some((summary, document, paths)) = host.remake().with_quarantined(&id, |entry| { (summary(entry), entry.document.clone(), entry.paths.clone()) }) else { return not_found(); @@ -93,7 +93,7 @@ pub(crate) async fn get<H: Host>(State(host): State<H>, Path(id): Path<String>) pub(crate) async fn document<H: Host>(State(host): State<H>, Path(id): Path<String>) -> Response { let Some(mut document) = host - .compaction() + .remake() .with_quarantined(&id, |entry| entry.document.clone()) else { return not_found(); @@ -123,7 +123,7 @@ pub(crate) async fn replay<H: Host>(State(host): State<H>, Path(id): Path<String if !host.is_node_ready().await { return error_response(StatusCode::SERVICE_UNAVAILABLE, "node is not ready", None); } - let Some(entry) = host.compaction().take_quarantined(&id) else { + let Some(entry) = host.remake().take_quarantined(&id) else { return not_found(); }; @@ -137,7 +137,7 @@ pub(crate) async fn replay<H: Host>(State(host): State<H>, Path(id): Path<String if stats.failed > 0 && stats.replayed == 0 { // Nothing went in; keep the entry for another look. - host.compaction().restore_quarantined(entry); + host.remake().restore_quarantined(entry); return error_response( StatusCode::CONFLICT, "none of the writes could be replayed", @@ -150,7 +150,7 @@ pub(crate) async fn replay<H: Host>(State(host): State<H>, Path(id): Path<String } pub(crate) async fn discard<H: Host>(State(host): State<H>, Path(id): Path<String>) -> Response { - match host.compaction().take_quarantined(&id) { + match host.remake().take_quarantined(&id) { Some(_) => { info!(quarantine = %id, "Discarded quarantined changes"); StatusCode::NO_CONTENT.into_response() diff --git a/src/compaction/records.rs b/src/remake/records.rs index f533ee2..219fc9b 100644 --- a/src/compaction/records.rs +++ b/src/remake/records.rs @@ -1,4 +1,4 @@ -//! Compaction records: `_continuum/compactions/<id>` in the replicated document. +//! Remake records: `_continuum/remakes/<id>` in the replicated document. //! //! A record has up to three parts, each with a single kind of writer, so concurrent //! writes never fight over the same key: @@ -19,7 +19,7 @@ use automerge::{AutoCommit, ChangeHash}; use serde::{Deserialize, Serialize}; use tracing::warn; -use super::snapshot::{SnapshotRequest, COMPACTIONS, META_ROOT}; +use super::snapshot::{SnapshotRequest, META_ROOT, REMAKES}; use crate::document::replica::Epoch; use crate::document::store::{self, StoreError}; @@ -187,7 +187,7 @@ impl Phase { } impl Record { - /// The epoch this compaction committed to, if committed and readable. + /// The epoch this remake committed to, if committed and readable. pub(crate) fn committed_epoch(&self) -> Option<Epoch> { match &self.decision { Some(Decision::Committed { to_epoch, .. }) => to_epoch.to_epoch(), @@ -195,11 +195,11 @@ impl Record { } } - /// Everything needed to (re)build this compaction's snapshot; None if the proposal + /// Everything needed to (re)build this remake's snapshot; None if the proposal /// is unreadable. pub(crate) fn snapshot_request(&self, id: &str) -> Option<SnapshotRequest> { Some(SnapshotRequest { - compaction_id: id.to_owned(), + remake_id: id.to_owned(), previous: self.proposal.from_epoch.to_epoch()?, base_heads: self.proposal.base_change_hashes()?, policy: serde_json::to_value(&self.proposal.policy).ok()?, @@ -220,7 +220,7 @@ impl Record { // --- document access --------------------------------------------------------- fn records_dir() -> Vec<String> { - vec![META_ROOT.to_owned(), COMPACTIONS.to_owned()] + vec![META_ROOT.to_owned(), REMAKES.to_owned()] } pub(crate) fn record_path(id: &str, part: &str) -> Vec<String> { @@ -238,14 +238,14 @@ pub(crate) fn load(doc: &AutoCommit, id: &str) -> Option<Record> { Ok(Some(value)) => value, Ok(None) => return None, Err(error) => { - warn!(compaction = %id, error = %error, "Failed to read compaction record"); + warn!(remake = %id, error = %error, "Failed to read remake record"); return None; } }; match serde_json::from_value(value) { Ok(record) => Some(record), Err(error) => { - warn!(compaction = %id, error = %error, "Skipping unreadable compaction record"); + warn!(remake = %id, error = %error, "Skipping unreadable remake record"); None } } diff --git a/src/compaction/snapshot.rs b/src/remake/snapshot.rs index 0d49d48..da23fdf 100644 --- a/src/compaction/snapshot.rs +++ b/src/remake/snapshot.rs @@ -1,6 +1,6 @@ //! Deterministic snapshot of the document at agreed base heads. //! -//! Every node builds the snapshot of a committed compaction on its own. For that to +//! Every node builds the snapshot of a committed remake on its own. For that to //! work, the snapshot must be byte-identical everywhere: same operations, same order, //! same actor, same timestamp. Its change hash then identifies the new epoch, and a //! node whose build produces a different hash knows it must fetch the snapshot instead. @@ -8,7 +8,7 @@ //! Determinism rests on: //! * the content: the merged view at the base heads (`store::materialize_into`), which //! depends only on the base, not on how a node's history got there; -//! * a fixed actor derived from the compaction ID and generation; +//! * a fixed actor derived from the remake ID and generation; //! * commit time 0 and no message; //! * canonical (sorted) JSON for the metadata; //! * the same Automerge version on every node (encodings may differ across versions). @@ -33,20 +33,20 @@ use crate::document::replica::Epoch; use crate::document::store::{self, StoreError}; pub(crate) const META_ROOT: &str = "_continuum"; -pub(crate) const COMPACTIONS: &str = "compactions"; +pub(crate) const REMAKES: &str = "remakes"; pub(crate) const EPOCHS: &str = "epochs"; /// Everything that determines a snapshot besides the base content. pub(crate) struct SnapshotParams<'a> { - /// ID of the committed compaction; seeds the snapshot actor. - pub(crate) compaction_id: &'a str, + /// ID of the committed remake; seeds the snapshot actor. + pub(crate) remake_id: &'a str, /// The epoch being compacted. The snapshot starts generation `previous + 1`. pub(crate) previous: Epoch, /// Heads of the base. Every node must hold all of these changes. pub(crate) base_heads: &'a [ChangeHash], - /// Retention policy from the compaction request; stored verbatim (canonicalized). + /// Retention policy from the remake request; stored verbatim (canonicalized). pub(crate) policy: &'a JsonValue, - /// When the compaction was proposed (unix ms); recorded for replay windows. + /// When the remake was proposed (unix ms); recorded for replay windows. pub(crate) proposed_at: u64, } @@ -77,7 +77,7 @@ pub(crate) enum SnapshotError { /// Owned version of [`SnapshotParams`], for moving into a blocking task. #[derive(Clone, Debug)] pub(crate) struct SnapshotRequest { - pub(crate) compaction_id: String, + pub(crate) remake_id: String, pub(crate) previous: Epoch, pub(crate) base_heads: Vec<ChangeHash>, pub(crate) policy: JsonValue, @@ -87,7 +87,7 @@ pub(crate) struct SnapshotRequest { impl SnapshotRequest { pub(crate) fn params(&self) -> SnapshotParams<'_> { SnapshotParams { - compaction_id: &self.compaction_id, + remake_id: &self.remake_id, previous: self.previous, base_heads: &self.base_heads, policy: &self.policy, @@ -114,12 +114,12 @@ pub(crate) async fn build_detached( /// The protocol's view of an epoch, as recorded in its snapshot. #[derive(Clone, Debug)] pub(crate) struct EpochMetadata { - pub(crate) compaction_id: String, + pub(crate) remake_id: String, pub(crate) previous: Epoch, /// Highest sequence number per actor of `previous` contained in the snapshot. pub(crate) clock: BTreeMap<String, u64>, pub(crate) policy: Policy, - /// When the compaction was proposed (unix ms): the point the cluster moved on. + /// When the remake was proposed (unix ms): the point the cluster moved on. pub(crate) proposed_at: u64, } @@ -127,7 +127,7 @@ pub(crate) struct EpochMetadata { #[serde(rename_all = "camelCase")] struct MetadataRecord { generation: u64, - compaction_id: String, + remake_id: String, previous: EpochRecord, clock: BTreeMap<String, u64>, policy: Policy, @@ -153,7 +153,7 @@ fn parse_metadata(doc: &AutoCommit, generation: u64) -> Result<EpochMetadata, St .ok_or_else(|| "invalid previous epoch in metadata".to_owned())?; Ok(EpochMetadata { - compaction_id: record.compaction_id, + remake_id: record.remake_id, previous, clock: record.clock, policy: record.policy, @@ -183,7 +183,7 @@ pub(crate) fn verify_document(doc: &mut AutoCommit, epoch: Epoch) -> Result<Epoc } /// Metadata of an earlier generation, as the current document holds it. Snapshots -/// carry the metadata of all earlier epochs forward (only compaction records stay +/// carry the metadata of all earlier epochs forward (only remake records stay /// behind), so a node many epochs behind can still find the epoch that followed its own. pub(crate) fn read_epoch_metadata( doc: &AutoCommit, @@ -217,18 +217,17 @@ pub(crate) fn build( let clock = clock_of(&mut base); let generation = params.previous.generation + 1; - let mut snapshot = - AutoCommit::new().with_actor(snapshot_actor(params.compaction_id, generation)); + let mut snapshot = AutoCommit::new().with_actor(snapshot_actor(params.remake_id, generation)); - // Compaction records describe the old epoch's protocol and stay behind. - store::materialize_into(&base, &mut snapshot, &is_compaction_record)?; + // Remake records describe the old epoch's protocol and stay behind. + store::materialize_into(&base, &mut snapshot, &is_remake_record)?; let mut base_heads: Vec<String> = params.base_heads.iter().map(|h| h.to_string()).collect(); base_heads.sort(); let metadata = canonical(json!({ "generation": generation, - "compactionId": params.compaction_id, + "remakeId": params.remake_id, "previous": { "generation": params.previous.generation, "id": params.previous.id_hex(), @@ -267,19 +266,19 @@ fn clock_of(doc: &mut AutoCommit) -> BTreeMap<String, u64> { clock } -/// The same 16-byte actor on every node, unique per compaction and generation. -fn snapshot_actor(compaction_id: &str, generation: u64) -> ActorId { +/// The same 16-byte actor on every node, unique per remake and generation. +fn snapshot_actor(remake_id: &str, generation: u64) -> ActorId { let digest = Sha256::new() .chain_update(b"continuum.snapshot\0") - .chain_update(compaction_id.as_bytes()) + .chain_update(remake_id.as_bytes()) .chain_update(b"\0") .chain_update(generation.to_be_bytes()) .finalize(); ActorId::from(&digest[..16]) } -fn is_compaction_record(path: &[String]) -> bool { - matches!(path, [root, records] if root == META_ROOT && records == COMPACTIONS) +fn is_remake_record(path: &[String]) -> bool { + matches!(path, [root, records] if root == META_ROOT && records == REMAKES) } /// Rebuilds every object with sorted keys, so the result is the same whichever key @@ -309,7 +308,7 @@ mod tests { fn params<'a>(heads: &'a [ChangeHash], policy: &'a JsonValue) -> SnapshotParams<'a> { SnapshotParams { - compaction_id: "c-1", + remake_id: "c-1", previous: Epoch::GENESIS, base_heads: heads, policy, @@ -459,11 +458,11 @@ mod tests { } #[test] - fn metadata_written_and_compaction_records_dropped() { + fn metadata_written_and_remake_records_dropped() { let mut a = doc(b"actor-a"); store::write_path( &mut a, - &[META_ROOT.into(), COMPACTIONS.into(), "c-1".into()], + &[META_ROOT.into(), REMAKES.into(), "c-1".into()], &json!({ "state": "committed" }), ) .unwrap(); @@ -482,12 +481,12 @@ mod tests { let meta = store::read_path(&snap.doc, &[META_ROOT.into()]) .unwrap() .unwrap(); - assert!(meta.get(COMPACTIONS).is_none()); + assert!(meta.get(REMAKES).is_none()); assert_eq!(meta["forgotten"]["pk"]["at"], json!(1)); let epoch = &meta[EPOCHS]["1"]; assert_eq!(epoch["generation"], json!(1)); - assert_eq!(epoch["compactionId"], json!("c-1")); + assert_eq!(epoch["remakeId"], json!("c-1")); assert_eq!(epoch["previous"]["generation"], json!(0)); assert_eq!(epoch["policy"], policy); assert_eq!(epoch["baseHeads"].as_array().unwrap().len(), heads.len()); @@ -585,7 +584,7 @@ mod tests { #[test] fn fetched_document_is_verified_and_late_changes_replayed() { - use crate::compaction::delta; + use crate::remake::delta; // Old epoch: the base, then a late write by this node. let mut late = doc(b"late-node"); @@ -610,7 +609,7 @@ mod tests { let mut fetched = AutoCommit::load(&bytes).unwrap(); let metadata = verify_document(&mut fetched, snap.epoch).unwrap(); assert_eq!(metadata.previous, Epoch::GENESIS); - assert_eq!(metadata.compaction_id, "c-1"); + assert_eq!(metadata.remake_id, "c-1"); assert_eq!( metadata.policy.replay_window, Policy::default_replay_window() diff --git a/src/compaction/switch.rs b/src/remake/switch.rs index 67ff471..b4ab829 100644 --- a/src/compaction/switch.rs +++ b/src/remake/switch.rs @@ -1,6 +1,6 @@ //! Switching this node to a committed epoch. //! -//! The worker watches the compaction records. When a compaction of the local epoch is +//! The worker watches the remake records. When a remake of the local epoch is //! committed, it waits one keepalive interval (so the commit record can reach peers //! through ordinary sync before this node stops syncing the old epoch), then: //! @@ -28,7 +28,7 @@ use super::catchup; use super::delta::{self, ReplayStats}; use super::local::LocalStatus; use super::records::{self, unix_millis, EpochRecord, Record}; -use super::snapshot::{self, COMPACTIONS, META_ROOT}; +use super::snapshot::{self, META_ROOT, REMAKES}; use super::Host; use crate::document::replica::Epoch; use crate::document::store; @@ -37,9 +37,9 @@ use crate::swarm::sync::paths_related; const TICK: Duration = Duration::from_secs(1); pub(crate) async fn run_worker<H: Host>(host: H) { - info!("Starting compaction worker"); + info!("Starting remake worker"); - let records_dir = vec![META_ROOT.to_owned(), COMPACTIONS.to_owned()]; + let records_dir = vec![META_ROOT.to_owned(), REMAKES.to_owned()]; let mut changes = host.subscribe_changes(); let mut changes_closed = false; let mut tick = tokio::time::interval(TICK); @@ -53,7 +53,7 @@ pub(crate) async fn run_worker<H: Host>(host: H) { Ok(set) => set.paths.iter().any(|path| paths_related(path, &records_dir)), Err(broadcast::error::RecvError::Lagged(_)) => true, Err(broadcast::error::RecvError::Closed) => { - warn!("Change events closed; compaction worker continues on its timer only"); + warn!("Change events closed; remake worker continues on its timer only"); changes_closed = true; false } @@ -68,11 +68,11 @@ pub(crate) async fn run_worker<H: Host>(host: H) { async fn check<H: Host>(host: &H) { let local = host.current_epoch(); - host.compaction().prune_archive(local.generation); + host.remake().prune_archive(local.generation); // While rejoining, the document is being filled from peers: its records are not // complete enough to act on. - if host.compaction().is_rejoining() { + if host.remake().is_rejoining() { return catchup::check(host).await; } @@ -94,29 +94,29 @@ async fn check<H: Host>(host: &H) { // The settle protocol prevents this; never guess which one is right. Peers // that did switch can still be followed. let ids: Vec<&str> = several.iter().map(|(id, _)| id.as_str()).collect(); - error!(compactions = ?ids, "Several compactions committed for this epoch; not switching on my own"); + error!(remakes = ?ids, "Several remakes committed for this epoch; not switching on my own"); return catchup::check(host).await; } }; if host - .compaction() + .remake() .status(&id) .is_some_and(|status| status.error.is_some()) { - // Our own switch failed (reported in the compaction's status): fetch the epoch + // Our own switch failed (reported in the remake's status): fetch the epoch // from a peer that made it. return catchup::check(host).await; } let switch_delay = host.dormant_after() / 4; // one keepalive interval - if host.compaction().first_seen(&id).elapsed() < switch_delay { + if host.remake().first_seen(&id).elapsed() < switch_delay { return; } match switch(host, &id, &record).await { Ok(stats) => { - host.compaction().set_status( + host.remake().set_status( &id, LocalStatus { switched_at: Some(unix_millis()), @@ -128,8 +128,8 @@ async fn check<H: Host>(host: &H) { ); } Err(reason) => { - error!(compaction = %id, reason = %reason, "Epoch switch failed"); - host.compaction().set_status( + error!(remake = %id, reason = %reason, "Epoch switch failed"); + host.remake().set_status( &id, LocalStatus { error: Some(reason), @@ -143,7 +143,7 @@ async fn check<H: Host>(host: &H) { async fn switch<H: Host>(host: &H, id: &str, record: &Record) -> Result<ReplayStats, String> { let (Some(expected), Some(request)) = (record.committed_epoch(), record.snapshot_request(id)) else { - return Err("unreadable compaction record".to_owned()); + return Err("unreadable remake record".to_owned()); }; let previous = request.previous; let base = request.base_heads.clone(); @@ -155,12 +155,12 @@ async fn switch<H: Host>(host: &H, id: &str, record: &Record) -> Result<ReplaySt let carry_record = record.proposal.coordinator == host.node_id(); let record_path = records::record_path(id, ""); - info!(compaction = %id, from = %previous, to = %expected, "Switching epoch"); + info!(remake = %id, from = %previous, to = %expected, "Switching epoch"); // 1. Consistent copy, under the read lock only. let copy = host.read_doc(|doc| doc.clone()).await; let prebuilt = host - .compaction() + .remake() .take_prebuilt(id) .filter(|snapshot| snapshot.epoch == expected); @@ -183,10 +183,10 @@ async fn switch<H: Host>(host: &H, id: &str, record: &Record) -> Result<ReplaySt // A fresh actor: this node's writes in the new epoch. let mut doc = snapshot.doc.with_actor(ActorId::random()); - // Keep this compaction's record visible in the new epoch, for its history. + // Keep this remake's record visible in the new epoch, for its history. if carry_record { if let Err(error) = store::copy_path(©, &mut doc, &record_path) { - warn!(error = %error, "Could not carry the compaction record into the new epoch"); + warn!(error = %error, "Could not carry the remake record into the new epoch"); } } @@ -202,11 +202,11 @@ async fn switch<H: Host>(host: &H, id: &str, record: &Record) -> Result<ReplaySt let old = swap_in(host, expected, new_doc, copy_heads).await?; // 4. Keep the old document as configured. - host.compaction() + host.remake() .archive(previous, old, retention, expected.generation); info!( - compaction = %id, + remake = %id, epoch = %expected, replayed = stats.replayed, failed = stats.failed, diff --git a/src/state.rs b/src/state.rs index 18bface..51ca58a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,8 +12,8 @@ use tokio::sync::{broadcast, mpsc, watch, Mutex, RwLock}; use tokio::time::Instant; use tracing::{debug, error, warn}; -use crate::compaction::{Compaction, Host, PeerSummary}; use crate::document::replica::{Epoch, Replica}; +use crate::remake::{Host, PeerSummary, RemakeState}; use crate::swarm::sync::{ChangeSet, SyncEvent}; use crate::transport; use crate::transport::firewall::Firewall; @@ -298,8 +298,8 @@ pub(crate) struct AppState { pub(crate) timing: Timing, - /// Per-node compaction state (prebuilt snapshots, switch status, archive). - pub(crate) compaction: Arc<Compaction>, + /// Per-node remake state (prebuilt snapshots, switch status, archive). + pub(crate) remake: Arc<RemakeState>, /// While rejoining a newer epoch, the previous document, served read-only (and /// marked stale) instead of answering 503. @@ -621,10 +621,10 @@ pub(crate) fn unix_millis() -> u64 { .unwrap_or(0) } -/// The node as seen by the compaction module. +/// The node as seen by the remake module. impl Host for AppState { - fn compaction(&self) -> &Compaction { - &self.compaction + fn remake(&self) -> &RemakeState { + &self.remake } fn node_id(&self) -> String { diff --git a/src/swarm/bootstrap.rs b/src/swarm/bootstrap.rs index 2907e93..47fadfa 100644 --- a/src/swarm/bootstrap.rs +++ b/src/swarm/bootstrap.rs @@ -54,7 +54,7 @@ pub(crate) async fn bootstrap(state: &AppState, seeds: &[String]) -> Result<()> if let Some(cluster_id) = state.cluster_id().await { // A node joining a compacted cluster rejoins the newer epoch first; it is // ready only once that rejoin verified the epoch. - if state.initial_sync_done.load(Ordering::Acquire) && !state.compaction.is_rejoining() { + if state.initial_sync_done.load(Ordering::Acquire) && !state.remake.is_rejoining() { state.mark_ready(cluster_id.clone()).await?; info!(cluster_id = %cluster_id, "Node is ready"); return Ok(()); diff --git a/src/swarm/peer.rs b/src/swarm/peer.rs index eb97a7e..b092b03 100644 --- a/src/swarm/peer.rs +++ b/src/swarm/peer.rs @@ -534,7 +534,7 @@ async fn observe_peer_epoch(state: &AppState, peer: &str, remote: Epoch) -> bool .send(SyncEvent::PeerNeedsSync(peer.to_owned())); } } - // Expected after a compaction: either side switches or catches up. + // Expected after a remake: either side switches or catches up. EpochRelation::PeerAhead | EpochRelation::PeerBehind => { info!( peer = %peer, |
