diff --git a/pageserver/src/tenant/storage_layer/inmemory_layer.rs b/pageserver/src/tenant/storage_layer/inmemory_layer.rs index e487bee1f2..a19a2a86b9 100644 --- a/pageserver/src/tenant/storage_layer/inmemory_layer.rs +++ b/pageserver/src/tenant/storage_layer/inmemory_layer.rs @@ -392,6 +392,10 @@ impl InMemoryLayer { self.end_lsn.get().copied().unwrap_or(Lsn::MAX) } + pub(crate) fn start_lsn(&self) -> Lsn { + self.start_lsn + } + pub(crate) fn get_lsn_range(&self) -> Range { self.start_lsn..self.end_lsn_or_max() } diff --git a/pageserver/src/tenant/timeline/compaction.rs b/pageserver/src/tenant/timeline/compaction.rs index 8b9ace1e5b..82a418313c 100644 --- a/pageserver/src/tenant/timeline/compaction.rs +++ b/pageserver/src/tenant/timeline/compaction.rs @@ -646,6 +646,21 @@ impl Timeline { readable_points.push(*child_lsn); } readable_points.push(head_lsn); + + // The Timeline get page process will walk all InMemoryLayers before it starts walking historic + // layers. That means it might fail to see image layers that overlap with the LSN range of + // InMemoryLayers, so there is a de-facto read point at the start_lsn of the oldest InMemoryLayer. + // + // This behavior in the getpage path is considered a but, and including InMemoryLayer's start_lsn here + // is a workaround. See https://github.com/neondatabase/neon/issues/9185 + if let Some(oldest_inmemory_layer) = layer_map.frozen_layers.front() { + readable_points.push(oldest_inmemory_layer.start_lsn()) + } else if let Some(open_layer) = layer_map.open_layer.as_ref() { + readable_points.push(open_layer.start_lsn()); + } + + readable_points.sort(); + readable_points };