lsm_tree/config/
mod.rs

1// Copyright (c) 2024-present, fjall-rs
2// This source code is licensed under both the Apache 2.0 and MIT License
3// (found in the LICENSE-* files in the repository)
4
5mod block_size;
6mod compression;
7mod filter;
8mod hash_ratio;
9mod pinning;
10mod restart_interval;
11
12pub use block_size::BlockSizePolicy;
13pub use compression::CompressionPolicy;
14pub use filter::{BloomConstructionPolicy, FilterPolicy, FilterPolicyEntry};
15pub use hash_ratio::HashRatioPolicy;
16pub use pinning::PinningPolicy;
17pub use restart_interval::RestartIntervalPolicy;
18
19use crate::{
20    path::absolute_path, version::DEFAULT_LEVEL_COUNT, AnyTree, BlobTree, Cache, CompressionType,
21    DescriptorTable, Tree,
22};
23use std::{
24    path::{Path, PathBuf},
25    sync::Arc,
26};
27
28/// LSM-tree type
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
30pub enum TreeType {
31    /// Standard LSM-tree, see [`Tree`]
32    Standard,
33
34    /// Key-value separated LSM-tree, see [`BlobTree`]
35    Blob,
36}
37
38impl From<TreeType> for u8 {
39    fn from(val: TreeType) -> Self {
40        match val {
41            TreeType::Standard => 0,
42            TreeType::Blob => 1,
43        }
44    }
45}
46
47impl TryFrom<u8> for TreeType {
48    type Error = ();
49
50    fn try_from(value: u8) -> Result<Self, Self::Error> {
51        match value {
52            0 => Ok(Self::Standard),
53            1 => Ok(Self::Blob),
54            _ => Err(()),
55        }
56    }
57}
58
59const DEFAULT_FILE_FOLDER: &str = ".lsm.data";
60
61/// Options for key-value separation
62#[derive(Clone, Debug, PartialEq)]
63pub struct KvSeparationOptions {
64    /// What type of compression is used for blobs
65    pub compression: CompressionType,
66
67    /// Blob file (value log segment) target size in bytes
68    #[doc(hidden)]
69    pub file_target_size: u64,
70
71    /// Key-value separation threshold in bytes
72    #[doc(hidden)]
73    pub separation_threshold: u32,
74
75    pub(crate) staleness_threshold: f32,
76
77    pub(crate) age_cutoff: f32,
78}
79
80impl Default for KvSeparationOptions {
81    fn default() -> Self {
82        Self {
83            #[cfg(feature="lz4")]
84            compression:   CompressionType::Lz4,
85
86            #[cfg(not(feature="lz4"))]
87            compression: CompressionType::None,
88
89            file_target_size: /* 64 MiB */ 64 * 1_024 * 1_024,
90            separation_threshold: /* 1 KiB */ 1_024,
91
92            staleness_threshold: 0.33,
93            age_cutoff: 0.20,
94        }
95    }
96}
97
98impl KvSeparationOptions {
99    /// Sets the blob compression method.
100    #[must_use]
101    pub fn compression(mut self, compression: CompressionType) -> Self {
102        self.compression = compression;
103        self
104    }
105
106    /// Sets the target size of blob files.
107    ///
108    /// Smaller blob files allow more granular garbage collection
109    /// which allows lower space amp for lower write I/O cost.
110    ///
111    /// Larger blob files decrease the number of files on disk and maintenance
112    /// overhead.
113    ///
114    /// Defaults to 64 MiB.
115    #[must_use]
116    pub fn file_target_size(mut self, bytes: u64) -> Self {
117        self.file_target_size = bytes;
118        self
119    }
120
121    /// Sets the key-value separation threshold in bytes.
122    ///
123    /// Smaller value will reduce compaction overhead and thus write amplification,
124    /// at the cost of lower read performance.
125    ///
126    /// Defaults to 1 KiB.
127    #[must_use]
128    pub fn separation_threshold(mut self, bytes: u32) -> Self {
129        self.separation_threshold = bytes;
130        self
131    }
132
133    /// Sets the staleness threshold percentage.
134    ///
135    /// The staleness percentage determines how much a blob file needs to be fragmented to be
136    /// picked up by the garbage collection.
137    ///
138    /// Defaults to 33%.
139    #[must_use]
140    pub fn staleness_threshold(mut self, ratio: f32) -> Self {
141        self.staleness_threshold = ratio;
142        self
143    }
144
145    /// Sets the age cutoff threshold.
146    ///
147    /// Defaults to 20%.
148    #[must_use]
149    pub fn age_cutoff(mut self, ratio: f32) -> Self {
150        self.age_cutoff = ratio;
151        self
152    }
153}
154
155#[derive(Clone)]
156/// Tree configuration builder
157pub struct Config {
158    /// Folder path
159    #[doc(hidden)]
160    pub path: PathBuf,
161
162    /// Block cache to use
163    #[doc(hidden)]
164    pub cache: Arc<Cache>,
165
166    /// Descriptor table to use
167    #[doc(hidden)]
168    pub descriptor_table: Arc<DescriptorTable>,
169
170    /// Number of levels of the LSM tree (depth of tree)
171    ///
172    /// Once set, the level count is fixed (in the "manifest" file)
173    pub level_count: u8,
174
175    /// What type of compression is used for data blocks
176    pub data_block_compression_policy: CompressionPolicy,
177
178    /// What type of compression is used for index blocks
179    pub index_block_compression_policy: CompressionPolicy,
180
181    /// Restart interval inside data blocks
182    pub data_block_restart_interval_policy: RestartIntervalPolicy,
183
184    /// Restart interval inside index blocks
185    pub index_block_restart_interval_policy: RestartIntervalPolicy,
186
187    /// Block size of data blocks
188    pub data_block_size_policy: BlockSizePolicy,
189
190    /// Block size of index blocks
191    pub index_block_size_policy: BlockSizePolicy,
192
193    /// Whether to pin index blocks
194    pub index_block_pinning_policy: PinningPolicy,
195
196    /// Whether to pin filter blocks
197    pub filter_block_pinning_policy: PinningPolicy,
198
199    /// Data block hash ratio
200    pub data_block_hash_ratio_policy: HashRatioPolicy,
201
202    /// If `true`, the last level will not build filters, reducing the filter size of a database
203    /// by ~90% typically
204    pub(crate) expect_point_read_hits: bool,
205
206    /// Filter construction policy
207    pub filter_policy: FilterPolicy,
208
209    #[doc(hidden)]
210    pub kv_separation_opts: Option<KvSeparationOptions>,
211}
212
213impl Default for Config {
214    fn default() -> Self {
215        Self {
216            path: absolute_path(Path::new(DEFAULT_FILE_FOLDER)),
217            descriptor_table: Arc::new(DescriptorTable::new(256)),
218
219            cache: Arc::new(Cache::with_capacity_bytes(
220                /* 16 MiB */ 16 * 1_024 * 1_024,
221            )),
222
223            data_block_restart_interval_policy: RestartIntervalPolicy::all(16),
224            index_block_restart_interval_policy: RestartIntervalPolicy::all(1),
225
226            level_count: DEFAULT_LEVEL_COUNT,
227
228            data_block_size_policy: BlockSizePolicy::default(),
229            index_block_size_policy: BlockSizePolicy::default(),
230
231            index_block_pinning_policy: PinningPolicy::new(&[true, true, false]),
232            filter_block_pinning_policy: PinningPolicy::new(&[true, false]),
233
234            data_block_compression_policy: CompressionPolicy::default(),
235            index_block_compression_policy: CompressionPolicy::all(CompressionType::None),
236
237            data_block_hash_ratio_policy: HashRatioPolicy::all(0.0),
238
239            filter_policy: FilterPolicy::default(),
240
241            expect_point_read_hits: false,
242
243            kv_separation_opts: None,
244        }
245    }
246}
247
248impl Config {
249    /// Initializes a new config
250    pub fn new<P: AsRef<Path>>(path: P) -> Self {
251        Self {
252            path: absolute_path(path.as_ref()),
253            ..Default::default()
254        }
255    }
256
257    /// Sets the global cache.
258    ///
259    /// You can create a global [`Cache`] and share it between multiple
260    /// trees to cap global cache memory usage.
261    ///
262    /// Defaults to a cache with 16 MiB of capacity *per tree*.
263    #[must_use]
264    pub fn use_cache(mut self, cache: Arc<Cache>) -> Self {
265        self.cache = cache;
266        self
267    }
268
269    #[must_use]
270    #[doc(hidden)]
271    pub fn use_descriptor_table(mut self, descriptor_table: Arc<DescriptorTable>) -> Self {
272        self.descriptor_table = descriptor_table;
273        self
274    }
275
276    /// If `true`, the last level will not build filters, reducing the filter size of a database
277    /// by ~90% typically.
278    ///
279    /// **Enable this only if you know that point reads generally are expected to find a key-value pair.**
280    #[must_use]
281    pub fn expect_point_read_hits(mut self, b: bool) -> Self {
282        self.expect_point_read_hits = b;
283        self
284    }
285
286    /// Sets the pinning policy for filter blocks.
287    #[must_use]
288    pub fn filter_block_pinning_policy(mut self, policy: PinningPolicy) -> Self {
289        self.filter_block_pinning_policy = policy;
290        self
291    }
292
293    /// Sets the pinning policy for index blocks.
294    #[must_use]
295    pub fn index_block_pinning_policy(mut self, policy: PinningPolicy) -> Self {
296        self.index_block_pinning_policy = policy;
297        self
298    }
299
300    /// Sets the restart interval inside data blocks.
301    ///
302    /// A higher restart interval saves space while increasing lookup times
303    /// inside data blocks.
304    ///
305    /// Default = 16
306    #[must_use]
307    pub fn data_block_restart_interval_policy(mut self, policy: RestartIntervalPolicy) -> Self {
308        self.data_block_restart_interval_policy = policy;
309        self
310    }
311
312    // TODO: not supported yet in index blocks
313    // /// Sets the restart interval inside index blocks.
314    // ///
315    // /// A higher restart interval saves space while increasing lookup times
316    // /// inside index blocks.
317    // ///
318    // /// Default = 1
319    // #[must_use]
320    // pub fn index_block_restart_interval_policy(mut self, policy: RestartIntervalPolicy) -> Self {
321    //     self.index_block_restart_interval_policy = policy;
322    //     self
323    // }
324
325    /// Sets the filter construction policy.
326    #[must_use]
327    pub fn filter_policy(mut self, policy: FilterPolicy) -> Self {
328        self.filter_policy = policy;
329        self
330    }
331
332    /// Sets the compression method for data blocks.
333    #[must_use]
334    pub fn data_block_compression_policy(mut self, policy: CompressionPolicy) -> Self {
335        self.data_block_compression_policy = policy;
336        self
337    }
338
339    /// Sets the compression method for index blocks.
340    #[must_use]
341    pub fn index_block_compression_policy(mut self, policy: CompressionPolicy) -> Self {
342        self.index_block_compression_policy = policy;
343        self
344    }
345
346    // TODO: level count is fixed to 7 right now
347    // /// Sets the number of levels of the LSM tree (depth of tree).
348    // ///
349    // /// Defaults to 7, like `LevelDB` and `RocksDB`.
350    // ///
351    // /// Cannot be changed once set.
352    // ///
353    // /// # Panics
354    // ///
355    // /// Panics if `n` is 0.
356    // #[must_use]
357    // pub fn level_count(mut self, n: u8) -> Self {
358    //     assert!(n > 0);
359
360    //     self.level_count = n;
361    //     self
362    // }
363
364    /// Sets the data block size policy.
365    #[must_use]
366    pub fn data_block_size_policy(mut self, policy: BlockSizePolicy) -> Self {
367        self.data_block_size_policy = policy;
368        self
369    }
370
371    // TODO: 3.0.0 does nothing until we have partitioned indexes
372    // /// Sets the index block size policy.
373    // #[must_use]
374    // pub fn index_block_size_policy(mut self, policy: BlockSizePolicy) -> Self {
375    //     self.index_block_size_policy = policy;
376    //     self
377    // }
378
379    /// Sets the hash ratio policy for data blocks.
380    ///
381    /// If greater than 0.0, a hash index is embedded into data blocks that can speed up reads
382    /// inside the data block.
383    #[must_use]
384    pub fn data_block_hash_ratio_policy(mut self, policy: HashRatioPolicy) -> Self {
385        self.data_block_hash_ratio_policy = policy;
386        self
387    }
388
389    /// Toggles key-value separation.
390    #[must_use]
391    pub fn with_kv_separation(mut self, opts: Option<KvSeparationOptions>) -> Self {
392        self.kv_separation_opts = opts;
393        self
394    }
395
396    /// Opens a tree using the config.
397    ///
398    /// # Errors
399    ///
400    /// Will return `Err` if an IO error occurs.
401    pub fn open(self) -> crate::Result<AnyTree> {
402        Ok(if self.kv_separation_opts.is_some() {
403            AnyTree::Blob(BlobTree::open(self)?)
404        } else {
405            AnyTree::Standard(Tree::open(self)?)
406        })
407    }
408}