pub(crate) mod blob_lifetime;
mod block_size;
mod column_encoding;
mod compression;
mod delete_strategy;
mod filter;
mod flush_durability;
mod hash_ratio;
mod locator;
mod pinning;
mod restart_interval;
pub use blob_lifetime::{LifetimeGroups, LifetimeHint, LifetimeHintFn};
pub use block_size::{BlockSizePolicy, MAX_BLOCK_SIZE};
pub use column_encoding::{ColumnEncoding, ColumnEncodingPolicy};
pub use compression::CompressionPolicy;
pub use delete_strategy::{DeleteStrategy, DeleteStrategyPolicy};
pub use filter::{BloomConstructionPolicy, FilterAdvisor, FilterPolicy, FilterPolicyEntry};
pub use flush_durability::FlushDurability;
pub use hash_ratio::HashRatioPolicy;
pub use locator::{LocatorPolicy, LocatorPolicyEntry, LocatorPrecision};
pub use pinning::PinningPolicy;
pub use restart_interval::RestartIntervalPolicy;
pub type PartitioningPolicy = PinningPolicy;
use crate::path::PathBuf;
use crate::{
AnyTree, BlobTree, Cache, CompressionType, DescriptorTable, SharedSequenceNumberGenerator,
Tree,
compaction::filter::Factory,
comparator::SharedComparator,
encryption::EncryptionProvider,
file::TABLES_FOLDER,
fs::{Fs, SyncMode},
merge_operator::MergeOperator,
path::absolute_path,
prefix::PrefixExtractor,
};
#[cfg(feature = "std")]
use crate::{SequenceNumberCounter, comparator, path::Path, version::DEFAULT_LEVEL_COUNT};
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::ops::Range;
pub const DEFAULT_COLUMNAR_ROW_GROUP_SIZE: u32 = 16 * 1_024;
pub const DEFAULT_COLUMNAR_PAGE_SIZE: u32 = 4_096;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReadBudget {
io_buffer: u32,
in_flight: u16,
}
impl ReadBudget {
pub const MIN_IO_BUFFER: u32 = 64;
#[must_use]
pub const fn new(io_buffer: u32, in_flight: u16) -> Self {
Self {
io_buffer: if io_buffer < Self::MIN_IO_BUFFER {
Self::MIN_IO_BUFFER
} else {
io_buffer
},
in_flight: if in_flight == 0 { 1 } else { in_flight },
}
}
#[must_use]
pub const fn io_buffer(&self) -> u32 {
self.io_buffer
}
#[must_use]
pub const fn in_flight(&self) -> u16 {
self.in_flight
}
}
impl Default for ReadBudget {
fn default() -> Self {
Self::new(1 << 20, 16)
}
}
pub const DEFAULT_COLUMNAR_SCAN_BUDGET: u64 = 1 << 20;
pub const DEFAULT_MULTI_GET_METADATA_BUDGET: u64 = 8 << 20;
pub const DEFAULT_TABLE_TARGET_SIZE: u64 = 64 * 1_024 * 1_024;
#[derive(Clone)]
pub struct LevelRoute {
pub levels: Range<u8>,
pub path: PathBuf,
pub fs: Arc<dyn Fs>,
}
impl core::fmt::Debug for LevelRoute {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LevelRoute")
.field("levels", &self.levels)
.field("path", &self.path)
.finish_non_exhaustive()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum ManifestRecoveryMode {
#[default]
AbsoluteConsistency,
TolerateCorruptedTailRecords,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TreeType {
Standard,
Blob,
}
impl From<TreeType> for u8 {
fn from(val: TreeType) -> Self {
match val {
TreeType::Standard => 0,
TreeType::Blob => 1,
}
}
}
impl TryFrom<u8> for TreeType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Standard),
1 => Ok(Self::Blob),
_ => Err(()),
}
}
}
#[cfg_attr(
not(feature = "std"),
allow(
dead_code,
reason = "default data-folder path used only on the std-gated default-config path"
)
)]
const DEFAULT_FILE_FOLDER: &str = ".lsm.data";
#[derive(Clone, Debug, PartialEq)]
pub struct KvSeparationOptions {
#[doc(hidden)]
pub file_target_size: u64,
#[doc(hidden)]
pub separation_threshold: u32,
#[doc(hidden)]
pub cell_separation_thresholds: alloc::vec::Vec<(u16, u32)>,
#[doc(hidden)]
pub staleness_threshold: f32,
#[doc(hidden)]
pub age_cutoff: f32,
#[doc(hidden)]
pub scan_prefetch: u16,
#[doc(hidden)]
pub locality_relocation: Option<BlobLocalityRelocation>,
#[doc(hidden)]
pub lifetime_groups: LifetimeGroups,
#[doc(hidden)]
pub lifetime_hint: Option<LifetimeHint>,
#[cfg(zstd_any)]
#[doc(hidden)]
pub zstd_dictionary: Option<alloc::sync::Arc<crate::compression::ZstdDictionary>>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum BlobCacheFill {
Always,
#[default]
Never,
}
impl BlobCacheFill {
#[must_use]
pub const fn fills(self) -> bool {
matches!(self, Self::Always)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct BlobLocalityRelocation {
pub max_depth: core::num::NonZeroU64,
pub budget: f32,
}
pub(crate) fn cell_threshold(thresholds: &[(u16, u32)], default: u32, column: u16) -> u32 {
thresholds
.iter()
.find(|(set, _)| *set == column)
.map_or(default, |(_, bytes)| *bytes)
}
impl Default for KvSeparationOptions {
fn default() -> Self {
Self {
file_target_size: 64 * 1_024 * 1_024,
separation_threshold: 1_024,
cell_separation_thresholds: alloc::vec::Vec::new(),
staleness_threshold: 0.25,
age_cutoff: 0.25,
scan_prefetch: 64,
locality_relocation: None,
lifetime_groups: LifetimeGroups::MAX,
lifetime_hint: None,
#[cfg(zstd_any)]
zstd_dictionary: None,
}
}
}
impl KvSeparationOptions {
#[must_use]
pub fn file_target_size(mut self, bytes: u64) -> Self {
self.file_target_size = bytes;
self
}
#[must_use]
pub fn separation_threshold(mut self, bytes: u32) -> Self {
self.separation_threshold = bytes;
self
}
#[must_use]
pub fn cell_separation_threshold(mut self, column: u16, bytes: u32) -> Self {
match self
.cell_separation_thresholds
.iter_mut()
.find(|(set, _)| *set == column)
{
Some(entry) => entry.1 = bytes,
None => self.cell_separation_thresholds.push((column, bytes)),
}
self
}
#[must_use]
pub(crate) fn lifetime_classifier(&self) -> blob_lifetime::LifetimeClassifier<'_> {
blob_lifetime::LifetimeClassifier {
groups: self.lifetime_groups,
hint: self.lifetime_hint.as_ref(),
}
}
#[must_use]
pub(crate) fn cell_threshold(&self, column: u16) -> u32 {
cell_threshold(
&self.cell_separation_thresholds,
self.separation_threshold,
column,
)
}
#[must_use]
pub fn scan_prefetch(mut self, items: u16) -> Self {
self.scan_prefetch = items;
self
}
#[must_use]
pub fn relocate_for_locality(mut self, max_depth: core::num::NonZeroU64, budget: f32) -> Self {
self.locality_relocation = Some(BlobLocalityRelocation { max_depth, budget });
self
}
#[must_use]
pub fn lifetime_groups(mut self, groups: LifetimeGroups) -> Self {
self.lifetime_groups = groups;
self
}
#[must_use]
pub fn lifetime_hint(mut self, hint: LifetimeHint) -> Self {
self.lifetime_hint = Some(hint);
self
}
#[must_use]
pub fn staleness_threshold(mut self, ratio: f32) -> Self {
self.staleness_threshold = ratio;
self
}
#[must_use]
pub fn age_cutoff(mut self, ratio: f32) -> Self {
self.age_cutoff = ratio;
self
}
#[cfg(zstd_any)]
#[must_use]
pub fn dict(
mut self,
dictionary: alloc::sync::Arc<crate::compression::ZstdDictionary>,
) -> Self {
self.zstd_dictionary = Some(dictionary);
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum OutputCuts {
Aligned,
TargetSize,
}
#[derive(Clone)]
pub struct Config {
#[doc(hidden)]
pub path: PathBuf,
#[doc(hidden)]
pub fs: Arc<dyn Fs>,
#[doc(hidden)]
pub level_routes: Option<Vec<LevelRoute>>,
#[doc(hidden)]
pub cache: Arc<Cache>,
#[doc(hidden)]
pub descriptor_table: Option<Arc<DescriptorTable>>,
pub level_count: u8,
pub data_block_restart_interval_policy: RestartIntervalPolicy,
pub index_block_restart_interval_policy: RestartIntervalPolicy,
pub data_block_size_policy: BlockSizePolicy,
pub columnar_row_group_size_policy: BlockSizePolicy,
pub columnar_page_size_policy: BlockSizePolicy,
pub column_encoding_policy: ColumnEncodingPolicy,
pub columnar_read_budget: ReadBudget,
pub columnar_scan_budget: u64,
pub multi_get_metadata_budget: u64,
pub index_block_pinning_policy: PinningPolicy,
pub filter_block_pinning_policy: PinningPolicy,
pub top_level_index_block_pinning_policy: PinningPolicy,
pub top_level_filter_block_pinning_policy: PinningPolicy,
pub data_block_hash_ratio_policy: HashRatioPolicy,
pub index_block_partitioning_policy: PartitioningPolicy,
pub filter_block_partitioning_policy: PartitioningPolicy,
pub index_block_partition_size_policy: BlockSizePolicy,
pub filter_block_partition_size_policy: BlockSizePolicy,
pub(crate) expect_point_read_hits: bool,
pub(crate) compaction_output_cuts: OutputCuts,
pub(crate) page_ecc: bool,
#[expect(
clippy::struct_field_names,
reason = "name mirrors the type for grep-ability across the persist + Tree handle init wiring"
)]
pub(crate) initial_runtime_config: crate::runtime_config::RuntimeConfig,
pub filter_policy: FilterPolicy,
pub filter_advisor: Option<FilterAdvisor>,
pub locator_policy: LocatorPolicy,
pub compaction_filter_factory: Option<Arc<dyn Factory>>,
pub prefix_extractor: Option<Arc<dyn PrefixExtractor>>,
pub key_domains: Option<Arc<dyn crate::KeyDomains>>,
pub merge_operator: Option<Arc<dyn MergeOperator>>,
#[doc(hidden)]
pub kv_separation_opts: Option<KvSeparationOptions>,
#[doc(hidden)]
pub(crate) comparator: SharedComparator,
pub(crate) encryption: Option<Arc<dyn EncryptionProvider>>,
pub(crate) clock: Option<Arc<dyn crate::Clock>>,
pub(crate) manifest_recovery_mode: ManifestRecoveryMode,
pub(crate) sync_mode: SyncMode,
pub(crate) flush_durability: FlushDurability,
pub(crate) writeback_bytes: u64,
pub(crate) table_target_size: u64,
pub(crate) directory_lock: bool,
#[cfg(feature = "std")]
pub(crate) recovery_progress: Option<Arc<crate::RecoveryProgress>>,
pub(crate) manifest_log_rotate_bytes: u64,
pub(crate) table_file_pool: usize,
pub(crate) table_file_refill: crate::table::file_pool::RefillOn,
pub(crate) repair_retention_floor: crate::SeqNo,
pub(crate) compaction_rate_limit: u64,
pub(crate) compaction_rate_limiter: Option<Arc<crate::rate_limiter::RateLimiter>>,
pub(crate) live_compaction_rate_limiter: Option<Arc<crate::rate_limiter::RateLimiter>>,
pub(crate) compaction_capacity: Option<Arc<crate::CapacityPool>>,
pub(crate) write_rate_limit: u64,
pub(crate) write_rate_limiter: Option<Arc<crate::rate_limiter::RateLimiter>>,
#[cfg(feature = "std")]
pub(crate) write_buffer_budget: Option<Arc<crate::backpressure::WriteBufferBudget>>,
#[cfg(feature = "std")] pub(crate) compaction_threads: usize,
#[cfg(feature = "std")]
pub(crate) compaction_pool: Option<Arc<dyn crate::table::writer::CompactionSpawner>>,
#[cfg(feature = "std")]
pub(crate) parallel_compression_inline_below: Option<u32>,
#[cfg(feature = "std")]
pub(crate) subcompaction_min_bytes: u64,
#[cfg(all(test, feature = "std"))]
pub(crate) fail_one_subcompaction: Arc<core::sync::atomic::AtomicBool>,
#[cfg(all(test, feature = "std"))]
pub(crate) stop_serial_merge_after_first_item: Arc<core::sync::atomic::AtomicBool>,
#[cfg(all(test, feature = "std"))]
pub(crate) fail_tight_after_first_slice: Arc<core::sync::atomic::AtomicBool>,
#[cfg(all(test, feature = "std"))]
pub(crate) tight_slice_squeeze: Arc<core::sync::atomic::AtomicU64>,
#[cfg(all(test, feature = "std"))]
pub(crate) fail_tight_blob_reopen: Arc<core::sync::atomic::AtomicBool>,
#[cfg(all(test, feature = "std"))]
pub(crate) before_output_install: Arc<std::sync::Mutex<Option<OutputInstallHook>>>,
#[cfg(all(test, feature = "std"))]
pub(crate) while_outputs_finish: Arc<std::sync::Mutex<Option<OutputInstallHook>>>,
#[cfg(zstd_any)]
pub(crate) zstd_dictionary: Option<Arc<crate::compression::ZstdDictionary>>,
#[cfg(zstd_any)]
pub(crate) zstd_dictionaries: Arc<arc_swap::ArcSwap<crate::compression::ZstdDictionaries>>,
pub(crate) seqno: SharedSequenceNumberGenerator,
pub(crate) visible_seqno: SharedSequenceNumberGenerator,
}
#[cfg(feature = "std")]
impl Default for Config {
fn default() -> Self {
Self {
path: absolute_path(Path::new(DEFAULT_FILE_FOLDER)),
fs: crate::fs::default_fs(),
level_routes: None,
descriptor_table: Some(Arc::new(DescriptorTable::new(256))),
seqno: SharedSequenceNumberGenerator::from(SequenceNumberCounter::default()),
visible_seqno: SharedSequenceNumberGenerator::from(SequenceNumberCounter::default()),
cache: Arc::new(Cache::with_capacity_bytes(
16 * 1_024 * 1_024,
)),
data_block_restart_interval_policy: RestartIntervalPolicy::all(16),
index_block_restart_interval_policy: RestartIntervalPolicy::all(1),
level_count: DEFAULT_LEVEL_COUNT,
data_block_size_policy: BlockSizePolicy::all(4_096),
columnar_row_group_size_policy: BlockSizePolicy::all(DEFAULT_COLUMNAR_ROW_GROUP_SIZE),
columnar_page_size_policy: BlockSizePolicy::all(DEFAULT_COLUMNAR_PAGE_SIZE),
column_encoding_policy: ColumnEncodingPolicy::default(),
columnar_read_budget: ReadBudget::default(),
columnar_scan_budget: DEFAULT_COLUMNAR_SCAN_BUDGET,
multi_get_metadata_budget: DEFAULT_MULTI_GET_METADATA_BUDGET,
index_block_pinning_policy: PinningPolicy::new([true, true, false]),
filter_block_pinning_policy: PinningPolicy::new([true, false]),
top_level_index_block_pinning_policy: PinningPolicy::all(true), top_level_filter_block_pinning_policy: PinningPolicy::all(true),
index_block_partitioning_policy: PinningPolicy::all(true),
filter_block_partitioning_policy: PinningPolicy::new([false, false, false, true]),
index_block_partition_size_policy: BlockSizePolicy::all(4_096),
filter_block_partition_size_policy: BlockSizePolicy::all(4_096),
data_block_hash_ratio_policy: HashRatioPolicy::all(0.0),
locator_policy: LocatorPolicy::block_level(),
filter_policy: FilterPolicy::all(FilterPolicyEntry::Bloom(
BloomConstructionPolicy::BitsPerKey(10.0),
)),
filter_advisor: None,
compaction_filter_factory: None,
merge_operator: None,
prefix_extractor: None,
key_domains: None,
expect_point_read_hits: false,
compaction_output_cuts: OutputCuts::Aligned,
page_ecc: false,
initial_runtime_config: crate::runtime_config::RuntimeConfig::default(),
kv_separation_opts: None,
#[cfg(zstd_any)]
zstd_dictionary: None,
#[cfg(zstd_any)]
zstd_dictionaries: Arc::new(arc_swap::ArcSwap::from_pointee(
crate::compression::ZstdDictionaries::new(),
)),
comparator: comparator::default_comparator(),
encryption: None,
clock: None,
manifest_recovery_mode: ManifestRecoveryMode::AbsoluteConsistency,
sync_mode: SyncMode::Normal,
flush_durability: FlushDurability::Synced,
writeback_bytes: 1_024 * 1_024,
table_target_size: DEFAULT_TABLE_TARGET_SIZE,
directory_lock: true,
#[cfg(feature = "std")]
recovery_progress: None,
manifest_log_rotate_bytes: 1024 * 1024,
table_file_pool: crate::table::file_pool::DEFAULT_TABLE_FILE_POOL,
table_file_refill: crate::table::file_pool::RefillOn::Background,
repair_retention_floor: 0,
compaction_rate_limit: 0,
compaction_rate_limiter: None,
live_compaction_rate_limiter: None,
compaction_capacity: None,
write_rate_limit: 0,
write_rate_limiter: None,
#[cfg(feature = "std")]
write_buffer_budget: None,
#[cfg(feature = "std")]
compaction_threads: std::thread::available_parallelism()
.map_or(1, |n| (n.get() / 2).max(1)),
#[cfg(feature = "std")]
compaction_pool: None,
#[cfg(feature = "std")]
parallel_compression_inline_below: None,
#[cfg(feature = "std")]
subcompaction_min_bytes: crate::compaction::worker::SUBCOMPACTION_MIN_INPUT_BYTES,
#[cfg(all(test, feature = "std"))]
fail_one_subcompaction: Arc::new(core::sync::atomic::AtomicBool::new(false)),
#[cfg(all(test, feature = "std"))]
stop_serial_merge_after_first_item: Arc::new(core::sync::atomic::AtomicBool::new(
false,
)),
#[cfg(all(test, feature = "std"))]
fail_tight_after_first_slice: Arc::new(core::sync::atomic::AtomicBool::new(false)),
#[cfg(all(test, feature = "std"))]
tight_slice_squeeze: Arc::new(core::sync::atomic::AtomicU64::new(0)),
#[cfg(all(test, feature = "std"))]
fail_tight_blob_reopen: Arc::new(core::sync::atomic::AtomicBool::new(false)),
#[cfg(all(test, feature = "std"))]
before_output_install: Arc::new(std::sync::Mutex::new(None)),
#[cfg(all(test, feature = "std"))]
while_outputs_finish: Arc::new(std::sync::Mutex::new(None)),
}
}
}
#[cfg(all(test, feature = "std"))]
pub(crate) type OutputInstallHook = Box<dyn FnOnce() + Send>;
#[cfg(all(test, feature = "std"))]
impl Config {
pub(crate) fn arm_before_output_install(&self, hook: impl FnOnce() + Send + 'static) {
#[expect(clippy::unwrap_used, reason = "test-only seam")]
self.before_output_install
.lock()
.unwrap()
.replace(Box::new(hook));
}
pub(crate) fn fire_before_output_install(&self) {
#[expect(clippy::unwrap_used, reason = "test-only seam")]
let hook = self.before_output_install.lock().unwrap().take();
if let Some(hook) = hook {
hook();
}
}
pub(crate) fn arm_while_outputs_finish(&self, hook: impl FnOnce() + Send + 'static) {
#[expect(clippy::unwrap_used, reason = "test-only seam")]
self.while_outputs_finish
.lock()
.unwrap()
.replace(Box::new(hook));
}
pub(crate) fn fire_while_outputs_finish(&self) {
#[expect(clippy::unwrap_used, reason = "test-only seam")]
let hook = self.while_outputs_finish.lock().unwrap().take();
if let Some(hook) = hook {
hook();
}
}
}
#[cfg_attr(
not(feature = "std"),
allow(
dead_code,
reason = "directory-lock filename used only by the std-gated lock-acquisition path"
)
)]
pub(crate) const DIRECTORY_LOCK_FILE: &str = "LOCK";
#[cfg(feature = "std")]
pub(crate) enum DirectoryLock {
Acquire,
Held(Option<Box<dyn crate::fs::FsFile>>),
}
#[cfg(feature = "std")]
pub(crate) fn acquire_directory_lock(
fs: &dyn Fs,
dir: &Path,
enabled: bool,
) -> crate::Result<Option<Box<dyn crate::fs::FsFile>>> {
if !enabled {
return Ok(None);
}
let lock_path = dir.join(DIRECTORY_LOCK_FILE);
let file = fs.open(
&lock_path,
&crate::fs::FsOpenOptions::new()
.read(true)
.write(true)
.create(true)
.write_origin(crate::fs::WriteOrigin::Metadata),
)?;
if file.try_lock_exclusive()? {
Ok(Some(file))
} else {
Err(crate::Error::Locked(dir.display().to_string()))
}
}
impl Config {
#[cfg(feature = "std")]
pub fn new<P: AsRef<Path>>(
path: P,
seqno: SequenceNumberCounter,
visible_seqno: SequenceNumberCounter,
) -> Self {
Self {
path: absolute_path(path.as_ref()),
seqno: Arc::new(seqno),
visible_seqno: Arc::new(visible_seqno),
..Default::default()
}
}
#[must_use]
pub fn with_fs<F: Fs>(mut self, fs: F) -> Self {
self.fs = Arc::new(fs);
self
}
#[must_use]
pub fn with_shared_fs(mut self, fs: Arc<dyn Fs>) -> Self {
self.fs = fs;
self
}
pub fn open(self) -> crate::Result<AnyTree> {
self.open_with(
#[cfg(feature = "std")]
DirectoryLock::Acquire,
crate::tree::NewFolderNames::Synced,
)
.map(|(tree, _)| tree)
}
pub fn open_many(configs: impl IntoIterator<Item = Self>) -> crate::Result<Vec<AnyTree>> {
crate::tree::open_batch(configs)
}
pub(crate) fn open_with(
self,
#[cfg(feature = "std")] lock: DirectoryLock,
names: crate::tree::NewFolderNames,
) -> crate::Result<(AnyTree, Option<crate::tree::Creation>)> {
#[cfg(zstd_any)]
if self
.encryption
.as_ref()
.is_some_and(|enc| !enc.supports_aad_block_path())
{
return Err(crate::Error::Encrypt(
"encryption provider does not implement the AAD-bound block path \
(encrypt_block_aad / decrypt_block_aad) required for encrypted \
blocks on a zstd build",
));
}
Ok(if self.kv_separation_opts.is_some() {
let (tree, creation) = BlobTree::open(
self,
#[cfg(feature = "std")]
lock,
names,
)?;
(AnyTree::Blob(tree), creation)
} else {
let (tree, creation) = Tree::open(
self,
#[cfg(feature = "std")]
lock,
names,
)?;
(AnyTree::Standard(tree), creation)
})
}
#[cfg(feature = "std")]
pub fn restore_pinned(
self,
layout: &[u8],
entries: impl IntoIterator<Item = crate::PinnedEntry>,
range_tombstones: impl IntoIterator<Item = crate::PinnedRangeTombstone>,
) -> crate::Result<AnyTree> {
crate::pin::restore(self, layout, entries, range_tombstones)
}
#[cfg(zstd_any)]
pub(crate) fn current_zstd_dictionaries(&self) -> crate::compression::ZstdDictionaries {
self.zstd_dictionaries.load().as_ref().clone()
}
#[cfg(zstd_any)]
pub(crate) fn install_own_zstd_dictionaries(&mut self) -> crate::Result<()> {
let folder = self.path.join(crate::file::DICTS_FOLDER);
crate::dicts::sweep_temps(&*self.fs, &folder)?;
let dicts = crate::dicts::read_all(&*self.fs, &folder, self.encryption.as_deref())?;
self.install_zstd_dictionary_set(dicts);
Ok(())
}
#[cfg(zstd_any)]
pub(crate) fn install_own_zstd_dictionaries_skipping_damaged(
&mut self,
) -> crate::Result<alloc::vec::Vec<crate::file::DictId>> {
let folder = self.path.join(crate::file::DICTS_FOLDER);
crate::dicts::sweep_temps(&*self.fs, &folder)?;
let (dicts, damaged) = crate::dicts::read_all_skipping_damaged(
&*self.fs,
&folder,
self.encryption.as_deref(),
)?;
self.install_zstd_dictionary_set(dicts);
Ok(damaged)
}
#[cfg(zstd_any)]
fn install_zstd_dictionary_set(&mut self, mut dicts: crate::compression::ZstdDictionaries) {
if let Some(supplied) = self.zstd_dictionary.clone() {
dicts = dicts.with(supplied);
}
if let Some(supplied) = self
.kv_separation_opts
.as_ref()
.and_then(|o| o.zstd_dictionary.clone())
{
dicts = dicts.with(supplied);
}
self.zstd_dictionaries = Arc::new(arc_swap::ArcSwap::from_pointee(dicts));
}
#[cfg(feature = "std")]
pub fn new_with_generators<P: AsRef<Path>>(
path: P,
seqno: SharedSequenceNumberGenerator,
visible_seqno: SharedSequenceNumberGenerator,
) -> Self {
Self {
path: absolute_path(path.as_ref()),
seqno,
visible_seqno,
..Default::default()
}
}
}
#[cfg(all(test, zstd_any))]
mod tests;
impl Config {
fn route_for_level(&self, level: u8) -> Option<&LevelRoute> {
self.level_routes
.as_ref()?
.iter()
.find(|route| route.levels.contains(&level))
}
#[must_use]
pub fn tables_folder_for_level(&self, level: u8) -> (PathBuf, Arc<dyn Fs>) {
match self.route_for_level(level) {
Some(route) => (route.path.join(TABLES_FOLDER), route.fs.clone()),
None => (self.path.join(TABLES_FOLDER), self.fs.clone()),
}
}
#[must_use]
pub(crate) fn min_available_space(&self) -> u64 {
let mut free = self.fs.available_space(&self.path).unwrap_or(u64::MAX);
if let Some(routes) = &self.level_routes {
for route in routes {
free = free.min(route.fs.available_space(&route.path).unwrap_or(u64::MAX));
}
}
free
}
#[must_use]
pub fn all_tables_folders(&self) -> Vec<(PathBuf, Arc<dyn Fs>)> {
let primary_fs: Arc<dyn Fs> = self.fs.clone();
let mut folders: Vec<(PathBuf, Arc<dyn Fs>)> =
vec![(self.path.join(TABLES_FOLDER), primary_fs)];
if let Some(routes) = &self.level_routes {
for route in routes {
let folder = route.path.join(TABLES_FOLDER);
if !folders.iter().any(|(p, _)| *p == folder) {
folders.push((folder, route.fs.clone()));
}
}
}
folders
}
#[must_use]
pub fn level_routes(mut self, routes: Vec<LevelRoute>) -> Self {
for route in &routes {
assert!(
route.levels.start < route.levels.end,
"empty or inverted level route range: {:?}",
route.levels,
);
}
for (i, a) in routes.iter().enumerate() {
for b in routes.iter().skip(i + 1) {
assert!(
a.levels.end <= b.levels.start || b.levels.end <= a.levels.start,
"overlapping level routes: {:?} and {:?}",
a.levels,
b.levels,
);
}
}
self.level_routes = if routes.is_empty() {
None
} else {
Some(
routes
.into_iter()
.map(|mut r| {
r.path = absolute_path(&r.path);
r
})
.collect(),
)
};
self
}
#[must_use]
pub fn seqno_generator(mut self, generator: SharedSequenceNumberGenerator) -> Self {
self.seqno = generator;
self
}
#[must_use]
pub fn visible_seqno_generator(mut self, generator: SharedSequenceNumberGenerator) -> Self {
self.visible_seqno = generator;
self
}
#[must_use]
pub fn use_cache(mut self, cache: Arc<Cache>) -> Self {
self.cache = cache;
self
}
#[must_use]
pub fn use_descriptor_table(mut self, descriptor_table: Option<Arc<DescriptorTable>>) -> Self {
self.descriptor_table = descriptor_table;
self
}
#[must_use]
pub fn expect_point_read_hits(mut self, b: bool) -> Self {
self.expect_point_read_hits = b;
self
}
#[must_use]
pub fn compaction_output_alignment(mut self, enabled: bool) -> Self {
self.compaction_output_cuts = if enabled {
OutputCuts::Aligned
} else {
OutputCuts::TargetSize
};
self
}
#[must_use]
pub fn page_ecc(mut self, enabled: bool) -> Self {
self.page_ecc = enabled;
self
}
#[must_use]
pub fn with_directory_lock(mut self, enabled: bool) -> Self {
self.directory_lock = enabled;
self
}
#[cfg(feature = "std")]
#[must_use]
pub fn with_recovery_progress(mut self, progress: Arc<crate::RecoveryProgress>) -> Self {
self.recovery_progress = Some(progress);
self
}
#[must_use]
pub fn ecc_scheme(mut self, scheme: crate::runtime_config::EccScheme) -> Self {
self.initial_runtime_config.ecc_scheme = scheme;
self
}
#[must_use]
pub fn disable_cow_on_sst_files(mut self, enabled: bool) -> Self {
self.initial_runtime_config.disable_cow_on_sst_files = enabled;
self
}
#[must_use]
pub fn use_reflink_for_checkpoint(mut self, enabled: bool) -> Self {
self.initial_runtime_config.use_reflink_for_checkpoint = enabled;
self
}
#[must_use]
pub fn with_runtime_config(mut self, runtime: crate::runtime_config::RuntimeConfig) -> Self {
let kept = core::mem::replace(&mut self.initial_runtime_config, runtime);
self.initial_runtime_config.data_block_compression_policy =
kept.data_block_compression_policy;
self.initial_runtime_config.index_block_compression_policy =
kept.index_block_compression_policy;
self.initial_runtime_config.blob_compression = kept.blob_compression;
self
}
#[must_use]
pub fn filter_block_partitioning_policy(mut self, policy: PinningPolicy) -> Self {
self.filter_block_partitioning_policy = policy;
self
}
#[must_use]
pub fn index_block_partitioning_policy(mut self, policy: PinningPolicy) -> Self {
self.index_block_partitioning_policy = policy;
self
}
#[must_use]
pub fn filter_block_pinning_policy(mut self, policy: PinningPolicy) -> Self {
self.filter_block_pinning_policy = policy;
self
}
#[must_use]
pub fn index_block_pinning_policy(mut self, policy: PinningPolicy) -> Self {
self.index_block_pinning_policy = policy;
self
}
#[must_use]
pub fn data_block_restart_interval_policy(mut self, policy: RestartIntervalPolicy) -> Self {
assert!(
policy.iter().all(|interval| *interval > 0),
"data block restart interval must be greater than zero",
);
self.data_block_restart_interval_policy = policy;
self
}
#[must_use]
pub fn index_block_restart_interval_policy(mut self, policy: RestartIntervalPolicy) -> Self {
assert!(
policy.iter().all(|interval| *interval > 0),
"index block restart interval must be greater than zero",
);
self.index_block_restart_interval_policy = policy;
self
}
#[must_use]
pub fn filter_policy(mut self, policy: FilterPolicy) -> Self {
self.filter_policy = policy;
self
}
#[must_use]
pub fn filter_advisor(mut self, advisor: Option<FilterAdvisor>) -> Self {
self.filter_advisor = advisor;
self
}
#[must_use]
pub fn locator_policy(mut self, policy: LocatorPolicy) -> Self {
self.locator_policy = policy;
self
}
#[must_use]
pub fn data_block_compression_policy(mut self, policy: CompressionPolicy) -> Self {
self.initial_runtime_config.data_block_compression_policy = policy;
self
}
#[must_use]
pub fn index_block_compression_policy(mut self, policy: CompressionPolicy) -> Self {
self.initial_runtime_config.index_block_compression_policy = policy;
self
}
#[must_use]
pub fn blob_compression(mut self, compression: CompressionType) -> Self {
self.initial_runtime_config.blob_compression = compression;
self
}
#[must_use]
pub fn blob_cache_fill(mut self, fill: BlobCacheFill) -> Self {
self.initial_runtime_config.blob_cache_fill = fill;
self
}
#[must_use]
pub fn data_block_size_policy(mut self, policy: BlockSizePolicy) -> Self {
self.data_block_size_policy = policy;
self
}
#[must_use]
pub fn index_block_partition_size_policy(mut self, policy: BlockSizePolicy) -> Self {
self.index_block_partition_size_policy = policy;
self
}
#[must_use]
pub fn filter_block_partition_size_policy(mut self, policy: BlockSizePolicy) -> Self {
self.filter_block_partition_size_policy = policy;
self
}
#[must_use]
pub fn columnar_row_group_size_policy(mut self, policy: BlockSizePolicy) -> Self {
self.columnar_row_group_size_policy = policy;
self
}
#[must_use]
pub fn columnar_page_size_policy(mut self, policy: BlockSizePolicy) -> Self {
self.columnar_page_size_policy = policy;
self
}
#[must_use]
pub fn column_encoding_policy(mut self, policy: ColumnEncodingPolicy) -> Self {
self.column_encoding_policy = policy;
self
}
#[must_use]
pub fn columnar_read_budget(mut self, budget: ReadBudget) -> Self {
self.columnar_read_budget = budget;
self
}
#[must_use]
pub fn columnar_scan_budget(mut self, bytes: u64) -> Self {
self.columnar_scan_budget = bytes;
self
}
#[must_use]
pub fn multi_get_metadata_budget(mut self, bytes: u64) -> Self {
self.multi_get_metadata_budget = bytes;
self
}
#[must_use]
pub fn data_block_hash_ratio_policy(mut self, policy: HashRatioPolicy) -> Self {
self.data_block_hash_ratio_policy = policy;
self
}
#[must_use]
pub fn with_kv_separation(mut self, opts: Option<KvSeparationOptions>) -> Self {
self.kv_separation_opts = opts;
self
}
#[must_use]
pub fn with_compaction_filter_factory(mut self, factory: Option<Arc<dyn Factory>>) -> Self {
self.compaction_filter_factory = factory;
self
}
#[must_use]
pub fn prefix_extractor(mut self, extractor: Arc<dyn PrefixExtractor>) -> Self {
self.prefix_extractor = Some(extractor);
self
}
#[must_use]
pub fn key_domains(mut self, domains: Arc<dyn crate::KeyDomains>) -> Self {
self.key_domains = Some(domains);
self
}
#[must_use]
pub fn with_merge_operator(mut self, op: Option<Arc<dyn MergeOperator>>) -> Self {
self.merge_operator = op;
self
}
#[must_use]
pub fn comparator(mut self, comparator: SharedComparator) -> Self {
self.comparator = comparator;
self
}
#[must_use]
pub fn with_encryption(mut self, encryption: Option<Arc<dyn EncryptionProvider>>) -> Self {
self.encryption = encryption;
self
}
#[must_use]
pub fn with_clock(mut self, clock: Arc<dyn crate::Clock>) -> Self {
self.clock = Some(clock);
self
}
#[must_use]
pub fn manifest_recovery_mode(mut self, mode: ManifestRecoveryMode) -> Self {
self.manifest_recovery_mode = mode;
self
}
#[must_use]
pub fn sync_mode(mut self, mode: SyncMode) -> Self {
self.sync_mode = mode;
self
}
#[must_use]
pub fn flush_durability(mut self, durability: FlushDurability) -> Self {
self.flush_durability = durability;
self
}
#[must_use]
pub fn writeback_bytes(mut self, bytes: u64) -> Self {
self.writeback_bytes = bytes;
self
}
#[must_use]
pub fn table_target_size(mut self, bytes: u64) -> Self {
self.table_target_size = bytes;
self
}
#[must_use]
pub fn manifest_log_rotate_bytes(mut self, bytes: u64) -> Self {
self.manifest_log_rotate_bytes = bytes;
self
}
#[must_use]
pub fn table_file_pool(mut self, files: usize) -> Self {
self.table_file_pool = files;
self
}
#[doc(hidden)]
#[must_use]
pub fn table_file_refill_inline(mut self, inline: bool) -> Self {
use crate::table::file_pool::RefillOn;
self.table_file_refill = if inline {
RefillOn::ClaimingThread
} else {
RefillOn::Background
};
self
}
#[must_use]
pub fn repair_retention_floor(mut self, floor: crate::SeqNo) -> Self {
self.repair_retention_floor = floor;
self
}
#[must_use]
pub fn compaction_rate_limit(mut self, bytes_per_sec: u64) -> Self {
self.compaction_rate_limit = bytes_per_sec;
self
}
#[must_use]
pub fn compaction_rate_limiter(
mut self,
limiter: Arc<crate::rate_limiter::RateLimiter>,
) -> Self {
self.compaction_rate_limiter = Some(limiter);
self
}
#[must_use]
pub fn compaction_capacity(mut self, pool: Arc<crate::CapacityPool>) -> Self {
self.compaction_capacity = Some(pool);
self
}
pub(crate) fn tree_compaction_capacity(&self) -> Arc<crate::CapacityPool> {
self.compaction_capacity.clone().unwrap_or_default()
}
#[must_use]
pub fn write_rate_limit(mut self, bytes_per_sec: u64) -> Self {
self.write_rate_limit = bytes_per_sec;
self
}
#[must_use]
pub fn write_rate_limiter(mut self, limiter: Arc<crate::rate_limiter::RateLimiter>) -> Self {
self.write_rate_limiter = Some(limiter);
self
}
#[cfg(feature = "std")]
#[must_use]
pub fn write_buffer_budget(
mut self,
budget: Arc<crate::backpressure::WriteBufferBudget>,
) -> Self {
self.write_buffer_budget = Some(budget);
self
}
pub(crate) fn tree_write_rate_limiter(&self) -> Arc<crate::rate_limiter::RateLimiter> {
self.write_rate_limiter.clone().unwrap_or_else(|| {
Arc::new(crate::rate_limiter::RateLimiter::new(self.write_rate_limit))
})
}
pub(crate) fn tree_compaction_rate_limiter(&self) -> Arc<crate::rate_limiter::RateLimiter> {
self.compaction_rate_limiter.clone().unwrap_or_else(|| {
Arc::new(crate::rate_limiter::RateLimiter::new(
self.compaction_rate_limit,
))
})
}
#[cfg(feature = "std")]
#[must_use]
pub fn compaction_threads(mut self, threads: usize) -> Self {
self.compaction_threads = threads.max(1);
self
}
#[cfg(feature = "std")]
#[must_use]
pub fn parallel_compression_inline_below(mut self, bytes: Option<u32>) -> Self {
self.parallel_compression_inline_below = bytes;
self
}
#[cfg(feature = "std")]
pub(crate) fn parallel_compression(&self) -> Option<crate::table::writer::ParallelCompression> {
self.compaction_pool
.clone()
.map(|spawner| crate::table::writer::ParallelCompression {
spawner,
threads: self.compaction_threads,
inline_below: self.parallel_compression_inline_below,
})
}
#[cfg(feature = "std")]
#[must_use]
pub fn subcompaction_min_bytes(mut self, bytes: u64) -> Self {
self.subcompaction_min_bytes = bytes;
self
}
#[cfg(feature = "std")]
#[must_use]
pub fn compaction_pool(
mut self,
pool: Option<Arc<dyn crate::table::writer::CompactionSpawner>>,
) -> Self {
self.compaction_pool = pool;
self
}
#[cfg(zstd_any)]
#[must_use]
pub fn zstd_dictionary(
mut self,
dictionary: Option<Arc<crate::compression::ZstdDictionary>>,
) -> Self {
self.zstd_dictionary = dictionary;
self
}
}
#[cfg(test)]
mod builder_tests;