Skip to main content

scry_index/
config.rs

1//! Configuration for the learned index.
2
3/// Configuration parameters for [`LearnedMap`](crate::LearnedMap).
4#[derive(Debug, Clone)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Config {
7    /// Expansion factor for node arrays.
8    ///
9    /// Controls the ratio of array size to key count. A factor of 2.0 means
10    /// the array is twice as large as the number of keys (50% gaps), leaving
11    /// room for future inserts without conflicts.
12    ///
13    /// Higher values reduce conflicts but use more memory.
14    ///
15    /// Default: `2.0`. Must be `>= 1.0`.
16    pub expansion_factor: f64,
17
18    /// Whether to automatically rebuild the tree after a threshold of inserts.
19    ///
20    /// When enabled, the map periodically rebuilds with optimal FMCD model
21    /// fitting to keep the tree shallow and lookups fast. The rebuild threshold
22    /// is `(len / 4).clamp(16, 10_000)`.
23    ///
24    /// Default: `true`.
25    pub auto_rebuild: bool,
26
27    /// Maximum subtree depth before a localized rebuild is triggered.
28    ///
29    /// When an insert descends through more child nodes than this threshold,
30    /// the inserting thread rebuilds the degraded subtree inline. Only applies
31    /// when `auto_rebuild` is `true`. Set to `usize::MAX` to disable.
32    ///
33    /// Default: `8`.
34    pub rebuild_depth_threshold: usize,
35
36    /// Maximum tombstone ratio before a localized rebuild is triggered.
37    ///
38    /// When a node's tombstone count (slots nulled by remove) exceeds this
39    /// fraction of its capacity, the removing thread rebuilds the parent
40    /// subtree inline. Only applies when `auto_rebuild` is `true`.
41    ///
42    /// Set to `1.0` to disable tombstone compaction.
43    ///
44    /// Default: `0.5` (compact when >50% of slots are tombstones).
45    pub tombstone_ratio_threshold: f64,
46
47    /// Extra key range headroom for model fitting.
48    ///
49    /// When greater than 0, the model covers `(1 + range_headroom)` times the
50    /// actual key range, leaving space for keys beyond the current max. This
51    /// prevents all out-of-range keys from clamping to the last slot.
52    ///
53    /// The array size grows proportionally to maintain per-key slot density.
54    /// Only applies during model fitting (bulk load and rebuild).
55    ///
56    /// Default: `0.0` (no headroom). Root rebuilds internally use `1.0`.
57    pub range_headroom: f64,
58}
59
60impl Config {
61    /// Create a new configuration with default values.
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Set the expansion factor.
67    ///
68    /// # Panics
69    ///
70    /// Panics if `factor < 1.0`.
71    pub fn expansion_factor(mut self, factor: f64) -> Self {
72        assert!(factor >= 1.0, "expansion_factor must be >= 1.0");
73        self.expansion_factor = factor;
74        self
75    }
76
77    /// Enable or disable automatic rebuilds.
78    pub fn auto_rebuild(mut self, enabled: bool) -> Self {
79        self.auto_rebuild = enabled;
80        self
81    }
82
83    /// Set the maximum subtree depth before localized rebuild triggers.
84    pub fn rebuild_depth_threshold(mut self, threshold: usize) -> Self {
85        self.rebuild_depth_threshold = threshold;
86        self
87    }
88
89    /// Set the tombstone ratio threshold for compaction.
90    ///
91    /// # Panics
92    ///
93    /// Panics if `threshold` is not in `(0.0, 1.0]`.
94    pub fn tombstone_ratio_threshold(mut self, threshold: f64) -> Self {
95        assert!(
96            threshold > 0.0 && threshold <= 1.0,
97            "tombstone_ratio_threshold must be in (0.0, 1.0], got {threshold}"
98        );
99        self.tombstone_ratio_threshold = threshold;
100        self
101    }
102
103    /// Set the key range headroom for model fitting.
104    ///
105    /// # Panics
106    ///
107    /// Panics if `headroom < 0.0`.
108    pub fn range_headroom(mut self, headroom: f64) -> Self {
109        assert!(headroom >= 0.0, "range_headroom must be >= 0.0");
110        self.range_headroom = headroom;
111        self
112    }
113}
114
115impl Default for Config {
116    fn default() -> Self {
117        Self {
118            expansion_factor: 2.0,
119            auto_rebuild: true,
120            rebuild_depth_threshold: 8,
121            tombstone_ratio_threshold: 0.5,
122            range_headroom: 0.0,
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn default_config() {
133        let config = Config::default();
134        assert!((config.expansion_factor - 2.0).abs() < f64::EPSILON);
135    }
136
137    #[test]
138    fn builder_pattern() {
139        let config = Config::new().expansion_factor(3.0);
140        assert!((config.expansion_factor - 3.0).abs() < f64::EPSILON);
141    }
142
143    #[test]
144    #[should_panic(expected = "expansion_factor must be >= 1.0")]
145    fn reject_low_expansion() {
146        Config::new().expansion_factor(0.5);
147    }
148
149    #[test]
150    fn tombstone_threshold_builder() {
151        let config = Config::new().tombstone_ratio_threshold(0.75);
152        assert!((config.tombstone_ratio_threshold - 0.75).abs() < f64::EPSILON);
153    }
154
155    #[test]
156    fn tombstone_threshold_default() {
157        let config = Config::default();
158        assert!((config.tombstone_ratio_threshold - 0.5).abs() < f64::EPSILON);
159    }
160
161    #[test]
162    #[should_panic(expected = "tombstone_ratio_threshold must be in (0.0, 1.0]")]
163    fn reject_zero_tombstone_threshold() {
164        Config::new().tombstone_ratio_threshold(0.0);
165    }
166
167    #[test]
168    #[should_panic(expected = "tombstone_ratio_threshold must be in (0.0, 1.0]")]
169    fn reject_high_tombstone_threshold() {
170        Config::new().tombstone_ratio_threshold(1.5);
171    }
172}