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}