#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::useless_vec,
clippy::unnecessary_map_or,
reason = "test code"
)]
mod test;
use super::{Choice, CompactionStrategy, Input as CompactionInput};
use crate::{
HashSet, TableId,
compaction::state::{CompactionState, hidden_set::HiddenSet},
config::Config,
slice_windows::{GrowingWindowsExt, ShrinkingWindowsExt},
table::{Table, util::aggregate_run_key_range},
version::{Level, Version, run::Ranged},
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct MergeCost {
promoted: u64,
total: u64,
}
impl MergeCost {
fn cheaper_than(self, other: Self) -> bool {
u128::from(self.total) * u128::from(other.promoted)
< u128::from(other.total) * u128::from(self.promoted)
}
}
struct MergeRanking<T> {
bound: u64,
offered: usize,
within: Option<(usize, MergeCost, T)>,
any: Option<(usize, MergeCost, T)>,
second: Option<MergeCost>,
}
impl<T: Copy> MergeRanking<T> {
fn new(overshoot: u64, slack: u64) -> Self {
Self {
bound: overshoot.saturating_add(slack),
offered: 0,
within: None,
any: None,
second: None,
}
}
fn offer(&mut self, cost: MergeCost, candidate: T) {
let index = self.offered;
self.offered += 1;
if cost.promoted <= self.bound
&& self
.within
.as_ref()
.is_none_or(|(_, best, _)| cost.cheaper_than(*best))
{
self.within = Some((index, cost, candidate));
}
match &self.any {
Some((_, best, _)) if !cost.cheaper_than(*best) => {
if self.second.is_none_or(|second| cost.cheaper_than(second)) {
self.second = Some(cost);
}
}
previous => {
self.second = previous.as_ref().map(|(_, best, _)| *best);
self.any = Some((index, cost, candidate));
}
}
}
fn finish(self) -> Option<((usize, MergeCost, T), Option<MergeCost>)> {
match (self.within, self.any) {
(Some(within), Some(any)) if within.0 != any.0 => Some((within, Some(any.1))),
(Some(chosen), _) | (None, Some(chosen)) => Some((chosen, self.second)),
(None, None) => None,
}
}
fn offered(&self) -> usize {
self.offered
}
}
fn pick_minimal_compaction(
curr_level: &Level,
next_level: &Level,
hidden_set: &HiddenSet,
overshoot: u64,
table_base_size: u64,
promotion_slack: u64,
cmp: &dyn crate::comparator::UserComparator,
) -> Option<(HashSet<TableId>, bool)> {
for curr_run in curr_level.iter() {
if let Some(window) = curr_run.shrinking_windows().find(|window| {
if hidden_set.is_blocked(window.iter().map(Table::id)) {
return false;
}
if next_level.is_empty() {
return true;
}
let key_range = aggregate_run_key_range(window);
next_level
.iter()
.all(|run| run.get_overlapping_cmp(&key_range, cmp).is_empty())
}) {
let ids = window.iter().map(Table::id).collect();
return Some((ids, true));
}
}
if next_level.is_empty() {
return None;
}
let pull_in = |window: &[Table]| {
let key_range = aggregate_run_key_range(window);
curr_level
.iter()
.flat_map(|run| run.get_contained_cmp(&key_range, cmp))
.collect::<Vec<&Table>>()
};
let mut ranking = MergeRanking::new(overshoot, promotion_slack);
let windows = next_level.iter().flat_map(|run| {
run.growing_windows().take_while(|window| {
let size = window.iter().map(Table::file_size).sum::<u64>();
size <= (50 * table_base_size)
})
});
for window in windows {
if hidden_set.is_blocked(window.iter().map(Table::id)) {
continue;
}
let curr_level_pull_in = pull_in(window);
let curr_level_size = curr_level_pull_in
.iter()
.map(|t| Table::file_size(t))
.sum::<u64>();
if curr_level_size == 0
|| hidden_set.is_blocked(curr_level_pull_in.iter().map(|t| Table::id(t)))
{
continue;
}
let next_level_size = window.iter().map(Table::file_size).sum::<u64>();
ranking.offer(
MergeCost {
promoted: curr_level_size,
total: curr_level_size + next_level_size,
},
window,
);
}
let offered = ranking.offered();
let ((chosen, cost, window), runner_up) = ranking.finish()?;
log::debug!(
"leveled: merge candidate {chosen} of {offered} chosen at {cost:?}, runner-up \
{runner_up:?} (overshoot {overshoot}, slack {promotion_slack})",
);
let mut ids: HashSet<_> = window.iter().map(Table::id).collect();
ids.extend(pull_in(window).iter().map(|t| Table::id(t)));
Some((ids, false))
}
#[doc(hidden)]
pub const NAME: &str = "LeveledCompaction";
#[derive(Clone)]
pub struct Strategy {
l0_threshold: u8,
target_size: u64,
level_ratio_policy: Vec<f32>,
dynamic: bool,
multi_level: bool,
}
impl Default for Strategy {
fn default() -> Self {
Self {
l0_threshold: 4,
target_size: 64 * 1_024 * 1_024,
level_ratio_policy: vec![10.0],
dynamic: false,
multi_level: false,
}
}
}
impl Strategy {
#[must_use]
pub fn with_level_ratio_policy(mut self, policy: Vec<f32>) -> Self {
self.level_ratio_policy = policy;
self
}
#[must_use]
pub fn with_l0_threshold(mut self, threshold: u8) -> Self {
self.l0_threshold = threshold;
self
}
#[must_use]
pub fn with_table_target_size(mut self, bytes: u64) -> Self {
self.target_size = bytes;
self
}
#[must_use]
pub fn with_dynamic_level_bytes(mut self, enabled: bool) -> Self {
self.dynamic = enabled;
self
}
#[must_use]
pub fn with_multi_level(mut self, enabled: bool) -> Self {
self.multi_level = enabled;
self
}
fn level_base_size(&self) -> u64 {
self.target_size * u64::from(self.l0_threshold)
}
fn level_target_size(&self, canonical_level_idx: u8) -> u64 {
assert!(
canonical_level_idx >= 1,
"level_target_size does not apply to L0",
);
if canonical_level_idx == 1 {
self.level_base_size()
} else {
#[expect(
clippy::cast_precision_loss,
reason = "precision loss is acceptable for level size calculations"
)]
let mut size = self.level_base_size() as f32;
for idx in 0..=(canonical_level_idx - 2) {
let ratio = self
.level_ratio_policy
.get(usize::from(idx))
.copied()
.unwrap_or_else(|| self.level_ratio_policy.last().copied().unwrap_or(10.0));
size *= ratio;
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "size is always positive and will never even come close to u64::MAX"
)]
{
size as u64
}
}
}
fn compute_level_targets(
&self,
version: &Version,
level_shift: usize,
state: &CompactionState,
) -> [u64; 7] {
let mut targets = [u64::MAX; 7];
targets[0] = u64::MAX;
if self.dynamic {
let mut lmax_idx = None;
for (idx, lvl) in version.iter_levels().enumerate().skip(1) {
if !lvl.is_empty() {
lmax_idx = Some(idx);
}
}
if let Some(lmax_idx) = lmax_idx {
#[expect(
clippy::expect_used,
reason = "lmax_idx was found by iterating levels, so it must exist"
)]
let lmax_level = version.level(lmax_idx).expect("level should exist");
let lmax_size: u64 = lmax_level
.iter()
.flat_map(|run| run.iter())
.filter(|table| !state.hidden_set().is_hidden(table.id()))
.map(Table::file_size)
.sum();
if let Some(slot) = targets.get_mut(lmax_idx) {
*slot = lmax_size;
}
#[expect(
clippy::cast_precision_loss,
reason = "precision loss is acceptable for level size calculations"
)]
let mut current_target = lmax_size as f64;
let dynamic_l1_idx = level_shift + 1;
for idx in (dynamic_l1_idx..lmax_idx).rev() {
let canonical = idx - level_shift;
let ratio_idx = canonical.saturating_sub(1);
let ratio = f64::from(
self.level_ratio_policy
.get(ratio_idx)
.copied()
.unwrap_or_else(|| {
self.level_ratio_policy.last().copied().unwrap_or(10.0)
}),
);
if !ratio.is_finite() || ratio <= 0.0 {
return self.compute_static_targets(level_shift);
}
current_target /= ratio;
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "target is always positive"
)]
if let Some(slot) = targets.get_mut(idx) {
*slot = current_target as u64;
}
}
let static_l1 = self.level_base_size();
if targets.get(dynamic_l1_idx).copied().unwrap_or(0) < static_l1 {
return self.compute_static_targets(level_shift);
}
return targets;
}
}
self.compute_static_targets(level_shift)
}
fn compute_static_targets(&self, level_shift: usize) -> [u64; 7] {
let mut targets = [u64::MAX; 7];
for (idx, slot) in targets.iter_mut().enumerate().skip(1) {
if idx <= level_shift {
continue; }
#[expect(
clippy::cast_possible_truncation,
reason = "level index is bounded by level count (7)"
)]
{
*slot = self.level_target_size((idx - level_shift) as u8);
}
}
targets
}
}
impl CompactionStrategy for Strategy {
fn get_name(&self) -> &'static str {
NAME
}
fn get_config(&self) -> Vec<crate::KvPair> {
vec![
(
crate::UserKey::from("leveled_l0_threshold"),
crate::UserValue::from(self.l0_threshold.to_le_bytes()),
),
(
crate::UserKey::from("leveled_target_size"),
crate::UserValue::from(self.target_size.to_le_bytes()),
),
(
crate::UserKey::from("leveled_level_ratio_policy"),
crate::UserValue::from({
use crate::io::{LittleEndian, WriteBytesExt};
let mut v = vec![];
#[expect(
clippy::expect_used,
clippy::cast_possible_truncation,
reason = "writing into Vec should not fail; policies have length of 255 max"
)]
v.write_u8(self.level_ratio_policy.len() as u8)
.expect("cannot fail");
for &f in &self.level_ratio_policy {
#[expect(clippy::expect_used, reason = "writing into Vec should not fail")]
v.write_f32::<LittleEndian>(f).expect("cannot fail");
}
v
}),
),
(
crate::UserKey::from("leveled_dynamic"),
crate::UserValue::from([u8::from(self.dynamic)]),
),
(
crate::UserKey::from("leveled_multi_level"),
crate::UserValue::from([u8::from(self.multi_level)]),
),
]
}
fn pending_compaction_bytes(&self, version: &Version) -> u64 {
let targets = self.compute_level_targets(version, 0, &CompactionState::default());
let mut debt = 0u64;
for (idx, level) in version.iter_levels().enumerate() {
if idx == 0 {
if level.table_count() >= usize::from(self.l0_threshold) {
debt += level.size();
}
} else if let Some(&target) = targets.get(idx) {
debt += level.size().saturating_sub(target);
}
}
debt
}
#[expect(clippy::too_many_lines)]
fn choose(&self, version: &Version, config: &Config, state: &CompactionState) -> Choice {
assert!(
version.level_count() == usize::from(config.level_count),
"leveled compaction requires version.level_count() == config.level_count (got version={}, config={})",
version.level_count(),
config.level_count,
);
let cmp = config.comparator.as_ref();
'trivial_lmax: {
#[expect(
clippy::expect_used,
reason = "level 0 is guaranteed to exist in a valid version"
)]
let l0 = version.level(0).expect("first level should exist");
if !l0.is_empty() && l0.is_disjoint() {
let lmax_index = version.level_count() - 1;
if (1..lmax_index).any(|idx| {
#[expect(
clippy::expect_used,
reason = "levels within level_count are guaranteed to exist"
)]
let level = version.level(idx).expect("level should exist");
!level.is_empty()
}) {
break 'trivial_lmax;
}
#[expect(
clippy::expect_used,
reason = "lmax_index is derived from level_count so level is guaranteed to exist"
)]
let lmax = version.level(lmax_index).expect("last level should exist");
if !lmax
.aggregate_key_range_cmp(cmp)
.overlaps_with_key_range_cmp(&l0.aggregate_key_range_cmp(cmp), cmp)
{
return Choice::Move(CompactionInput {
table_ids: l0.list_ids(),
#[expect(
clippy::cast_possible_truncation,
reason = "level count is at most 7, fits in u8"
)]
dest_level: lmax_index as u8,
canonical_level: 1,
target_size: self.target_size,
});
}
}
}
#[expect(clippy::map_unwrap_or)]
let first_non_empty_level = version
.iter_levels()
.enumerate()
.skip(1)
.find(|(_, lvl)| !lvl.is_empty())
.map(|(idx, _)| idx)
.unwrap_or_else(|| version.level_count() - 1);
let mut canonical_l1_idx = first_non_empty_level;
let mut level_shift = canonical_l1_idx - 1;
if canonical_l1_idx > 1 && version.iter_levels().skip(1).any(|lvl| !lvl.is_empty()) {
let need_new_l1 = version
.iter_levels()
.enumerate()
.skip(1)
.filter(|(_, lvl)| !lvl.is_empty())
.all(|(idx, level)| {
let level_size = level
.iter()
.flat_map(|x| x.iter())
.filter(|x| !state.hidden_set().is_hidden(x.id()))
.map(Table::file_size)
.sum::<u64>();
#[expect(
clippy::cast_possible_truncation,
reason = "level index is bounded by level count (7, technically 255)"
)]
let target_size = self.level_target_size((idx - level_shift) as u8);
level_size > target_size
});
if need_new_l1 {
canonical_l1_idx -= 1;
level_shift -= 1;
}
}
'trivial: {
let first_level = version.l0();
let target_level_idx = first_non_empty_level.min(canonical_l1_idx);
if first_level.run_count() == 1 {
if version.level_is_busy(0, state.hidden_set())
|| version.level_is_busy(target_level_idx, state.hidden_set())
{
break 'trivial;
}
let Some(target_level) = &version.level(target_level_idx) else {
break 'trivial;
};
if target_level.run_count() != 1 {
break 'trivial;
}
let key_range = first_level.aggregate_key_range_cmp(cmp);
let get_overlapping = target_level
.iter()
.flat_map(|run| run.get_overlapping_cmp(&key_range, cmp))
.map(Table::id)
.next();
if get_overlapping.is_none() && first_level.is_disjoint() {
#[expect(
clippy::cast_possible_truncation,
reason = "level index is bounded by level count (7)"
)]
return Choice::Move(CompactionInput {
table_ids: first_level.list_ids(),
dest_level: target_level_idx as u8,
canonical_level: 1,
target_size: self.target_size,
});
}
}
}
{
let first_level = version.l0();
if first_level.run_count() > 1
&& first_level.table_count() < usize::from(self.l0_threshold)
&& !version.level_is_busy(0, state.hidden_set())
{
return Choice::Merge(CompactionInput {
table_ids: first_level.list_ids(),
dest_level: 0,
canonical_level: 0,
target_size: self.target_size,
});
}
}
let level_targets = self.compute_level_targets(version, level_shift, state);
let mut scores = [( 0.0, 0u64); 7];
{
let first_level = version.l0();
if first_level.table_count() >= usize::from(self.l0_threshold) {
#[expect(
clippy::cast_precision_loss,
reason = "precision loss is acceptable for scoring calculations"
)]
let ratio = (first_level.table_count() as f64) / f64::from(self.l0_threshold);
scores[0] = (ratio, 0);
}
for (idx, level) in version.iter_levels().enumerate().skip(1) {
if level.is_empty() {
continue;
}
let level_size = level
.iter()
.flat_map(|x| x.iter())
.filter(|x| !state.hidden_set().is_hidden(x.id()))
.map(Table::file_size)
.sum::<u64>();
#[expect(clippy::indexing_slicing)]
let target_size = level_targets[idx];
#[expect(
clippy::indexing_slicing,
reason = "idx is from iter_levels().enumerate() so always < 7 = scores.len()"
)]
if level_size > target_size {
#[expect(
clippy::cast_precision_loss,
reason = "precision loss is acceptable for scoring calculations"
)]
let score = level_size as f64 / target_size as f64;
scores[idx] = (score, level_size - target_size);
if version
.level(idx + 1)
.is_some_and(|next_level| next_level.is_empty())
{
scores[idx] = (99.99, 999);
}
}
}
{
scores[6] = (0.0, 0);
}
}
#[expect(clippy::expect_used, reason = "highest score is expected to exist")]
let (level_idx_with_highest_score, (score, overshoot_bytes)) = scores
.into_iter()
.enumerate()
.max_by(|(_, (score_a, _)), (_, (score_b, _))| {
score_a
.partial_cmp(score_b)
.unwrap_or(core::cmp::Ordering::Equal)
})
.expect("should have highest score somewhere");
if score < 1.0 {
return Choice::DoNothing;
}
if level_idx_with_highest_score == 0 {
let Some(first_level) = version.level(0) else {
return Choice::DoNothing;
};
if version.level_is_busy(0, state.hidden_set())
|| version.level_is_busy(canonical_l1_idx, state.hidden_set())
{
return Choice::DoNothing;
}
let Some(target_level) = &version.level(canonical_l1_idx) else {
return Choice::DoNothing;
};
let mut table_ids = first_level.list_ids();
let key_range = first_level.aggregate_key_range_cmp(cmp);
let target_level_overlapping_table_ids: Vec<_> = target_level
.iter()
.flat_map(|run| run.get_overlapping_cmp(&key_range, cmp))
.map(Table::id)
.collect();
table_ids.extend(&target_level_overlapping_table_ids);
if self.multi_level {
let l1_score = scores.get(canonical_l1_idx).map_or(0.0, |(s, _)| *s);
let l2_idx = canonical_l1_idx + 1;
if l1_score > 1.0
&& l2_idx < version.level_count()
&& !version.level_is_busy(l2_idx, state.hidden_set())
&& let Some(l2) = version.level(l2_idx)
{
table_ids.extend(target_level.list_ids());
{
let mut input_ranges: Vec<_> = target_level
.iter()
.chain(first_level.iter())
.flat_map(|run| run.iter())
.map(|t| t.key_range().clone())
.collect();
input_ranges.sort_by(|a, b| cmp.compare(a.min(), b.min()));
let merged = crate::KeyRange::merge_sorted_cmp(input_ranges, cmp);
for run in l2.iter() {
for interval in &merged {
for l2t in run.get_overlapping_cmp(interval, cmp) {
table_ids.insert(Table::id(l2t));
}
}
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "level index is bounded by level count (7)"
)]
return Choice::Merge(CompactionInput {
table_ids,
dest_level: l2_idx as u8,
canonical_level: 2,
target_size: self.target_size,
});
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "level index is bounded by level count (7, technically 255)"
)]
let choice = CompactionInput {
table_ids,
dest_level: canonical_l1_idx as u8,
canonical_level: 1,
target_size: self.target_size,
};
if target_level_overlapping_table_ids.is_empty() && first_level.is_disjoint() {
return Choice::Move(choice);
}
return Choice::Merge(choice);
}
#[expect(clippy::cast_possible_truncation)]
let curr_level_index = level_idx_with_highest_score as u8;
let next_level_index = curr_level_index + 1;
let Some(level) = version.level(level_idx_with_highest_score) else {
return Choice::DoNothing;
};
let Some(next_level) = version.level(next_level_index as usize) else {
return Choice::DoNothing;
};
let Some((table_ids, can_trivial_move)) = pick_minimal_compaction(
level,
next_level,
state.hidden_set(),
overshoot_bytes,
self.target_size,
self.target_size,
cmp,
) else {
return Choice::DoNothing;
};
#[expect(
clippy::cast_possible_truncation,
reason = "level shift is bounded by level count (7, technically 255)"
)]
let choice = CompactionInput {
table_ids,
dest_level: next_level_index,
canonical_level: next_level_index - (level_shift as u8),
target_size: self.target_size,
};
if can_trivial_move && level.is_disjoint() {
return Choice::Move(choice);
}
Choice::Merge(choice)
}
}