LCOV - code coverage report
Current view: top level - pageserver/compaction/src - identify_levels.rs (source / functions) Coverage Total Hit
Test: 050dd70dd490b28fffe527eae9fb8a1222b5c59c.info Lines: 95.9 % 269 258
Test Date: 2024-06-25 21:28:46 Functions: 57.1 % 28 16

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

Generated by: LCOV version 2.1-beta