Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

storage controller: don't hold detached tenants in memory #10264

Merged
merged 13 commits into from
Jan 8, 2025
19 changes: 19 additions & 0 deletions storage_controller/src/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub(crate) enum DatabaseOperation {
TenantGenerations,
ShardGenerations,
ListTenantShards,
LoadTenant,
InsertTenantShards,
UpdateTenantShard,
DeleteTenant,
Expand Down Expand Up @@ -349,6 +350,24 @@ impl Persistence {
.await
}

/// When restoring a previously detached tenant into memory, load it from the database
pub(crate) async fn load_tenant(
&self,
filter_tenant_id: TenantId,
) -> DatabaseResult<Vec<TenantShardPersistence>> {
use crate::schema::tenant_shards::dsl::*;
self.with_measured_conn(
DatabaseOperation::LoadTenant,
move |conn| -> DatabaseResult<_> {
let query = tenant_shards.filter(tenant_id.eq(filter_tenant_id.to_string()));
let result = query.load::<TenantShardPersistence>(conn)?;

Ok(result)
},
)
.await
}

/// Tenants must be persisted before we schedule them for the first time. This enables us
/// to correctly retain generation monotonicity, and the externally provided placement policy & config.
pub(crate) async fn insert_tenant_shards(
Expand Down
64 changes: 61 additions & 3 deletions storage_controller/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,9 @@ impl Service {
}
}
}

// If the shard we just finished with is configured to be detached and is fully idle, then
// we may drop it from memory.
}

async fn process_results(
Expand Down Expand Up @@ -2436,6 +2439,49 @@ impl Service {
}
}

/// For APIs that might act on tenants with [`PlacementPolicy::Detached`], first check if
/// the tenant is present in memory. If not, load it from the database. If it is found
/// in neither location, return a NotFound error.
async fn maybe_load_tenant(&self, tenant_id: TenantId) -> Result<(), ApiError> {
let present_in_memory = {
let locked = self.inner.read().unwrap();
locked
.tenants
.range(TenantShardId::tenant_range(tenant_id))
.next()
.is_some()
};

if present_in_memory {
return Ok(());
}

let tenant_shards = self.persistence.load_tenant(tenant_id).await?;
if tenant_shards.is_empty() {
return Err(ApiError::NotFound(
anyhow::anyhow!("Tenant {} not found", tenant_id).into(),
));
}

// TODO: choose a fresh AZ to use for this tenant when un-detaching: there definitely isn't a running
// compute, so no benefit to making AZ sticky across detaches.

let mut locked = self.inner.write().unwrap();
tracing::info!(
"Loaded {} shards for tenant {}",
tenant_shards.len(),
tenant_id
);
locked.tenants.extend(tenant_shards.into_iter().map(|p| {
let intent = IntentState::new();
let shard =
TenantShard::from_persistent(p, intent).expect("Corrupt shard row in database");
(shard.tenant_shard_id, shard)
}));

Ok(())
}

/// This API is used by the cloud control plane to migrate unsharded tenants that it created
/// directly with pageservers into this service.
///
Expand All @@ -2462,14 +2508,26 @@ impl Service {
)
.await;

if !tenant_shard_id.is_unsharded() {
let tenant_id = if !tenant_shard_id.is_unsharded() {
return Err(ApiError::BadRequest(anyhow::anyhow!(
"This API is for importing single-sharded or unsharded tenants"
)));
}
} else {
tenant_shard_id.tenant_id
};

// In case we are waking up a Detached tenant
match self.maybe_load_tenant(tenant_id).await {
Ok(()) | Err(ApiError::NotFound(_)) => {
// This is a creation or an update
VladLazar marked this conversation as resolved.
Show resolved Hide resolved
}
Err(e) => {
return Err(e);
}
};

// First check if this is a creation or an update
let create_or_update = self.tenant_location_config_prepare(tenant_shard_id.tenant_id, req);
let create_or_update = self.tenant_location_config_prepare(tenant_id, req);

let mut result = TenantLocationConfigResponse {
shards: Vec::new(),
Expand Down