LCOV - code coverage report
Current view: top level - pageserver/compaction/src - identify_levels.rs (source / functions) Coverage Total Hit
Test: 691a4c28fe7169edd60b367c52d448a0a6605f1f.info Lines: 95.5 % 266 254
Test Date: 2024-05-10 13:18:37 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           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              : }
        

Generated by: LCOV version 2.1-beta