From f466c0199581cfdad9c953c34a1f6bb0b40de78e Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 11 Mar 2025 12:43:55 +0100 Subject: [PATCH] pageserver: add `max_logical_size_per_shard` for `get_top_tenants` (#11157) ## Problem In #11122, we want to split shards once the logical size of the largest timeline exceeds a split threshold. However, `get_top_tenants` currently only returns `max_logical_size`, which tracks the max _total_ logical size of a timeline across all shards. This is problematic, because the storage controller needs to fetch a list of N tenants that are eligible for splits, but the API doesn't currently have a way to express this. For example, with a split threshold of 1 GB, a tenant with `max_logical_size` of 4 GB is eligible to split if it has 1 or 2 shards, but not if it already has 4 shards. We need to express this in per-shard terms, otherwise the `get_top_tenants` endpoint may end up only returning tenants that can't be split, blocking splits entirely. Touches https://github.com/neondatabase/neon/pull/11122. Touches https://github.com/neondatabase/cloud/issues/22532. ## Summary of changes Add `TenantShardItem::max_logical_size_per_shard` containing `max_logical_size / shard_count`, and `TenantSorting::MaxLogicalSizePerShard` to order and filter by it. --- libs/pageserver_api/src/models.rs | 18 +++++++++++++++--- pageserver/src/http/routes.rs | 1 + pageserver/src/tenant.rs | 5 +++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/libs/pageserver_api/src/models.rs b/libs/pageserver_api/src/models.rs index 13a9b5d89e..b1ebad83b1 100644 --- a/libs/pageserver_api/src/models.rs +++ b/libs/pageserver_api/src/models.rs @@ -1476,8 +1476,14 @@ pub struct TenantScanRemoteStorageResponse { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] pub enum TenantSorting { + /// Total size of layers on local disk for all timelines in a shard. ResidentSize, + /// The logical size of the largest timeline within a _tenant_ (not shard). Only tracked on + /// shard 0, contains the sum across all shards. MaxLogicalSize, + /// The logical size of the largest timeline within a _tenant_ (not shard), divided by number of + /// shards. Only tracked on shard 0, and estimates the per-shard logical size. + MaxLogicalSizePerShard, } impl Default for TenantSorting { @@ -1507,14 +1513,20 @@ pub struct TopTenantShardsRequest { pub struct TopTenantShardItem { pub id: TenantShardId, - /// Total size of layers on local disk for all timelines in this tenant + /// Total size of layers on local disk for all timelines in this shard. pub resident_size: u64, - /// Total size of layers in remote storage for all timelines in this tenant + /// Total size of layers in remote storage for all timelines in this shard. pub physical_size: u64, - /// The largest logical size of a timeline within this tenant + /// The largest logical size of a timeline within this _tenant_ (not shard). This is only + /// tracked on shard 0, and contains the sum of the logical size across all shards. pub max_logical_size: u64, + + /// The largest logical size of a timeline within this _tenant_ (not shard) divided by number of + /// shards. This is only tracked on shard 0, and is only an estimate as we divide it evenly by + /// shard count, rounded up. + pub max_logical_size_per_shard: u64, } #[derive(Serialize, Deserialize, Debug, Default)] diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 77bfab47e0..e5848bfd25 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -3223,6 +3223,7 @@ async fn post_top_tenants( match order_by { TenantSorting::ResidentSize => sizes.resident_size, TenantSorting::MaxLogicalSize => sizes.max_logical_size, + TenantSorting::MaxLogicalSizePerShard => sizes.max_logical_size_per_shard, } } diff --git a/pageserver/src/tenant.rs b/pageserver/src/tenant.rs index 3a34c8e254..62e1cdac0c 100644 --- a/pageserver/src/tenant.rs +++ b/pageserver/src/tenant.rs @@ -3842,6 +3842,7 @@ impl Tenant { resident_size: 0, physical_size: 0, max_logical_size: 0, + max_logical_size_per_shard: 0, }; for timeline in self.timelines.lock().unwrap().values() { @@ -3858,6 +3859,10 @@ impl Tenant { ); } + result.max_logical_size_per_shard = result + .max_logical_size + .div_ceil(self.tenant_shard_id.shard_count.count() as u64); + result } }