LCOV - code coverage report
Current view: top level - pageserver/compaction/src - identify_levels.rs (source / functions) Coverage Total Hit
Test: 09e7485004805bd42b53a0c369170b3228136512.info Lines: 95.8 % 264 253
Test Date: 2024-11-21 18:36:18 Functions: 72.7 % 22 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         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         6302 :         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         1489 :                 let mut active_depth = 0;
     232         1489 :                 for (_end_lsn, is_image, _idx) in active_set.iter().rev() {
     233         1438 :                     if *is_image {
     234            2 :                         break;
     235         1436 :                     }
     236         1436 :                     active_depth += 1;
     237              :                 }
     238         1489 :                 if active_depth > max_depth {
     239          741 :                     max_depth = active_depth;
     240          748 :                 }
     241         1377 :             }
     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              : }
        

Generated by: LCOV version 2.1-beta