From 547914fe1965db241da83362f454d5cc7da49bdf Mon Sep 17 00:00:00 2001 From: John Spray Date: Fri, 6 Oct 2023 17:15:18 +0100 Subject: [PATCH] pageserver: adjust timeline deletion for generations (#5453) ## Problem Spun off from https://github.com/neondatabase/neon/pull/5449 Timeline deletion does the following: 1. Delete layers referenced in the index 2. Delete everything else in the timeline prefix, except the index 3. Delete the index. When generations were added, the filter in step 2 got outdated, such that the index objects were deleted along with everything else at step 2. That didn't really break anything, but it makes an automated test unhappy and is a violation of the original intent of the code, which presumably intends to upload an invariant that as long as any objects for a timeline exist, the index exists. (Eventually, this index-object-last complexity can go away: when we do https://github.com/neondatabase/neon/issues/5080, there is no need to keep the index_part around, as deletions can always be retried any time any where.) ## Summary of changes After object listing, split the listed objects into layers and index objects. Delete the layers first, then the index objects. --- .../src/tenant/remote_timeline_client.rs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pageserver/src/tenant/remote_timeline_client.rs b/pageserver/src/tenant/remote_timeline_client.rs index 6fedf781d4..622f9b1b9e 100644 --- a/pageserver/src/tenant/remote_timeline_client.rs +++ b/pageserver/src/tenant/remote_timeline_client.rs @@ -901,9 +901,27 @@ impl RemoteTimelineClient { .await .context("list prefixes")?; - let remaining: Vec = remaining + // We will delete the current index_part object last, since it acts as a deletion + // marker via its deleted_at attribute + let latest_index = remaining + .iter() + .filter(|p| { + p.object_name() + .map(|n| n.starts_with(IndexPart::FILE_NAME)) + .unwrap_or(false) + }) + .filter_map(|path| parse_remote_index_path(path.clone()).map(|gen| (path, gen))) + .max_by_key(|i| i.1) + .map(|i| i.0.clone()) + .unwrap_or( + // No generation-suffixed indices, assume we are dealing with + // a legacy index. + remote_index_path(&self.tenant_id, &self.timeline_id, Generation::none()), + ); + + let remaining_layers: Vec = remaining .into_iter() - .filter(|p| p.object_name() != Some(IndexPart::FILE_NAME)) + .filter(|p| p!= &latest_index) .inspect(|path| { if let Some(name) = path.object_name() { info!(%name, "deleting a file not referenced from index_part.json"); @@ -913,9 +931,11 @@ impl RemoteTimelineClient { }) .collect(); - let not_referenced_count = remaining.len(); - if !remaining.is_empty() { - self.deletion_queue_client.push_immediate(remaining).await?; + let not_referenced_count = remaining_layers.len(); + if !remaining_layers.is_empty() { + self.deletion_queue_client + .push_immediate(remaining_layers) + .await?; } fail::fail_point!("timeline-delete-before-index-delete", |_| { @@ -924,11 +944,9 @@ impl RemoteTimelineClient { ))? }); - let index_file_path = timeline_storage_path.join(Utf8Path::new(IndexPart::FILE_NAME)); - debug!("enqueuing index part deletion"); self.deletion_queue_client - .push_immediate([index_file_path].to_vec()) + .push_immediate([latest_index].to_vec()) .await?; // Timeline deletion is rare and we have probably emitted a reasonably number of objects: wait