lsm_tree/compaction/
fifo.rs1use super::{Choice, CompactionStrategy};
6use crate::{
7 compaction::state::CompactionState, config::Config, time::unix_timestamp, version::Version,
8 HashSet, KvPair,
9};
10
11#[doc(hidden)]
12pub const NAME: &str = "FifoCompaction";
13
14#[derive(Clone)]
32pub struct Strategy {
33 pub limit: u64,
35
36 pub ttl_seconds: Option<u64>,
38}
39
40impl Strategy {
41 #[must_use]
43 pub fn new(limit: u64, ttl_seconds: Option<u64>) -> Self {
44 Self { limit, ttl_seconds }
45 }
46}
47
48impl CompactionStrategy for Strategy {
49 fn get_name(&self) -> &'static str {
50 NAME
51 }
52
53 fn get_config(&self) -> Vec<KvPair> {
54 vec![
55 (
56 crate::UserKey::from("fifo_limit"),
57 crate::UserValue::from(self.limit.to_le_bytes()),
58 ),
59 (
60 crate::UserKey::from("fifo_ttl"),
61 crate::UserValue::from(if self.ttl_seconds.is_some() {
62 [1u8]
63 } else {
64 [0u8]
65 }),
66 ),
67 (
68 crate::UserKey::from("fifo_ttl_seconds"),
69 crate::UserValue::from(self.ttl_seconds.map(u64::to_le_bytes).unwrap_or_default()),
70 ),
71 ]
72 }
73
74 fn choose(&self, version: &Version, _: &Config, state: &CompactionState) -> Choice {
75 let first_level = version.l0();
76
77 if first_level.is_empty() {
79 return Choice::DoNothing;
80 }
81
82 assert!(first_level.is_disjoint(), "L0 needs to be disjoint");
83
84 assert!(
85 !version.level_is_busy(0, state.hidden_set()),
86 "FIFO compaction never compacts",
87 );
88
89 let db_size = first_level.size() + version.blob_files.on_disk_size();
91
92 let mut ids_to_drop = HashSet::default();
93
94 let ttl_cutoff = match self.ttl_seconds {
97 Some(s) if s > 0 => Some(
98 unix_timestamp()
99 .as_nanos()
100 .saturating_sub(u128::from(s) * 1_000_000_000u128),
101 ),
102 _ => None,
103 };
104
105 let mut ttl_dropped_bytes = 0u64;
106 let mut alive = Vec::new();
107
108 for table in first_level.iter().flat_map(|run| run.iter()) {
109 let expired =
110 ttl_cutoff.is_some_and(|cutoff| u128::from(table.metadata.created_at) <= cutoff);
111
112 if expired {
113 ids_to_drop.insert(table.id());
114 let linked_blob_file_bytes = table.referenced_blob_bytes().unwrap_or_default();
115 ttl_dropped_bytes += table.file_size() + linked_blob_file_bytes;
116 } else {
117 alive.push(table);
118 }
119 }
120
121 let size_after_ttl = db_size.saturating_sub(ttl_dropped_bytes);
123
124 if size_after_ttl > self.limit {
126 let overshoot = size_after_ttl - self.limit;
127
128 let mut collected_bytes = 0;
129
130 alive.sort_by_key(|t| t.metadata.created_at);
132
133 for table in alive {
134 if collected_bytes >= overshoot {
135 break;
136 }
137
138 ids_to_drop.insert(table.id());
139
140 let linked_blob_file_bytes = table.referenced_blob_bytes().unwrap_or_default();
141 collected_bytes += table.file_size() + linked_blob_file_bytes;
142 }
143 }
144
145 if ids_to_drop.is_empty() {
146 Choice::DoNothing
147 } else {
148 Choice::Drop(ids_to_drop)
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::Strategy;
156 use crate::{AbstractTree, Config, KvSeparationOptions, SequenceNumberCounter};
157 use std::sync::Arc;
158
159 #[test]
160 fn fifo_empty_levels() -> crate::Result<()> {
161 let dir = tempfile::tempdir()?;
162 let tree = Config::new(
163 dir.path(),
164 SequenceNumberCounter::default(),
165 SequenceNumberCounter::default(),
166 )
167 .open()?;
168
169 let fifo = Arc::new(Strategy::new(1, None));
170 tree.compact(fifo, 0)?;
171
172 assert_eq!(0, tree.table_count());
173 Ok(())
174 }
175
176 #[test]
177 fn fifo_below_limit() -> crate::Result<()> {
178 let dir = tempfile::tempdir()?;
179 let tree = Config::new(
180 dir.path(),
181 SequenceNumberCounter::default(),
182 SequenceNumberCounter::default(),
183 )
184 .open()?;
185
186 for i in 0..4u8 {
187 tree.insert([b'k', i].as_slice(), "v", u64::from(i));
188 tree.flush_active_memtable(u64::from(i))?;
189 }
190
191 let before = tree.table_count();
192 let fifo = Arc::new(Strategy::new(u64::MAX, None));
193 tree.compact(fifo, 4)?;
194
195 assert_eq!(before, tree.table_count());
196 Ok(())
197 }
198
199 #[test]
200 fn fifo_more_than_limit() -> crate::Result<()> {
201 let dir = tempfile::tempdir()?;
202 let tree = Config::new(
203 dir.path(),
204 SequenceNumberCounter::default(),
205 SequenceNumberCounter::default(),
206 )
207 .open()?;
208
209 for i in 0..4u8 {
210 tree.insert([b'k', i].as_slice(), "v", u64::from(i));
211 tree.flush_active_memtable(u64::from(i))?;
212 }
213
214 let before = tree.table_count();
215 let fifo = Arc::new(Strategy::new(1, None));
217 tree.compact(fifo, 4)?;
218
219 assert!(tree.table_count() < before);
220 Ok(())
221 }
222
223 #[test]
224 fn fifo_more_than_limit_blobs() -> crate::Result<()> {
225 let dir = tempfile::tempdir()?;
226 let tree = Config::new(
227 dir.path(),
228 SequenceNumberCounter::default(),
229 SequenceNumberCounter::default(),
230 )
231 .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1)))
232 .open()?;
233
234 for i in 0..3u8 {
235 tree.insert([b'k', i].as_slice(), "$", u64::from(i));
236 tree.flush_active_memtable(u64::from(i))?;
237 }
238
239 let before = tree.table_count();
240 let fifo = Arc::new(Strategy::new(1, None));
241 tree.compact(fifo, 3)?;
242
243 assert!(tree.table_count() < before);
244 Ok(())
245 }
246
247 #[test]
248 fn fifo_ttl() -> crate::Result<()> {
249 let dir = tempfile::tempdir()?;
250 let tree = Config::new(
251 dir.path(),
252 SequenceNumberCounter::default(),
253 SequenceNumberCounter::default(),
254 )
255 .open()?;
256
257 crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_000)));
259 tree.insert("a", "1", 0);
260 tree.flush_active_memtable(0)?;
261
262 crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_005)));
264 tree.insert("b", "2", 1);
265 tree.flush_active_memtable(1)?;
266
267 crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(1_011)));
269
270 assert_eq!(2, tree.table_count());
271
272 let fifo = Arc::new(Strategy::new(u64::MAX, Some(10)));
273 tree.compact(fifo, 2)?;
274
275 assert_eq!(1, tree.table_count());
276
277 crate::time::set_unix_timestamp_for_test(None);
279 Ok(())
280 }
281
282 #[test]
283 fn fifo_ttl_then_limit_additional_drops_blob_unit() -> crate::Result<()> {
284 let dir = tempfile::tempdir()?;
285 let tree = Config::new(
286 dir.path(),
287 SequenceNumberCounter::default(),
288 SequenceNumberCounter::default(),
289 )
290 .with_kv_separation(Some(KvSeparationOptions::default().separation_threshold(1)))
291 .open()?;
292
293 tree.insert("a", "$", 0);
295 tree.flush_active_memtable(0)?;
296 tree.insert("b", "$", 1);
297 tree.flush_active_memtable(1)?;
298
299 crate::time::set_unix_timestamp_for_test(Some(std::time::Duration::from_secs(10_000_000)));
300
301 let fifo = Arc::new(Strategy::new(1, Some(1)));
303 tree.compact(fifo, 2)?;
304
305 assert_eq!(0, tree.table_count());
306
307 crate::time::set_unix_timestamp_for_test(None);
308 Ok(())
309 }
310}