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 20 : pub async fn identify_level<K, L>(
49 20 : all_layers: Vec<L>,
50 20 : end_lsn: Lsn,
51 20 : lsn_max_size: u64,
52 20 : ) -> anyhow::Result<Option<Level<L>>>
53 20 : where
54 20 : K: CompactionKey,
55 20 : L: CompactionLayer<K> + Clone,
56 20 : {
57 20 : // filter out layers that are above the `end_lsn`, they are completely irrelevant.
58 20 : let mut layers = Vec::new();
59 244 : for l in all_layers {
60 226 : 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 2 : 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 224 : }
65 224 : // include image layers sitting exacty at `end_lsn`.
66 224 : let is_image = !l.is_delta();
67 224 : if (is_image && l.lsn_range().start > end_lsn)
68 224 : || (!is_image && l.lsn_range().start >= end_lsn)
69 : {
70 10 : continue;
71 214 : }
72 214 : layers.push(l);
73 : }
74 : // All the remaining layers either belong to this level, or are below it.
75 18 : 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 18 : if layers.is_empty() {
82 0 : return Ok(None);
83 18 : }
84 18 :
85 18 : // Walk the ranges in LSN order.
86 18 : //
87 18 : // ----- end_lsn
88 18 : // |
89 18 : // |
90 18 : // v
91 18 : //
92 504 : layers.sort_by_key(|l| l.lsn_range().end);
93 18 : let mut candidate_start_lsn = end_lsn;
94 18 : let mut candidate_layers: Vec<L> = Vec::new();
95 18 : let mut current_best_start_lsn = end_lsn;
96 18 : let mut current_best_layers: Vec<L> = Vec::new();
97 18 : let mut iter = layers.into_iter();
98 : loop {
99 142 : let Some(l) = iter.next_back() else {
100 : // Reached end. Accept the last candidate
101 12 : current_best_start_lsn = candidate_start_lsn;
102 12 : current_best_layers.extend_from_slice(&std::mem::take(&mut candidate_layers));
103 12 : break;
104 : };
105 130 : 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 130 : let r = l.lsn_range();
113 130 :
114 130 : // Image layers don't restrict our choice of cutoff LSN
115 130 : 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 128 : if r.end <= candidate_start_lsn {
127 122 : // Hooray, there are no crossing LSNs. And we have visited
128 122 : // through all the layers within candidate..end_lsn. The
129 122 : // current candidate can be accepted.
130 122 : current_best_start_lsn = r.end;
131 122 : current_best_layers.extend_from_slice(&std::mem::take(&mut candidate_layers));
132 122 : candidate_start_lsn = r.start;
133 122 : }
134 :
135 : // Is it small enough to be considered part of this level?
136 128 : if r.end.0 - r.start.0 > lsn_max_size {
137 : // Too large, this layer belongs to next level. Stop.
138 6 : 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 6 : break;
145 122 : }
146 122 :
147 122 : // If this crosses the candidate lsn, push it down.
148 122 : if r.start < candidate_start_lsn {
149 4 : trace!(
150 0 : "layer {} prevents from stopping at {}",
151 0 : l.short_id(),
152 : candidate_start_lsn
153 : );
154 4 : candidate_start_lsn = r.start;
155 118 : }
156 2 : }
157 :
158 : // Include this layer in our candidate
159 124 : candidate_layers.push(l);
160 : }
161 :
162 18 : Ok(if current_best_start_lsn == end_lsn {
163 : // empty level
164 2 : None
165 : } else {
166 16 : Some(Level {
167 16 : lsn_range: current_best_start_lsn..end_lsn,
168 16 : layers: current_best_layers,
169 16 : })
170 : })
171 20 : }
172 :
173 : impl<L> Level<L> {
174 : /// Count the number of deltas stacked on each other.
175 16 : pub fn depth<K>(&self) -> u64
176 16 : where
177 16 : K: CompactionKey,
178 16 : L: CompactionLayer<K>,
179 16 : {
180 16 : struct Event<K> {
181 16 : key: K,
182 16 : layer_idx: usize,
183 16 : start: bool,
184 16 : }
185 16 : let mut events: Vec<Event<K>> = Vec::new();
186 118 : for (idx, l) in self.layers.iter().enumerate() {
187 118 : events.push(Event {
188 118 : key: l.key_range().start,
189 118 : layer_idx: idx,
190 118 : start: true,
191 118 : });
192 118 : events.push(Event {
193 118 : key: l.key_range().end,
194 118 : layer_idx: idx,
195 118 : start: false,
196 118 : });
197 118 : }
198 1540 : events.sort_by_key(|e| (e.key, e.start));
199 16 :
200 16 : // Sweep the key space left to right. Stop at each distinct key, and
201 16 : // count the number of deltas on top of the highest image at that key.
202 16 : //
203 16 : // This is a little inefficient, as we walk through the active_set on
204 16 : // every key. We could increment/decrement a counter on each step
205 16 : // instead, but that'd require a bit more complex bookkeeping.
206 16 : let mut active_set: BTreeSet<(Lsn, bool, usize)> = BTreeSet::new();
207 16 : let mut max_depth = 0;
208 16 : let mut events_iter = events.iter().peekable();
209 252 : while let Some(e) = events_iter.next() {
210 236 : let l = &self.layers[e.layer_idx];
211 236 : let is_image = !l.is_delta();
212 236 :
213 236 : // update the active set
214 236 : if e.start {
215 118 : active_set.insert((l.lsn_range().end, is_image, e.layer_idx));
216 118 : } else {
217 118 : active_set.remove(&(l.lsn_range().end, is_image, e.layer_idx));
218 118 : }
219 :
220 : // recalculate depth if this was the last event at this point
221 236 : let more_events_at_this_key = events_iter
222 236 : .peek()
223 236 : .map_or(false, |next_e| next_e.key == e.key);
224 236 : if !more_events_at_this_key {
225 50 : let mut active_depth = 0;
226 124 : for (_end_lsn, is_image, _idx) in active_set.iter().rev() {
227 124 : if *is_image {
228 4 : break;
229 120 : }
230 120 : active_depth += 1;
231 : }
232 50 : if active_depth > max_depth {
233 18 : max_depth = active_depth;
234 32 : }
235 186 : }
236 : }
237 16 : debug_assert_eq!(active_set, BTreeSet::new());
238 16 : max_depth
239 16 : }
240 : }
241 :
242 : #[cfg(test)]
243 : mod tests {
244 : use super::*;
245 : use crate::simulator::{Key, MockDeltaLayer, MockImageLayer, MockLayer};
246 : use std::sync::{Arc, Mutex};
247 :
248 40 : fn delta(key_range: Range<Key>, lsn_range: Range<Lsn>) -> MockLayer {
249 40 : MockLayer::Delta(Arc::new(MockDeltaLayer {
250 40 : key_range,
251 40 : lsn_range,
252 40 : // identify_level() doesn't pay attention to the rest of the fields
253 40 : file_size: 0,
254 40 : deleted: Mutex::new(false),
255 40 : records: vec![],
256 40 : }))
257 40 : }
258 :
259 2 : fn image(key_range: Range<Key>, lsn: Lsn) -> MockLayer {
260 2 : MockLayer::Image(Arc::new(MockImageLayer {
261 2 : key_range,
262 2 : lsn_range: lsn..(lsn + 1),
263 2 : // identify_level() doesn't pay attention to the rest of the fields
264 2 : file_size: 0,
265 2 : deleted: Mutex::new(false),
266 2 : }))
267 2 : }
268 :
269 : #[tokio::test]
270 2 : async fn test_identify_level() -> anyhow::Result<()> {
271 2 : let layers = vec![
272 2 : delta(Key::MIN..Key::MAX, Lsn(0x8000)..Lsn(0x9000)),
273 2 : delta(Key::MIN..Key::MAX, Lsn(0x5000)..Lsn(0x7000)),
274 2 : delta(Key::MIN..Key::MAX, Lsn(0x4000)..Lsn(0x5000)),
275 2 : delta(Key::MIN..Key::MAX, Lsn(0x3000)..Lsn(0x4000)),
276 2 : delta(Key::MIN..Key::MAX, Lsn(0x2000)..Lsn(0x3000)),
277 2 : delta(Key::MIN..Key::MAX, Lsn(0x1000)..Lsn(0x2000)),
278 2 : ];
279 2 :
280 2 : // All layers fit in the max file size
281 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
282 2 : .await?
283 2 : .unwrap();
284 2 : assert_eq!(level.depth(), 6);
285 2 :
286 2 : // Same LSN with smaller max file size. The second layer from the top is larger
287 2 : // and belongs to next level.
288 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x1000)
289 2 : .await?
290 2 : .unwrap();
291 2 : assert_eq!(level.depth(), 1);
292 2 :
293 2 : // Call with a smaller LSN
294 2 : let level = identify_level(layers.clone(), Lsn(0x3000), 0x1000)
295 2 : .await?
296 2 : .unwrap();
297 2 : assert_eq!(level.depth(), 2);
298 2 :
299 2 : // Call with an LSN that doesn't partition the space
300 2 : let result = identify_level(layers, Lsn(0x6000), 0x1000).await;
301 2 : assert!(result.is_err());
302 2 : Ok(())
303 2 : }
304 :
305 : #[tokio::test]
306 2 : async fn test_overlapping_lsn_ranges() -> anyhow::Result<()> {
307 2 : // The files LSN ranges overlap, so even though there are more files that
308 2 : // fit under the file size, they are not included in the level because they
309 2 : // overlap so that we'd need to include the oldest file, too, which is
310 2 : // larger
311 2 : let layers = vec![
312 2 : delta(Key::MIN..Key::MAX, Lsn(0x4000)..Lsn(0x5000)),
313 2 : delta(Key::MIN..Key::MAX, Lsn(0x3000)..Lsn(0x4000)), // overlap
314 2 : delta(Key::MIN..Key::MAX, Lsn(0x2500)..Lsn(0x3500)), // overlap
315 2 : delta(Key::MIN..Key::MAX, Lsn(0x2000)..Lsn(0x3000)), // overlap
316 2 : delta(Key::MIN..Key::MAX, Lsn(0x1000)..Lsn(0x2500)), // larger
317 2 : ];
318 2 :
319 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x1000)
320 2 : .await?
321 2 : .unwrap();
322 2 : assert_eq!(level.depth(), 1);
323 2 :
324 2 : Ok(())
325 2 : }
326 :
327 : #[tokio::test]
328 2 : async fn test_depth_nonoverlapping() -> anyhow::Result<()> {
329 2 : // The key ranges don't overlap, so depth is only 1.
330 2 : let layers = vec![
331 2 : delta(4000..5000, Lsn(0x6000)..Lsn(0x7000)),
332 2 : delta(3000..4000, Lsn(0x7000)..Lsn(0x8000)),
333 2 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
334 2 : ];
335 2 :
336 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
337 2 : .await?
338 2 : .unwrap();
339 2 : assert_eq!(level.layers.len(), 3);
340 2 : assert_eq!(level.depth(), 1);
341 2 :
342 2 : // Staggered. The 1st and 3rd layer don't overlap with each other.
343 2 : let layers = vec![
344 2 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
345 2 : delta(1500..2500, Lsn(0x7000)..Lsn(0x8000)),
346 2 : delta(2000..3000, Lsn(0x6000)..Lsn(0x7000)),
347 2 : ];
348 2 :
349 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
350 2 : .await?
351 2 : .unwrap();
352 2 : assert_eq!(level.layers.len(), 3);
353 2 : assert_eq!(level.depth(), 2);
354 2 : Ok(())
355 2 : }
356 :
357 : #[tokio::test]
358 2 : async fn test_depth_images() -> anyhow::Result<()> {
359 2 : let layers: Vec<MockLayer> = vec![
360 2 : delta(1000..2000, Lsn(0x8000)..Lsn(0x9000)),
361 2 : delta(1500..2500, Lsn(0x7000)..Lsn(0x8000)),
362 2 : delta(2000..3000, Lsn(0x6000)..Lsn(0x7000)),
363 2 : // This covers the same key range as the 2nd delta layer. The depth
364 2 : // in that key range is therefore 0.
365 2 : image(1500..2500, Lsn(0x9000)),
366 2 : ];
367 2 :
368 2 : let level = identify_level(layers.clone(), Lsn(0x10000), 0x2000)
369 2 : .await?
370 2 : .unwrap();
371 2 : assert_eq!(level.layers.len(), 4);
372 2 : assert_eq!(level.depth(), 1);
373 2 : Ok(())
374 2 : }
375 : }
|