Line data Source code
1 : //! An LSM tree consists of multiple levels, each exponentially larger than the
2 : //! previous level. And each level consists of multiple "tiers". With tiered
3 : //! compaction, a level is compacted when it has accumulated more than N tiers,
4 : //! forming one tier on the next level.
5 : //!
6 : //! In the pageserver, we don't explicitly track the levels and tiers. Instead,
7 : //! we identify them by looking at the shapes of the layers. It's an easy task
8 : //! for a human, but it's not straightforward to come up with the exact
9 : //! rules. Especially if there are cases like interrupted, half-finished
10 : //! compactions, or highly skewed data distributions that have let us "skip"
11 : //! some levels. It's not critical to classify all cases correctly; at worst we
12 : //! delay some compaction work, and suffer from more read amplification, or we
13 : //! perform some unnecessary compaction work.
14 : //!
15 : //! `identify_level` performs that shape-matching.
16 : //!
17 : //! It returns a Level struct, which has `depth()` function to count the number
18 : //! of "tiers" in the level. The tier count is the max depth of stacked layers
19 : //! within the level. That's a good measure, because the point of compacting is
20 : //! to reduce read amplification, and the depth is what determines that.
21 : //!
22 : //! One interesting effect of this is that if we generate very small delta
23 : //! layers at L0, e.g. because the L0 layers are flushed by timeout rather than
24 : //! because they reach the target size, the L0 compaction will combine them to
25 : //! one larger file. But if the combined file is still smaller than the target
26 : //! file size, the file will still be considered to be part of L0 at the next
27 : //! iteration.
28 :
29 : use anyhow::bail;
30 : use std::collections::BTreeSet;
31 : use std::ops::Range;
32 : use utils::lsn::Lsn;
33 :
34 : use crate::interface::*;
35 :
36 : use tracing::{info, trace};
37 :
38 : pub struct Level<L> {
39 : pub lsn_range: Range<Lsn>,
40 : pub layers: Vec<L>,
41 : }
42 :
43 : /// Identify an LSN > `end_lsn` that partitions the LSN space, so that there are
44 : /// no layers that cross the boundary LSN.
45 : ///
46 : /// A further restriction is that all layers in the returned partition cover at
47 : /// most 'lsn_max_size' LSN bytes.
48 1011 : pub async fn identify_level<K, L>(
49 1011 : all_layers: Vec<L>,
50 1011 : end_lsn: Lsn,
51 1011 : lsn_max_size: u64,
52 1011 : ) -> anyhow::Result<Option<Level<L>>>
53 1011 : where
54 1011 : K: CompactionKey,
55 1011 : L: CompactionLayer<K> + Clone,
56 1011 : {
57 1011 : // filter out layers that are above the `end_lsn`, they are completely irrelevant.
58 1011 : let mut layers = Vec::new();
59 7039 : for l in all_layers {
60 6029 : if l.lsn_range().start < end_lsn && l.lsn_range().end > end_lsn {
61 : // shouldn't happen. Indicates that the caller passed a bogus
62 : // end_lsn.
63 1 : bail!("identify_level() called with end_lsn that does not partition the LSN space: end_lsn {} intersects with layer {}", end_lsn, l.short_id());
64 6028 : }
65 6028 : // include image layers sitting exacty at `end_lsn`.
66 6028 : let is_image = !l.is_delta();
67 6028 : if (is_image && l.lsn_range().start > end_lsn)
68 6028 : || (!is_image && l.lsn_range().start >= end_lsn)
69 : {
70 5 : continue;
71 6023 : }
72 6023 : layers.push(l);
73 : }
74 : // All the remaining layers either belong to this level, or are below it.
75 1010 : info!(
76 0 : "identify level at {}, size {}, num layers below: {}",
77 0 : end_lsn,
78 0 : lsn_max_size,
79 0 : layers.len()
80 : );
81 1010 : if layers.is_empty() {
82 90 : return Ok(None);
83 920 : }
84 920 :
85 920 : // Walk the ranges in LSN order.
86 920 : //
87 920 : // ----- end_lsn
88 920 : // |
89 920 : // |
90 920 : // v
91 920 : //
92 10262 : layers.sort_by_key(|l| l.lsn_range().end);
93 920 : let mut candidate_start_lsn = end_lsn;
94 920 : let mut candidate_layers: Vec<L> = Vec::new();
95 920 : let mut current_best_start_lsn = end_lsn;
96 920 : let mut current_best_layers: Vec<L> = Vec::new();
97 920 : let mut iter = layers.into_iter();
98 : loop {
99 2368 : let Some(l) = iter.next_back() else {
100 : // Reached end. Accept the last candidate
101 282 : current_best_start_lsn = candidate_start_lsn;
102 282 : current_best_layers.extend_from_slice(&std::mem::take(&mut candidate_layers));
103 282 : break;
104 : };
105 2086 : trace!(
106 0 : "inspecting {} for candidate {}, current best {}",
107 0 : l.short_id(),
108 : candidate_start_lsn,
109 : current_best_start_lsn
110 : );
111 :
112 2086 : let r = l.lsn_range();
113 2086 :
114 2086 : // Image layers don't restrict our choice of cutoff LSN
115 2086 : if l.is_delta() {
116 : // Is this candidate workable? In other words, are there any
117 : // delta layers that span across this LSN
118 : //
119 : // Valid: Not valid:
120 : // + +
121 : // | | +
122 : // + <- candidate + | <- candidate
123 : // + +
124 : // |
125 : // +
126 2085 : if r.end <= candidate_start_lsn {
127 2070 : // Hooray, there are no crossing LSNs. And we have visited
128 2070 : // through all the layers within candidate..end_lsn. The
129 2070 : // current candidate can be accepted.
130 2070 : current_best_start_lsn = r.end;
131 2070 : current_best_layers.extend_from_slice(&std::mem::take(&mut candidate_layers));
132 2070 : candidate_start_lsn = r.start;
133 2070 : }
134 :
135 : // Is it small enough to be considered part of this level?
136 2085 : if r.end.0 - r.start.0 > lsn_max_size {
137 : // Too large, this layer belongs to next level. Stop.
138 638 : trace!(
139 0 : "too large {}, size {} vs {}",
140 0 : l.short_id(),
141 0 : r.end.0 - r.start.0,
142 : lsn_max_size
143 : );
144 638 : break;
145 1447 : }
146 1447 :
147 1447 : // If this crosses the candidate lsn, push it down.
148 1447 : if r.start < candidate_start_lsn {
149 2 : trace!(
150 0 : "layer {} prevents from stopping at {}",
151 0 : l.short_id(),
152 : candidate_start_lsn
153 : );
154 2 : candidate_start_lsn = r.start;
155 1445 : }
156 1 : }
157 :
158 : // Include this layer in our candidate
159 1448 : candidate_layers.push(l);
160 : }
161 :
162 920 : Ok(if current_best_start_lsn == end_lsn {
163 : // empty level
164 181 : None
165 : } else {
166 739 : Some(Level {
167 739 : lsn_range: current_best_start_lsn..end_lsn,
168 739 : layers: current_best_layers,
169 739 : })
170 : })
171 1011 : }
172 :
173 : impl<L> Level<L> {
174 : /// Count the number of deltas stacked on each other.
175 739 : pub fn depth<K>(&self) -> u64
176 739 : where
177 739 : K: CompactionKey,
178 739 : L: CompactionLayer<K>,
179 739 : {
180 : struct Event<K> {
181 : key: K,
182 : layer_idx: usize,
183 : start: bool,
184 : }
185 739 : let mut events: Vec<Event<K>> = Vec::new();
186 1445 : for (idx, l) in self.layers.iter().enumerate() {
187 1445 : let key_range = l.key_range();
188 1445 : if key_range.end == key_range.start.next() && l.is_delta() {
189 : // Ignore single-key delta layers as they can be stacked on top of each other
190 : // as that is the only way to cut further.
191 12 : continue;
192 1433 : }
193 1433 : events.push(Event {
194 1433 : key: l.key_range().start,
195 1433 : layer_idx: idx,
196 1433 : start: true,
197 1433 : });
198 1433 : events.push(Event {
199 1433 : key: l.key_range().end,
200 1433 : layer_idx: idx,
201 1433 : start: false,
202 1433 : });
203 : }
204 6304 : events.sort_by_key(|e| (e.key, e.start));
205 739 :
206 739 : // Sweep the key space left to right. Stop at each distinct key, and
207 739 : // count the number of deltas on top of the highest image at that key.
208 739 : //
209 739 : // This is a little inefficient, as we walk through the active_set on
210 739 : // every key. We could increment/decrement a counter on each step
211 739 : // instead, but that'd require a bit more complex bookkeeping.
212 739 : let mut active_set: BTreeSet<(Lsn, bool, usize)> = BTreeSet::new();
213 739 : let mut max_depth = 0;
214 739 : let mut events_iter = events.iter().peekable();
215 3605 : while let Some(e) = events_iter.next() {
216 2866 : let l = &self.layers[e.layer_idx];
217 2866 : let is_image = !l.is_delta();
218 2866 :
219 2866 : // update the active set
220 2866 : if e.start {
221 1433 : active_set.insert((l.lsn_range().end, is_image, e.layer_idx));
222 1433 : } else {
223 1433 : active_set.remove(&(l.lsn_range().end, is_image, e.layer_idx));
224 1433 : }
225 :
226 : // recalculate depth if this was the last event at this point
227 2866 : let more_events_at_this_key = events_iter
228 2866 : .peek()
229 2866 : .map_or(false, |next_e| next_e.key == e.key);
230 2866 : if !more_events_at_this_key {
231 1488 : let mut active_depth = 0;
232 1488 : for (_end_lsn, is_image, _idx) in active_set.iter().rev() {
233 1437 : if *is_image {
234 2 : break;
235 1435 : }
236 1435 : active_depth += 1;
237 : }
238 1488 : if active_depth > max_depth {
239 740 : max_depth = active_depth;
240 748 : }
241 1378 : }
242 : }
243 739 : debug_assert_eq!(active_set, BTreeSet::new());
244 739 : max_depth
245 739 : }
246 : }
247 :
248 : #[cfg(test)]
249 : mod tests {
250 : use super::*;
251 : use crate::simulator::{Key, MockDeltaLayer, MockImageLayer, MockLayer};
252 : use std::sync::{Arc, Mutex};
253 :
254 20 : fn delta(key_range: Range<Key>, lsn_range: Range<Lsn>) -> MockLayer {
255 20 : MockLayer::Delta(Arc::new(MockDeltaLayer {
256 20 : key_range,
257 20 : lsn_range,
258 20 : // identify_level() doesn't pay attention to the rest of the fields
259 20 : file_size: 0,
260 20 : deleted: Mutex::new(false),
261 20 : records: vec![],
262 20 : }))
263 20 : }
264 :
265 1 : fn image(key_range: Range<Key>, lsn: Lsn) -> MockLayer {
266 1 : MockLayer::Image(Arc::new(MockImageLayer {
267 1 : key_range,
268 1 : lsn_range: lsn..(lsn + 1),
269 1 : // identify_level() doesn't pay attention to the rest of the fields
270 1 : file_size: 0,
271 1 : deleted: Mutex::new(false),
272 1 : }))
273 1 : }
274 :
275 : #[tokio::test]
276 1 : async fn test_identify_level() -> anyhow::Result<()> {
277 1 : let layers = vec![
278 1 : delta(Key::MIN..Key::MAX, Lsn(0x8000)..Lsn(0x9000)),
279 1 : delta(Key::MIN..Key::MAX, Lsn(0x5000)..Lsn(0x7000)),
280 1 : delta(Key::MIN..Key::MAX, Lsn(0x4000)..Lsn(0x5000)),
281 1 : delta(Key::MIN..Key::MAX, Lsn(0x3000)..Lsn(0x4000)),
282 1 : delta(Key::MIN..Key::MAX, Lsn(0x2000)..Lsn(0x3000)),
283 1 : delta(Key::MIN..Key::MAX, Lsn(0x1000)..Lsn(0x2000)),
284 1 : ];
285 1 :
286 1 : // All layers fit in the max file size
287 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
288 1 : .await?
289 1 : .unwrap();
290 1 : assert_eq!(level.depth(), 6);
291 1 :
292 1 : // Same LSN with smaller max file size. The second layer from the top is larger
293 1 : // and belongs to next level.
294 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x1000)
295 1 : .await?
296 1 : .unwrap();
297 1 : assert_eq!(level.depth(), 1);
298 1 :
299 1 : // Call with a smaller LSN
300 1 : let level = identify_level(layers.clone(), Lsn(0x3000), 0x1000)
301 1 : .await?
302 1 : .unwrap();
303 1 : assert_eq!(level.depth(), 2);
304 1 :
305 1 : // Call with an LSN that doesn't partition the space
306 1 : let result = identify_level(layers, Lsn(0x6000), 0x1000).await;
307 1 : assert!(result.is_err());
308 1 : Ok(())
309 1 : }
310 :
311 : #[tokio::test]
312 1 : async fn test_overlapping_lsn_ranges() -> anyhow::Result<()> {
313 1 : // The files LSN ranges overlap, so even though there are more files that
314 1 : // fit under the file size, they are not included in the level because they
315 1 : // overlap so that we'd need to include the oldest file, too, which is
316 1 : // larger
317 1 : let layers = vec![
318 1 : delta(Key::MIN..Key::MAX, Lsn(0x4000)..Lsn(0x5000)),
319 1 : delta(Key::MIN..Key::MAX, Lsn(0x3000)..Lsn(0x4000)), // overlap
320 1 : delta(Key::MIN..Key::MAX, Lsn(0x2500)..Lsn(0x3500)), // overlap
321 1 : delta(Key::MIN..Key::MAX, Lsn(0x2000)..Lsn(0x3000)), // overlap
322 1 : delta(Key::MIN..Key::MAX, Lsn(0x1000)..Lsn(0x2500)), // larger
323 1 : ];
324 1 :
325 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x1000)
326 1 : .await?
327 1 : .unwrap();
328 1 : assert_eq!(level.depth(), 1);
329 1 :
330 1 : Ok(())
331 1 : }
332 :
333 : #[tokio::test]
334 1 : async fn test_depth_nonoverlapping() -> anyhow::Result<()> {
335 1 : // The key ranges don't overlap, so depth is only 1.
336 1 : let layers = vec![
337 1 : delta(4000..5000, Lsn(0x6000)..Lsn(0x7000)),
338 1 : delta(3000..4000, Lsn(0x7000)..Lsn(0x8000)),
339 1 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
340 1 : ];
341 1 :
342 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
343 1 : .await?
344 1 : .unwrap();
345 1 : assert_eq!(level.layers.len(), 3);
346 1 : assert_eq!(level.depth(), 1);
347 1 :
348 1 : // Staggered. The 1st and 3rd layer don't overlap with each other.
349 1 : let layers = vec![
350 1 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
351 1 : delta(1500..2500, Lsn(0x7000)..Lsn(0x8000)),
352 1 : delta(2000..3000, Lsn(0x6000)..Lsn(0x7000)),
353 1 : ];
354 1 :
355 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
356 1 : .await?
357 1 : .unwrap();
358 1 : assert_eq!(level.layers.len(), 3);
359 1 : assert_eq!(level.depth(), 2);
360 1 : Ok(())
361 1 : }
362 :
363 : #[tokio::test]
364 1 : async fn test_depth_images() -> anyhow::Result<()> {
365 1 : let layers: Vec<MockLayer> = vec![
366 1 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
367 1 : delta(1500..2500, Lsn(0x7000)..Lsn(0x8000)),
368 1 : delta(2000..3000, Lsn(0x6000)..Lsn(0x7000)),
369 1 : // This covers the same key range as the 2nd delta layer. The depth
370 1 : // in that key range is therefore 0.
371 1 : image(1500..2500, Lsn(0x9000)),
372 1 : ];
373 1 :
374 1 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
375 1 : .await?
376 1 : .unwrap();
377 1 : assert_eq!(level.layers.len(), 4);
378 1 : assert_eq!(level.depth(), 1);
379 1 : Ok(())
380 1 : }
381 : }
|