use std::{fmt, time::Duration};
use crate::store::key::{Subspace, subspace_prefix};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum SubspaceName {
System,
Snapshot,
Current,
History,
Inline,
Index,
SchemaVersion,
Changelog,
Unknown(Vec<u8>),
}
impl SubspaceName {
pub(crate) fn of_prefix(prefix: &[u8]) -> Self {
Subspace::ALL
.into_iter()
.find(|subspace| subspace_prefix(*subspace) == prefix)
.map_or_else(|| Self::Unknown(prefix.to_vec()), Self::from)
}
pub(crate) fn subspace(&self) -> Option<Subspace> {
match self {
Self::System => Some(Subspace::System),
Self::Snapshot => Some(Subspace::Snapshot),
Self::Current => Some(Subspace::Current),
Self::History => Some(Subspace::History),
Self::Inline => Some(Subspace::Inline),
Self::Index => Some(Subspace::Index),
Self::SchemaVersion => Some(Subspace::SchemaVersion),
Self::Changelog => Some(Subspace::Changelog),
Self::Unknown(_) => None,
}
}
}
impl From<Subspace> for SubspaceName {
fn from(subspace: Subspace) -> Self {
match subspace {
Subspace::System => Self::System,
Subspace::Snapshot => Self::Snapshot,
Subspace::Current => Self::Current,
Subspace::History => Self::History,
Subspace::Inline => Self::Inline,
Subspace::Index => Self::Index,
Subspace::SchemaVersion => Self::SchemaVersion,
Subspace::Changelog => Self::Changelog,
}
}
}
impl fmt::Display for SubspaceName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::System => f.write_str("system"),
Self::Snapshot => f.write_str("snapshot"),
Self::Current => f.write_str("current"),
Self::History => f.write_str("history"),
Self::Inline => f.write_str("inline"),
Self::Index => f.write_str("index"),
Self::SchemaVersion => f.write_str("schema_version"),
Self::Changelog => f.write_str("changelog"),
Self::Unknown(prefix) => {
f.write_str("unknown(")?;
for byte in prefix {
write!(f, "{byte:02x}")?;
}
f.write_str(")")
}
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CensusRequest {
pub count_live_entries: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LiveCount {
pub keys: u64,
pub key_bytes: u64,
pub value_bytes: u64,
pub scheduled_files: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SubspaceCensus {
pub subspace: SubspaceName,
pub bytes: u64,
pub l0_ssts: u32,
pub sorted_runs: u32,
pub sorted_run_ssts: u32,
pub live: Option<LiveCount>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct StoreObjects {
pub total_objects: u64,
pub total_bytes: u64,
pub wal_objects: u64,
pub wal_bytes: u64,
pub manifest_objects: u64,
pub manifest_bytes: u64,
pub sst_objects: u64,
pub sst_bytes: u64,
pub other_objects: u64,
pub other_bytes: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct StoreCensus {
pub manifest_id: u64,
pub subspaces: Vec<SubspaceCensus>,
pub objects: Option<StoreObjects>,
}
impl StoreCensus {
#[must_use]
pub fn unaccounted_bytes(&self) -> Option<u64> {
self.objects
.map(|objects| objects.total_bytes.saturating_sub(objects.sst_bytes))
}
#[must_use]
pub fn total_bytes(&self) -> u64 {
self.subspaces
.iter()
.fold(0, |total, subspace| total.saturating_add(subspace.bytes))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum CompactionTarget {
#[default]
WholeStore,
Subspace(SubspaceName),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CompactStoreRequest {
pub target: CompactionTarget,
pub wait: Option<Duration>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum MergeOutcome {
Completed,
Failed(String),
Pending,
Skipped(&'static str),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SubspaceMerge {
pub subspace: SubspaceName,
pub outcome: MergeOutcome,
pub bytes_before: u64,
pub bytes_after: Option<u64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CompactStoreReport {
pub merges: Vec<SubspaceMerge>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subspace_names_round_trip_through_prefixes() {
for subspace in Subspace::ALL {
let name = SubspaceName::from(subspace);
assert_eq!(SubspaceName::of_prefix(&subspace_prefix(subspace)), name);
assert_eq!(name.subspace(), Some(subspace));
}
let unknown = SubspaceName::of_prefix(&[0xfe]);
assert_eq!(unknown, SubspaceName::Unknown(vec![0xfe]));
assert_eq!(unknown.subspace(), None);
assert_eq!(unknown.to_string(), "unknown(fe)");
assert_eq!(SubspaceName::of_prefix(&[]), SubspaceName::Unknown(vec![]));
}
#[test]
fn subspaces_display_as_their_written_names() {
assert_eq!(SubspaceName::Current.to_string(), "current");
assert_eq!(SubspaceName::SchemaVersion.to_string(), "schema_version");
}
#[test]
fn unaccounted_bytes_is_everything_outside_the_ssts() {
let mut census = StoreCensus {
manifest_id: 1,
objects: None,
subspaces: Vec::new(),
};
assert_eq!(census.unaccounted_bytes(), None, "unlisted stays unknown");
census.objects = Some(StoreObjects {
total_bytes: 1_000,
sst_bytes: 250,
wal_bytes: 700,
manifest_bytes: 50,
..StoreObjects::default()
});
assert_eq!(census.unaccounted_bytes(), Some(750));
}
#[test]
fn total_bytes_sums_every_subspace() {
let census = StoreCensus {
manifest_id: 7,
objects: None,
subspaces: vec![
SubspaceCensus {
subspace: SubspaceName::Current,
bytes: 10,
l0_ssts: 1,
sorted_runs: 0,
sorted_run_ssts: 0,
live: None,
},
SubspaceCensus {
subspace: SubspaceName::Index,
bytes: 32,
l0_ssts: 0,
sorted_runs: 1,
sorted_run_ssts: 2,
live: None,
},
],
};
assert_eq!(census.total_bytes(), 42);
}
}