use std::sync::Arc;
use super::Compactions;
use super::pick::{Busy, Pick, Shape, targets};
pub(crate) use super::request::Request;
use crate::CompactionStyle;
use crate::db::installer::Flusher;
use crate::db::tables::user_key;
use tessarikv_constants::LEVELS;
use tessarikv_format::TableMeta;
#[cfg(test)]
#[path = "manual_tests.rs"]
mod tests;
pub(crate) const QUEUE: usize = 16;
pub(super) struct Active {
request: Request,
level: usize,
pub(super) waiting: Option<u64>,
fresh_from: u64,
}
pub(super) enum Step {
Run(Pick, usize),
Blocked,
Done,
}
pub(super) enum Next {
Run(Pick),
Blocked,
Idle,
}
impl Compactions {
pub(super) fn manual_step(
&mut self,
flusher: &Flusher,
keyspace: u32,
levels: &[Vec<Arc<TableMeta>>; LEVELS],
shape: Shape,
busy: &Busy,
) -> Next {
loop {
if self.active.is_none() {
let Some(request) = self.queued.pop_front() else {
return Next::Idle;
};
self.active = Some(Active::new(request, flusher.numbers.bound()));
}
let Some(active) = self.active.as_mut() else {
return Next::Idle;
};
if active.waiting.is_some() || active.request.keyspace != keyspace {
return Next::Idle;
}
let style = flusher.keyspace_options(keyspace).compaction();
match active.next(levels, style, shape, busy) {
Step::Run(pick, next) => {
active.level = next;
return Next::Run(pick);
}
Step::Blocked => return Next::Blocked,
Step::Done => {
if let Some(finished) = self.active.take() {
finished.finish();
}
}
}
}
}
pub(super) fn recorded(&mut self, number: u64) {
if let Some(active) = &mut self.active
&& active.waiting == Some(number)
{
active.waiting = None;
}
}
}
impl Active {
pub(super) fn new(request: Request, fresh_from: u64) -> Self {
Self {
request,
level: 0,
waiting: None,
fresh_from,
}
}
pub(super) fn finish(self) {
let _ = self.request.done.send(Ok(()));
}
pub(super) fn request(&self) -> &Request {
&self.request
}
pub(super) fn into_request(self) -> Request {
self.request
}
pub(super) fn next(
&self,
levels: &[Vec<Arc<TableMeta>>; LEVELS],
style: CompactionStyle,
shape: Shape,
busy: &Busy,
) -> Step {
let Some(bottom) = LEVELS.checked_sub(1) else {
return Step::Done;
};
let mut level = self.level;
let found = match style {
CompactionStyle::Fifo { .. } => None,
CompactionStyle::Universal { .. } => (level == 0)
.then(|| self.universal(levels, bottom))
.flatten()
.map(|pick| (pick, bottom.saturating_add(1))),
_ => loop {
if level > bottom {
break None;
}
let (pick, next) = self.leveled(levels, level, bottom, shape);
if let Some(pick) = pick {
break Some((pick, next));
}
level = next;
},
};
match found {
None => Step::Done,
Some((pick, next)) if busy.allows(&pick) => Step::Run(pick, next),
Some(_) => Step::Blocked,
}
}
fn leveled(
&self,
levels: &[Vec<Arc<TableMeta>>; LEVELS],
level: usize,
bottom: usize,
shape: Shape,
) -> (Option<Pick>, usize) {
let tables = |at: usize| levels.get(at).map_or(&[][..], Vec::as_slice);
if level == 0 {
let first_used = (1..bottom).find(|at| !tables(*at).is_empty());
let output = first_used
.unwrap_or(bottom)
.min(targets(levels, shape, bottom).0);
let inputs = tables(0);
let pick = inputs
.iter()
.any(|table| self.touches(table))
.then(|| down(inputs.to_vec(), tables(output), output))
.flatten();
return (pick, output);
}
let next = level.saturating_add(1);
let inputs: Vec<_> = tables(level)
.iter()
.filter(|table| self.touches(table))
.cloned()
.collect();
if level < bottom {
return (down(inputs, tables(next), next), next);
}
let stale: Vec<_> = inputs
.into_iter()
.filter(|table| table.id < self.fresh_from)
.collect();
let pick = (!stale.is_empty()).then(|| Pick {
output_level: bottom,
inputs: stale,
overlaps: Vec::new(),
drop: false,
});
(pick, next)
}
fn universal(&self, levels: &[Vec<Arc<TableMeta>>; LEVELS], bottom: usize) -> Option<Pick> {
let runs = levels.first()?;
let deepest = levels.get(bottom)?;
if !runs.iter().chain(deepest).any(|table| self.touches(table)) {
return None;
}
let (inputs, overlaps) = if runs.is_empty() {
(deepest.clone(), Vec::new())
} else {
(runs.clone(), deepest.clone())
};
Some(Pick {
output_level: bottom,
inputs,
overlaps,
drop: false,
})
}
fn touches(&self, table: &TableMeta) -> bool {
let after_start = self.request.start.as_slice() <= user_key(&table.largest);
let before_end = self
.request
.end
.as_deref()
.is_none_or(|end| user_key(&table.smallest) < end);
after_start && before_end
}
}
fn down(inputs: Vec<Arc<TableMeta>>, below: &[Arc<TableMeta>], output: usize) -> Option<Pick> {
let smallest = inputs.iter().map(|table| user_key(&table.smallest)).min()?;
let largest = inputs.iter().map(|table| user_key(&table.largest)).max()?;
let overlaps = below
.iter()
.filter(|table| {
user_key(&table.smallest) <= largest && smallest <= user_key(&table.largest)
})
.cloned()
.collect();
Some(Pick {
output_level: output,
inputs,
overlaps,
drop: false,
})
}