diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs
index 3c0c23a56d..ac162602db 100644
--- a/pageserver/src/http/routes.rs
+++ b/pageserver/src/http/routes.rs
@@ -71,6 +71,7 @@ use crate::tenant::remote_timeline_client::{
use crate::tenant::secondary::SecondaryController;
use crate::tenant::size::ModelInputs;
use crate::tenant::storage_layer::{IoConcurrency, LayerAccessStatsReset, LayerName};
+use crate::tenant::timeline::detach_ancestor::DetachBehavior;
use crate::tenant::timeline::offload::{OffloadError, offload_timeline};
use crate::tenant::timeline::{
CompactFlags, CompactOptions, CompactRequest, CompactionError, Timeline, WaitLsnTimeout,
@@ -2489,8 +2490,29 @@ async fn timeline_download_remote_layers_handler_get(
json_response(StatusCode::OK, info)
}
-async fn timeline_detach_ancestor_handler(
+async fn timeline_detach_ancestor_handler_v1(
request: Request
,
+ cancel: CancellationToken,
+) -> Result, ApiError> {
+ timeline_detach_ancestor_handler_common(request, DetachBehavior::NoAncestorAndReparent, cancel)
+ .await
+}
+
+async fn timeline_detach_ancestor_handler_v2(
+ request: Request,
+ cancel: CancellationToken,
+) -> Result, ApiError> {
+ timeline_detach_ancestor_handler_common(
+ request,
+ DetachBehavior::MultipleLevelAndNoReparent,
+ cancel,
+ )
+ .await
+}
+
+async fn timeline_detach_ancestor_handler_common(
+ request: Request,
+ behavior: DetachBehavior,
_cancel: CancellationToken,
) -> Result, ApiError> {
use pageserver_api::models::detach_ancestor::AncestorDetached;
@@ -2548,7 +2570,7 @@ async fn timeline_detach_ancestor_handler(
let timeline = tenant.get_timeline(timeline_id, true)?;
let progress = timeline
- .prepare_to_detach_from_ancestor(&tenant, options, ctx)
+ .prepare_to_detach_from_ancestor(&tenant, options, behavior, ctx)
.await?;
// uncomment to allow early as possible Tenant::drop
@@ -2563,6 +2585,7 @@ async fn timeline_detach_ancestor_handler(
tenant_shard_id,
timeline_id,
prepared,
+ behavior,
attempt,
ctx,
)
@@ -3742,7 +3765,11 @@ pub fn make_router(
)
.put(
"/v1/tenant/:tenant_shard_id/timeline/:timeline_id/detach_ancestor",
- |r| api_handler(r, timeline_detach_ancestor_handler),
+ |r| api_handler(r, timeline_detach_ancestor_handler_v1),
+ )
+ .put(
+ "/v1/tenant/:tenant_shard_id/timeline/:timeline_id/detach_ancestor_v2",
+ |r| api_handler(r, timeline_detach_ancestor_handler_v2),
)
.delete("/v1/tenant/:tenant_shard_id/timeline/:timeline_id", |r| {
api_handler(r, timeline_delete_handler)
diff --git a/pageserver/src/tenant/mgr.rs b/pageserver/src/tenant/mgr.rs
index c6536cf1ab..092bfdf6c1 100644
--- a/pageserver/src/tenant/mgr.rs
+++ b/pageserver/src/tenant/mgr.rs
@@ -1914,6 +1914,7 @@ impl TenantManager {
tenant_shard_id: TenantShardId,
timeline_id: TimelineId,
prepared: PreparedTimelineDetach,
+ behavior: detach_ancestor::DetachBehavior,
mut attempt: detach_ancestor::Attempt,
ctx: &RequestContext,
) -> Result, detach_ancestor::Error> {
@@ -1962,6 +1963,7 @@ impl TenantManager {
prepared,
attempt.ancestor_timeline_id,
attempt.ancestor_lsn,
+ behavior,
ctx,
)
.await?;
diff --git a/pageserver/src/tenant/timeline.rs b/pageserver/src/tenant/timeline.rs
index 2979408b97..0502e6a02f 100644
--- a/pageserver/src/tenant/timeline.rs
+++ b/pageserver/src/tenant/timeline.rs
@@ -5388,9 +5388,10 @@ impl Timeline {
self: &Arc,
tenant: &crate::tenant::Tenant,
options: detach_ancestor::Options,
+ behavior: detach_ancestor::DetachBehavior,
ctx: &RequestContext,
) -> Result {
- detach_ancestor::prepare(self, tenant, options, ctx).await
+ detach_ancestor::prepare(self, tenant, behavior, options, ctx).await
}
/// Second step of detach from ancestor; detaches the `self` from it's current ancestor and
@@ -5408,6 +5409,7 @@ impl Timeline {
prepared: detach_ancestor::PreparedTimelineDetach,
ancestor_timeline_id: TimelineId,
ancestor_lsn: Lsn,
+ behavior: detach_ancestor::DetachBehavior,
ctx: &RequestContext,
) -> Result {
detach_ancestor::detach_and_reparent(
@@ -5416,6 +5418,7 @@ impl Timeline {
prepared,
ancestor_timeline_id,
ancestor_lsn,
+ behavior,
ctx,
)
.await
diff --git a/pageserver/src/tenant/timeline/detach_ancestor.rs b/pageserver/src/tenant/timeline/detach_ancestor.rs
index 9d9d7b6a36..1c2c09b468 100644
--- a/pageserver/src/tenant/timeline/detach_ancestor.rs
+++ b/pageserver/src/tenant/timeline/detach_ancestor.rs
@@ -127,13 +127,23 @@ pub(crate) struct PreparedTimelineDetach {
layers: Vec,
}
-/// TODO: this should be part of PageserverConf because we cannot easily modify cplane arguments.
+// TODO: this should be part of PageserverConf because we cannot easily modify cplane arguments.
#[derive(Debug)]
pub(crate) struct Options {
pub(crate) rewrite_concurrency: std::num::NonZeroUsize,
pub(crate) copy_concurrency: std::num::NonZeroUsize,
}
+/// Controls the detach ancestor behavior.
+/// - When set to `NoAncestorAndReparent`, we will only detach a branch if it has only one level of ancestor. It will automatically reparent the children of the ancestor.
+/// - When set to `MultipleLevelAndNoReparent`, we will detach a branch from multiple levels of ancestors, and no reparenting will happen for the children of the ancestor.
+/// - Detach ancstor will always reparent the children of the detached branch.
+#[derive(Debug, Clone, Copy)]
+pub enum DetachBehavior {
+ NoAncestorAndReparent,
+ MultipleLevelAndNoReparent,
+}
+
impl Default for Options {
fn default() -> Self {
Self {
@@ -168,6 +178,7 @@ impl Attempt {
pub(super) async fn prepare(
detached: &Arc,
tenant: &Tenant,
+ behavior: DetachBehavior,
options: Options,
ctx: &RequestContext,
) -> Result