Line data Source code
1 : //! This module acts as a switchboard to access different repositories managed by this
2 : //! page server.
3 :
4 : use camino::{Utf8DirEntry, Utf8Path, Utf8PathBuf};
5 : use futures::stream::StreamExt;
6 : use itertools::Itertools;
7 : use pageserver_api::key::Key;
8 : use pageserver_api::models::ShardParameters;
9 : use pageserver_api::shard::{ShardCount, ShardIdentity, ShardNumber, TenantShardId};
10 : use rand::{distributions::Alphanumeric, Rng};
11 : use std::borrow::Cow;
12 : use std::cmp::Ordering;
13 : use std::collections::{BTreeMap, HashMap};
14 : use std::ops::Deref;
15 : use std::sync::Arc;
16 : use std::time::{Duration, Instant};
17 : use tokio::fs;
18 : use utils::timeout::{timeout_cancellable, TimeoutCancellableError};
19 :
20 : use anyhow::Context;
21 : use once_cell::sync::Lazy;
22 : use tokio::task::JoinSet;
23 : use tokio_util::sync::CancellationToken;
24 : use tracing::*;
25 :
26 : use remote_storage::GenericRemoteStorage;
27 : use utils::{completion, crashsafe};
28 :
29 : use crate::config::PageServerConf;
30 : use crate::context::{DownloadBehavior, RequestContext};
31 : use crate::control_plane_client::{
32 : ControlPlaneClient, ControlPlaneGenerationsApi, RetryForeverError,
33 : };
34 : use crate::deletion_queue::DeletionQueueClient;
35 : use crate::http::routes::ACTIVE_TENANT_TIMEOUT;
36 : use crate::metrics::{TENANT, TENANT_MANAGER as METRICS};
37 : use crate::task_mgr::{self, TaskKind};
38 : use crate::tenant::config::{
39 : AttachedLocationConfig, AttachmentMode, LocationConf, LocationMode, SecondaryLocationConfig,
40 : TenantConfOpt,
41 : };
42 : use crate::tenant::delete::DeleteTenantFlow;
43 : use crate::tenant::span::debug_assert_current_span_has_tenant_id;
44 : use crate::tenant::{AttachedTenantConf, SpawnMode, Tenant, TenantState};
45 : use crate::{InitializationOrder, IGNORED_TENANT_FILE_NAME, TEMP_FILE_SUFFIX};
46 :
47 : use utils::crashsafe::path_with_suffix_extension;
48 : use utils::fs_ext::PathExt;
49 : use utils::generation::Generation;
50 : use utils::id::{TenantId, TimelineId};
51 :
52 : use super::delete::DeleteTenantError;
53 : use super::secondary::SecondaryTenant;
54 : use super::TenantSharedResources;
55 :
56 : /// For a tenant that appears in TenantsMap, it may either be
57 : /// - `Attached`: has a full Tenant object, is elegible to service
58 : /// reads and ingest WAL.
59 : /// - `Secondary`: is only keeping a local cache warm.
60 : ///
61 : /// Secondary is a totally distinct state rather than being a mode of a `Tenant`, because
62 : /// that way we avoid having to carefully switch a tenant's ingestion etc on and off during
63 : /// its lifetime, and we can preserve some important safety invariants like `Tenant` always
64 : /// having a properly acquired generation (Secondary doesn't need a generation)
65 0 : #[derive(Clone)]
66 : pub(crate) enum TenantSlot {
67 : Attached(Arc<Tenant>),
68 : Secondary(Arc<SecondaryTenant>),
69 : /// In this state, other administrative operations acting on the TenantId should
70 : /// block, or return a retry indicator equivalent to HTTP 503.
71 : InProgress(utils::completion::Barrier),
72 : }
73 :
74 : impl std::fmt::Debug for TenantSlot {
75 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 0 : match self {
77 0 : Self::Attached(tenant) => write!(f, "Attached({})", tenant.current_state()),
78 0 : Self::Secondary(_) => write!(f, "Secondary"),
79 0 : Self::InProgress(_) => write!(f, "InProgress"),
80 : }
81 0 : }
82 : }
83 :
84 : impl TenantSlot {
85 : /// Return the `Tenant` in this slot if attached, else None
86 0 : fn get_attached(&self) -> Option<&Arc<Tenant>> {
87 0 : match self {
88 0 : Self::Attached(t) => Some(t),
89 0 : Self::Secondary(_) => None,
90 0 : Self::InProgress(_) => None,
91 : }
92 0 : }
93 : }
94 :
95 : /// The tenants known to the pageserver.
96 : /// The enum variants are used to distinguish the different states that the pageserver can be in.
97 : pub(crate) enum TenantsMap {
98 : /// [`init_tenant_mgr`] is not done yet.
99 : Initializing,
100 : /// [`init_tenant_mgr`] is done, all on-disk tenants have been loaded.
101 : /// New tenants can be added using [`tenant_map_acquire_slot`].
102 : Open(BTreeMap<TenantShardId, TenantSlot>),
103 : /// The pageserver has entered shutdown mode via [`shutdown_all_tenants`].
104 : /// Existing tenants are still accessible, but no new tenants can be created.
105 : ShuttingDown(BTreeMap<TenantShardId, TenantSlot>),
106 : }
107 :
108 : pub(crate) enum TenantsMapRemoveResult {
109 : Occupied(TenantSlot),
110 : Vacant,
111 : InProgress(utils::completion::Barrier),
112 : }
113 :
114 : /// When resolving a TenantId to a shard, we may be looking for the 0th
115 : /// shard, or we might be looking for whichever shard holds a particular page.
116 : pub(crate) enum ShardSelector {
117 : /// Only return the 0th shard, if it is present. If a non-0th shard is present,
118 : /// ignore it.
119 : Zero,
120 : /// Pick the first shard we find for the TenantId
121 : First,
122 : /// Pick the shard that holds this key
123 : Page(Key),
124 : }
125 :
126 : impl TenantsMap {
127 : /// Convenience function for typical usage, where we want to get a `Tenant` object, for
128 : /// working with attached tenants. If the TenantId is in the map but in Secondary state,
129 : /// None is returned.
130 0 : pub(crate) fn get(&self, tenant_shard_id: &TenantShardId) -> Option<&Arc<Tenant>> {
131 0 : match self {
132 0 : TenantsMap::Initializing => None,
133 0 : TenantsMap::Open(m) | TenantsMap::ShuttingDown(m) => {
134 0 : m.get(tenant_shard_id).and_then(|slot| slot.get_attached())
135 : }
136 : }
137 0 : }
138 :
139 : /// A page service client sends a TenantId, and to look up the correct Tenant we must
140 : /// resolve this to a fully qualified TenantShardId.
141 0 : fn resolve_attached_shard(
142 0 : &self,
143 0 : tenant_id: &TenantId,
144 0 : selector: ShardSelector,
145 0 : ) -> Option<TenantShardId> {
146 0 : let mut want_shard = None;
147 0 : match self {
148 0 : TenantsMap::Initializing => None,
149 0 : TenantsMap::Open(m) | TenantsMap::ShuttingDown(m) => {
150 0 : for slot in m.range(TenantShardId::tenant_range(*tenant_id)) {
151 : // Ignore all slots that don't contain an attached tenant
152 0 : let tenant = match &slot.1 {
153 0 : TenantSlot::Attached(t) => t,
154 0 : _ => continue,
155 : };
156 :
157 0 : match selector {
158 0 : ShardSelector::First => return Some(*slot.0),
159 0 : ShardSelector::Zero if slot.0.shard_number == ShardNumber(0) => {
160 0 : return Some(*slot.0)
161 : }
162 0 : ShardSelector::Page(key) => {
163 0 : // First slot we see for this tenant, calculate the expected shard number
164 0 : // for the key: we will use this for checking if this and subsequent
165 0 : // slots contain the key, rather than recalculating the hash each time.
166 0 : if want_shard.is_none() {
167 0 : want_shard = Some(tenant.shard_identity.get_shard_number(&key));
168 0 : }
169 :
170 0 : if Some(tenant.shard_identity.number) == want_shard {
171 0 : return Some(*slot.0);
172 0 : }
173 : }
174 0 : _ => continue,
175 : }
176 : }
177 :
178 : // Fall through: we didn't find an acceptable shard
179 0 : None
180 : }
181 : }
182 0 : }
183 :
184 : /// Only for use from DeleteTenantFlow. This method directly removes a TenantSlot from the map.
185 : ///
186 : /// The normal way to remove a tenant is using a SlotGuard, which will gracefully remove the guarded
187 : /// slot if the enclosed tenant is shutdown.
188 0 : pub(crate) fn remove(&mut self, tenant_shard_id: TenantShardId) -> TenantsMapRemoveResult {
189 0 : use std::collections::btree_map::Entry;
190 0 : match self {
191 0 : TenantsMap::Initializing => TenantsMapRemoveResult::Vacant,
192 0 : TenantsMap::Open(m) | TenantsMap::ShuttingDown(m) => match m.entry(tenant_shard_id) {
193 0 : Entry::Occupied(entry) => match entry.get() {
194 0 : TenantSlot::InProgress(barrier) => {
195 0 : TenantsMapRemoveResult::InProgress(barrier.clone())
196 : }
197 0 : _ => TenantsMapRemoveResult::Occupied(entry.remove()),
198 : },
199 0 : Entry::Vacant(_entry) => TenantsMapRemoveResult::Vacant,
200 : },
201 : }
202 0 : }
203 :
204 0 : pub(crate) fn len(&self) -> usize {
205 0 : match self {
206 0 : TenantsMap::Initializing => 0,
207 0 : TenantsMap::Open(m) | TenantsMap::ShuttingDown(m) => m.len(),
208 : }
209 0 : }
210 : }
211 :
212 : /// This is "safe" in that that it won't leave behind a partially deleted directory
213 : /// at the original path, because we rename with TEMP_FILE_SUFFIX before starting deleting
214 : /// the contents.
215 : ///
216 : /// This is pageserver-specific, as it relies on future processes after a crash to check
217 : /// for TEMP_FILE_SUFFIX when loading things.
218 0 : async fn safe_remove_tenant_dir_all(path: impl AsRef<Utf8Path>) -> std::io::Result<()> {
219 0 : let tmp_path = safe_rename_tenant_dir(path).await?;
220 0 : fs::remove_dir_all(tmp_path).await
221 0 : }
222 :
223 0 : async fn safe_rename_tenant_dir(path: impl AsRef<Utf8Path>) -> std::io::Result<Utf8PathBuf> {
224 0 : let parent = path
225 0 : .as_ref()
226 0 : .parent()
227 0 : // It is invalid to call this function with a relative path. Tenant directories
228 0 : // should always have a parent.
229 0 : .ok_or(std::io::Error::new(
230 0 : std::io::ErrorKind::InvalidInput,
231 0 : "Path must be absolute",
232 0 : ))?;
233 0 : let rand_suffix = rand::thread_rng()
234 0 : .sample_iter(&Alphanumeric)
235 0 : .take(8)
236 0 : .map(char::from)
237 0 : .collect::<String>()
238 0 : + TEMP_FILE_SUFFIX;
239 0 : let tmp_path = path_with_suffix_extension(&path, &rand_suffix);
240 0 : fs::rename(path.as_ref(), &tmp_path).await?;
241 0 : fs::File::open(parent).await?.sync_all().await?;
242 0 : Ok(tmp_path)
243 0 : }
244 :
245 : static TENANTS: Lazy<std::sync::RwLock<TenantsMap>> =
246 2 : Lazy::new(|| std::sync::RwLock::new(TenantsMap::Initializing));
247 :
248 : /// The TenantManager is responsible for storing and mutating the collection of all tenants
249 : /// that this pageserver process has state for. Every Tenant and SecondaryTenant instance
250 : /// lives inside the TenantManager.
251 : ///
252 : /// The most important role of the TenantManager is to prevent conflicts: e.g. trying to attach
253 : /// the same tenant twice concurrently, or trying to configure the same tenant into secondary
254 : /// and attached modes concurrently.
255 : pub struct TenantManager {
256 : conf: &'static PageServerConf,
257 : // TODO: currently this is a &'static pointing to TENANTs. When we finish refactoring
258 : // out of that static variable, the TenantManager can own this.
259 : // See https://github.com/neondatabase/neon/issues/5796
260 : tenants: &'static std::sync::RwLock<TenantsMap>,
261 : resources: TenantSharedResources,
262 : }
263 :
264 0 : fn emergency_generations(
265 0 : tenant_confs: &HashMap<TenantShardId, anyhow::Result<LocationConf>>,
266 0 : ) -> HashMap<TenantShardId, Generation> {
267 0 : tenant_confs
268 0 : .iter()
269 0 : .filter_map(|(tid, lc)| {
270 0 : let lc = match lc {
271 0 : Ok(lc) => lc,
272 0 : Err(_) => return None,
273 : };
274 0 : let gen = match &lc.mode {
275 0 : LocationMode::Attached(alc) => Some(alc.generation),
276 0 : LocationMode::Secondary(_) => None,
277 : };
278 :
279 0 : gen.map(|g| (*tid, g))
280 0 : })
281 0 : .collect()
282 0 : }
283 :
284 0 : async fn init_load_generations(
285 0 : conf: &'static PageServerConf,
286 0 : tenant_confs: &HashMap<TenantShardId, anyhow::Result<LocationConf>>,
287 0 : resources: &TenantSharedResources,
288 0 : cancel: &CancellationToken,
289 0 : ) -> anyhow::Result<Option<HashMap<TenantShardId, Generation>>> {
290 0 : let generations = if conf.control_plane_emergency_mode {
291 0 : error!(
292 0 : "Emergency mode! Tenants will be attached unsafely using their last known generation"
293 0 : );
294 0 : emergency_generations(tenant_confs)
295 0 : } else if let Some(client) = ControlPlaneClient::new(conf, cancel) {
296 0 : info!("Calling control plane API to re-attach tenants");
297 : // If we are configured to use the control plane API, then it is the source of truth for what tenants to load.
298 0 : match client.re_attach().await {
299 0 : Ok(tenants) => tenants,
300 : Err(RetryForeverError::ShuttingDown) => {
301 0 : anyhow::bail!("Shut down while waiting for control plane re-attach response")
302 : }
303 : }
304 : } else {
305 0 : info!("Control plane API not configured, tenant generations are disabled");
306 0 : return Ok(None);
307 : };
308 :
309 : // The deletion queue needs to know about the startup attachment state to decide which (if any) stored
310 : // deletion list entries may still be valid. We provide that by pushing a recovery operation into
311 : // the queue. Sequential processing of te queue ensures that recovery is done before any new tenant deletions
312 : // are processed, even though we don't block on recovery completing here.
313 : //
314 : // Must only do this if remote storage is enabled, otherwise deletion queue
315 : // is not running and channel push will fail.
316 0 : if resources.remote_storage.is_some() {
317 0 : resources
318 0 : .deletion_queue_client
319 0 : .recover(generations.clone())?;
320 0 : }
321 :
322 0 : Ok(Some(generations))
323 0 : }
324 :
325 : /// Given a directory discovered in the pageserver's tenants/ directory, attempt
326 : /// to load a tenant config from it.
327 : ///
328 : /// If file is missing, return Ok(None)
329 0 : fn load_tenant_config(
330 0 : conf: &'static PageServerConf,
331 0 : dentry: Utf8DirEntry,
332 0 : ) -> anyhow::Result<Option<(TenantShardId, anyhow::Result<LocationConf>)>> {
333 0 : let tenant_dir_path = dentry.path().to_path_buf();
334 0 : if crate::is_temporary(&tenant_dir_path) {
335 0 : info!("Found temporary tenant directory, removing: {tenant_dir_path}");
336 : // No need to use safe_remove_tenant_dir_all because this is already
337 : // a temporary path
338 0 : if let Err(e) = std::fs::remove_dir_all(&tenant_dir_path) {
339 0 : error!(
340 0 : "Failed to remove temporary directory '{}': {:?}",
341 0 : tenant_dir_path, e
342 0 : );
343 0 : }
344 0 : return Ok(None);
345 0 : }
346 :
347 : // This case happens if we crash during attachment before writing a config into the dir
348 0 : let is_empty = tenant_dir_path
349 0 : .is_empty_dir()
350 0 : .with_context(|| format!("Failed to check whether {tenant_dir_path:?} is an empty dir"))?;
351 0 : if is_empty {
352 0 : info!("removing empty tenant directory {tenant_dir_path:?}");
353 0 : if let Err(e) = std::fs::remove_dir(&tenant_dir_path) {
354 0 : error!(
355 0 : "Failed to remove empty tenant directory '{}': {e:#}",
356 0 : tenant_dir_path
357 0 : )
358 0 : }
359 0 : return Ok(None);
360 0 : }
361 0 :
362 0 : let tenant_ignore_mark_file = tenant_dir_path.join(IGNORED_TENANT_FILE_NAME);
363 0 : if tenant_ignore_mark_file.exists() {
364 0 : info!("Found an ignore mark file {tenant_ignore_mark_file:?}, skipping the tenant");
365 0 : return Ok(None);
366 0 : }
367 :
368 0 : let tenant_shard_id = match tenant_dir_path
369 0 : .file_name()
370 0 : .unwrap_or_default()
371 0 : .parse::<TenantShardId>()
372 : {
373 0 : Ok(id) => id,
374 : Err(_) => {
375 0 : warn!("Invalid tenant path (garbage in our repo directory?): {tenant_dir_path}",);
376 0 : return Ok(None);
377 : }
378 : };
379 :
380 0 : Ok(Some((
381 0 : tenant_shard_id,
382 0 : Tenant::load_tenant_config(conf, &tenant_shard_id),
383 0 : )))
384 0 : }
385 :
386 : /// Initial stage of load: walk the local tenants directory, clean up any temp files,
387 : /// and load configurations for the tenants we found.
388 : ///
389 : /// Do this in parallel, because we expect 10k+ tenants, so serial execution can take
390 : /// seconds even on reasonably fast drives.
391 0 : async fn init_load_tenant_configs(
392 0 : conf: &'static PageServerConf,
393 0 : ) -> anyhow::Result<HashMap<TenantShardId, anyhow::Result<LocationConf>>> {
394 0 : let tenants_dir = conf.tenants_path();
395 :
396 0 : let dentries = tokio::task::spawn_blocking(move || -> anyhow::Result<Vec<Utf8DirEntry>> {
397 0 : let dir_entries = tenants_dir
398 0 : .read_dir_utf8()
399 0 : .with_context(|| format!("Failed to list tenants dir {tenants_dir:?}"))?;
400 :
401 0 : Ok(dir_entries.collect::<Result<Vec<_>, std::io::Error>>()?)
402 0 : })
403 0 : .await??;
404 :
405 0 : let mut configs = HashMap::new();
406 0 :
407 0 : let mut join_set = JoinSet::new();
408 0 : for dentry in dentries {
409 0 : join_set.spawn_blocking(move || load_tenant_config(conf, dentry));
410 0 : }
411 :
412 0 : while let Some(r) = join_set.join_next().await {
413 0 : if let Some((tenant_id, tenant_config)) = r?? {
414 0 : configs.insert(tenant_id, tenant_config);
415 0 : }
416 : }
417 :
418 0 : Ok(configs)
419 0 : }
420 :
421 : /// Initialize repositories with locally available timelines.
422 : /// Timelines that are only partially available locally (remote storage has more data than this pageserver)
423 : /// are scheduled for download and added to the tenant once download is completed.
424 0 : #[instrument(skip_all)]
425 : pub async fn init_tenant_mgr(
426 : conf: &'static PageServerConf,
427 : resources: TenantSharedResources,
428 : init_order: InitializationOrder,
429 : cancel: CancellationToken,
430 : ) -> anyhow::Result<TenantManager> {
431 : let mut tenants = BTreeMap::new();
432 :
433 : let ctx = RequestContext::todo_child(TaskKind::Startup, DownloadBehavior::Warn);
434 :
435 : // Scan local filesystem for attached tenants
436 : let tenant_configs = init_load_tenant_configs(conf).await?;
437 :
438 : // Determine which tenants are to be attached
439 : let tenant_generations =
440 : init_load_generations(conf, &tenant_configs, &resources, &cancel).await?;
441 :
442 0 : tracing::info!(
443 0 : "Attaching {} tenants at startup, warming up {} at a time",
444 0 : tenant_configs.len(),
445 0 : conf.concurrent_tenant_warmup.initial_permits()
446 0 : );
447 : TENANT.startup_scheduled.inc_by(tenant_configs.len() as u64);
448 :
449 : // Construct `Tenant` objects and start them running
450 : for (tenant_shard_id, location_conf) in tenant_configs {
451 : let tenant_dir_path = conf.tenant_path(&tenant_shard_id);
452 :
453 : let mut location_conf = match location_conf {
454 : Ok(l) => l,
455 : Err(e) => {
456 0 : warn!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), "Marking tenant broken, failed to {e:#}");
457 :
458 : tenants.insert(
459 : tenant_shard_id,
460 : TenantSlot::Attached(Tenant::create_broken_tenant(
461 : conf,
462 : tenant_shard_id,
463 : format!("{}", e),
464 : )),
465 : );
466 : continue;
467 : }
468 : };
469 :
470 : let generation = if let Some(generations) = &tenant_generations {
471 : // We have a generation map: treat it as the authority for whether
472 : // this tenant is really attached.
473 : if let Some(gen) = generations.get(&tenant_shard_id) {
474 : if let LocationMode::Attached(attached) = &location_conf.mode {
475 : if attached.generation > *gen {
476 0 : tracing::error!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(),
477 0 : "Control plane gave decreasing generation ({gen:?}) in re-attach response for tenant that was attached in generation {:?}, demoting to secondary",
478 0 : attached.generation
479 0 : );
480 :
481 : // We cannot safely attach this tenant given a bogus generation number, but let's avoid throwing away
482 : // local disk content: demote to secondary rather than detaching.
483 : tenants.insert(
484 : tenant_shard_id,
485 : TenantSlot::Secondary(SecondaryTenant::new(
486 : tenant_shard_id,
487 : location_conf.shard,
488 : location_conf.tenant_conf.clone(),
489 : &SecondaryLocationConfig { warm: false },
490 : )),
491 : );
492 : }
493 : }
494 : *gen
495 : } else {
496 : match &location_conf.mode {
497 : LocationMode::Secondary(secondary_config) => {
498 : // We do not require the control plane's permission for secondary mode
499 : // tenants, because they do no remote writes and hence require no
500 : // generation number
501 0 : info!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), "Loaded tenant in secondary mode");
502 : tenants.insert(
503 : tenant_shard_id,
504 : TenantSlot::Secondary(SecondaryTenant::new(
505 : tenant_shard_id,
506 : location_conf.shard,
507 : location_conf.tenant_conf,
508 : secondary_config,
509 : )),
510 : );
511 : }
512 : LocationMode::Attached(_) => {
513 : // TODO: augment re-attach API to enable the control plane to
514 : // instruct us about secondary attachments. That way, instead of throwing
515 : // away local state, we can gracefully fall back to secondary here, if the control
516 : // plane tells us so.
517 : // (https://github.com/neondatabase/neon/issues/5377)
518 0 : info!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), "Detaching tenant, control plane omitted it in re-attach response");
519 : if let Err(e) = safe_remove_tenant_dir_all(&tenant_dir_path).await {
520 0 : error!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(),
521 0 : "Failed to remove detached tenant directory '{tenant_dir_path}': {e:?}",
522 0 : );
523 : }
524 : }
525 : };
526 :
527 : continue;
528 : }
529 : } else {
530 : // Legacy mode: no generation information, any tenant present
531 : // on local disk may activate
532 0 : info!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), "Starting tenant in legacy mode, no generation",);
533 : Generation::none()
534 : };
535 :
536 : // Presence of a generation number implies attachment: attach the tenant
537 : // if it wasn't already, and apply the generation number.
538 : location_conf.attach_in_generation(generation);
539 : Tenant::persist_tenant_config(conf, &tenant_shard_id, &location_conf).await?;
540 :
541 : let shard_identity = location_conf.shard;
542 : match tenant_spawn(
543 : conf,
544 : tenant_shard_id,
545 : &tenant_dir_path,
546 : resources.clone(),
547 : AttachedTenantConf::try_from(location_conf)?,
548 : shard_identity,
549 : Some(init_order.clone()),
550 : &TENANTS,
551 : SpawnMode::Normal,
552 : &ctx,
553 : ) {
554 : Ok(tenant) => {
555 : tenants.insert(tenant_shard_id, TenantSlot::Attached(tenant));
556 : }
557 : Err(e) => {
558 0 : error!(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), "Failed to start tenant: {e:#}");
559 : }
560 : }
561 : }
562 :
563 0 : info!("Processed {} local tenants at startup", tenants.len());
564 :
565 : let mut tenants_map = TENANTS.write().unwrap();
566 : assert!(matches!(&*tenants_map, &TenantsMap::Initializing));
567 : METRICS.tenant_slots.set(tenants.len() as u64);
568 : *tenants_map = TenantsMap::Open(tenants);
569 :
570 : Ok(TenantManager {
571 : conf,
572 : tenants: &TENANTS,
573 : resources,
574 : })
575 : }
576 :
577 : /// Wrapper for Tenant::spawn that checks invariants before running, and inserts
578 : /// a broken tenant in the map if Tenant::spawn fails.
579 : #[allow(clippy::too_many_arguments)]
580 0 : pub(crate) fn tenant_spawn(
581 0 : conf: &'static PageServerConf,
582 0 : tenant_shard_id: TenantShardId,
583 0 : tenant_path: &Utf8Path,
584 0 : resources: TenantSharedResources,
585 0 : location_conf: AttachedTenantConf,
586 0 : shard_identity: ShardIdentity,
587 0 : init_order: Option<InitializationOrder>,
588 0 : tenants: &'static std::sync::RwLock<TenantsMap>,
589 0 : mode: SpawnMode,
590 0 : ctx: &RequestContext,
591 0 : ) -> anyhow::Result<Arc<Tenant>> {
592 0 : anyhow::ensure!(
593 0 : tenant_path.is_dir(),
594 0 : "Cannot load tenant from path {tenant_path:?}, it either does not exist or not a directory"
595 : );
596 0 : anyhow::ensure!(
597 0 : !crate::is_temporary(tenant_path),
598 0 : "Cannot load tenant from temporary path {tenant_path:?}"
599 : );
600 : anyhow::ensure!(
601 0 : !tenant_path.is_empty_dir().with_context(|| {
602 0 : format!("Failed to check whether {tenant_path:?} is an empty dir")
603 0 : })?,
604 0 : "Cannot load tenant from empty directory {tenant_path:?}"
605 : );
606 :
607 0 : let tenant_ignore_mark = conf.tenant_ignore_mark_file_path(&tenant_shard_id);
608 0 : anyhow::ensure!(
609 0 : !conf.tenant_ignore_mark_file_path(&tenant_shard_id).exists(),
610 0 : "Cannot load tenant, ignore mark found at {tenant_ignore_mark:?}"
611 : );
612 :
613 0 : let tenant = match Tenant::spawn(
614 0 : conf,
615 0 : tenant_shard_id,
616 0 : resources,
617 0 : location_conf,
618 0 : shard_identity,
619 0 : init_order,
620 0 : tenants,
621 0 : mode,
622 0 : ctx,
623 0 : ) {
624 0 : Ok(tenant) => tenant,
625 0 : Err(e) => {
626 0 : error!("Failed to spawn tenant {tenant_shard_id}, reason: {e:#}");
627 0 : Tenant::create_broken_tenant(conf, tenant_shard_id, format!("{e:#}"))
628 : }
629 : };
630 :
631 0 : Ok(tenant)
632 0 : }
633 :
634 : ///
635 : /// Shut down all tenants. This runs as part of pageserver shutdown.
636 : ///
637 : /// NB: We leave the tenants in the map, so that they remain accessible through
638 : /// the management API until we shut it down. If we removed the shut-down tenants
639 : /// from the tenants map, the management API would return 404 for these tenants,
640 : /// because TenantsMap::get() now returns `None`.
641 : /// That could be easily misinterpreted by control plane, the consumer of the
642 : /// management API. For example, it could attach the tenant on a different pageserver.
643 : /// We would then be in split-brain once this pageserver restarts.
644 0 : #[instrument(skip_all)]
645 : pub(crate) async fn shutdown_all_tenants() {
646 : shutdown_all_tenants0(&TENANTS).await
647 : }
648 :
649 2 : async fn shutdown_all_tenants0(tenants: &std::sync::RwLock<TenantsMap>) {
650 2 : let mut join_set = JoinSet::new();
651 :
652 : // Atomically, 1. create the shutdown tasks and 2. prevent creation of new tenants.
653 2 : let (total_in_progress, total_attached) = {
654 2 : let mut m = tenants.write().unwrap();
655 2 : match &mut *m {
656 : TenantsMap::Initializing => {
657 0 : *m = TenantsMap::ShuttingDown(BTreeMap::default());
658 0 : info!("tenants map is empty");
659 0 : return;
660 : }
661 2 : TenantsMap::Open(tenants) => {
662 2 : let mut shutdown_state = BTreeMap::new();
663 2 : let mut total_in_progress = 0;
664 2 : let mut total_attached = 0;
665 :
666 2 : for (tenant_shard_id, v) in std::mem::take(tenants).into_iter() {
667 2 : match v {
668 0 : TenantSlot::Attached(t) => {
669 0 : shutdown_state.insert(tenant_shard_id, TenantSlot::Attached(t.clone()));
670 0 : join_set.spawn(
671 0 : async move {
672 0 : let freeze_and_flush = true;
673 :
674 0 : let res = {
675 0 : let (_guard, shutdown_progress) = completion::channel();
676 0 : t.shutdown(shutdown_progress, freeze_and_flush).await
677 : };
678 :
679 0 : if let Err(other_progress) = res {
680 : // join the another shutdown in progress
681 0 : other_progress.wait().await;
682 0 : }
683 :
684 : // we cannot afford per tenant logging here, because if s3 is degraded, we are
685 : // going to log too many lines
686 0 : debug!("tenant successfully stopped");
687 0 : }
688 0 : .instrument(info_span!("shutdown", tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug())),
689 : );
690 :
691 0 : total_attached += 1;
692 : }
693 0 : TenantSlot::Secondary(state) => {
694 0 : // We don't need to wait for this individually per-tenant: the
695 0 : // downloader task will be waited on eventually, this cancel
696 0 : // is just to encourage it to drop out if it is doing work
697 0 : // for this tenant right now.
698 0 : state.cancel.cancel();
699 0 :
700 0 : shutdown_state.insert(tenant_shard_id, TenantSlot::Secondary(state));
701 0 : }
702 2 : TenantSlot::InProgress(notify) => {
703 2 : // InProgress tenants are not visible in TenantsMap::ShuttingDown: we will
704 2 : // wait for their notifications to fire in this function.
705 2 : join_set.spawn(async move {
706 2 : notify.wait().await;
707 2 : });
708 2 :
709 2 : total_in_progress += 1;
710 2 : }
711 : }
712 : }
713 2 : *m = TenantsMap::ShuttingDown(shutdown_state);
714 2 : (total_in_progress, total_attached)
715 : }
716 : TenantsMap::ShuttingDown(_) => {
717 0 : error!("already shutting down, this function isn't supposed to be called more than once");
718 0 : return;
719 : }
720 : }
721 : };
722 :
723 2 : let started_at = std::time::Instant::now();
724 :
725 2 : info!(
726 2 : "Waiting for {} InProgress tenants and {} Attached tenants to shut down",
727 2 : total_in_progress, total_attached
728 2 : );
729 :
730 2 : let total = join_set.len();
731 2 : let mut panicked = 0;
732 2 : let mut buffering = true;
733 2 : const BUFFER_FOR: std::time::Duration = std::time::Duration::from_millis(500);
734 2 : let mut buffered = std::pin::pin!(tokio::time::sleep(BUFFER_FOR));
735 :
736 6 : while !join_set.is_empty() {
737 4 : tokio::select! {
738 2 : Some(joined) = join_set.join_next() => {
739 : match joined {
740 : Ok(()) => {},
741 : Err(join_error) if join_error.is_cancelled() => {
742 : unreachable!("we are not cancelling any of the tasks");
743 : }
744 : Err(join_error) if join_error.is_panic() => {
745 : // cannot really do anything, as this panic is likely a bug
746 : panicked += 1;
747 : }
748 : Err(join_error) => {
749 0 : warn!("unknown kind of JoinError: {join_error}");
750 : }
751 : }
752 : if !buffering {
753 : // buffer so that every 500ms since the first update (or starting) we'll log
754 : // how far away we are; this is because we will get SIGKILL'd at 10s, and we
755 : // are not able to log *then*.
756 : buffering = true;
757 : buffered.as_mut().reset(tokio::time::Instant::now() + BUFFER_FOR);
758 : }
759 : },
760 : _ = &mut buffered, if buffering => {
761 : buffering = false;
762 2 : info!(remaining = join_set.len(), total, elapsed_ms = started_at.elapsed().as_millis(), "waiting for tenants to shutdown");
763 : }
764 : }
765 : }
766 :
767 2 : if panicked > 0 {
768 0 : warn!(
769 0 : panicked,
770 0 : total, "observed panicks while shutting down tenants"
771 0 : );
772 2 : }
773 :
774 : // caller will log how long we took
775 2 : }
776 :
777 0 : #[derive(Debug, thiserror::Error)]
778 : pub(crate) enum SetNewTenantConfigError {
779 : #[error(transparent)]
780 : GetTenant(#[from] GetTenantError),
781 : #[error(transparent)]
782 : Persist(anyhow::Error),
783 : #[error(transparent)]
784 : Other(anyhow::Error),
785 : }
786 :
787 0 : pub(crate) async fn set_new_tenant_config(
788 0 : conf: &'static PageServerConf,
789 0 : new_tenant_conf: TenantConfOpt,
790 0 : tenant_id: TenantId,
791 0 : ) -> Result<(), SetNewTenantConfigError> {
792 0 : // Legacy API: does not support sharding
793 0 : let tenant_shard_id = TenantShardId::unsharded(tenant_id);
794 :
795 0 : info!("configuring tenant {tenant_id}");
796 0 : let tenant = get_tenant(tenant_shard_id, true)?;
797 :
798 0 : if !tenant.tenant_shard_id().shard_count.is_unsharded() {
799 : // Note that we use ShardParameters::default below.
800 0 : return Err(SetNewTenantConfigError::Other(anyhow::anyhow!(
801 0 : "This API may only be used on single-sharded tenants, use the /location_config API for sharded tenants"
802 0 : )));
803 0 : }
804 0 :
805 0 : // This is a legacy API that only operates on attached tenants: the preferred
806 0 : // API to use is the location_config/ endpoint, which lets the caller provide
807 0 : // the full LocationConf.
808 0 : let location_conf = LocationConf::attached_single(
809 0 : new_tenant_conf.clone(),
810 0 : tenant.generation,
811 0 : &ShardParameters::default(),
812 0 : );
813 0 :
814 0 : Tenant::persist_tenant_config(conf, &tenant_shard_id, &location_conf)
815 0 : .await
816 0 : .map_err(SetNewTenantConfigError::Persist)?;
817 0 : tenant.set_new_tenant_config(new_tenant_conf);
818 0 : Ok(())
819 0 : }
820 :
821 0 : #[derive(thiserror::Error, Debug)]
822 : pub(crate) enum UpsertLocationError {
823 : #[error("Bad config request: {0}")]
824 : BadRequest(anyhow::Error),
825 :
826 : #[error("Cannot change config in this state: {0}")]
827 : Unavailable(#[from] TenantMapError),
828 :
829 : #[error("Tenant is already being modified")]
830 : InProgress,
831 :
832 : #[error("Failed to flush: {0}")]
833 : Flush(anyhow::Error),
834 :
835 : #[error("Internal error: {0}")]
836 : Other(#[from] anyhow::Error),
837 : }
838 :
839 : impl TenantManager {
840 : /// Convenience function so that anyone with a TenantManager can get at the global configuration, without
841 : /// having to pass it around everywhere as a separate object.
842 0 : pub(crate) fn get_conf(&self) -> &'static PageServerConf {
843 0 : self.conf
844 0 : }
845 :
846 : /// Gets the attached tenant from the in-memory data, erroring if it's absent, in secondary mode, or is not fitting to the query.
847 : /// `active_only = true` allows to query only tenants that are ready for operations, erroring on other kinds of tenants.
848 0 : pub(crate) fn get_attached_tenant_shard(
849 0 : &self,
850 0 : tenant_shard_id: TenantShardId,
851 0 : active_only: bool,
852 0 : ) -> Result<Arc<Tenant>, GetTenantError> {
853 0 : let locked = self.tenants.read().unwrap();
854 :
855 0 : let peek_slot = tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)?;
856 :
857 0 : match peek_slot {
858 0 : Some(TenantSlot::Attached(tenant)) => match tenant.current_state() {
859 : TenantState::Broken {
860 0 : reason,
861 0 : backtrace: _,
862 0 : } if active_only => Err(GetTenantError::Broken(reason)),
863 0 : TenantState::Active => Ok(Arc::clone(tenant)),
864 : _ => {
865 0 : if active_only {
866 0 : Err(GetTenantError::NotActive(tenant_shard_id))
867 : } else {
868 0 : Ok(Arc::clone(tenant))
869 : }
870 : }
871 : },
872 0 : Some(TenantSlot::InProgress(_)) => Err(GetTenantError::NotActive(tenant_shard_id)),
873 : None | Some(TenantSlot::Secondary(_)) => {
874 0 : Err(GetTenantError::NotFound(tenant_shard_id.tenant_id))
875 : }
876 : }
877 0 : }
878 :
879 0 : pub(crate) fn get_secondary_tenant_shard(
880 0 : &self,
881 0 : tenant_shard_id: TenantShardId,
882 0 : ) -> Option<Arc<SecondaryTenant>> {
883 0 : let locked = self.tenants.read().unwrap();
884 0 :
885 0 : let peek_slot = tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)
886 0 : .ok()
887 0 : .flatten();
888 :
889 0 : match peek_slot {
890 0 : Some(TenantSlot::Secondary(s)) => Some(s.clone()),
891 0 : _ => None,
892 : }
893 0 : }
894 :
895 : /// Whether the `TenantManager` is responsible for the tenant shard
896 0 : pub(crate) fn manages_tenant_shard(&self, tenant_shard_id: TenantShardId) -> bool {
897 0 : let locked = self.tenants.read().unwrap();
898 0 :
899 0 : let peek_slot = tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)
900 0 : .ok()
901 0 : .flatten();
902 0 :
903 0 : peek_slot.is_some()
904 0 : }
905 :
906 0 : #[instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug()))]
907 : pub(crate) async fn upsert_location(
908 : &self,
909 : tenant_shard_id: TenantShardId,
910 : new_location_config: LocationConf,
911 : flush: Option<Duration>,
912 : mut spawn_mode: SpawnMode,
913 : ctx: &RequestContext,
914 : ) -> Result<Option<Arc<Tenant>>, UpsertLocationError> {
915 : debug_assert_current_span_has_tenant_id();
916 0 : info!("configuring tenant location to state {new_location_config:?}");
917 :
918 : enum FastPathModified {
919 : Attached(Arc<Tenant>),
920 : Secondary(Arc<SecondaryTenant>),
921 : }
922 :
923 : // Special case fast-path for updates to existing slots: if our upsert is only updating configuration,
924 : // then we do not need to set the slot to InProgress, we can just call into the
925 : // existng tenant.
926 : let fast_path_taken = {
927 : let locked = self.tenants.read().unwrap();
928 : let peek_slot =
929 : tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Write)?;
930 : match (&new_location_config.mode, peek_slot) {
931 : (LocationMode::Attached(attach_conf), Some(TenantSlot::Attached(tenant))) => {
932 : match attach_conf.generation.cmp(&tenant.generation) {
933 : Ordering::Equal => {
934 : // A transition from Attached to Attached in the same generation, we may
935 : // take our fast path and just provide the updated configuration
936 : // to the tenant.
937 : tenant.set_new_location_config(
938 : AttachedTenantConf::try_from(new_location_config.clone())
939 : .map_err(UpsertLocationError::BadRequest)?,
940 : );
941 :
942 : Some(FastPathModified::Attached(tenant.clone()))
943 : }
944 : Ordering::Less => {
945 : return Err(UpsertLocationError::BadRequest(anyhow::anyhow!(
946 : "Generation {:?} is less than existing {:?}",
947 : attach_conf.generation,
948 : tenant.generation
949 : )));
950 : }
951 : Ordering::Greater => {
952 : // Generation advanced, fall through to general case of replacing `Tenant` object
953 : None
954 : }
955 : }
956 : }
957 : (
958 : LocationMode::Secondary(secondary_conf),
959 : Some(TenantSlot::Secondary(secondary_tenant)),
960 : ) => {
961 : secondary_tenant.set_config(secondary_conf);
962 : secondary_tenant.set_tenant_conf(&new_location_config.tenant_conf);
963 : Some(FastPathModified::Secondary(secondary_tenant.clone()))
964 : }
965 : _ => {
966 : // Not an Attached->Attached transition, fall through to general case
967 : None
968 : }
969 : }
970 : };
971 :
972 : // Fast-path continued: having dropped out of the self.tenants lock, do the async
973 : // phase of writing config and/or waiting for flush, before returning.
974 : match fast_path_taken {
975 : Some(FastPathModified::Attached(tenant)) => {
976 : Tenant::persist_tenant_config(self.conf, &tenant_shard_id, &new_location_config)
977 : .await?;
978 :
979 : // Transition to AttachedStale means we may well hold a valid generation
980 : // still, and have been requested to go stale as part of a migration. If
981 : // the caller set `flush`, then flush to remote storage.
982 : if let LocationMode::Attached(AttachedLocationConfig {
983 : generation: _,
984 : attach_mode: AttachmentMode::Stale,
985 : }) = &new_location_config.mode
986 : {
987 : if let Some(flush_timeout) = flush {
988 : match tokio::time::timeout(flush_timeout, tenant.flush_remote()).await {
989 : Ok(Err(e)) => {
990 : return Err(UpsertLocationError::Flush(e));
991 : }
992 : Ok(Ok(_)) => return Ok(Some(tenant)),
993 : Err(_) => {
994 0 : tracing::warn!(
995 0 : timeout_ms = flush_timeout.as_millis(),
996 0 : "Timed out waiting for flush to remote storage, proceeding anyway."
997 0 : )
998 : }
999 : }
1000 : }
1001 : }
1002 :
1003 : return Ok(Some(tenant));
1004 : }
1005 : Some(FastPathModified::Secondary(_secondary_tenant)) => {
1006 : Tenant::persist_tenant_config(self.conf, &tenant_shard_id, &new_location_config)
1007 : .await?;
1008 :
1009 : return Ok(None);
1010 : }
1011 : None => {
1012 : // Proceed with the general case procedure, where we will shutdown & remove any existing
1013 : // slot contents and replace with a fresh one
1014 : }
1015 : };
1016 :
1017 : // General case for upserts to TenantsMap, excluding the case above: we will substitute an
1018 : // InProgress value to the slot while we make whatever changes are required. The state for
1019 : // the tenant is inaccessible to the outside world while we are doing this, but that is sensible:
1020 : // the state is ill-defined while we're in transition. Transitions are async, but fast: we do
1021 : // not do significant I/O, and shutdowns should be prompt via cancellation tokens.
1022 : let mut slot_guard = tenant_map_acquire_slot(&tenant_shard_id, TenantSlotAcquireMode::Any)
1023 0 : .map_err(|e| match e {
1024 : TenantSlotError::AlreadyExists(_, _) | TenantSlotError::NotFound(_) => {
1025 0 : unreachable!("Called with mode Any")
1026 : }
1027 0 : TenantSlotError::InProgress => UpsertLocationError::InProgress,
1028 0 : TenantSlotError::MapState(s) => UpsertLocationError::Unavailable(s),
1029 0 : })?;
1030 :
1031 : match slot_guard.get_old_value() {
1032 : Some(TenantSlot::Attached(tenant)) => {
1033 : // The case where we keep a Tenant alive was covered above in the special case
1034 : // for Attached->Attached transitions in the same generation. By this point,
1035 : // if we see an attached tenant we know it will be discarded and should be
1036 : // shut down.
1037 : let (_guard, progress) = utils::completion::channel();
1038 :
1039 : match tenant.get_attach_mode() {
1040 : AttachmentMode::Single | AttachmentMode::Multi => {
1041 : // Before we leave our state as the presumed holder of the latest generation,
1042 : // flush any outstanding deletions to reduce the risk of leaking objects.
1043 : self.resources.deletion_queue_client.flush_advisory()
1044 : }
1045 : AttachmentMode::Stale => {
1046 : // If we're stale there's not point trying to flush deletions
1047 : }
1048 : };
1049 :
1050 0 : info!("Shutting down attached tenant");
1051 : match tenant.shutdown(progress, false).await {
1052 : Ok(()) => {}
1053 : Err(barrier) => {
1054 0 : info!("Shutdown already in progress, waiting for it to complete");
1055 : barrier.wait().await;
1056 : }
1057 : }
1058 : slot_guard.drop_old_value().expect("We just shut it down");
1059 :
1060 : // Edge case: if we were called with SpawnMode::Create, but a Tenant already existed, then
1061 : // the caller thinks they're creating but the tenant already existed. We must switch to
1062 : // Normal mode so that when starting this Tenant we properly probe remote storage for timelines,
1063 : // rather than assuming it to be empty.
1064 : spawn_mode = SpawnMode::Normal;
1065 : }
1066 : Some(TenantSlot::Secondary(state)) => {
1067 0 : info!("Shutting down secondary tenant");
1068 : state.shutdown().await;
1069 : }
1070 : Some(TenantSlot::InProgress(_)) => {
1071 : // This should never happen: acquire_slot should error out
1072 : // if the contents of a slot were InProgress.
1073 : return Err(UpsertLocationError::Other(anyhow::anyhow!(
1074 : "Acquired an InProgress slot, this is a bug."
1075 : )));
1076 : }
1077 : None => {
1078 : // Slot was vacant, nothing needs shutting down.
1079 : }
1080 : }
1081 :
1082 : let tenant_path = self.conf.tenant_path(&tenant_shard_id);
1083 : let timelines_path = self.conf.timelines_path(&tenant_shard_id);
1084 :
1085 : // Directory structure is the same for attached and secondary modes:
1086 : // create it if it doesn't exist. Timeline load/creation expects the
1087 : // timelines/ subdir to already exist.
1088 : //
1089 : // Does not need to be fsync'd because local storage is just a cache.
1090 : tokio::fs::create_dir_all(&timelines_path)
1091 : .await
1092 0 : .with_context(|| format!("Creating {timelines_path}"))?;
1093 :
1094 : // Before activating either secondary or attached mode, persist the
1095 : // configuration, so that on restart we will re-attach (or re-start
1096 : // secondary) on the tenant.
1097 : Tenant::persist_tenant_config(self.conf, &tenant_shard_id, &new_location_config).await?;
1098 :
1099 : let new_slot = match &new_location_config.mode {
1100 : LocationMode::Secondary(secondary_config) => {
1101 : let shard_identity = new_location_config.shard;
1102 : TenantSlot::Secondary(SecondaryTenant::new(
1103 : tenant_shard_id,
1104 : shard_identity,
1105 : new_location_config.tenant_conf,
1106 : secondary_config,
1107 : ))
1108 : }
1109 : LocationMode::Attached(_attach_config) => {
1110 : let shard_identity = new_location_config.shard;
1111 :
1112 : // Testing hack: if we are configured with no control plane, then drop the generation
1113 : // from upserts. This enables creating generation-less tenants even though neon_local
1114 : // always uses generations when calling the location conf API.
1115 : let attached_conf = if cfg!(feature = "testing") {
1116 : let mut conf = AttachedTenantConf::try_from(new_location_config)?;
1117 : if self.conf.control_plane_api.is_none() {
1118 : conf.location.generation = Generation::none();
1119 : }
1120 : conf
1121 : } else {
1122 : AttachedTenantConf::try_from(new_location_config)?
1123 : };
1124 :
1125 : let tenant = tenant_spawn(
1126 : self.conf,
1127 : tenant_shard_id,
1128 : &tenant_path,
1129 : self.resources.clone(),
1130 : attached_conf,
1131 : shard_identity,
1132 : None,
1133 : self.tenants,
1134 : spawn_mode,
1135 : ctx,
1136 : )?;
1137 :
1138 : TenantSlot::Attached(tenant)
1139 : }
1140 : };
1141 :
1142 : let attached_tenant = if let TenantSlot::Attached(tenant) = &new_slot {
1143 : Some(tenant.clone())
1144 : } else {
1145 : None
1146 : };
1147 :
1148 : match slot_guard.upsert(new_slot) {
1149 : Err(TenantSlotUpsertError::InternalError(e)) => {
1150 : Err(UpsertLocationError::Other(anyhow::anyhow!(e)))
1151 : }
1152 : Err(TenantSlotUpsertError::MapState(e)) => Err(UpsertLocationError::Unavailable(e)),
1153 : Err(TenantSlotUpsertError::ShuttingDown((new_slot, _completion))) => {
1154 : // If we just called tenant_spawn() on a new tenant, and can't insert it into our map, then
1155 : // we must not leak it: this would violate the invariant that after shutdown_all_tenants, all tenants
1156 : // are shutdown.
1157 : //
1158 : // We must shut it down inline here.
1159 : match new_slot {
1160 : TenantSlot::InProgress(_) => {
1161 : // Unreachable because we never insert an InProgress
1162 : unreachable!()
1163 : }
1164 : TenantSlot::Attached(tenant) => {
1165 : let (_guard, progress) = utils::completion::channel();
1166 0 : info!("Shutting down just-spawned tenant, because tenant manager is shut down");
1167 : match tenant.shutdown(progress, false).await {
1168 : Ok(()) => {
1169 0 : info!("Finished shutting down just-spawned tenant");
1170 : }
1171 : Err(barrier) => {
1172 0 : info!("Shutdown already in progress, waiting for it to complete");
1173 : barrier.wait().await;
1174 : }
1175 : }
1176 : }
1177 : TenantSlot::Secondary(secondary_tenant) => {
1178 : secondary_tenant.shutdown().await;
1179 : }
1180 : }
1181 :
1182 : Err(UpsertLocationError::Unavailable(
1183 : TenantMapError::ShuttingDown,
1184 : ))
1185 : }
1186 : Ok(()) => Ok(attached_tenant),
1187 : }
1188 : }
1189 :
1190 : /// Resetting a tenant is equivalent to detaching it, then attaching it again with the same
1191 : /// LocationConf that was last used to attach it. Optionally, the local file cache may be
1192 : /// dropped before re-attaching.
1193 : ///
1194 : /// This is not part of a tenant's normal lifecycle: it is used for debug/support, in situations
1195 : /// where an issue is identified that would go away with a restart of the tenant.
1196 : ///
1197 : /// This does not have any special "force" shutdown of a tenant: it relies on the tenant's tasks
1198 : /// to respect the cancellation tokens used in normal shutdown().
1199 0 : #[instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), %drop_cache))]
1200 : pub(crate) async fn reset_tenant(
1201 : &self,
1202 : tenant_shard_id: TenantShardId,
1203 : drop_cache: bool,
1204 : ctx: &RequestContext,
1205 : ) -> anyhow::Result<()> {
1206 : let mut slot_guard = tenant_map_acquire_slot(&tenant_shard_id, TenantSlotAcquireMode::Any)?;
1207 : let Some(old_slot) = slot_guard.get_old_value() else {
1208 : anyhow::bail!("Tenant not found when trying to reset");
1209 : };
1210 :
1211 : let Some(tenant) = old_slot.get_attached() else {
1212 : slot_guard.revert();
1213 : anyhow::bail!("Tenant is not in attached state");
1214 : };
1215 :
1216 : let (_guard, progress) = utils::completion::channel();
1217 : match tenant.shutdown(progress, false).await {
1218 : Ok(()) => {
1219 : slot_guard.drop_old_value()?;
1220 : }
1221 : Err(_barrier) => {
1222 : slot_guard.revert();
1223 : anyhow::bail!("Cannot reset Tenant, already shutting down");
1224 : }
1225 : }
1226 :
1227 : let tenant_path = self.conf.tenant_path(&tenant_shard_id);
1228 : let timelines_path = self.conf.timelines_path(&tenant_shard_id);
1229 : let config = Tenant::load_tenant_config(self.conf, &tenant_shard_id)?;
1230 :
1231 : if drop_cache {
1232 0 : tracing::info!("Dropping local file cache");
1233 :
1234 : match tokio::fs::read_dir(&timelines_path).await {
1235 : Err(e) => {
1236 0 : tracing::warn!("Failed to list timelines while dropping cache: {}", e);
1237 : }
1238 : Ok(mut entries) => {
1239 : while let Some(entry) = entries.next_entry().await? {
1240 : tokio::fs::remove_dir_all(entry.path()).await?;
1241 : }
1242 : }
1243 : }
1244 : }
1245 :
1246 : let shard_identity = config.shard;
1247 : let tenant = tenant_spawn(
1248 : self.conf,
1249 : tenant_shard_id,
1250 : &tenant_path,
1251 : self.resources.clone(),
1252 : AttachedTenantConf::try_from(config)?,
1253 : shard_identity,
1254 : None,
1255 : self.tenants,
1256 : SpawnMode::Normal,
1257 : ctx,
1258 : )?;
1259 :
1260 : slot_guard.upsert(TenantSlot::Attached(tenant))?;
1261 :
1262 : Ok(())
1263 : }
1264 :
1265 0 : pub(crate) fn get_attached_active_tenant_shards(&self) -> Vec<Arc<Tenant>> {
1266 0 : let locked = self.tenants.read().unwrap();
1267 0 : match &*locked {
1268 0 : TenantsMap::Initializing => Vec::new(),
1269 0 : TenantsMap::Open(map) | TenantsMap::ShuttingDown(map) => map
1270 0 : .values()
1271 0 : .filter_map(|slot| {
1272 0 : slot.get_attached()
1273 0 : .and_then(|t| if t.is_active() { Some(t.clone()) } else { None })
1274 0 : })
1275 0 : .collect(),
1276 : }
1277 0 : }
1278 : // Do some synchronous work for all tenant slots in Secondary state. The provided
1279 : // callback should be small and fast, as it will be called inside the global
1280 : // TenantsMap lock.
1281 0 : pub(crate) fn foreach_secondary_tenants<F>(&self, mut func: F)
1282 0 : where
1283 0 : // TODO: let the callback return a hint to drop out of the loop early
1284 0 : F: FnMut(&TenantShardId, &Arc<SecondaryTenant>),
1285 0 : {
1286 0 : let locked = self.tenants.read().unwrap();
1287 :
1288 0 : let map = match &*locked {
1289 0 : TenantsMap::Initializing | TenantsMap::ShuttingDown(_) => return,
1290 0 : TenantsMap::Open(m) => m,
1291 : };
1292 :
1293 0 : for (tenant_id, slot) in map {
1294 0 : if let TenantSlot::Secondary(state) = slot {
1295 : // Only expose secondary tenants that are not currently shutting down
1296 0 : if !state.cancel.is_cancelled() {
1297 0 : func(tenant_id, state)
1298 0 : }
1299 0 : }
1300 : }
1301 0 : }
1302 :
1303 : /// Total list of all tenant slots: this includes attached, secondary, and InProgress.
1304 0 : pub(crate) fn list(&self) -> Vec<(TenantShardId, TenantSlot)> {
1305 0 : let locked = self.tenants.read().unwrap();
1306 0 : match &*locked {
1307 0 : TenantsMap::Initializing => Vec::new(),
1308 0 : TenantsMap::Open(map) | TenantsMap::ShuttingDown(map) => {
1309 0 : map.iter().map(|(k, v)| (*k, v.clone())).collect()
1310 : }
1311 : }
1312 0 : }
1313 :
1314 0 : pub(crate) async fn delete_tenant(
1315 0 : &self,
1316 0 : tenant_shard_id: TenantShardId,
1317 0 : activation_timeout: Duration,
1318 0 : ) -> Result<(), DeleteTenantError> {
1319 0 : super::span::debug_assert_current_span_has_tenant_id();
1320 : // We acquire a SlotGuard during this function to protect against concurrent
1321 : // changes while the ::prepare phase of DeleteTenantFlow executes, but then
1322 : // have to return the Tenant to the map while the background deletion runs.
1323 : //
1324 : // TODO: refactor deletion to happen outside the lifetime of a Tenant.
1325 : // Currently, deletion requires a reference to the tenants map in order to
1326 : // keep the Tenant in the map until deletion is complete, and then remove
1327 : // it at the end.
1328 : //
1329 : // See https://github.com/neondatabase/neon/issues/5080
1330 :
1331 0 : let slot_guard =
1332 0 : tenant_map_acquire_slot(&tenant_shard_id, TenantSlotAcquireMode::MustExist)?;
1333 :
1334 : // unwrap is safe because we used MustExist mode when acquiring
1335 0 : let tenant = match slot_guard.get_old_value().as_ref().unwrap() {
1336 0 : TenantSlot::Attached(tenant) => tenant.clone(),
1337 : _ => {
1338 : // Express "not attached" as equivalent to "not found"
1339 0 : return Err(DeleteTenantError::NotAttached);
1340 : }
1341 : };
1342 :
1343 0 : match tenant.current_state() {
1344 0 : TenantState::Broken { .. } | TenantState::Stopping { .. } => {
1345 0 : // If a tenant is broken or stopping, DeleteTenantFlow can
1346 0 : // handle it: broken tenants proceed to delete, stopping tenants
1347 0 : // are checked for deletion already in progress.
1348 0 : }
1349 : _ => {
1350 0 : tenant
1351 0 : .wait_to_become_active(activation_timeout)
1352 0 : .await
1353 0 : .map_err(|e| match e {
1354 : GetActiveTenantError::WillNotBecomeActive(_) => {
1355 0 : DeleteTenantError::InvalidState(tenant.current_state())
1356 : }
1357 0 : GetActiveTenantError::Cancelled => DeleteTenantError::Cancelled,
1358 0 : GetActiveTenantError::NotFound(_) => DeleteTenantError::NotAttached,
1359 : GetActiveTenantError::WaitForActiveTimeout {
1360 0 : latest_state: _latest_state,
1361 0 : wait_time: _wait_time,
1362 0 : } => DeleteTenantError::InvalidState(tenant.current_state()),
1363 0 : })?;
1364 : }
1365 : }
1366 :
1367 0 : let result = DeleteTenantFlow::run(
1368 0 : self.conf,
1369 0 : self.resources.remote_storage.clone(),
1370 0 : &TENANTS,
1371 0 : tenant,
1372 0 : )
1373 0 : .await;
1374 :
1375 : // The Tenant goes back into the map in Stopping state, it will eventually be removed by DeleteTenantFLow
1376 0 : slot_guard.revert();
1377 0 : result
1378 0 : }
1379 :
1380 0 : #[instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), new_shard_count=%new_shard_count.literal()))]
1381 : pub(crate) async fn shard_split(
1382 : &self,
1383 : tenant_shard_id: TenantShardId,
1384 : new_shard_count: ShardCount,
1385 : ctx: &RequestContext,
1386 : ) -> anyhow::Result<Vec<TenantShardId>> {
1387 : let tenant = get_tenant(tenant_shard_id, true)?;
1388 :
1389 : // Plan: identify what the new child shards will be
1390 : if new_shard_count.count() <= tenant_shard_id.shard_count.count() {
1391 : anyhow::bail!("Requested shard count is not an increase");
1392 : }
1393 : let expansion_factor = new_shard_count.count() / tenant_shard_id.shard_count.count();
1394 : if !expansion_factor.is_power_of_two() {
1395 : anyhow::bail!("Requested split is not a power of two");
1396 : }
1397 :
1398 : let parent_shard_identity = tenant.shard_identity;
1399 : let parent_tenant_conf = tenant.get_tenant_conf();
1400 : let parent_generation = tenant.generation;
1401 :
1402 : let child_shards = tenant_shard_id.split(new_shard_count);
1403 0 : tracing::info!(
1404 0 : "Shard {} splits into: {}",
1405 0 : tenant_shard_id.to_index(),
1406 0 : child_shards
1407 0 : .iter()
1408 0 : .map(|id| format!("{}", id.to_index()))
1409 0 : .join(",")
1410 0 : );
1411 :
1412 : // Phase 1: Write out child shards' remote index files, in the parent tenant's current generation
1413 : if let Err(e) = tenant.split_prepare(&child_shards).await {
1414 : // If [`Tenant::split_prepare`] fails, we must reload the tenant, because it might
1415 : // have been left in a partially-shut-down state.
1416 0 : tracing::warn!("Failed to prepare for split: {e}, reloading Tenant before returning");
1417 : self.reset_tenant(tenant_shard_id, false, ctx).await?;
1418 : return Err(e);
1419 : }
1420 :
1421 : self.resources.deletion_queue_client.flush_advisory();
1422 :
1423 : // Phase 2: Put the parent shard to InProgress and grab a reference to the parent Tenant
1424 : drop(tenant);
1425 : let mut parent_slot_guard =
1426 : tenant_map_acquire_slot(&tenant_shard_id, TenantSlotAcquireMode::Any)?;
1427 : let parent = match parent_slot_guard.get_old_value() {
1428 : Some(TenantSlot::Attached(t)) => t,
1429 : Some(TenantSlot::Secondary(_)) => anyhow::bail!("Tenant location in secondary mode"),
1430 : Some(TenantSlot::InProgress(_)) => {
1431 : // tenant_map_acquire_slot never returns InProgress, if a slot was InProgress
1432 : // it would return an error.
1433 : unreachable!()
1434 : }
1435 : None => {
1436 : // We don't actually need the parent shard to still be attached to do our work, but it's
1437 : // a weird enough situation that the caller probably didn't want us to continue working
1438 : // if they had detached the tenant they requested the split on.
1439 : anyhow::bail!("Detached parent shard in the middle of split!")
1440 : }
1441 : };
1442 :
1443 : // Optimization: hardlink layers from the parent into the children, so that they don't have to
1444 : // re-download & duplicate the data referenced in their initial IndexPart
1445 : self.shard_split_hardlink(parent, child_shards.clone())
1446 : .await?;
1447 :
1448 : // Take a snapshot of where the parent's WAL ingest had got to: we will wait for
1449 : // child shards to reach this point.
1450 : let mut target_lsns = HashMap::new();
1451 : for timeline in parent.timelines.lock().unwrap().clone().values() {
1452 : target_lsns.insert(timeline.timeline_id, timeline.get_last_record_lsn());
1453 : }
1454 :
1455 : // TODO: we should have the parent shard stop its WAL ingest here, it's a waste of resources
1456 : // and could slow down the children trying to catch up.
1457 :
1458 : // Phase 3: Spawn the child shards
1459 : for child_shard in &child_shards {
1460 : let mut child_shard_identity = parent_shard_identity;
1461 : child_shard_identity.count = child_shard.shard_count;
1462 : child_shard_identity.number = child_shard.shard_number;
1463 :
1464 : let child_location_conf = LocationConf {
1465 : mode: LocationMode::Attached(AttachedLocationConfig {
1466 : generation: parent_generation,
1467 : attach_mode: AttachmentMode::Single,
1468 : }),
1469 : shard: child_shard_identity,
1470 : tenant_conf: parent_tenant_conf.clone(),
1471 : };
1472 :
1473 : self.upsert_location(
1474 : *child_shard,
1475 : child_location_conf,
1476 : None,
1477 : SpawnMode::Normal,
1478 : ctx,
1479 : )
1480 : .await?;
1481 : }
1482 :
1483 : // Phase 4: wait for child chards WAL ingest to catch up to target LSN
1484 : for child_shard_id in &child_shards {
1485 : let child_shard_id = *child_shard_id;
1486 : let child_shard = {
1487 : let locked = TENANTS.read().unwrap();
1488 : let peek_slot =
1489 : tenant_map_peek_slot(&locked, &child_shard_id, TenantSlotPeekMode::Read)?;
1490 0 : peek_slot.and_then(|s| s.get_attached()).cloned()
1491 : };
1492 : if let Some(t) = child_shard {
1493 : // Wait for the child shard to become active: this should be very quick because it only
1494 : // has to download the index_part that we just uploaded when creating it.
1495 : if let Err(e) = t.wait_to_become_active(ACTIVE_TENANT_TIMEOUT).await {
1496 : // This is not fatal: we have durably created the child shard. It just makes the
1497 : // split operation less seamless for clients, as we will may detach the parent
1498 : // shard before the child shards are fully ready to serve requests.
1499 0 : tracing::warn!("Failed to wait for shard {child_shard_id} to activate: {e}");
1500 : continue;
1501 : }
1502 :
1503 : let timelines = t.timelines.lock().unwrap().clone();
1504 : for timeline in timelines.values() {
1505 : let Some(target_lsn) = target_lsns.get(&timeline.timeline_id) else {
1506 : continue;
1507 : };
1508 :
1509 0 : tracing::info!(
1510 0 : "Waiting for child shard {}/{} to reach target lsn {}...",
1511 0 : child_shard_id,
1512 0 : timeline.timeline_id,
1513 0 : target_lsn
1514 0 : );
1515 : if let Err(e) = timeline.wait_lsn(*target_lsn, ctx).await {
1516 : // Failure here might mean shutdown, in any case this part is an optimization
1517 : // and we shouldn't hold up the split operation.
1518 0 : tracing::warn!(
1519 0 : "Failed to wait for timeline {} to reach lsn {target_lsn}: {e}",
1520 0 : timeline.timeline_id
1521 0 : );
1522 : } else {
1523 0 : tracing::info!(
1524 0 : "Child shard {}/{} reached target lsn {}",
1525 0 : child_shard_id,
1526 0 : timeline.timeline_id,
1527 0 : target_lsn
1528 0 : );
1529 : }
1530 : }
1531 : }
1532 : }
1533 :
1534 : // Phase 5: Shut down the parent shard, and erase it from disk
1535 : let (_guard, progress) = completion::channel();
1536 : match parent.shutdown(progress, false).await {
1537 : Ok(()) => {}
1538 : Err(other) => {
1539 : other.wait().await;
1540 : }
1541 : }
1542 : let local_tenant_directory = self.conf.tenant_path(&tenant_shard_id);
1543 : let tmp_path = safe_rename_tenant_dir(&local_tenant_directory)
1544 : .await
1545 0 : .with_context(|| format!("local tenant directory {local_tenant_directory:?} rename"))?;
1546 : task_mgr::spawn(
1547 : task_mgr::BACKGROUND_RUNTIME.handle(),
1548 : TaskKind::MgmtRequest,
1549 : None,
1550 : None,
1551 : "tenant_files_delete",
1552 : false,
1553 0 : async move {
1554 0 : fs::remove_dir_all(tmp_path.as_path())
1555 0 : .await
1556 0 : .with_context(|| format!("tenant directory {:?} deletion", tmp_path))
1557 0 : },
1558 : );
1559 :
1560 : parent_slot_guard.drop_old_value()?;
1561 :
1562 : // Phase 6: Release the InProgress on the parent shard
1563 : drop(parent_slot_guard);
1564 :
1565 : Ok(child_shards)
1566 : }
1567 :
1568 : /// Part of [`Self::shard_split`]: hard link parent shard layers into child shards, as an optimization
1569 : /// to avoid the children downloading them again.
1570 : ///
1571 : /// For each resident layer in the parent shard, we will hard link it into all of the child shards.
1572 0 : async fn shard_split_hardlink(
1573 0 : &self,
1574 0 : parent_shard: &Tenant,
1575 0 : child_shards: Vec<TenantShardId>,
1576 0 : ) -> anyhow::Result<()> {
1577 0 : debug_assert_current_span_has_tenant_id();
1578 0 :
1579 0 : let parent_path = self.conf.tenant_path(parent_shard.get_tenant_shard_id());
1580 0 : let (parent_timelines, parent_layers) = {
1581 0 : let mut parent_layers = Vec::new();
1582 0 : let timelines = parent_shard.timelines.lock().unwrap().clone();
1583 0 : let parent_timelines = timelines.keys().cloned().collect::<Vec<_>>();
1584 0 : for timeline in timelines.values() {
1585 0 : let timeline_layers = timeline
1586 0 : .layers
1587 0 : .read()
1588 0 : .await
1589 0 : .resident_layers()
1590 0 : .collect::<Vec<_>>()
1591 0 : .await;
1592 0 : for layer in timeline_layers {
1593 0 : let relative_path = layer
1594 0 : .local_path()
1595 0 : .strip_prefix(&parent_path)
1596 0 : .context("Removing prefix from parent layer path")?;
1597 0 : parent_layers.push(relative_path.to_owned());
1598 : }
1599 : }
1600 : debug_assert!(
1601 0 : !parent_layers.is_empty(),
1602 0 : "shutdown cannot empty the layermap"
1603 : );
1604 0 : (parent_timelines, parent_layers)
1605 0 : };
1606 0 :
1607 0 : let mut child_prefixes = Vec::new();
1608 0 : let mut create_dirs = Vec::new();
1609 :
1610 0 : for child in child_shards {
1611 0 : let child_prefix = self.conf.tenant_path(&child);
1612 0 : create_dirs.push(child_prefix.clone());
1613 0 : create_dirs.extend(
1614 0 : parent_timelines
1615 0 : .iter()
1616 0 : .map(|t| self.conf.timeline_path(&child, t)),
1617 0 : );
1618 0 :
1619 0 : child_prefixes.push(child_prefix);
1620 0 : }
1621 :
1622 : // Since we will do a large number of small filesystem metadata operations, batch them into
1623 : // spawn_blocking calls rather than doing each one as a tokio::fs round-trip.
1624 0 : let jh = tokio::task::spawn_blocking(move || -> anyhow::Result<usize> {
1625 0 : for dir in &create_dirs {
1626 0 : if let Err(e) = std::fs::create_dir_all(dir) {
1627 : // Ignore AlreadyExists errors, drop out on all other errors
1628 0 : match e.kind() {
1629 0 : std::io::ErrorKind::AlreadyExists => {}
1630 : _ => {
1631 0 : return Err(anyhow::anyhow!(e).context(format!("Creating {dir}")));
1632 : }
1633 : }
1634 0 : }
1635 : }
1636 :
1637 0 : for child_prefix in child_prefixes {
1638 0 : for relative_layer in &parent_layers {
1639 0 : let parent_path = parent_path.join(relative_layer);
1640 0 : let child_path = child_prefix.join(relative_layer);
1641 0 : if let Err(e) = std::fs::hard_link(&parent_path, &child_path) {
1642 0 : match e.kind() {
1643 0 : std::io::ErrorKind::AlreadyExists => {}
1644 : std::io::ErrorKind::NotFound => {
1645 0 : tracing::info!(
1646 0 : "Layer {} not found during hard-linking, evicted during split?",
1647 0 : relative_layer
1648 0 : );
1649 : }
1650 : _ => {
1651 0 : return Err(anyhow::anyhow!(e).context(format!(
1652 0 : "Hard linking {relative_layer} into {child_prefix}"
1653 0 : )))
1654 : }
1655 : }
1656 0 : }
1657 : }
1658 : }
1659 :
1660 : // Durability is not required for correctness, but if we crashed during split and
1661 : // then came restarted with empty timeline dirs, it would be very inefficient to
1662 : // re-populate from remote storage.
1663 0 : for dir in create_dirs {
1664 0 : if let Err(e) = crashsafe::fsync(&dir) {
1665 : // Something removed a newly created timeline dir out from underneath us? Extremely
1666 : // unexpected, but not worth panic'ing over as this whole function is just an
1667 : // optimization.
1668 0 : tracing::warn!("Failed to fsync directory {dir}: {e}")
1669 0 : }
1670 : }
1671 :
1672 0 : Ok(parent_layers.len())
1673 0 : });
1674 0 :
1675 0 : match jh.await {
1676 0 : Ok(Ok(layer_count)) => {
1677 0 : tracing::info!(count = layer_count, "Hard linked layers into child shards");
1678 : }
1679 0 : Ok(Err(e)) => {
1680 : // This is an optimization, so we tolerate failure.
1681 0 : tracing::warn!("Error hard-linking layers, proceeding anyway: {e}")
1682 : }
1683 0 : Err(e) => {
1684 0 : // This is something totally unexpected like a panic, so bail out.
1685 0 : anyhow::bail!("Error joining hard linking task: {e}");
1686 : }
1687 : }
1688 :
1689 0 : Ok(())
1690 0 : }
1691 : }
1692 :
1693 0 : #[derive(Debug, thiserror::Error)]
1694 : pub(crate) enum GetTenantError {
1695 : /// NotFound is a TenantId rather than TenantShardId, because this error type is used from
1696 : /// getters that use a TenantId and a ShardSelector, not just getters that target a specific shard.
1697 : #[error("Tenant {0} not found")]
1698 : NotFound(TenantId),
1699 :
1700 : #[error("Tenant {0} is not active")]
1701 : NotActive(TenantShardId),
1702 : /// Broken is logically a subset of NotActive, but a distinct error is useful as
1703 : /// NotActive is usually a retryable state for API purposes, whereas Broken
1704 : /// is a stuck error state
1705 : #[error("Tenant is broken: {0}")]
1706 : Broken(String),
1707 :
1708 : // Initializing or shutting down: cannot authoritatively say whether we have this tenant
1709 : #[error("Tenant map is not available: {0}")]
1710 : MapState(#[from] TenantMapError),
1711 : }
1712 :
1713 : /// Gets the tenant from the in-memory data, erroring if it's absent or is not fitting to the query.
1714 : /// `active_only = true` allows to query only tenants that are ready for operations, erroring on other kinds of tenants.
1715 : ///
1716 : /// This method is cancel-safe.
1717 0 : pub(crate) fn get_tenant(
1718 0 : tenant_shard_id: TenantShardId,
1719 0 : active_only: bool,
1720 0 : ) -> Result<Arc<Tenant>, GetTenantError> {
1721 0 : let locked = TENANTS.read().unwrap();
1722 :
1723 0 : let peek_slot = tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)?;
1724 :
1725 0 : match peek_slot {
1726 0 : Some(TenantSlot::Attached(tenant)) => match tenant.current_state() {
1727 : TenantState::Broken {
1728 0 : reason,
1729 0 : backtrace: _,
1730 0 : } if active_only => Err(GetTenantError::Broken(reason)),
1731 0 : TenantState::Active => Ok(Arc::clone(tenant)),
1732 : _ => {
1733 0 : if active_only {
1734 0 : Err(GetTenantError::NotActive(tenant_shard_id))
1735 : } else {
1736 0 : Ok(Arc::clone(tenant))
1737 : }
1738 : }
1739 : },
1740 0 : Some(TenantSlot::InProgress(_)) => Err(GetTenantError::NotActive(tenant_shard_id)),
1741 : None | Some(TenantSlot::Secondary(_)) => {
1742 0 : Err(GetTenantError::NotFound(tenant_shard_id.tenant_id))
1743 : }
1744 : }
1745 0 : }
1746 :
1747 0 : #[derive(thiserror::Error, Debug)]
1748 : pub(crate) enum GetActiveTenantError {
1749 : /// We may time out either while TenantSlot is InProgress, or while the Tenant
1750 : /// is in a non-Active state
1751 : #[error(
1752 : "Timed out waiting {wait_time:?} for tenant active state. Latest state: {latest_state:?}"
1753 : )]
1754 : WaitForActiveTimeout {
1755 : latest_state: Option<TenantState>,
1756 : wait_time: Duration,
1757 : },
1758 :
1759 : /// The TenantSlot is absent, or in secondary mode
1760 : #[error(transparent)]
1761 : NotFound(#[from] GetTenantError),
1762 :
1763 : /// Cancellation token fired while we were waiting
1764 : #[error("cancelled")]
1765 : Cancelled,
1766 :
1767 : /// Tenant exists, but is in a state that cannot become active (e.g. Stopping, Broken)
1768 : #[error("will not become active. Current state: {0}")]
1769 : WillNotBecomeActive(TenantState),
1770 : }
1771 :
1772 : /// Get a [`Tenant`] in its active state. If the tenant_id is currently in [`TenantSlot::InProgress`]
1773 : /// state, then wait for up to `timeout`. If the [`Tenant`] is not currently in [`TenantState::Active`],
1774 : /// then wait for up to `timeout` (minus however long we waited for the slot).
1775 0 : pub(crate) async fn get_active_tenant_with_timeout(
1776 0 : tenant_id: TenantId,
1777 0 : shard_selector: ShardSelector,
1778 0 : timeout: Duration,
1779 0 : cancel: &CancellationToken,
1780 0 : ) -> Result<Arc<Tenant>, GetActiveTenantError> {
1781 0 : enum WaitFor {
1782 0 : Barrier(utils::completion::Barrier),
1783 0 : Tenant(Arc<Tenant>),
1784 0 : }
1785 0 :
1786 0 : let wait_start = Instant::now();
1787 0 : let deadline = wait_start + timeout;
1788 :
1789 0 : let (wait_for, tenant_shard_id) = {
1790 0 : let locked = TENANTS.read().unwrap();
1791 :
1792 : // Resolve TenantId to TenantShardId
1793 0 : let tenant_shard_id = locked
1794 0 : .resolve_attached_shard(&tenant_id, shard_selector)
1795 0 : .ok_or(GetActiveTenantError::NotFound(GetTenantError::NotFound(
1796 0 : tenant_id,
1797 0 : )))?;
1798 :
1799 0 : let peek_slot = tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)
1800 0 : .map_err(GetTenantError::MapState)?;
1801 0 : match peek_slot {
1802 0 : Some(TenantSlot::Attached(tenant)) => {
1803 0 : match tenant.current_state() {
1804 : TenantState::Active => {
1805 : // Fast path: we don't need to do any async waiting.
1806 0 : return Ok(tenant.clone());
1807 : }
1808 : _ => {
1809 0 : tenant.activate_now();
1810 0 : (WaitFor::Tenant(tenant.clone()), tenant_shard_id)
1811 : }
1812 : }
1813 : }
1814 : Some(TenantSlot::Secondary(_)) => {
1815 0 : return Err(GetActiveTenantError::NotFound(GetTenantError::NotActive(
1816 0 : tenant_shard_id,
1817 0 : )))
1818 : }
1819 0 : Some(TenantSlot::InProgress(barrier)) => {
1820 0 : (WaitFor::Barrier(barrier.clone()), tenant_shard_id)
1821 : }
1822 : None => {
1823 0 : return Err(GetActiveTenantError::NotFound(GetTenantError::NotFound(
1824 0 : tenant_id,
1825 0 : )))
1826 : }
1827 : }
1828 : };
1829 :
1830 0 : let tenant = match wait_for {
1831 0 : WaitFor::Barrier(barrier) => {
1832 0 : tracing::debug!("Waiting for tenant InProgress state to pass...");
1833 0 : timeout_cancellable(
1834 0 : deadline.duration_since(Instant::now()),
1835 0 : cancel,
1836 0 : barrier.wait(),
1837 0 : )
1838 0 : .await
1839 0 : .map_err(|e| match e {
1840 0 : TimeoutCancellableError::Timeout => GetActiveTenantError::WaitForActiveTimeout {
1841 0 : latest_state: None,
1842 0 : wait_time: wait_start.elapsed(),
1843 0 : },
1844 0 : TimeoutCancellableError::Cancelled => GetActiveTenantError::Cancelled,
1845 0 : })?;
1846 : {
1847 0 : let locked = TENANTS.read().unwrap();
1848 0 : let peek_slot =
1849 0 : tenant_map_peek_slot(&locked, &tenant_shard_id, TenantSlotPeekMode::Read)
1850 0 : .map_err(GetTenantError::MapState)?;
1851 0 : match peek_slot {
1852 0 : Some(TenantSlot::Attached(tenant)) => tenant.clone(),
1853 : _ => {
1854 0 : return Err(GetActiveTenantError::NotFound(GetTenantError::NotActive(
1855 0 : tenant_shard_id,
1856 0 : )))
1857 : }
1858 : }
1859 : }
1860 : }
1861 0 : WaitFor::Tenant(tenant) => tenant,
1862 : };
1863 :
1864 0 : tracing::debug!("Waiting for tenant to enter active state...");
1865 0 : tenant
1866 0 : .wait_to_become_active(deadline.duration_since(Instant::now()))
1867 0 : .await?;
1868 0 : Ok(tenant)
1869 0 : }
1870 :
1871 0 : #[derive(Debug, thiserror::Error)]
1872 : pub(crate) enum DeleteTimelineError {
1873 : #[error("Tenant {0}")]
1874 : Tenant(#[from] GetTenantError),
1875 :
1876 : #[error("Timeline {0}")]
1877 : Timeline(#[from] crate::tenant::DeleteTimelineError),
1878 : }
1879 :
1880 0 : #[derive(Debug, thiserror::Error)]
1881 : pub(crate) enum TenantStateError {
1882 : #[error("Tenant {0} is stopping")]
1883 : IsStopping(TenantShardId),
1884 : #[error(transparent)]
1885 : SlotError(#[from] TenantSlotError),
1886 : #[error(transparent)]
1887 : SlotUpsertError(#[from] TenantSlotUpsertError),
1888 : #[error(transparent)]
1889 : Other(#[from] anyhow::Error),
1890 : }
1891 :
1892 0 : pub(crate) async fn detach_tenant(
1893 0 : conf: &'static PageServerConf,
1894 0 : tenant_shard_id: TenantShardId,
1895 0 : detach_ignored: bool,
1896 0 : deletion_queue_client: &DeletionQueueClient,
1897 0 : ) -> Result<(), TenantStateError> {
1898 0 : let tmp_path = detach_tenant0(
1899 0 : conf,
1900 0 : &TENANTS,
1901 0 : tenant_shard_id,
1902 0 : detach_ignored,
1903 0 : deletion_queue_client,
1904 0 : )
1905 0 : .await?;
1906 : // Although we are cleaning up the tenant, this task is not meant to be bound by the lifetime of the tenant in memory.
1907 : // After a tenant is detached, there are no more task_mgr tasks for that tenant_id.
1908 0 : let task_tenant_id = None;
1909 0 : task_mgr::spawn(
1910 0 : task_mgr::BACKGROUND_RUNTIME.handle(),
1911 0 : TaskKind::MgmtRequest,
1912 0 : task_tenant_id,
1913 0 : None,
1914 0 : "tenant_files_delete",
1915 0 : false,
1916 0 : async move {
1917 0 : fs::remove_dir_all(tmp_path.as_path())
1918 0 : .await
1919 0 : .with_context(|| format!("tenant directory {:?} deletion", tmp_path))
1920 0 : },
1921 0 : );
1922 0 : Ok(())
1923 0 : }
1924 :
1925 0 : async fn detach_tenant0(
1926 0 : conf: &'static PageServerConf,
1927 0 : tenants: &std::sync::RwLock<TenantsMap>,
1928 0 : tenant_shard_id: TenantShardId,
1929 0 : detach_ignored: bool,
1930 0 : deletion_queue_client: &DeletionQueueClient,
1931 0 : ) -> Result<Utf8PathBuf, TenantStateError> {
1932 0 : let tenant_dir_rename_operation = |tenant_id_to_clean: TenantShardId| async move {
1933 0 : let local_tenant_directory = conf.tenant_path(&tenant_id_to_clean);
1934 0 : safe_rename_tenant_dir(&local_tenant_directory)
1935 0 : .await
1936 0 : .with_context(|| format!("local tenant directory {local_tenant_directory:?} rename"))
1937 0 : };
1938 :
1939 0 : let removal_result = remove_tenant_from_memory(
1940 0 : tenants,
1941 0 : tenant_shard_id,
1942 0 : tenant_dir_rename_operation(tenant_shard_id),
1943 0 : )
1944 0 : .await;
1945 :
1946 : // Flush pending deletions, so that they have a good chance of passing validation
1947 : // before this tenant is potentially re-attached elsewhere.
1948 0 : deletion_queue_client.flush_advisory();
1949 0 :
1950 0 : // Ignored tenants are not present in memory and will bail the removal from memory operation.
1951 0 : // Before returning the error, check for ignored tenant removal case — we only need to clean its local files then.
1952 0 : if detach_ignored
1953 : && matches!(
1954 0 : removal_result,
1955 : Err(TenantStateError::SlotError(TenantSlotError::NotFound(_)))
1956 : )
1957 : {
1958 0 : let tenant_ignore_mark = conf.tenant_ignore_mark_file_path(&tenant_shard_id);
1959 0 : if tenant_ignore_mark.exists() {
1960 0 : info!("Detaching an ignored tenant");
1961 0 : let tmp_path = tenant_dir_rename_operation(tenant_shard_id)
1962 0 : .await
1963 0 : .with_context(|| {
1964 0 : format!("Ignored tenant {tenant_shard_id} local directory rename")
1965 0 : })?;
1966 0 : return Ok(tmp_path);
1967 0 : }
1968 0 : }
1969 :
1970 0 : removal_result
1971 0 : }
1972 :
1973 0 : pub(crate) async fn load_tenant(
1974 0 : conf: &'static PageServerConf,
1975 0 : tenant_id: TenantId,
1976 0 : generation: Generation,
1977 0 : broker_client: storage_broker::BrokerClientChannel,
1978 0 : remote_storage: Option<GenericRemoteStorage>,
1979 0 : deletion_queue_client: DeletionQueueClient,
1980 0 : ctx: &RequestContext,
1981 0 : ) -> Result<(), TenantMapInsertError> {
1982 0 : // This is a legacy API (replaced by `/location_conf`). It does not support sharding
1983 0 : let tenant_shard_id = TenantShardId::unsharded(tenant_id);
1984 :
1985 0 : let slot_guard =
1986 0 : tenant_map_acquire_slot(&tenant_shard_id, TenantSlotAcquireMode::MustNotExist)?;
1987 0 : let tenant_path = conf.tenant_path(&tenant_shard_id);
1988 0 :
1989 0 : let tenant_ignore_mark = conf.tenant_ignore_mark_file_path(&tenant_shard_id);
1990 0 : if tenant_ignore_mark.exists() {
1991 0 : std::fs::remove_file(&tenant_ignore_mark).with_context(|| {
1992 0 : format!(
1993 0 : "Failed to remove tenant ignore mark {tenant_ignore_mark:?} during tenant loading"
1994 0 : )
1995 0 : })?;
1996 0 : }
1997 :
1998 0 : let resources = TenantSharedResources {
1999 0 : broker_client,
2000 0 : remote_storage,
2001 0 : deletion_queue_client,
2002 0 : };
2003 :
2004 0 : let mut location_conf =
2005 0 : Tenant::load_tenant_config(conf, &tenant_shard_id).map_err(TenantMapInsertError::Other)?;
2006 0 : location_conf.attach_in_generation(generation);
2007 0 :
2008 0 : Tenant::persist_tenant_config(conf, &tenant_shard_id, &location_conf).await?;
2009 :
2010 0 : let shard_identity = location_conf.shard;
2011 0 : let new_tenant = tenant_spawn(
2012 0 : conf,
2013 0 : tenant_shard_id,
2014 0 : &tenant_path,
2015 0 : resources,
2016 0 : AttachedTenantConf::try_from(location_conf)?,
2017 0 : shard_identity,
2018 0 : None,
2019 0 : &TENANTS,
2020 0 : SpawnMode::Normal,
2021 0 : ctx,
2022 0 : )
2023 0 : .with_context(|| format!("Failed to schedule tenant processing in path {tenant_path:?}"))?;
2024 :
2025 0 : slot_guard.upsert(TenantSlot::Attached(new_tenant))?;
2026 0 : Ok(())
2027 0 : }
2028 :
2029 0 : pub(crate) async fn ignore_tenant(
2030 0 : conf: &'static PageServerConf,
2031 0 : tenant_id: TenantId,
2032 0 : ) -> Result<(), TenantStateError> {
2033 0 : ignore_tenant0(conf, &TENANTS, tenant_id).await
2034 0 : }
2035 :
2036 0 : #[instrument(skip_all, fields(shard_id))]
2037 : async fn ignore_tenant0(
2038 : conf: &'static PageServerConf,
2039 : tenants: &std::sync::RwLock<TenantsMap>,
2040 : tenant_id: TenantId,
2041 : ) -> Result<(), TenantStateError> {
2042 : // This is a legacy API (replaced by `/location_conf`). It does not support sharding
2043 : let tenant_shard_id = TenantShardId::unsharded(tenant_id);
2044 : tracing::Span::current().record(
2045 : "shard_id",
2046 : tracing::field::display(tenant_shard_id.shard_slug()),
2047 : );
2048 :
2049 0 : remove_tenant_from_memory(tenants, tenant_shard_id, async {
2050 0 : let ignore_mark_file = conf.tenant_ignore_mark_file_path(&tenant_shard_id);
2051 0 : fs::File::create(&ignore_mark_file)
2052 0 : .await
2053 0 : .context("Failed to create ignore mark file")
2054 0 : .and_then(|_| {
2055 0 : crashsafe::fsync_file_and_parent(&ignore_mark_file)
2056 0 : .context("Failed to fsync ignore mark file")
2057 0 : })
2058 0 : .with_context(|| format!("Failed to crate ignore mark for tenant {tenant_shard_id}"))?;
2059 0 : Ok(())
2060 0 : })
2061 : .await
2062 : }
2063 :
2064 0 : #[derive(Debug, thiserror::Error)]
2065 : pub(crate) enum TenantMapListError {
2066 : #[error("tenant map is still initiailizing")]
2067 : Initializing,
2068 : }
2069 :
2070 : ///
2071 : /// Get list of tenants, for the mgmt API
2072 : ///
2073 0 : pub(crate) async fn list_tenants(
2074 0 : ) -> Result<Vec<(TenantShardId, TenantState, Generation)>, TenantMapListError> {
2075 0 : let tenants = TENANTS.read().unwrap();
2076 0 : let m = match &*tenants {
2077 0 : TenantsMap::Initializing => return Err(TenantMapListError::Initializing),
2078 0 : TenantsMap::Open(m) | TenantsMap::ShuttingDown(m) => m,
2079 0 : };
2080 0 : Ok(m.iter()
2081 0 : .filter_map(|(id, tenant)| match tenant {
2082 0 : TenantSlot::Attached(tenant) => {
2083 0 : Some((*id, tenant.current_state(), tenant.generation()))
2084 : }
2085 0 : TenantSlot::Secondary(_) => None,
2086 0 : TenantSlot::InProgress(_) => None,
2087 0 : })
2088 0 : .collect())
2089 0 : }
2090 :
2091 0 : #[derive(Debug, thiserror::Error)]
2092 : pub(crate) enum TenantMapInsertError {
2093 : #[error(transparent)]
2094 : SlotError(#[from] TenantSlotError),
2095 : #[error(transparent)]
2096 : SlotUpsertError(#[from] TenantSlotUpsertError),
2097 : #[error(transparent)]
2098 : Other(#[from] anyhow::Error),
2099 : }
2100 :
2101 : /// Superset of TenantMapError: issues that can occur when acquiring a slot
2102 : /// for a particular tenant ID.
2103 0 : #[derive(Debug, thiserror::Error)]
2104 : pub(crate) enum TenantSlotError {
2105 : /// When acquiring a slot with the expectation that the tenant already exists.
2106 : #[error("Tenant {0} not found")]
2107 : NotFound(TenantShardId),
2108 :
2109 : /// When acquiring a slot with the expectation that the tenant does not already exist.
2110 : #[error("tenant {0} already exists, state: {1:?}")]
2111 : AlreadyExists(TenantShardId, TenantState),
2112 :
2113 : // Tried to read a slot that is currently being mutated by another administrative
2114 : // operation.
2115 : #[error("tenant has a state change in progress, try again later")]
2116 : InProgress,
2117 :
2118 : #[error(transparent)]
2119 : MapState(#[from] TenantMapError),
2120 : }
2121 :
2122 : /// Superset of TenantMapError: issues that can occur when using a SlotGuard
2123 : /// to insert a new value.
2124 0 : #[derive(thiserror::Error)]
2125 : pub(crate) enum TenantSlotUpsertError {
2126 : /// An error where the slot is in an unexpected state, indicating a code bug
2127 : #[error("Internal error updating Tenant")]
2128 : InternalError(Cow<'static, str>),
2129 :
2130 : #[error(transparent)]
2131 : MapState(TenantMapError),
2132 :
2133 : // If we encounter TenantManager shutdown during upsert, we must carry the Completion
2134 : // from the SlotGuard, so that the caller can hold it while they clean up: otherwise
2135 : // TenantManager shutdown might race ahead before we're done cleaning up any Tenant that
2136 : // was protected by the SlotGuard.
2137 : #[error("Shutting down")]
2138 : ShuttingDown((TenantSlot, utils::completion::Completion)),
2139 : }
2140 :
2141 : impl std::fmt::Debug for TenantSlotUpsertError {
2142 0 : fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2143 0 : match self {
2144 0 : Self::InternalError(reason) => write!(f, "Internal Error {reason}"),
2145 0 : Self::MapState(map_error) => write!(f, "Tenant map state: {map_error:?}"),
2146 0 : Self::ShuttingDown(_completion) => write!(f, "Tenant map shutting down"),
2147 : }
2148 0 : }
2149 : }
2150 :
2151 0 : #[derive(Debug, thiserror::Error)]
2152 : enum TenantSlotDropError {
2153 : /// It is only legal to drop a TenantSlot if its contents are fully shut down
2154 : #[error("Tenant was not shut down")]
2155 : NotShutdown,
2156 : }
2157 :
2158 : /// Errors that can happen any time we are walking the tenant map to try and acquire
2159 : /// the TenantSlot for a particular tenant.
2160 0 : #[derive(Debug, thiserror::Error)]
2161 : pub enum TenantMapError {
2162 : // Tried to read while initializing
2163 : #[error("tenant map is still initializing")]
2164 : StillInitializing,
2165 :
2166 : // Tried to read while shutting down
2167 : #[error("tenant map is shutting down")]
2168 : ShuttingDown,
2169 : }
2170 :
2171 : /// Guards a particular tenant_id's content in the TenantsMap. While this
2172 : /// structure exists, the TenantsMap will contain a [`TenantSlot::InProgress`]
2173 : /// for this tenant, which acts as a marker for any operations targeting
2174 : /// this tenant to retry later, or wait for the InProgress state to end.
2175 : ///
2176 : /// This structure enforces the important invariant that we do not have overlapping
2177 : /// tasks that will try use local storage for a the same tenant ID: we enforce that
2178 : /// the previous contents of a slot have been shut down before the slot can be
2179 : /// left empty or used for something else
2180 : ///
2181 : /// Holders of a SlotGuard should explicitly dispose of it, using either `upsert`
2182 : /// to provide a new value, or `revert` to put the slot back into its initial
2183 : /// state. If the SlotGuard is dropped without calling either of these, then
2184 : /// we will leave the slot empty if our `old_value` is already shut down, else
2185 : /// we will replace the slot with `old_value` (equivalent to doing a revert).
2186 : ///
2187 : /// The `old_value` may be dropped before the SlotGuard is dropped, by calling
2188 : /// `drop_old_value`. It is an error to call this without shutting down
2189 : /// the conents of `old_value`.
2190 : pub struct SlotGuard {
2191 : tenant_shard_id: TenantShardId,
2192 : old_value: Option<TenantSlot>,
2193 : upserted: bool,
2194 :
2195 : /// [`TenantSlot::InProgress`] carries the corresponding Barrier: it will
2196 : /// release any waiters as soon as this SlotGuard is dropped.
2197 : completion: utils::completion::Completion,
2198 : }
2199 :
2200 : impl SlotGuard {
2201 2 : fn new(
2202 2 : tenant_shard_id: TenantShardId,
2203 2 : old_value: Option<TenantSlot>,
2204 2 : completion: utils::completion::Completion,
2205 2 : ) -> Self {
2206 2 : Self {
2207 2 : tenant_shard_id,
2208 2 : old_value,
2209 2 : upserted: false,
2210 2 : completion,
2211 2 : }
2212 2 : }
2213 :
2214 : /// Get any value that was present in the slot before we acquired ownership
2215 : /// of it: in state transitions, this will be the old state.
2216 2 : fn get_old_value(&self) -> &Option<TenantSlot> {
2217 2 : &self.old_value
2218 2 : }
2219 :
2220 : /// Emplace a new value in the slot. This consumes the guard, and after
2221 : /// returning, the slot is no longer protected from concurrent changes.
2222 0 : fn upsert(mut self, new_value: TenantSlot) -> Result<(), TenantSlotUpsertError> {
2223 0 : if !self.old_value_is_shutdown() {
2224 : // This is a bug: callers should never try to drop an old value without
2225 : // shutting it down
2226 0 : return Err(TenantSlotUpsertError::InternalError(
2227 0 : "Old TenantSlot value not shut down".into(),
2228 0 : ));
2229 0 : }
2230 :
2231 0 : let replaced = {
2232 0 : let mut locked = TENANTS.write().unwrap();
2233 0 :
2234 0 : if let TenantSlot::InProgress(_) = new_value {
2235 : // It is never expected to try and upsert InProgress via this path: it should
2236 : // only be written via the tenant_map_acquire_slot path. If we hit this it's a bug.
2237 0 : return Err(TenantSlotUpsertError::InternalError(
2238 0 : "Attempt to upsert an InProgress state".into(),
2239 0 : ));
2240 0 : }
2241 :
2242 0 : let m = match &mut *locked {
2243 : TenantsMap::Initializing => {
2244 0 : return Err(TenantSlotUpsertError::MapState(
2245 0 : TenantMapError::StillInitializing,
2246 0 : ))
2247 : }
2248 : TenantsMap::ShuttingDown(_) => {
2249 0 : return Err(TenantSlotUpsertError::ShuttingDown((
2250 0 : new_value,
2251 0 : self.completion.clone(),
2252 0 : )));
2253 : }
2254 0 : TenantsMap::Open(m) => m,
2255 0 : };
2256 0 :
2257 0 : let replaced = m.insert(self.tenant_shard_id, new_value);
2258 0 : self.upserted = true;
2259 0 :
2260 0 : METRICS.tenant_slots.set(m.len() as u64);
2261 0 :
2262 0 : replaced
2263 : };
2264 :
2265 : // Sanity check: on an upsert we should always be replacing an InProgress marker
2266 0 : match replaced {
2267 : Some(TenantSlot::InProgress(_)) => {
2268 : // Expected case: we find our InProgress in the map: nothing should have
2269 : // replaced it because the code that acquires slots will not grant another
2270 : // one for the same TenantId.
2271 0 : Ok(())
2272 : }
2273 : None => {
2274 0 : METRICS.unexpected_errors.inc();
2275 0 : error!(
2276 0 : tenant_shard_id = %self.tenant_shard_id,
2277 0 : "Missing InProgress marker during tenant upsert, this is a bug."
2278 0 : );
2279 0 : Err(TenantSlotUpsertError::InternalError(
2280 0 : "Missing InProgress marker during tenant upsert".into(),
2281 0 : ))
2282 : }
2283 0 : Some(slot) => {
2284 0 : METRICS.unexpected_errors.inc();
2285 0 : error!(tenant_shard_id=%self.tenant_shard_id, "Unexpected contents of TenantSlot during upsert, this is a bug. Contents: {:?}", slot);
2286 0 : Err(TenantSlotUpsertError::InternalError(
2287 0 : "Unexpected contents of TenantSlot".into(),
2288 0 : ))
2289 : }
2290 : }
2291 0 : }
2292 :
2293 : /// Replace the InProgress slot with whatever was in the guard when we started
2294 0 : fn revert(mut self) {
2295 0 : if let Some(value) = self.old_value.take() {
2296 0 : match self.upsert(value) {
2297 0 : Err(TenantSlotUpsertError::InternalError(_)) => {
2298 0 : // We already logged the error, nothing else we can do.
2299 0 : }
2300 : Err(
2301 : TenantSlotUpsertError::MapState(_) | TenantSlotUpsertError::ShuttingDown(_),
2302 0 : ) => {
2303 0 : // If the map is shutting down, we need not replace anything
2304 0 : }
2305 0 : Ok(()) => {}
2306 : }
2307 0 : }
2308 0 : }
2309 :
2310 : /// We may never drop our old value until it is cleanly shut down: otherwise we might leave
2311 : /// rogue background tasks that would write to the local tenant directory that this guard
2312 : /// is responsible for protecting
2313 2 : fn old_value_is_shutdown(&self) -> bool {
2314 2 : match self.old_value.as_ref() {
2315 2 : Some(TenantSlot::Attached(tenant)) => tenant.gate.close_complete(),
2316 0 : Some(TenantSlot::Secondary(secondary_tenant)) => secondary_tenant.gate.close_complete(),
2317 : Some(TenantSlot::InProgress(_)) => {
2318 : // A SlotGuard cannot be constructed for a slot that was already InProgress
2319 0 : unreachable!()
2320 : }
2321 0 : None => true,
2322 : }
2323 2 : }
2324 :
2325 : /// The guard holder is done with the old value of the slot: they are obliged to already
2326 : /// shut it down before we reach this point.
2327 2 : fn drop_old_value(&mut self) -> Result<(), TenantSlotDropError> {
2328 2 : if !self.old_value_is_shutdown() {
2329 0 : Err(TenantSlotDropError::NotShutdown)
2330 : } else {
2331 2 : self.old_value.take();
2332 2 : Ok(())
2333 : }
2334 2 : }
2335 : }
2336 :
2337 : impl Drop for SlotGuard {
2338 2 : fn drop(&mut self) {
2339 2 : if self.upserted {
2340 0 : return;
2341 2 : }
2342 2 : // Our old value is already shutdown, or it never existed: it is safe
2343 2 : // for us to fully release the TenantSlot back into an empty state
2344 2 :
2345 2 : let mut locked = TENANTS.write().unwrap();
2346 :
2347 2 : let m = match &mut *locked {
2348 : TenantsMap::Initializing => {
2349 : // There is no map, this should never happen.
2350 2 : return;
2351 : }
2352 : TenantsMap::ShuttingDown(_) => {
2353 : // When we transition to shutdown, InProgress elements are removed
2354 : // from the map, so we do not need to clean up our Inprogress marker.
2355 : // See [`shutdown_all_tenants0`]
2356 0 : return;
2357 : }
2358 0 : TenantsMap::Open(m) => m,
2359 0 : };
2360 0 :
2361 0 : use std::collections::btree_map::Entry;
2362 0 : match m.entry(self.tenant_shard_id) {
2363 0 : Entry::Occupied(mut entry) => {
2364 0 : if !matches!(entry.get(), TenantSlot::InProgress(_)) {
2365 0 : METRICS.unexpected_errors.inc();
2366 0 : error!(tenant_shard_id=%self.tenant_shard_id, "Unexpected contents of TenantSlot during drop, this is a bug. Contents: {:?}", entry.get());
2367 0 : }
2368 :
2369 0 : if self.old_value_is_shutdown() {
2370 0 : entry.remove();
2371 0 : } else {
2372 0 : entry.insert(self.old_value.take().unwrap());
2373 0 : }
2374 : }
2375 : Entry::Vacant(_) => {
2376 0 : METRICS.unexpected_errors.inc();
2377 0 : error!(
2378 0 : tenant_shard_id = %self.tenant_shard_id,
2379 0 : "Missing InProgress marker during SlotGuard drop, this is a bug."
2380 0 : );
2381 : }
2382 : }
2383 :
2384 0 : METRICS.tenant_slots.set(m.len() as u64);
2385 2 : }
2386 : }
2387 :
2388 : enum TenantSlotPeekMode {
2389 : /// In Read mode, peek will be permitted to see the slots even if the pageserver is shutting down
2390 : Read,
2391 : /// In Write mode, trying to peek at a slot while the pageserver is shutting down is an error
2392 : Write,
2393 : }
2394 :
2395 0 : fn tenant_map_peek_slot<'a>(
2396 0 : tenants: &'a std::sync::RwLockReadGuard<'a, TenantsMap>,
2397 0 : tenant_shard_id: &TenantShardId,
2398 0 : mode: TenantSlotPeekMode,
2399 0 : ) -> Result<Option<&'a TenantSlot>, TenantMapError> {
2400 0 : match tenants.deref() {
2401 0 : TenantsMap::Initializing => Err(TenantMapError::StillInitializing),
2402 0 : TenantsMap::ShuttingDown(m) => match mode {
2403 : TenantSlotPeekMode::Read => Ok(Some(
2404 : // When reading in ShuttingDown state, we must translate None results
2405 : // into a ShuttingDown error, because absence of a tenant shard ID in the map
2406 : // isn't a reliable indicator of the tenant being gone: it might have been
2407 : // InProgress when shutdown started, and cleaned up from that state such
2408 : // that it's now no longer in the map. Callers will have to wait until
2409 : // we next start up to get a proper answer. This avoids incorrect 404 API responses.
2410 0 : m.get(tenant_shard_id).ok_or(TenantMapError::ShuttingDown)?,
2411 : )),
2412 0 : TenantSlotPeekMode::Write => Err(TenantMapError::ShuttingDown),
2413 : },
2414 0 : TenantsMap::Open(m) => Ok(m.get(tenant_shard_id)),
2415 : }
2416 0 : }
2417 :
2418 : enum TenantSlotAcquireMode {
2419 : /// Acquire the slot irrespective of current state, or whether it already exists
2420 : Any,
2421 : /// Return an error if trying to acquire a slot and it doesn't already exist
2422 : MustExist,
2423 : /// Return an error if trying to acquire a slot and it already exists
2424 : MustNotExist,
2425 : }
2426 :
2427 0 : fn tenant_map_acquire_slot(
2428 0 : tenant_shard_id: &TenantShardId,
2429 0 : mode: TenantSlotAcquireMode,
2430 0 : ) -> Result<SlotGuard, TenantSlotError> {
2431 0 : tenant_map_acquire_slot_impl(tenant_shard_id, &TENANTS, mode)
2432 0 : }
2433 :
2434 2 : fn tenant_map_acquire_slot_impl(
2435 2 : tenant_shard_id: &TenantShardId,
2436 2 : tenants: &std::sync::RwLock<TenantsMap>,
2437 2 : mode: TenantSlotAcquireMode,
2438 2 : ) -> Result<SlotGuard, TenantSlotError> {
2439 2 : use TenantSlotAcquireMode::*;
2440 2 : METRICS.tenant_slot_writes.inc();
2441 2 :
2442 2 : let mut locked = tenants.write().unwrap();
2443 2 : let span = tracing::info_span!("acquire_slot", tenant_id=%tenant_shard_id.tenant_id, shard_id = %tenant_shard_id.shard_slug());
2444 2 : let _guard = span.enter();
2445 :
2446 2 : let m = match &mut *locked {
2447 0 : TenantsMap::Initializing => return Err(TenantMapError::StillInitializing.into()),
2448 0 : TenantsMap::ShuttingDown(_) => return Err(TenantMapError::ShuttingDown.into()),
2449 2 : TenantsMap::Open(m) => m,
2450 2 : };
2451 2 :
2452 2 : use std::collections::btree_map::Entry;
2453 2 :
2454 2 : let entry = m.entry(*tenant_shard_id);
2455 2 :
2456 2 : match entry {
2457 0 : Entry::Vacant(v) => match mode {
2458 : MustExist => {
2459 0 : tracing::debug!("Vacant && MustExist: return NotFound");
2460 0 : Err(TenantSlotError::NotFound(*tenant_shard_id))
2461 : }
2462 : _ => {
2463 0 : let (completion, barrier) = utils::completion::channel();
2464 0 : v.insert(TenantSlot::InProgress(barrier));
2465 0 : tracing::debug!("Vacant, inserted InProgress");
2466 0 : Ok(SlotGuard::new(*tenant_shard_id, None, completion))
2467 : }
2468 : },
2469 2 : Entry::Occupied(mut o) => {
2470 2 : // Apply mode-driven checks
2471 2 : match (o.get(), mode) {
2472 : (TenantSlot::InProgress(_), _) => {
2473 0 : tracing::debug!("Occupied, failing for InProgress");
2474 0 : Err(TenantSlotError::InProgress)
2475 : }
2476 0 : (slot, MustNotExist) => match slot {
2477 0 : TenantSlot::Attached(tenant) => {
2478 0 : tracing::debug!("Attached && MustNotExist, return AlreadyExists");
2479 0 : Err(TenantSlotError::AlreadyExists(
2480 0 : *tenant_shard_id,
2481 0 : tenant.current_state(),
2482 0 : ))
2483 : }
2484 : _ => {
2485 : // FIXME: the AlreadyExists error assumes that we have a Tenant
2486 : // to get the state from
2487 0 : tracing::debug!("Occupied & MustNotExist, return AlreadyExists");
2488 0 : Err(TenantSlotError::AlreadyExists(
2489 0 : *tenant_shard_id,
2490 0 : TenantState::Broken {
2491 0 : reason: "Present but not attached".to_string(),
2492 0 : backtrace: "".to_string(),
2493 0 : },
2494 0 : ))
2495 : }
2496 : },
2497 : _ => {
2498 : // Happy case: the slot was not in any state that violated our mode
2499 2 : let (completion, barrier) = utils::completion::channel();
2500 2 : let old_value = o.insert(TenantSlot::InProgress(barrier));
2501 2 : tracing::debug!("Occupied, replaced with InProgress");
2502 2 : Ok(SlotGuard::new(
2503 2 : *tenant_shard_id,
2504 2 : Some(old_value),
2505 2 : completion,
2506 2 : ))
2507 : }
2508 : }
2509 : }
2510 : }
2511 2 : }
2512 :
2513 : /// Stops and removes the tenant from memory, if it's not [`TenantState::Stopping`] already, bails otherwise.
2514 : /// Allows to remove other tenant resources manually, via `tenant_cleanup`.
2515 : /// If the cleanup fails, tenant will stay in memory in [`TenantState::Broken`] state, and another removal
2516 : /// operation would be needed to remove it.
2517 2 : async fn remove_tenant_from_memory<V, F>(
2518 2 : tenants: &std::sync::RwLock<TenantsMap>,
2519 2 : tenant_shard_id: TenantShardId,
2520 2 : tenant_cleanup: F,
2521 2 : ) -> Result<V, TenantStateError>
2522 2 : where
2523 2 : F: std::future::Future<Output = anyhow::Result<V>>,
2524 2 : {
2525 2 : let mut slot_guard =
2526 2 : tenant_map_acquire_slot_impl(&tenant_shard_id, tenants, TenantSlotAcquireMode::MustExist)?;
2527 :
2528 : // allow pageserver shutdown to await for our completion
2529 2 : let (_guard, progress) = completion::channel();
2530 :
2531 : // The SlotGuard allows us to manipulate the Tenant object without fear of some
2532 : // concurrent API request doing something else for the same tenant ID.
2533 2 : let attached_tenant = match slot_guard.get_old_value() {
2534 2 : Some(TenantSlot::Attached(tenant)) => {
2535 2 : // whenever we remove a tenant from memory, we don't want to flush and wait for upload
2536 2 : let freeze_and_flush = false;
2537 2 :
2538 2 : // shutdown is sure to transition tenant to stopping, and wait for all tasks to complete, so
2539 2 : // that we can continue safely to cleanup.
2540 2 : match tenant.shutdown(progress, freeze_and_flush).await {
2541 2 : Ok(()) => {}
2542 0 : Err(_other) => {
2543 0 : // if pageserver shutdown or other detach/ignore is already ongoing, we don't want to
2544 0 : // wait for it but return an error right away because these are distinct requests.
2545 0 : slot_guard.revert();
2546 0 : return Err(TenantStateError::IsStopping(tenant_shard_id));
2547 : }
2548 : }
2549 2 : Some(tenant)
2550 : }
2551 0 : Some(TenantSlot::Secondary(secondary_state)) => {
2552 0 : tracing::info!("Shutting down in secondary mode");
2553 0 : secondary_state.shutdown().await;
2554 0 : None
2555 : }
2556 : Some(TenantSlot::InProgress(_)) => {
2557 : // Acquiring a slot guarantees its old value was not InProgress
2558 0 : unreachable!();
2559 : }
2560 0 : None => None,
2561 : };
2562 :
2563 2 : match tenant_cleanup
2564 2 : .await
2565 2 : .with_context(|| format!("Failed to run cleanup for tenant {tenant_shard_id}"))
2566 : {
2567 2 : Ok(hook_value) => {
2568 2 : // Success: drop the old TenantSlot::Attached.
2569 2 : slot_guard
2570 2 : .drop_old_value()
2571 2 : .expect("We just called shutdown");
2572 2 :
2573 2 : Ok(hook_value)
2574 : }
2575 0 : Err(e) => {
2576 : // If we had a Tenant, set it to Broken and put it back in the TenantsMap
2577 0 : if let Some(attached_tenant) = attached_tenant {
2578 0 : attached_tenant.set_broken(e.to_string()).await;
2579 0 : }
2580 : // Leave the broken tenant in the map
2581 0 : slot_guard.revert();
2582 0 :
2583 0 : Err(TenantStateError::Other(e))
2584 : }
2585 : }
2586 2 : }
2587 :
2588 : use {
2589 : crate::repository::GcResult, pageserver_api::models::TimelineGcRequest,
2590 : utils::http::error::ApiError,
2591 : };
2592 :
2593 0 : pub(crate) async fn immediate_gc(
2594 0 : tenant_shard_id: TenantShardId,
2595 0 : timeline_id: TimelineId,
2596 0 : gc_req: TimelineGcRequest,
2597 0 : cancel: CancellationToken,
2598 0 : ctx: &RequestContext,
2599 0 : ) -> Result<tokio::sync::oneshot::Receiver<Result<GcResult, anyhow::Error>>, ApiError> {
2600 0 : let guard = TENANTS.read().unwrap();
2601 :
2602 0 : let tenant = guard
2603 0 : .get(&tenant_shard_id)
2604 0 : .map(Arc::clone)
2605 0 : .with_context(|| format!("tenant {tenant_shard_id}"))
2606 0 : .map_err(|e| ApiError::NotFound(e.into()))?;
2607 :
2608 0 : let gc_horizon = gc_req.gc_horizon.unwrap_or_else(|| tenant.get_gc_horizon());
2609 0 : // Use tenant's pitr setting
2610 0 : let pitr = tenant.get_pitr_interval();
2611 0 :
2612 0 : // Run in task_mgr to avoid race with tenant_detach operation
2613 0 : let ctx = ctx.detached_child(TaskKind::GarbageCollector, DownloadBehavior::Download);
2614 0 : let (task_done, wait_task_done) = tokio::sync::oneshot::channel();
2615 0 : // TODO: spawning is redundant now, need to hold the gate
2616 0 : task_mgr::spawn(
2617 0 : &tokio::runtime::Handle::current(),
2618 0 : TaskKind::GarbageCollector,
2619 0 : Some(tenant_shard_id),
2620 0 : Some(timeline_id),
2621 0 : &format!("timeline_gc_handler garbage collection run for tenant {tenant_shard_id} timeline {timeline_id}"),
2622 0 : false,
2623 0 : async move {
2624 0 : fail::fail_point!("immediate_gc_task_pre");
2625 :
2626 : #[allow(unused_mut)]
2627 0 : let mut result = tenant
2628 0 : .gc_iteration(Some(timeline_id), gc_horizon, pitr, &cancel, &ctx)
2629 0 : .instrument(info_span!("manual_gc", tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), %timeline_id))
2630 0 : .await;
2631 : // FIXME: `gc_iteration` can return an error for multiple reasons; we should handle it
2632 : // better once the types support it.
2633 :
2634 : #[cfg(feature = "testing")]
2635 : {
2636 0 : if let Ok(result) = result.as_mut() {
2637 : // why not futures unordered? it seems it needs very much the same task structure
2638 : // but would only run on single task.
2639 0 : let mut js = tokio::task::JoinSet::new();
2640 0 : for layer in std::mem::take(&mut result.doomed_layers) {
2641 0 : js.spawn(layer.wait_drop());
2642 0 : }
2643 0 : tracing::info!(total = js.len(), "starting to wait for the gc'd layers to be dropped");
2644 0 : while let Some(res) = js.join_next().await {
2645 0 : res.expect("wait_drop should not panic");
2646 0 : }
2647 0 : }
2648 :
2649 0 : let timeline = tenant.get_timeline(timeline_id, false).ok();
2650 0 : let rtc = timeline.as_ref().and_then(|x| x.remote_client.as_ref());
2651 :
2652 0 : if let Some(rtc) = rtc {
2653 : // layer drops schedule actions on remote timeline client to actually do the
2654 : // deletions; don't care just exit fast about the shutdown error
2655 0 : drop(rtc.wait_completion().await);
2656 0 : }
2657 : }
2658 :
2659 0 : match task_done.send(result) {
2660 0 : Ok(_) => (),
2661 0 : Err(result) => error!("failed to send gc result: {result:?}"),
2662 : }
2663 0 : Ok(())
2664 0 : }
2665 0 : );
2666 0 :
2667 0 : // drop the guard until after we've spawned the task so that timeline shutdown will wait for the task
2668 0 : drop(guard);
2669 0 :
2670 0 : Ok(wait_task_done)
2671 0 : }
2672 :
2673 : #[cfg(test)]
2674 : mod tests {
2675 : use std::collections::BTreeMap;
2676 : use std::sync::Arc;
2677 : use tracing::Instrument;
2678 :
2679 : use crate::tenant::mgr::TenantSlot;
2680 :
2681 : use super::{super::harness::TenantHarness, TenantsMap};
2682 :
2683 2 : #[tokio::test(start_paused = true)]
2684 2 : async fn shutdown_awaits_in_progress_tenant() {
2685 2 : // Test that if an InProgress tenant is in the map during shutdown, the shutdown will gracefully
2686 2 : // wait for it to complete before proceeding.
2687 2 :
2688 2 : let h = TenantHarness::create("shutdown_awaits_in_progress_tenant").unwrap();
2689 2 : let (t, _ctx) = h.load().await;
2690 2 :
2691 2 : // harness loads it to active, which is forced and nothing is running on the tenant
2692 2 :
2693 2 : let id = t.tenant_shard_id();
2694 2 :
2695 2 : // tenant harness configures the logging and we cannot escape it
2696 2 : let span = h.span();
2697 2 : let _e = span.enter();
2698 2 :
2699 2 : let tenants = BTreeMap::from([(id, TenantSlot::Attached(t.clone()))]);
2700 2 : let tenants = Arc::new(std::sync::RwLock::new(TenantsMap::Open(tenants)));
2701 2 :
2702 2 : // Invoke remove_tenant_from_memory with a cleanup hook that blocks until we manually
2703 2 : // permit it to proceed: that will stick the tenant in InProgress
2704 2 :
2705 2 : let (until_cleanup_completed, can_complete_cleanup) = utils::completion::channel();
2706 2 : let (until_cleanup_started, cleanup_started) = utils::completion::channel();
2707 2 : let mut remove_tenant_from_memory_task = {
2708 2 : let jh = tokio::spawn({
2709 2 : let tenants = tenants.clone();
2710 2 : async move {
2711 2 : let cleanup = async move {
2712 2 : drop(until_cleanup_started);
2713 2 : can_complete_cleanup.wait().await;
2714 2 : anyhow::Ok(())
2715 2 : };
2716 2 : super::remove_tenant_from_memory(&tenants, id, cleanup).await
2717 2 : }
2718 2 : .instrument(h.span())
2719 2 : });
2720 2 :
2721 2 : // now the long cleanup should be in place, with the stopping state
2722 2 : cleanup_started.wait().await;
2723 2 : jh
2724 2 : };
2725 2 :
2726 2 : let mut shutdown_task = {
2727 2 : let (until_shutdown_started, shutdown_started) = utils::completion::channel();
2728 2 :
2729 2 : let shutdown_task = tokio::spawn(async move {
2730 2 : drop(until_shutdown_started);
2731 4 : super::shutdown_all_tenants0(&tenants).await;
2732 2 : });
2733 2 :
2734 2 : shutdown_started.wait().await;
2735 2 : shutdown_task
2736 2 : };
2737 2 :
2738 2 : let long_time = std::time::Duration::from_secs(15);
2739 4 : tokio::select! {
2740 4 : _ = &mut shutdown_task => unreachable!("shutdown should block on remove_tenant_from_memory completing"),
2741 4 : _ = &mut remove_tenant_from_memory_task => unreachable!("remove_tenant_from_memory_task should not complete until explicitly unblocked"),
2742 4 : _ = tokio::time::sleep(long_time) => {},
2743 4 : }
2744 2 :
2745 2 : drop(until_cleanup_completed);
2746 2 :
2747 2 : // Now that we allow it to proceed, shutdown should complete immediately
2748 2 : remove_tenant_from_memory_task.await.unwrap().unwrap();
2749 2 : shutdown_task.await.unwrap();
2750 2 : }
2751 : }
|