diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs
index 6f8f3e6389..d7ef70477f 100644
--- a/pageserver/src/http/routes.rs
+++ b/pageserver/src/http/routes.rs
@@ -1721,7 +1721,9 @@ async fn timeline_detach_ancestor_handler(
request: Request
,
_cancel: CancellationToken,
) -> Result, ApiError> {
- use crate::tenant::timeline::detach_ancestor::Options;
+ use crate::tenant::timeline::detach_ancestor;
+ use pageserver_api::models::detach_ancestor::AncestorDetached;
+
let tenant_shard_id: TenantShardId = parse_request_param(&request, "tenant_shard_id")?;
check_permission(&request, Some(tenant_shard_id.tenant_id))?;
let timeline_id: TimelineId = parse_request_param(&request, "timeline_id")?;
@@ -1729,7 +1731,7 @@ async fn timeline_detach_ancestor_handler(
let span = tracing::info_span!("detach_ancestor", tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), %timeline_id);
async move {
- let mut options = Options::default();
+ let mut options = detach_ancestor::Options::default();
let rewrite_concurrency =
parse_query_param::<_, std::num::NonZeroUsize>(&request, "rewrite_concurrency")?;
@@ -1757,27 +1759,36 @@ async fn timeline_detach_ancestor_handler(
let timeline = tenant.get_timeline(timeline_id, true)?;
- let (_guard, prepared) = timeline
+ let progress = timeline
.prepare_to_detach_from_ancestor(&tenant, options, ctx)
.await?;
- let res = state
- .tenant_manager
- .complete_detaching_timeline_ancestor(tenant_shard_id, timeline_id, prepared, ctx)
- .await;
+ // uncomment to allow early as possible Tenant::drop
+ // drop(tenant);
- match res {
- Ok(reparented_timelines) => {
- let resp = pageserver_api::models::detach_ancestor::AncestorDetached {
+ let resp = match progress {
+ detach_ancestor::Progress::Prepared(_guard, prepared) => {
+ // it would be great to tag the guard on to the tenant activation future
+ let reparented_timelines = state
+ .tenant_manager
+ .complete_detaching_timeline_ancestor(
+ tenant_shard_id,
+ timeline_id,
+ prepared,
+ ctx,
+ )
+ .await
+ .context("timeline detach ancestor completion")
+ .map_err(ApiError::InternalServerError)?;
+
+ AncestorDetached {
reparented_timelines,
- };
-
- json_response(StatusCode::OK, resp)
+ }
}
- Err(e) => Err(ApiError::InternalServerError(
- e.context("timeline detach completion"),
- )),
- }
+ detach_ancestor::Progress::Done(resp) => resp,
+ };
+
+ json_response(StatusCode::OK, resp)
}
.instrument(span)
.await
diff --git a/pageserver/src/tenant/remote_timeline_client.rs b/pageserver/src/tenant/remote_timeline_client.rs
index bc9364de61..66b759c8e0 100644
--- a/pageserver/src/tenant/remote_timeline_client.rs
+++ b/pageserver/src/tenant/remote_timeline_client.rs
@@ -241,7 +241,7 @@ use self::index::IndexPart;
use super::metadata::MetadataUpdate;
use super::storage_layer::{Layer, LayerName, ResidentLayer};
-use super::upload_queue::SetDeletedFlagProgress;
+use super::upload_queue::{NotInitialized, SetDeletedFlagProgress};
use super::Generation;
pub(crate) use download::{
@@ -1930,6 +1930,31 @@ impl RemoteTimelineClient {
}
}
}
+
+ /// Returns an accessor which will hold the UploadQueue mutex for accessing the upload queue
+ /// externally to RemoteTimelineClient.
+ pub(crate) fn initialized_upload_queue(
+ &self,
+ ) -> Result, NotInitialized> {
+ let mut inner = self.upload_queue.lock().unwrap();
+ inner.initialized_mut()?;
+ Ok(UploadQueueAccessor { inner })
+ }
+}
+
+pub(crate) struct UploadQueueAccessor<'a> {
+ inner: std::sync::MutexGuard<'a, UploadQueue>,
+}
+
+impl<'a> UploadQueueAccessor<'a> {
+ pub(crate) fn latest_uploaded_index_part(&self) -> &IndexPart {
+ match &*self.inner {
+ UploadQueue::Initialized(x) => &x.clean.0,
+ UploadQueue::Uninitialized | UploadQueue::Stopped(_) => {
+ unreachable!("checked before constructing")
+ }
+ }
+ }
}
pub fn remote_tenant_path(tenant_shard_id: &TenantShardId) -> RemotePath {
diff --git a/pageserver/src/tenant/remote_timeline_client/index.rs b/pageserver/src/tenant/remote_timeline_client/index.rs
index 6233a3477e..b439df8edb 100644
--- a/pageserver/src/tenant/remote_timeline_client/index.rs
+++ b/pageserver/src/tenant/remote_timeline_client/index.rs
@@ -176,6 +176,24 @@ pub(crate) struct Lineage {
///
/// If you are adding support for detaching from a hierarchy, consider changing the ancestry
/// into a `Vec<(TimelineId, Lsn)>` to be a path instead.
+ // FIXME: this is insufficient even for path of two timelines for future wal recovery
+ // purposes:
+ //
+ // assuming a "old main" which has received most of the WAL, and has a branch "new main",
+ // starting a bit before "old main" last_record_lsn. the current version works fine,
+ // because we will know to replay wal and branch at the recorded Lsn to do wal recovery.
+ //
+ // then assuming "new main" would similarly receive a branch right before its last_record_lsn,
+ // "new new main". the current implementation would just store ("new main", ancestor_lsn, _)
+ // here. however, we cannot recover from WAL using only that information, we would need the
+ // whole ancestry here:
+ //
+ // ```json
+ // [
+ // ["old main", ancestor_lsn("new main"), _],
+ // ["new main", ancestor_lsn("new new main"), _]
+ // ]
+ // ```
#[serde(skip_serializing_if = "Option::is_none", default)]
original_ancestor: Option<(TimelineId, Lsn, NaiveDateTime)>,
}
@@ -217,6 +235,14 @@ impl Lineage {
self.original_ancestor
.is_some_and(|(_, ancestor_lsn, _)| ancestor_lsn == lsn)
}
+
+ pub(crate) fn is_detached_from_original_ancestor(&self) -> bool {
+ self.original_ancestor.is_some()
+ }
+
+ pub(crate) fn is_reparented(&self) -> bool {
+ !self.reparenting_history.is_empty()
+ }
}
#[cfg(test)]
diff --git a/pageserver/src/tenant/timeline.rs b/pageserver/src/tenant/timeline.rs
index 0996616a67..239dce8786 100644
--- a/pageserver/src/tenant/timeline.rs
+++ b/pageserver/src/tenant/timeline.rs
@@ -4733,13 +4733,7 @@ impl Timeline {
tenant: &crate::tenant::Tenant,
options: detach_ancestor::Options,
ctx: &RequestContext,
- ) -> Result<
- (
- completion::Completion,
- detach_ancestor::PreparedTimelineDetach,
- ),
- detach_ancestor::Error,
- > {
+ ) -> Result {
detach_ancestor::prepare(self, tenant, options, ctx).await
}
diff --git a/pageserver/src/tenant/timeline/detach_ancestor.rs b/pageserver/src/tenant/timeline/detach_ancestor.rs
index 4fc89330ba..49ce3db3e6 100644
--- a/pageserver/src/tenant/timeline/detach_ancestor.rs
+++ b/pageserver/src/tenant/timeline/detach_ancestor.rs
@@ -10,6 +10,7 @@ use crate::{
},
virtual_file::{MaybeFatalIo, VirtualFile},
};
+use pageserver_api::models::detach_ancestor::AncestorDetached;
use tokio_util::sync::CancellationToken;
use tracing::Instrument;
use utils::{completion, generation::Generation, http::error::ApiError, id::TimelineId, lsn::Lsn};
@@ -39,6 +40,9 @@ pub(crate) enum Error {
#[error("unexpected error")]
Unexpected(#[source] anyhow::Error),
+
+ #[error("failpoint: {}", .0)]
+ Failpoint(&'static str),
}
impl From for ApiError {
@@ -57,11 +61,41 @@ impl From for ApiError {
| e @ Error::CopyDeltaPrefix(_)
| e @ Error::UploadRewritten(_)
| e @ Error::CopyFailed(_)
- | e @ Error::Unexpected(_) => ApiError::InternalServerError(e.into()),
+ | e @ Error::Unexpected(_)
+ | e @ Error::Failpoint(_) => ApiError::InternalServerError(e.into()),
}
}
}
+impl From for Error {
+ fn from(_: crate::tenant::upload_queue::NotInitialized) -> Self {
+ // treat all as shutting down signals, even though that is not entirely correct
+ // (uninitialized state)
+ Error::ShuttingDown
+ }
+}
+
+impl From for Error {
+ fn from(value: FlushLayerError) -> Self {
+ match value {
+ FlushLayerError::Cancelled => Error::ShuttingDown,
+ FlushLayerError::NotRunning(_) => {
+ // FIXME(#6424): technically statically unreachable right now, given how we never
+ // drop the sender
+ Error::ShuttingDown
+ }
+ FlushLayerError::CreateImageLayersError(_) | FlushLayerError::Other(_) => {
+ Error::FlushAncestor(value)
+ }
+ }
+ }
+}
+
+pub(crate) enum Progress {
+ Prepared(completion::Completion, PreparedTimelineDetach),
+ Done(AncestorDetached),
+}
+
pub(crate) struct PreparedTimelineDetach {
layers: Vec,
}
@@ -88,7 +122,7 @@ pub(super) async fn prepare(
tenant: &Tenant,
options: Options,
ctx: &RequestContext,
-) -> Result<(completion::Completion, PreparedTimelineDetach), Error> {
+) -> Result