Resolves the 142 findings from tidal/docs/reviews/CODE_REVIEW_m0-m10.md across the engine, server, net, and CLI surfaces: - WAL/session-journal durability, checkpoint format, and crash-recovery hardening - Replication shipper/receiver, tenant isolation, and migration paths - Cluster scatter-gather, router, standalone server + health/offload endpoints - tidalctl refactored into command modules with JSON output and WAL-state tooling - Cohort, governance, signal-ledger, and vector-registry correctness fixes - Expanded UAT/integration/durability test coverage across all milestones
109 lines
4.0 KiB
Rust
109 lines
4.0 KiB
Rust
//! Lock-free WAL-state inspection shared by the `status` and `diagnostics`
|
|
//! commands.
|
|
//!
|
|
//! [`gather_wal_state`] is the standalone path used by `status`: it lists
|
|
//! segments, reads the checkpoint, and sizes the directory directly.
|
|
//! [`WalState::from_report`] is the zero-extra-IO path used by `diagnostics`,
|
|
//! which already holds a full [`WalDiagnosticReport`] and must not re-scan the
|
|
//! directory or re-read the checkpoint.
|
|
|
|
use serde::Serialize;
|
|
|
|
use tidaldb::wal::diagnostics::WalDiagnosticReport;
|
|
|
|
/// Read-only snapshot of a WAL directory's segment + checkpoint state.
|
|
#[derive(Serialize)]
|
|
pub(crate) struct WalState {
|
|
pub(crate) segments: usize,
|
|
pub(crate) first_seq: u64,
|
|
pub(crate) last_segment_seq: u64,
|
|
pub(crate) checkpoint_seq: u64,
|
|
pub(crate) checkpoint_ts: u64,
|
|
pub(crate) wal_dir_bytes: u64,
|
|
}
|
|
|
|
impl WalState {
|
|
/// An empty WAL (no segments, no checkpoint, zero bytes).
|
|
pub(crate) const fn empty() -> Self {
|
|
Self {
|
|
segments: 0,
|
|
first_seq: 0,
|
|
last_segment_seq: 0,
|
|
checkpoint_seq: 0,
|
|
checkpoint_ts: 0,
|
|
wal_dir_bytes: 0,
|
|
}
|
|
}
|
|
|
|
/// Project an already-computed [`WalDiagnosticReport`] into a [`WalState`].
|
|
///
|
|
/// `diagnostics` runs [`diagnose_wal`](tidaldb::wal::diagnostics::diagnose_wal)
|
|
/// once and reuses its output here, so the segment scan and checkpoint read
|
|
/// happen exactly one time per invocation. `last_segment_seq` mirrors the
|
|
/// standalone [`gather_wal_state`] semantics: the `first_seq` of the last
|
|
/// segment file (the legacy "last segment sequence", not the last event
|
|
/// sequence). `wal_dir_bytes` uses the report's total segment bytes; unlike
|
|
/// [`dir_size`] this excludes the checkpoint/session-journal sidecar files,
|
|
/// which the diagnostics surface accounts for separately.
|
|
pub(crate) fn from_report(report: &WalDiagnosticReport) -> Self {
|
|
let first_seq = report.segments.first().map_or(0, |s| s.first_seq);
|
|
let last_segment_seq = report.segments.last().map_or(0, |s| s.first_seq);
|
|
Self {
|
|
segments: report.segment_count,
|
|
first_seq,
|
|
last_segment_seq,
|
|
checkpoint_seq: report.checkpoint_seq,
|
|
checkpoint_ts: report.checkpoint_ts,
|
|
wal_dir_bytes: report.total_segment_bytes,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gather WAL state by directly scanning the directory (the `status` path).
|
|
///
|
|
/// Returns an empty [`WalState`] when the WAL directory is absent (a fresh
|
|
/// store is not an error). Returns `Err` when the directory exists but its
|
|
/// segments, checkpoint, or size could not be read — the caller maps that to a
|
|
/// degraded exit code.
|
|
pub(crate) fn gather_wal_state(wal_dir: &std::path::Path) -> Result<WalState, String> {
|
|
if !wal_dir.exists() {
|
|
return Ok(WalState::empty());
|
|
}
|
|
|
|
let segments = tidaldb::wal::segment::list_segments(wal_dir).map_err(|e| format!("{e}"))?;
|
|
|
|
let first_seq = segments.first().map_or(0, |(seq, _)| *seq);
|
|
let last_segment_seq = segments.last().map_or(0, |(seq, _)| *seq);
|
|
|
|
let (checkpoint_seq, checkpoint_ts) =
|
|
match tidaldb::wal::checkpoint::CheckpointManager::read(wal_dir) {
|
|
Ok(Some((seq, ts))) => (seq, ts),
|
|
Ok(None) => (0, 0),
|
|
Err(e) => return Err(format!("checkpoint read error: {e}")),
|
|
};
|
|
|
|
let wal_dir_bytes = dir_size(wal_dir).map_err(|e| format!("{e}"))?;
|
|
|
|
Ok(WalState {
|
|
segments: segments.len(),
|
|
first_seq,
|
|
last_segment_seq,
|
|
checkpoint_seq,
|
|
checkpoint_ts,
|
|
wal_dir_bytes,
|
|
})
|
|
}
|
|
|
|
/// Sum the byte size of every regular file directly in `dir` (non-recursive).
|
|
pub(crate) fn dir_size(dir: &std::path::Path) -> Result<u64, std::io::Error> {
|
|
let mut total = 0u64;
|
|
for entry in std::fs::read_dir(dir)? {
|
|
let entry = entry?;
|
|
let meta = entry.metadata()?;
|
|
if meta.is_file() {
|
|
total += meta.len();
|
|
}
|
|
}
|
|
Ok(total)
|
|
}
|