use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tessarikv_format::{ManifestEdit, TableMeta, TableOptions};
use super::block_cache::BlockCache;
use super::table_blocks::open_table;
use super::tables::build;
use super::view::TableFile;
use crate::memtable::Memtable;
use crate::statistics::Ticker;
use crate::storage::Storage;
use crate::version::manifest::ManifestStore;
use crate::{Error, KeyspaceOptions, Result};
pub(super) struct Job {
pub(super) storage: Arc<dyn Storage>,
pub(super) dir: PathBuf,
pub(super) tables: Vec<(u32, TableOptions, Arc<Memtable>)>,
pub(super) flushed: u64,
pub(super) numbers: Arc<FileNumbers>,
pub(super) cache: Arc<BlockCache>,
}
impl Job {
pub(super) fn run(self, manifest: &mut ManifestStore) -> Result<Vec<TableFile>> {
let mut edit = ManifestEdit {
flushed_sequence: Some(self.flushed),
..ManifestEdit::default()
};
let mut written = Vec::new();
for (keyspace, options, table) in &self.tables {
let mut versions = table.iter();
versions.seek_to_first();
if versions.entry().is_none() && !table.has_tombstones() {
continue;
}
let id = self.numbers.take()?;
let path = table_path(&self.dir, id);
let mut file = self.storage.create(&path)?;
let built = build(table, *options, self.cache.sealing(), &mut |piece| {
file.append(piece)
})?;
file.sync()?;
drop(file);
let meta = TableMeta {
keyspace: *keyspace,
level: 0,
id,
size: built.size(),
smallest: built.smallest.clone(),
largest: built.largest.clone(),
min_sequence: built.min_sequence,
max_sequence: built.max_sequence,
};
edit.added.push(meta.clone());
written.push((meta, path, built));
}
if let Some(statistics) = self.cache.statistics() {
let bytes = written
.iter()
.fold(0_u64, |total, (meta, _, _)| total.saturating_add(meta.size));
statistics.record(Ticker::FlushBytes, bytes);
}
if !written.is_empty() {
self.storage.sync_dir(&self.dir)?;
edit.next_file = Some(self.numbers.bound());
}
manifest.append(&edit)?;
let mut files = Vec::with_capacity(written.len());
for (meta, path, built) in written {
let (table, blocks) = open_table(
self.storage.as_ref(),
&path,
meta.id,
&self.cache,
Some((built.tail_offset, &built.tail)),
)?;
files.push(TableFile {
meta: Arc::new(meta),
table,
blocks,
});
}
Ok(files)
}
}
#[derive(Debug)]
pub(super) struct FileNumbers {
next: AtomicU64,
}
impl FileNumbers {
pub(super) fn new(next: u64) -> Self {
Self {
next: AtomicU64::new(next),
}
}
pub(super) fn take(&self) -> Result<u64> {
let mut next = self.next.load(Ordering::Relaxed);
loop {
let after = next.checked_add(1).ok_or(Error::SequenceExhausted)?;
match self
.next
.compare_exchange_weak(next, after, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(taken) => return Ok(taken),
Err(seen) => next = seen,
}
}
}
pub(super) fn bound(&self) -> u64 {
self.next.load(Ordering::Relaxed)
}
}
pub(super) fn table_path(dir: &Path, id: u64) -> PathBuf {
dir.join(format!("{id:020}.sst"))
}
pub(super) fn table_options(keyspace: &KeyspaceOptions, level: usize) -> Result<TableOptions> {
let invalid = |option| Error::InvalidOptions {
option,
reason: "does not fit this platform's address size".to_owned(),
};
Ok(TableOptions {
block_bytes: usize::try_from(keyspace.block_bytes())
.map_err(|_| invalid("keyspace.block_bytes"))?,
restart_interval: usize::try_from(keyspace.block_restart_interval())
.map_err(|_| invalid("keyspace.block_restart_interval"))?,
bloom_bits_per_key: keyspace.bloom_bits_per_key(),
compression: keyspace.compression_at(level),
})
}