From c3f5e00ad14cd4a154a3f18a5ec33e707837061f Mon Sep 17 00:00:00 2001 From: Bojan Serafimov Date: Tue, 10 Jan 2023 15:32:18 -0500 Subject: [PATCH] Comments --- pageserver/src/tenant/layer_coverage.rs | 52 ++++++++++++++++++------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/pageserver/src/tenant/layer_coverage.rs b/pageserver/src/tenant/layer_coverage.rs index f43d9d2e71..14394d2b29 100644 --- a/pageserver/src/tenant/layer_coverage.rs +++ b/pageserver/src/tenant/layer_coverage.rs @@ -5,12 +5,21 @@ use std::ops::Range; // results are not the same on some tests. use rpds::RedBlackTreeMapSync; +/// Data structure that can efficiently: +/// - find the latest layer by lsn.end at a given key +/// - iterate the latest layers in a key range +/// - insert layers in non-decreasing lsn.start order +/// +/// The struct is parameterized over Value for easier +/// testing, but in practice it's some sort of layer. pub struct LayerCoverage { - /// Mapping key to the latest layer (if any) until the next key. + /// For every change in coverage (as we sweep the key space) + /// we store (lsn.end, value). + /// /// We use the Sync version of the map because we want Self to /// be Sync. Using nonsync might be faster, if we can work with /// that. - head: RedBlackTreeMapSync>, + nodes: RedBlackTreeMapSync>, } impl Default for LayerCoverage { @@ -22,20 +31,25 @@ impl Default for LayerCoverage { impl LayerCoverage { pub fn new() -> Self { Self { - head: RedBlackTreeMapSync::default(), + nodes: RedBlackTreeMapSync::default(), } } /// Helper function to subdivide the key range without changing any values + /// + /// Complexity: O(log N) fn add_node(self: &mut Self, key: i128) { - let value = match self.head.range(..=key).last() { + let value = match self.nodes.range(..=key).last() { Some((_, Some(v))) => Some(v.clone()), Some((_, None)) => None, None => None, }; - self.head.insert_mut(key, value); + self.nodes.insert_mut(key, value); } + /// Insert a layer. + /// + /// Complexity: worst case O(N), in practice O(log N). See not in implementation. pub fn insert(self: &mut Self, key: Range, lsn: Range, value: Value) { // NOTE The order of the following lines is important!! @@ -47,12 +61,13 @@ impl LayerCoverage { // // NOTE This loop is worst case O(N), but amortized O(log N) in the special // case when rectangles have no height. In practice I don't think we'll see - // the kind of layer intersections needed to trigger O(N) behavior. If we - // do it can be fixed using lazy propagation. + // the kind of layer intersections needed to trigger O(N) behavior. The worst + // case is N/2 horizontal layers overlapped with N/2 vertical layers in a + // grid pattern. let mut to_update = Vec::new(); let mut to_remove = Vec::new(); let mut prev_covered = false; - for (k, node) in self.head.range(key.clone()) { + for (k, node) in self.nodes.range(key.clone()) { let needs_cover = match node { None => true, Some((h, _)) => h < &lsn.end, @@ -69,16 +84,19 @@ impl LayerCoverage { to_remove.push(key.end); } for k in to_update { - self.head + self.nodes .insert_mut(k.clone(), Some((lsn.end.clone(), value.clone()))); } for k in to_remove { - self.head.remove_mut(&k); + self.nodes.remove_mut(&k); } } + /// Get the latest (by lsn.end) layer at a given key + /// + /// Complexity: O(log N) pub fn query(self: &Self, key: i128) -> Option { - self.head + self.nodes .range(..=key) .rev() .next()? @@ -87,24 +105,30 @@ impl LayerCoverage { .map(|(_, v)| v.clone()) } + /// Iterate the changes in layer coverage in a given range. You will likely + /// want to start with self.query(key.start), and then follow up with self.range + /// + /// Complexity: O(log N + result_size) pub fn range( self: &Self, key: Range, ) -> impl '_ + Iterator)> { - self.head + self.nodes .range(key) .map(|(k, v)| (k.clone(), v.as_ref().map(|x| x.1.clone()))) } + /// Like range, but covers the entire key space pub fn iter(self: &Self) -> impl '_ + Iterator)> { - self.head + self.nodes .iter() .map(|(k, v)| (k.clone(), v.as_ref().map(|x| x.1.clone()))) } + /// O(1) clone pub fn clone(self: &Self) -> Self { Self { - head: self.head.clone(), + nodes: self.nodes.clone(), } } }