diff --git a/pageserver/src/tenant.rs b/pageserver/src/tenant.rs index 7723bdb947..803862f68a 100644 --- a/pageserver/src/tenant.rs +++ b/pageserver/src/tenant.rs @@ -19,6 +19,7 @@ use enumset::EnumSet; use futures::stream::FuturesUnordered; use futures::FutureExt; use futures::StreamExt; +use pageserver_api::keyspace::KeySpace; use pageserver_api::models; use pageserver_api::models::AuxFilePolicy; use pageserver_api::models::TimelineState; @@ -2786,11 +2787,12 @@ impl Tenant { // Scan all timelines. For each timeline, remember the timeline ID and // the branch point where it was created. - let mut all_branchpoints: BTreeMap> = BTreeMap::new(); + let mut all_branchpoints: BTreeMap)>> = + BTreeMap::new(); timelines.iter().for_each(|(timeline_id, timeline_entry)| { if let Some(ancestor_timeline_id) = &timeline_entry.get_ancestor_timeline_id() { let ancestor_children = all_branchpoints.entry(*ancestor_timeline_id).or_default(); - ancestor_children.push((timeline_entry.get_ancestor_lsn(), *timeline_id)); + ancestor_children.push((timeline_entry.get_ancestor_lsn(), *timeline_id, None)); } }); @@ -2799,7 +2801,7 @@ impl Tenant { // Populate each timeline's GcInfo with information about its child branches for timeline in timelines.values() { - let mut branchpoints: Vec<(Lsn, TimelineId)> = all_branchpoints + let mut branchpoints: Vec<(Lsn, TimelineId, Option)> = all_branchpoints .remove(&timeline.timeline_id) .unwrap_or_default(); @@ -4305,7 +4307,7 @@ mod tests { { let branchpoints = &tline.gc_info.read().unwrap().retain_lsns; assert_eq!(branchpoints.len(), 1); - assert_eq!(branchpoints[0], (Lsn(0x40), NEW_TIMELINE_ID)); + assert_eq!(branchpoints[0], (Lsn(0x40), NEW_TIMELINE_ID, None)); } // You can read the key from the child branch even though the parent is diff --git a/pageserver/src/tenant/size.rs b/pageserver/src/tenant/size.rs index b080da6f1c..f3bafe747e 100644 --- a/pageserver/src/tenant/size.rs +++ b/pageserver/src/tenant/size.rs @@ -271,10 +271,14 @@ pub(super) async fn gather_inputs( let mut lsns: Vec<(Lsn, LsnKind)> = gc_info .retain_lsns .iter() - .filter(|(lsn, _child_id)| lsn > &ancestor_lsn) - .copied() - // this assumes there are no other retain_lsns than the branchpoints - .map(|(lsn, _child_id)| (lsn, LsnKind::BranchPoint)) + .filter_map(|(lsn, _child_id, _)| { + if lsn > &ancestor_lsn { + // this assumes there are no other retain_lsns than the branchpoints + Some((*lsn, LsnKind::BranchPoint)) + } else { + None + } + }) .collect::>(); lsns.extend(lease_points.iter().map(|&lsn| (lsn, LsnKind::LeasePoint))); diff --git a/pageserver/src/tenant/timeline.rs b/pageserver/src/tenant/timeline.rs index c865bbbad6..d75bccb498 100644 --- a/pageserver/src/tenant/timeline.rs +++ b/pageserver/src/tenant/timeline.rs @@ -453,12 +453,12 @@ pub struct WalReceiverInfo { /// Garbage Collection. #[derive(Default)] pub(crate) struct GcInfo { - /// Specific LSNs that are needed. + /// Record which parts of this timeline's history are still needed by children /// - /// Currently, this includes all points where child branches have - /// been forked off from. In the future, could also include - /// explicit user-defined snapshot points. - pub(crate) retain_lsns: Vec<(Lsn, TimelineId)>, + /// Optionally store each child's keyspace at their branch LSN: parts of the keyspace not covered here may be dropped during GC, as + /// the child will never read them. For example, a child which has covered its whole keyspace with image layers + /// will put an empty keyspace here. Children populate this: if it is None, presume the child may read any part of the keyspace. + pub(crate) retain_lsns: Vec<(Lsn, TimelineId, Option)>, /// The cutoff coordinates, which are combined by selecting the minimum. pub(crate) cutoffs: GcCutoffs, @@ -476,13 +476,21 @@ impl GcInfo { } pub(super) fn insert_child(&mut self, child_id: TimelineId, child_lsn: Lsn) { - self.retain_lsns.push((child_lsn, child_id)); + self.retain_lsns.push((child_lsn, child_id, None)); self.retain_lsns.sort_by_key(|i| i.0); } pub(super) fn remove_child(&mut self, child_id: TimelineId) { self.retain_lsns.retain(|i| i.1 != child_id); } + + /// When the child re-calculates which parts of the keyspace it will read from the ancestor, it posts + /// and update to the parent using this function, to enable the parent to perhaps GC more layers. + pub(super) fn notify_child_keyspace(&mut self, child_id: TimelineId, key_space: KeySpace) { + if let Ok(idx) = self.retain_lsns.binary_search_by_key(&child_id, |i| i.1) { + self.retain_lsns.get_mut(idx).unwrap().2 = Some(key_space); + } + } } /// The `GcInfo` component describing which Lsns need to be retained. @@ -5089,7 +5097,7 @@ impl Timeline { let retain_lsns = gc_info .retain_lsns .iter() - .map(|(lsn, _child_id)| *lsn) + .map(|(lsn, _child_id, _)| *lsn) .collect(); // Gets the maximum LSN that holds the valid lease.