Line data Source code
1 : //!
2 : //! Timeline repository implementation that keeps old data in files on disk, and
3 : //! the recent changes in memory. See tenant/*_layer.rs files.
4 : //! The functions here are responsible for locating the correct layer for the
5 : //! get/put call, walking back the timeline branching history as needed.
6 : //!
7 : //! The files are stored in the .neon/tenants/<tenant_id>/timelines/<timeline_id>
8 : //! directory. See docs/pageserver-storage.md for how the files are managed.
9 : //! In addition to the layer files, there is a metadata file in the same
10 : //! directory that contains information about the timeline, in particular its
11 : //! parent timeline, and the last LSN that has been written to disk.
12 : //!
13 :
14 : use anyhow::{bail, Context};
15 : use arc_swap::ArcSwap;
16 : use camino::Utf8Path;
17 : use camino::Utf8PathBuf;
18 : use enumset::EnumSet;
19 : use futures::stream::FuturesUnordered;
20 : use futures::FutureExt;
21 : use futures::StreamExt;
22 : use pageserver_api::models;
23 : use pageserver_api::models::TimelineState;
24 : use pageserver_api::models::WalRedoManagerStatus;
25 : use pageserver_api::shard::ShardIdentity;
26 : use pageserver_api::shard::ShardStripeSize;
27 : use pageserver_api::shard::TenantShardId;
28 : use remote_storage::DownloadError;
29 : use remote_storage::GenericRemoteStorage;
30 : use remote_storage::TimeoutOrCancel;
31 : use std::fmt;
32 : use storage_broker::BrokerClientChannel;
33 : use tokio::io::BufReader;
34 : use tokio::sync::watch;
35 : use tokio::task::JoinSet;
36 : use tokio_util::sync::CancellationToken;
37 : use tracing::*;
38 : use utils::backoff;
39 : use utils::completion;
40 : use utils::crashsafe::path_with_suffix_extension;
41 : use utils::failpoint_support;
42 : use utils::fs_ext;
43 : use utils::sync::gate::Gate;
44 : use utils::sync::gate::GateGuard;
45 : use utils::timeout::timeout_cancellable;
46 : use utils::timeout::TimeoutCancellableError;
47 : use utils::zstd::create_zst_tarball;
48 : use utils::zstd::extract_zst_tarball;
49 :
50 : use self::config::AttachedLocationConfig;
51 : use self::config::AttachmentMode;
52 : use self::config::LocationConf;
53 : use self::config::TenantConf;
54 : use self::delete::DeleteTenantFlow;
55 : use self::metadata::TimelineMetadata;
56 : use self::mgr::GetActiveTenantError;
57 : use self::mgr::GetTenantError;
58 : use self::mgr::TenantsMap;
59 : use self::remote_timeline_client::upload::upload_index_part;
60 : use self::remote_timeline_client::RemoteTimelineClient;
61 : use self::timeline::uninit::TimelineCreateGuard;
62 : use self::timeline::uninit::TimelineExclusionError;
63 : use self::timeline::uninit::UninitializedTimeline;
64 : use self::timeline::EvictionTaskTenantState;
65 : use self::timeline::TimelineResources;
66 : use self::timeline::WaitLsnError;
67 : use crate::config::PageServerConf;
68 : use crate::context::{DownloadBehavior, RequestContext};
69 : use crate::deletion_queue::DeletionQueueClient;
70 : use crate::deletion_queue::DeletionQueueError;
71 : use crate::import_datadir;
72 : use crate::is_uninit_mark;
73 : use crate::metrics::TENANT;
74 : use crate::metrics::{
75 : remove_tenant_metrics, BROKEN_TENANTS_SET, TENANT_STATE_METRIC, TENANT_SYNTHETIC_SIZE_METRIC,
76 : };
77 : use crate::repository::GcResult;
78 : use crate::task_mgr;
79 : use crate::task_mgr::TaskKind;
80 : use crate::tenant::config::LocationMode;
81 : use crate::tenant::config::TenantConfOpt;
82 : pub use crate::tenant::remote_timeline_client::index::IndexPart;
83 : use crate::tenant::remote_timeline_client::remote_initdb_archive_path;
84 : use crate::tenant::remote_timeline_client::MaybeDeletedIndexPart;
85 : use crate::tenant::remote_timeline_client::INITDB_PATH;
86 : use crate::tenant::storage_layer::DeltaLayer;
87 : use crate::tenant::storage_layer::ImageLayer;
88 : use crate::InitializationOrder;
89 : use std::cmp::min;
90 : use std::collections::hash_map::Entry;
91 : use std::collections::BTreeSet;
92 : use std::collections::HashMap;
93 : use std::collections::HashSet;
94 : use std::fmt::Debug;
95 : use std::fmt::Display;
96 : use std::fs;
97 : use std::fs::File;
98 : use std::ops::Bound::Included;
99 : use std::sync::atomic::AtomicU64;
100 : use std::sync::atomic::Ordering;
101 : use std::sync::Arc;
102 : use std::sync::Mutex;
103 : use std::time::{Duration, Instant};
104 :
105 : use crate::span;
106 : use crate::tenant::timeline::delete::DeleteTimelineFlow;
107 : use crate::tenant::timeline::uninit::cleanup_timeline_directory;
108 : use crate::virtual_file::VirtualFile;
109 : use crate::walredo::PostgresRedoManager;
110 : use crate::TEMP_FILE_SUFFIX;
111 : use once_cell::sync::Lazy;
112 : pub use pageserver_api::models::TenantState;
113 : use tokio::sync::Semaphore;
114 :
115 0 : static INIT_DB_SEMAPHORE: Lazy<Semaphore> = Lazy::new(|| Semaphore::new(8));
116 : use utils::{
117 : crashsafe,
118 : generation::Generation,
119 : id::TimelineId,
120 : lsn::{Lsn, RecordLsn},
121 : };
122 :
123 : /// Declare a failpoint that can use the `pause` failpoint action.
124 : /// We don't want to block the executor thread, hence, spawn_blocking + await.
125 : macro_rules! pausable_failpoint {
126 : ($name:literal) => {
127 : if cfg!(feature = "testing") {
128 : tokio::task::spawn_blocking({
129 : let current = tracing::Span::current();
130 : move || {
131 : let _entered = current.entered();
132 : tracing::info!("at failpoint {}", $name);
133 : fail::fail_point!($name);
134 : }
135 : })
136 : .await
137 : .expect("spawn_blocking");
138 : }
139 : };
140 : ($name:literal, $cond:expr) => {
141 : if cfg!(feature = "testing") {
142 : if $cond {
143 : pausable_failpoint!($name)
144 : }
145 : }
146 : };
147 : }
148 :
149 : pub mod blob_io;
150 : pub mod block_io;
151 : pub mod vectored_blob_io;
152 :
153 : pub mod disk_btree;
154 : pub(crate) mod ephemeral_file;
155 : pub mod layer_map;
156 :
157 : pub mod metadata;
158 : pub mod remote_timeline_client;
159 : pub mod storage_layer;
160 :
161 : pub mod config;
162 : pub mod delete;
163 : pub mod mgr;
164 : pub mod secondary;
165 : pub mod tasks;
166 : pub mod upload_queue;
167 :
168 : pub(crate) mod timeline;
169 :
170 : pub mod size;
171 :
172 : pub(crate) mod throttle;
173 :
174 : pub(crate) use crate::span::debug_assert_current_span_has_tenant_and_timeline_id;
175 : pub(crate) use timeline::{LogicalSizeCalculationCause, PageReconstructError, Timeline};
176 :
177 : // re-export for use in walreceiver
178 : pub use crate::tenant::timeline::WalReceiverInfo;
179 :
180 : /// The "tenants" part of `tenants/<tenant>/timelines...`
181 : pub const TENANTS_SEGMENT_NAME: &str = "tenants";
182 :
183 : /// Parts of the `.neon/tenants/<tenant_id>/timelines/<timeline_id>` directory prefix.
184 : pub const TIMELINES_SEGMENT_NAME: &str = "timelines";
185 :
186 : pub const TENANT_DELETED_MARKER_FILE_NAME: &str = "deleted";
187 :
188 : /// References to shared objects that are passed into each tenant, such
189 : /// as the shared remote storage client and process initialization state.
190 : #[derive(Clone)]
191 : pub struct TenantSharedResources {
192 : pub broker_client: storage_broker::BrokerClientChannel,
193 : pub remote_storage: Option<GenericRemoteStorage>,
194 : pub deletion_queue_client: DeletionQueueClient,
195 : }
196 :
197 : /// A [`Tenant`] is really an _attached_ tenant. The configuration
198 : /// for an attached tenant is a subset of the [`LocationConf`], represented
199 : /// in this struct.
200 : pub(super) struct AttachedTenantConf {
201 : tenant_conf: TenantConfOpt,
202 : location: AttachedLocationConfig,
203 : }
204 :
205 : impl AttachedTenantConf {
206 0 : fn new(tenant_conf: TenantConfOpt, location: AttachedLocationConfig) -> Self {
207 0 : Self {
208 0 : tenant_conf,
209 0 : location,
210 0 : }
211 0 : }
212 :
213 104 : fn try_from(location_conf: LocationConf) -> anyhow::Result<Self> {
214 104 : match &location_conf.mode {
215 104 : LocationMode::Attached(attach_conf) => Ok(Self {
216 104 : tenant_conf: location_conf.tenant_conf,
217 104 : location: *attach_conf,
218 104 : }),
219 : LocationMode::Secondary(_) => {
220 0 : anyhow::bail!("Attempted to construct AttachedTenantConf from a LocationConf in secondary mode")
221 : }
222 : }
223 104 : }
224 : }
225 : struct TimelinePreload {
226 : timeline_id: TimelineId,
227 : client: RemoteTimelineClient,
228 : index_part: Result<MaybeDeletedIndexPart, DownloadError>,
229 : }
230 :
231 : pub(crate) struct TenantPreload {
232 : deleting: bool,
233 : timelines: HashMap<TimelineId, TimelinePreload>,
234 : }
235 :
236 : /// When we spawn a tenant, there is a special mode for tenant creation that
237 : /// avoids trying to read anything from remote storage.
238 : pub(crate) enum SpawnMode {
239 : /// Activate as soon as possible
240 : Eager,
241 : /// Lazy activation in the background, with the option to skip the queue if the need comes up
242 : Lazy,
243 : /// Tenant has been created during the lifetime of this process
244 : Create,
245 : }
246 :
247 : ///
248 : /// Tenant consists of multiple timelines. Keep them in a hash table.
249 : ///
250 : pub struct Tenant {
251 : // Global pageserver config parameters
252 : pub conf: &'static PageServerConf,
253 :
254 : /// The value creation timestamp, used to measure activation delay, see:
255 : /// <https://github.com/neondatabase/neon/issues/4025>
256 : constructed_at: Instant,
257 :
258 : state: watch::Sender<TenantState>,
259 :
260 : // Overridden tenant-specific config parameters.
261 : // We keep TenantConfOpt sturct here to preserve the information
262 : // about parameters that are not set.
263 : // This is necessary to allow global config updates.
264 : tenant_conf: Arc<ArcSwap<AttachedTenantConf>>,
265 :
266 : tenant_shard_id: TenantShardId,
267 :
268 : // The detailed sharding information, beyond the number/count in tenant_shard_id
269 : shard_identity: ShardIdentity,
270 :
271 : /// The remote storage generation, used to protect S3 objects from split-brain.
272 : /// Does not change over the lifetime of the [`Tenant`] object.
273 : ///
274 : /// This duplicates the generation stored in LocationConf, but that structure is mutable:
275 : /// this copy enforces the invariant that generatio doesn't change during a Tenant's lifetime.
276 : generation: Generation,
277 :
278 : timelines: Mutex<HashMap<TimelineId, Arc<Timeline>>>,
279 :
280 : /// During timeline creation, we first insert the TimelineId to the
281 : /// creating map, then `timelines`, then remove it from the creating map.
282 : /// **Lock order**: if acquring both, acquire`timelines` before `timelines_creating`
283 : timelines_creating: std::sync::Mutex<HashSet<TimelineId>>,
284 :
285 : // This mutex prevents creation of new timelines during GC.
286 : // Adding yet another mutex (in addition to `timelines`) is needed because holding
287 : // `timelines` mutex during all GC iteration
288 : // may block for a long time `get_timeline`, `get_timelines_state`,... and other operations
289 : // with timelines, which in turn may cause dropping replication connection, expiration of wait_for_lsn
290 : // timeout...
291 : gc_cs: tokio::sync::Mutex<()>,
292 : walredo_mgr: Option<Arc<WalRedoManager>>,
293 :
294 : // provides access to timeline data sitting in the remote storage
295 : pub(crate) remote_storage: Option<GenericRemoteStorage>,
296 :
297 : // Access to global deletion queue for when this tenant wants to schedule a deletion
298 : deletion_queue_client: DeletionQueueClient,
299 :
300 : /// Cached logical sizes updated updated on each [`Tenant::gather_size_inputs`].
301 : cached_logical_sizes: tokio::sync::Mutex<HashMap<(TimelineId, Lsn), u64>>,
302 : cached_synthetic_tenant_size: Arc<AtomicU64>,
303 :
304 : eviction_task_tenant_state: tokio::sync::Mutex<EvictionTaskTenantState>,
305 :
306 : /// If the tenant is in Activating state, notify this to encourage it
307 : /// to proceed to Active as soon as possible, rather than waiting for lazy
308 : /// background warmup.
309 : pub(crate) activate_now_sem: tokio::sync::Semaphore,
310 :
311 : pub(crate) delete_progress: Arc<tokio::sync::Mutex<DeleteTenantFlow>>,
312 :
313 : // Cancellation token fires when we have entered shutdown(). This is a parent of
314 : // Timelines' cancellation token.
315 : pub(crate) cancel: CancellationToken,
316 :
317 : // Users of the Tenant such as the page service must take this Gate to avoid
318 : // trying to use a Tenant which is shutting down.
319 : pub(crate) gate: Gate,
320 :
321 : /// Throttle applied at the top of [`Timeline::get`].
322 : /// All [`Tenant::timelines`] of a given [`Tenant`] instance share the same [`throttle::Throttle`] instance.
323 : pub(crate) timeline_get_throttle:
324 : Arc<throttle::Throttle<&'static crate::metrics::tenant_throttling::TimelineGet>>,
325 : }
326 :
327 : impl std::fmt::Debug for Tenant {
328 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 0 : write!(f, "{} ({})", self.tenant_shard_id, self.current_state())
330 0 : }
331 : }
332 :
333 : pub(crate) enum WalRedoManager {
334 : Prod(PostgresRedoManager),
335 : #[cfg(test)]
336 : Test(harness::TestRedoManager),
337 : }
338 :
339 : impl From<PostgresRedoManager> for WalRedoManager {
340 0 : fn from(mgr: PostgresRedoManager) -> Self {
341 0 : Self::Prod(mgr)
342 0 : }
343 : }
344 :
345 : #[cfg(test)]
346 : impl From<harness::TestRedoManager> for WalRedoManager {
347 104 : fn from(mgr: harness::TestRedoManager) -> Self {
348 104 : Self::Test(mgr)
349 104 : }
350 : }
351 :
352 : impl WalRedoManager {
353 0 : pub(crate) fn maybe_quiesce(&self, idle_timeout: Duration) {
354 0 : match self {
355 0 : Self::Prod(mgr) => mgr.maybe_quiesce(idle_timeout),
356 0 : #[cfg(test)]
357 0 : Self::Test(_) => {
358 0 : // Not applicable to test redo manager
359 0 : }
360 0 : }
361 0 : }
362 :
363 : /// # Cancel-Safety
364 : ///
365 : /// This method is cancellation-safe.
366 6 : pub async fn request_redo(
367 6 : &self,
368 6 : key: crate::repository::Key,
369 6 : lsn: Lsn,
370 6 : base_img: Option<(Lsn, bytes::Bytes)>,
371 6 : records: Vec<(Lsn, crate::walrecord::NeonWalRecord)>,
372 6 : pg_version: u32,
373 6 : ) -> anyhow::Result<bytes::Bytes> {
374 6 : match self {
375 0 : Self::Prod(mgr) => {
376 0 : mgr.request_redo(key, lsn, base_img, records, pg_version)
377 0 : .await
378 : }
379 : #[cfg(test)]
380 6 : Self::Test(mgr) => {
381 6 : mgr.request_redo(key, lsn, base_img, records, pg_version)
382 0 : .await
383 : }
384 : }
385 6 : }
386 :
387 0 : pub(crate) fn status(&self) -> Option<WalRedoManagerStatus> {
388 0 : match self {
389 0 : WalRedoManager::Prod(m) => m.status(),
390 0 : #[cfg(test)]
391 0 : WalRedoManager::Test(_) => None,
392 0 : }
393 0 : }
394 : }
395 :
396 0 : #[derive(Debug, thiserror::Error, PartialEq, Eq)]
397 : pub enum GetTimelineError {
398 : #[error("Timeline {tenant_id}/{timeline_id} is not active, state: {state:?}")]
399 : NotActive {
400 : tenant_id: TenantShardId,
401 : timeline_id: TimelineId,
402 : state: TimelineState,
403 : },
404 : #[error("Timeline {tenant_id}/{timeline_id} was not found")]
405 : NotFound {
406 : tenant_id: TenantShardId,
407 : timeline_id: TimelineId,
408 : },
409 : }
410 :
411 0 : #[derive(Debug, thiserror::Error)]
412 : pub enum LoadLocalTimelineError {
413 : #[error("FailedToLoad")]
414 : Load(#[source] anyhow::Error),
415 : #[error("FailedToResumeDeletion")]
416 : ResumeDeletion(#[source] anyhow::Error),
417 : }
418 :
419 0 : #[derive(thiserror::Error)]
420 : pub enum DeleteTimelineError {
421 : #[error("NotFound")]
422 : NotFound,
423 :
424 : #[error("HasChildren")]
425 : HasChildren(Vec<TimelineId>),
426 :
427 : #[error("Timeline deletion is already in progress")]
428 : AlreadyInProgress(Arc<tokio::sync::Mutex<DeleteTimelineFlow>>),
429 :
430 : #[error(transparent)]
431 : Other(#[from] anyhow::Error),
432 : }
433 :
434 : impl Debug for DeleteTimelineError {
435 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 0 : match self {
437 0 : Self::NotFound => write!(f, "NotFound"),
438 0 : Self::HasChildren(c) => f.debug_tuple("HasChildren").field(c).finish(),
439 0 : Self::AlreadyInProgress(_) => f.debug_tuple("AlreadyInProgress").finish(),
440 0 : Self::Other(e) => f.debug_tuple("Other").field(e).finish(),
441 : }
442 0 : }
443 : }
444 :
445 : pub enum SetStoppingError {
446 : AlreadyStopping(completion::Barrier),
447 : Broken,
448 : }
449 :
450 : impl Debug for SetStoppingError {
451 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
452 0 : match self {
453 0 : Self::AlreadyStopping(_) => f.debug_tuple("AlreadyStopping").finish(),
454 0 : Self::Broken => write!(f, "Broken"),
455 : }
456 0 : }
457 : }
458 :
459 0 : #[derive(thiserror::Error, Debug)]
460 : pub enum CreateTimelineError {
461 : #[error("creation of timeline with the given ID is in progress")]
462 : AlreadyCreating,
463 : #[error("timeline already exists with different parameters")]
464 : Conflict,
465 : #[error(transparent)]
466 : AncestorLsn(anyhow::Error),
467 : #[error("ancestor timeline is not active")]
468 : AncestorNotActive,
469 : #[error("tenant shutting down")]
470 : ShuttingDown,
471 : #[error(transparent)]
472 : Other(#[from] anyhow::Error),
473 : }
474 :
475 : #[derive(thiserror::Error, Debug)]
476 : enum InitdbError {
477 : Other(anyhow::Error),
478 : Cancelled,
479 : Spawn(std::io::Result<()>),
480 : Failed(std::process::ExitStatus, Vec<u8>),
481 : }
482 :
483 : impl fmt::Display for InitdbError {
484 0 : fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
485 0 : match self {
486 0 : InitdbError::Cancelled => write!(f, "Operation was cancelled"),
487 0 : InitdbError::Spawn(e) => write!(f, "Spawn error: {:?}", e),
488 0 : InitdbError::Failed(status, stderr) => write!(
489 0 : f,
490 0 : "Command failed with status {:?}: {}",
491 0 : status,
492 0 : String::from_utf8_lossy(stderr)
493 0 : ),
494 0 : InitdbError::Other(e) => write!(f, "Error: {:?}", e),
495 : }
496 0 : }
497 : }
498 :
499 : impl From<std::io::Error> for InitdbError {
500 0 : fn from(error: std::io::Error) -> Self {
501 0 : InitdbError::Spawn(Err(error))
502 0 : }
503 : }
504 :
505 : enum CreateTimelineCause {
506 : Load,
507 : Delete,
508 : }
509 :
510 : impl Tenant {
511 : /// Yet another helper for timeline initialization.
512 : ///
513 : /// - Initializes the Timeline struct and inserts it into the tenant's hash map
514 : /// - Scans the local timeline directory for layer files and builds the layer map
515 : /// - Downloads remote index file and adds remote files to the layer map
516 : /// - Schedules remote upload tasks for any files that are present locally but missing from remote storage.
517 : ///
518 : /// If the operation fails, the timeline is left in the tenant's hash map in Broken state. On success,
519 : /// it is marked as Active.
520 : #[allow(clippy::too_many_arguments)]
521 6 : async fn timeline_init_and_sync(
522 6 : &self,
523 6 : timeline_id: TimelineId,
524 6 : resources: TimelineResources,
525 6 : index_part: Option<IndexPart>,
526 6 : metadata: TimelineMetadata,
527 6 : ancestor: Option<Arc<Timeline>>,
528 6 : _ctx: &RequestContext,
529 6 : ) -> anyhow::Result<()> {
530 6 : let tenant_id = self.tenant_shard_id;
531 :
532 6 : let timeline = self.create_timeline_struct(
533 6 : timeline_id,
534 6 : &metadata,
535 6 : ancestor.clone(),
536 6 : resources,
537 6 : CreateTimelineCause::Load,
538 6 : )?;
539 6 : let disk_consistent_lsn = timeline.get_disk_consistent_lsn();
540 6 : anyhow::ensure!(
541 6 : disk_consistent_lsn.is_valid(),
542 0 : "Timeline {tenant_id}/{timeline_id} has invalid disk_consistent_lsn"
543 : );
544 6 : assert_eq!(
545 6 : disk_consistent_lsn,
546 6 : metadata.disk_consistent_lsn(),
547 0 : "these are used interchangeably"
548 : );
549 :
550 6 : if let Some(index_part) = index_part.as_ref() {
551 6 : timeline
552 6 : .remote_client
553 6 : .as_ref()
554 6 : .unwrap()
555 6 : .init_upload_queue(index_part)?;
556 0 : } else if self.remote_storage.is_some() {
557 : // No data on the remote storage, but we have local metadata file. We can end up
558 : // here with timeline_create being interrupted before finishing index part upload.
559 : // By doing what we do here, the index part upload is retried.
560 : // If control plane retries timeline creation in the meantime, the mgmt API handler
561 : // for timeline creation will coalesce on the upload we queue here.
562 0 : let rtc = timeline.remote_client.as_ref().unwrap();
563 0 : rtc.init_upload_queue_for_empty_remote(&metadata)?;
564 0 : rtc.schedule_index_upload_for_metadata_update(&metadata)?;
565 0 : }
566 :
567 6 : timeline
568 6 : .load_layer_map(disk_consistent_lsn, index_part)
569 4 : .await
570 6 : .with_context(|| {
571 0 : format!("Failed to load layermap for timeline {tenant_id}/{timeline_id}")
572 6 : })?;
573 :
574 : {
575 : // avoiding holding it across awaits
576 6 : let mut timelines_accessor = self.timelines.lock().unwrap();
577 6 : match timelines_accessor.entry(timeline_id) {
578 : // We should never try and load the same timeline twice during startup
579 : Entry::Occupied(_) => {
580 0 : unreachable!(
581 0 : "Timeline {tenant_id}/{timeline_id} already exists in the tenant map"
582 0 : );
583 : }
584 6 : Entry::Vacant(v) => {
585 6 : v.insert(Arc::clone(&timeline));
586 6 : timeline.maybe_spawn_flush_loop();
587 6 : }
588 6 : }
589 6 : };
590 6 :
591 6 : // Sanity check: a timeline should have some content.
592 6 : anyhow::ensure!(
593 6 : ancestor.is_some()
594 4 : || timeline
595 4 : .layers
596 4 : .read()
597 0 : .await
598 4 : .layer_map()
599 4 : .iter_historic_layers()
600 4 : .next()
601 4 : .is_some(),
602 0 : "Timeline has no ancestor and no layer files"
603 : );
604 :
605 6 : Ok(())
606 6 : }
607 :
608 : /// Attach a tenant that's available in cloud storage.
609 : ///
610 : /// This returns quickly, after just creating the in-memory object
611 : /// Tenant struct and launching a background task to download
612 : /// the remote index files. On return, the tenant is most likely still in
613 : /// Attaching state, and it will become Active once the background task
614 : /// finishes. You can use wait_until_active() to wait for the task to
615 : /// complete.
616 : ///
617 : #[allow(clippy::too_many_arguments)]
618 0 : pub(crate) fn spawn(
619 0 : conf: &'static PageServerConf,
620 0 : tenant_shard_id: TenantShardId,
621 0 : resources: TenantSharedResources,
622 0 : attached_conf: AttachedTenantConf,
623 0 : shard_identity: ShardIdentity,
624 0 : init_order: Option<InitializationOrder>,
625 0 : tenants: &'static std::sync::RwLock<TenantsMap>,
626 0 : mode: SpawnMode,
627 0 : ctx: &RequestContext,
628 0 : ) -> anyhow::Result<Arc<Tenant>> {
629 0 : let wal_redo_manager = Arc::new(WalRedoManager::from(PostgresRedoManager::new(
630 0 : conf,
631 0 : tenant_shard_id,
632 0 : )));
633 0 :
634 0 : let TenantSharedResources {
635 0 : broker_client,
636 0 : remote_storage,
637 0 : deletion_queue_client,
638 0 : } = resources;
639 0 :
640 0 : let attach_mode = attached_conf.location.attach_mode;
641 0 : let generation = attached_conf.location.generation;
642 0 :
643 0 : let tenant = Arc::new(Tenant::new(
644 0 : TenantState::Attaching,
645 0 : conf,
646 0 : attached_conf,
647 0 : shard_identity,
648 0 : Some(wal_redo_manager),
649 0 : tenant_shard_id,
650 0 : remote_storage.clone(),
651 0 : deletion_queue_client,
652 0 : ));
653 0 :
654 0 : // The attach task will carry a GateGuard, so that shutdown() reliably waits for it to drop out if
655 0 : // we shut down while attaching.
656 0 : let attach_gate_guard = tenant
657 0 : .gate
658 0 : .enter()
659 0 : .expect("We just created the Tenant: nothing else can have shut it down yet");
660 0 :
661 0 : // Do all the hard work in the background
662 0 : let tenant_clone = Arc::clone(&tenant);
663 0 : let ctx = ctx.detached_child(TaskKind::Attach, DownloadBehavior::Warn);
664 0 : task_mgr::spawn(
665 0 : &tokio::runtime::Handle::current(),
666 0 : TaskKind::Attach,
667 0 : Some(tenant_shard_id),
668 0 : None,
669 0 : "attach tenant",
670 : false,
671 0 : async move {
672 0 :
673 0 : info!(
674 0 : ?attach_mode,
675 0 : "Attaching tenant"
676 0 : );
677 :
678 0 : let _gate_guard = attach_gate_guard;
679 0 :
680 0 : // Is this tenant being spawned as part of process startup?
681 0 : let starting_up = init_order.is_some();
682 0 : scopeguard::defer! {
683 0 : if starting_up {
684 0 : TENANT.startup_complete.inc();
685 0 : }
686 : }
687 :
688 : // Ideally we should use Tenant::set_broken_no_wait, but it is not supposed to be used when tenant is in loading state.
689 : enum BrokenVerbosity {
690 : Error,
691 : Info
692 : }
693 0 : let make_broken =
694 0 : |t: &Tenant, err: anyhow::Error, verbosity: BrokenVerbosity| {
695 0 : match verbosity {
696 : BrokenVerbosity::Info => {
697 0 : info!("attach cancelled, setting tenant state to Broken: {err}");
698 : },
699 : BrokenVerbosity::Error => {
700 0 : error!("attach failed, setting tenant state to Broken: {err:?}");
701 : }
702 : }
703 0 : t.state.send_modify(|state| {
704 0 : // The Stopping case is for when we have passed control on to DeleteTenantFlow:
705 0 : // if it errors, we will call make_broken when tenant is already in Stopping.
706 0 : assert!(
707 0 : matches!(*state, TenantState::Attaching | TenantState::Stopping { .. }),
708 0 : "the attach task owns the tenant state until activation is complete"
709 : );
710 :
711 0 : *state = TenantState::broken_from_reason(err.to_string());
712 0 : });
713 0 : };
714 :
715 0 : let mut init_order = init_order;
716 0 : // take the completion because initial tenant loading will complete when all of
717 0 : // these tasks complete.
718 0 : let _completion = init_order
719 0 : .as_mut()
720 0 : .and_then(|x| x.initial_tenant_load.take());
721 0 : let remote_load_completion = init_order
722 0 : .as_mut()
723 0 : .and_then(|x| x.initial_tenant_load_remote.take());
724 :
725 : enum AttachType<'a> {
726 : /// We are attaching this tenant lazily in the background.
727 : Warmup {
728 : _permit: tokio::sync::SemaphorePermit<'a>,
729 : during_startup: bool
730 : },
731 : /// We are attaching this tenant as soon as we can, because for example an
732 : /// endpoint tried to access it.
733 : OnDemand,
734 : /// During normal operations after startup, we are attaching a tenant, and
735 : /// eager attach was requested.
736 : Normal,
737 : }
738 :
739 0 : let attach_type = if matches!(mode, SpawnMode::Lazy) {
740 : // Before doing any I/O, wait for at least one of:
741 : // - A client attempting to access to this tenant (on-demand loading)
742 : // - A permit becoming available in the warmup semaphore (background warmup)
743 :
744 0 : tokio::select!(
745 0 : permit = tenant_clone.activate_now_sem.acquire() => {
746 : let _ = permit.expect("activate_now_sem is never closed");
747 0 : tracing::info!("Activating tenant (on-demand)");
748 : AttachType::OnDemand
749 : },
750 0 : permit = conf.concurrent_tenant_warmup.inner().acquire() => {
751 : let _permit = permit.expect("concurrent_tenant_warmup semaphore is never closed");
752 0 : tracing::info!("Activating tenant (warmup)");
753 : AttachType::Warmup {
754 : _permit,
755 : during_startup: init_order.is_some()
756 : }
757 : }
758 : _ = tenant_clone.cancel.cancelled() => {
759 : // This is safe, but should be pretty rare: it is interesting if a tenant
760 : // stayed in Activating for such a long time that shutdown found it in
761 : // that state.
762 0 : tracing::info!(state=%tenant_clone.current_state(), "Tenant shut down before activation");
763 : // Make the tenant broken so that set_stopping will not hang waiting for it to leave
764 : // the Attaching state. This is an over-reaction (nothing really broke, the tenant is
765 : // just shutting down), but ensures progress.
766 : make_broken(&tenant_clone, anyhow::anyhow!("Shut down while Attaching"), BrokenVerbosity::Info);
767 : return Ok(());
768 : },
769 : )
770 : } else {
771 : // SpawnMode::{Create,Eager} always cause jumping ahead of the
772 : // concurrent_tenant_warmup queue
773 0 : AttachType::Normal
774 : };
775 :
776 0 : let preload = match (&mode, &remote_storage) {
777 : (SpawnMode::Create, _) => {
778 0 : None
779 : },
780 0 : (SpawnMode::Eager | SpawnMode::Lazy, Some(remote_storage)) => {
781 0 : let _preload_timer = TENANT.preload.start_timer();
782 0 : let res = tenant_clone
783 0 : .preload(remote_storage, task_mgr::shutdown_token())
784 0 : .await;
785 0 : match res {
786 0 : Ok(p) => Some(p),
787 0 : Err(e) => {
788 0 : make_broken(&tenant_clone, anyhow::anyhow!(e), BrokenVerbosity::Error);
789 0 : return Ok(());
790 : }
791 : }
792 : }
793 : (_, None) => {
794 0 : let _preload_timer = TENANT.preload.start_timer();
795 0 : None
796 : }
797 : };
798 :
799 : // Remote preload is complete.
800 0 : drop(remote_load_completion);
801 :
802 0 : let pending_deletion = {
803 0 : match DeleteTenantFlow::should_resume_deletion(
804 0 : conf,
805 0 : preload.as_ref().map(|p| p.deleting).unwrap_or(false),
806 0 : &tenant_clone,
807 0 : )
808 0 : .await
809 : {
810 0 : Ok(should_resume_deletion) => should_resume_deletion,
811 0 : Err(err) => {
812 0 : make_broken(&tenant_clone, anyhow::anyhow!(err), BrokenVerbosity::Error);
813 0 : return Ok(());
814 : }
815 : }
816 : };
817 :
818 0 : info!("pending_deletion {}", pending_deletion.is_some());
819 :
820 0 : if let Some(deletion) = pending_deletion {
821 : // as we are no longer loading, signal completion by dropping
822 : // the completion while we resume deletion
823 0 : drop(_completion);
824 0 : let background_jobs_can_start =
825 0 : init_order.as_ref().map(|x| &x.background_jobs_can_start);
826 0 : if let Some(background) = background_jobs_can_start {
827 0 : info!("waiting for backgound jobs barrier");
828 0 : background.clone().wait().await;
829 0 : info!("ready for backgound jobs barrier");
830 0 : }
831 :
832 0 : let deleted = DeleteTenantFlow::resume_from_attach(
833 0 : deletion,
834 0 : &tenant_clone,
835 0 : preload,
836 0 : tenants,
837 0 : &ctx,
838 0 : )
839 0 : .await;
840 :
841 0 : if let Err(e) = deleted {
842 0 : make_broken(&tenant_clone, anyhow::anyhow!(e), BrokenVerbosity::Error);
843 0 : }
844 :
845 0 : return Ok(());
846 0 : }
847 :
848 : // We will time the duration of the attach phase unless this is a creation (attach will do no work)
849 0 : let attached = {
850 0 : let _attach_timer = match mode {
851 0 : SpawnMode::Create => None,
852 0 : SpawnMode::Eager | SpawnMode::Lazy => Some(TENANT.attach.start_timer()),
853 : };
854 0 : tenant_clone.attach(preload, mode, &ctx).await
855 : };
856 :
857 0 : match attached {
858 : Ok(()) => {
859 0 : info!("attach finished, activating");
860 0 : tenant_clone.activate(broker_client, None, &ctx);
861 : }
862 0 : Err(e) => {
863 0 : make_broken(&tenant_clone, anyhow::anyhow!(e), BrokenVerbosity::Error);
864 0 : }
865 : }
866 :
867 : // If we are doing an opportunistic warmup attachment at startup, initialize
868 : // logical size at the same time. This is better than starting a bunch of idle tenants
869 : // with cold caches and then coming back later to initialize their logical sizes.
870 : //
871 : // It also prevents the warmup proccess competing with the concurrency limit on
872 : // logical size calculations: if logical size calculation semaphore is saturated,
873 : // then warmup will wait for that before proceeding to the next tenant.
874 0 : if matches!(attach_type, AttachType::Warmup { during_startup: true, .. }) {
875 0 : let mut futs: FuturesUnordered<_> = tenant_clone.timelines.lock().unwrap().values().cloned().map(|t| t.await_initial_logical_size()).collect();
876 0 : tracing::info!("Waiting for initial logical sizes while warming up...");
877 0 : while futs.next().await.is_some() {}
878 0 : tracing::info!("Warm-up complete");
879 0 : }
880 :
881 0 : Ok(())
882 0 : }
883 0 : .instrument(tracing::info_span!(parent: None, "attach", tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug(), gen=?generation)),
884 : );
885 0 : Ok(tenant)
886 0 : }
887 :
888 208 : #[instrument(skip_all)]
889 : pub(crate) async fn preload(
890 : self: &Arc<Tenant>,
891 : remote_storage: &GenericRemoteStorage,
892 : cancel: CancellationToken,
893 : ) -> anyhow::Result<TenantPreload> {
894 : span::debug_assert_current_span_has_tenant_id();
895 : // Get list of remote timelines
896 : // download index files for every tenant timeline
897 104 : info!("listing remote timelines");
898 : let (remote_timeline_ids, other_keys) = remote_timeline_client::list_remote_timelines(
899 : remote_storage,
900 : self.tenant_shard_id,
901 : cancel.clone(),
902 : )
903 : .await?;
904 :
905 : let deleting = other_keys.contains(TENANT_DELETED_MARKER_FILE_NAME);
906 104 : info!(
907 104 : "found {} timelines, deleting={}",
908 104 : remote_timeline_ids.len(),
909 104 : deleting
910 104 : );
911 :
912 : for k in other_keys {
913 : if k != TENANT_DELETED_MARKER_FILE_NAME {
914 0 : warn!("Unexpected non timeline key {k}");
915 : }
916 : }
917 :
918 : Ok(TenantPreload {
919 : deleting,
920 : timelines: self
921 : .load_timeline_metadata(remote_timeline_ids, remote_storage, cancel)
922 : .await?,
923 : })
924 : }
925 :
926 : ///
927 : /// Background task that downloads all data for a tenant and brings it to Active state.
928 : ///
929 : /// No background tasks are started as part of this routine.
930 : ///
931 104 : async fn attach(
932 104 : self: &Arc<Tenant>,
933 104 : preload: Option<TenantPreload>,
934 104 : mode: SpawnMode,
935 104 : ctx: &RequestContext,
936 104 : ) -> anyhow::Result<()> {
937 104 : span::debug_assert_current_span_has_tenant_id();
938 104 :
939 104 : failpoint_support::sleep_millis_async!("before-attaching-tenant");
940 :
941 104 : let preload = match (preload, mode) {
942 104 : (Some(p), _) => p,
943 0 : (None, SpawnMode::Create) => TenantPreload {
944 0 : deleting: false,
945 0 : timelines: HashMap::new(),
946 0 : },
947 : (None, _) => {
948 0 : anyhow::bail!("local-only deployment is no longer supported, https://github.com/neondatabase/neon/issues/5624");
949 : }
950 : };
951 :
952 104 : let mut timelines_to_resume_deletions = vec![];
953 104 :
954 104 : let mut remote_index_and_client = HashMap::new();
955 104 : let mut timeline_ancestors = HashMap::new();
956 104 : let mut existent_timelines = HashSet::new();
957 110 : for (timeline_id, preload) in preload.timelines {
958 6 : let index_part = match preload.index_part {
959 6 : Ok(i) => {
960 6 : debug!("remote index part exists for timeline {timeline_id}");
961 : // We found index_part on the remote, this is the standard case.
962 6 : existent_timelines.insert(timeline_id);
963 6 : i
964 : }
965 : Err(DownloadError::NotFound) => {
966 : // There is no index_part on the remote. We only get here
967 : // if there is some prefix for the timeline in the remote storage.
968 : // This can e.g. be the initdb.tar.zst archive, maybe a
969 : // remnant from a prior incomplete creation or deletion attempt.
970 : // Delete the local directory as the deciding criterion for a
971 : // timeline's existence is presence of index_part.
972 0 : info!(%timeline_id, "index_part not found on remote");
973 0 : continue;
974 : }
975 0 : Err(e) => {
976 0 : // Some (possibly ephemeral) error happened during index_part download.
977 0 : // Pretend the timeline exists to not delete the timeline directory,
978 0 : // as it might be a temporary issue and we don't want to re-download
979 0 : // everything after it resolves.
980 0 : warn!(%timeline_id, "Failed to load index_part from remote storage, failed creation? ({e})");
981 :
982 0 : existent_timelines.insert(timeline_id);
983 0 : continue;
984 : }
985 : };
986 6 : match index_part {
987 6 : MaybeDeletedIndexPart::IndexPart(index_part) => {
988 6 : timeline_ancestors.insert(timeline_id, index_part.metadata.clone());
989 6 : remote_index_and_client.insert(timeline_id, (index_part, preload.client));
990 6 : }
991 0 : MaybeDeletedIndexPart::Deleted(index_part) => {
992 0 : info!(
993 0 : "timeline {} is deleted, picking to resume deletion",
994 0 : timeline_id
995 0 : );
996 0 : timelines_to_resume_deletions.push((timeline_id, index_part, preload.client));
997 : }
998 : }
999 : }
1000 :
1001 : // For every timeline, download the metadata file, scan the local directory,
1002 : // and build a layer map that contains an entry for each remote and local
1003 : // layer file.
1004 104 : let sorted_timelines = tree_sort_timelines(timeline_ancestors, |m| m.ancestor_timeline())?;
1005 110 : for (timeline_id, remote_metadata) in sorted_timelines {
1006 6 : let (index_part, remote_client) = remote_index_and_client
1007 6 : .remove(&timeline_id)
1008 6 : .expect("just put it in above");
1009 6 :
1010 6 : // TODO again handle early failure
1011 6 : self.load_remote_timeline(
1012 6 : timeline_id,
1013 6 : index_part,
1014 6 : remote_metadata,
1015 6 : TimelineResources {
1016 6 : remote_client: Some(remote_client),
1017 6 : deletion_queue_client: self.deletion_queue_client.clone(),
1018 6 : timeline_get_throttle: self.timeline_get_throttle.clone(),
1019 6 : },
1020 6 : ctx,
1021 6 : )
1022 8 : .await
1023 6 : .with_context(|| {
1024 0 : format!(
1025 0 : "failed to load remote timeline {} for tenant {}",
1026 0 : timeline_id, self.tenant_shard_id
1027 0 : )
1028 6 : })?;
1029 : }
1030 :
1031 : // Walk through deleted timelines, resume deletion
1032 104 : for (timeline_id, index_part, remote_timeline_client) in timelines_to_resume_deletions {
1033 0 : remote_timeline_client
1034 0 : .init_upload_queue_stopped_to_continue_deletion(&index_part)
1035 0 : .context("init queue stopped")
1036 0 : .map_err(LoadLocalTimelineError::ResumeDeletion)?;
1037 :
1038 0 : DeleteTimelineFlow::resume_deletion(
1039 0 : Arc::clone(self),
1040 0 : timeline_id,
1041 0 : &index_part.metadata,
1042 0 : Some(remote_timeline_client),
1043 0 : self.deletion_queue_client.clone(),
1044 0 : )
1045 0 : .instrument(tracing::info_span!("timeline_delete", %timeline_id))
1046 0 : .await
1047 0 : .context("resume_deletion")
1048 0 : .map_err(LoadLocalTimelineError::ResumeDeletion)?;
1049 : }
1050 :
1051 : // The local filesystem contents are a cache of what's in the remote IndexPart;
1052 : // IndexPart is the source of truth.
1053 104 : self.clean_up_timelines(&existent_timelines)?;
1054 :
1055 104 : fail::fail_point!("attach-before-activate", |_| {
1056 0 : anyhow::bail!("attach-before-activate");
1057 104 : });
1058 104 : failpoint_support::sleep_millis_async!("attach-before-activate-sleep", &self.cancel);
1059 :
1060 104 : info!("Done");
1061 :
1062 104 : Ok(())
1063 104 : }
1064 :
1065 : /// Check for any local timeline directories that are temporary, or do not correspond to a
1066 : /// timeline that still exists: this can happen if we crashed during a deletion/creation, or
1067 : /// if a timeline was deleted while the tenant was attached to a different pageserver.
1068 104 : fn clean_up_timelines(&self, existent_timelines: &HashSet<TimelineId>) -> anyhow::Result<()> {
1069 104 : let timelines_dir = self.conf.timelines_path(&self.tenant_shard_id);
1070 :
1071 104 : let entries = match timelines_dir.read_dir_utf8() {
1072 104 : Ok(d) => d,
1073 0 : Err(e) => {
1074 0 : if e.kind() == std::io::ErrorKind::NotFound {
1075 0 : return Ok(());
1076 : } else {
1077 0 : return Err(e).context("list timelines directory for tenant");
1078 : }
1079 : }
1080 : };
1081 :
1082 112 : for entry in entries {
1083 8 : let entry = entry.context("read timeline dir entry")?;
1084 8 : let entry_path = entry.path();
1085 :
1086 8 : let purge = if crate::is_temporary(entry_path)
1087 : // TODO: remove uninit mark code (https://github.com/neondatabase/neon/issues/5718)
1088 8 : || is_uninit_mark(entry_path)
1089 8 : || crate::is_delete_mark(entry_path)
1090 : {
1091 0 : true
1092 : } else {
1093 8 : match TimelineId::try_from(entry_path.file_name()) {
1094 8 : Ok(i) => {
1095 8 : // Purge if the timeline ID does not exist in remote storage: remote storage is the authority.
1096 8 : !existent_timelines.contains(&i)
1097 : }
1098 0 : Err(e) => {
1099 0 : tracing::warn!(
1100 0 : "Unparseable directory in timelines directory: {entry_path}, ignoring ({e})"
1101 0 : );
1102 : // Do not purge junk: if we don't recognize it, be cautious and leave it for a human.
1103 0 : false
1104 : }
1105 : }
1106 : };
1107 :
1108 8 : if purge {
1109 2 : tracing::info!("Purging stale timeline dentry {entry_path}");
1110 2 : if let Err(e) = match entry.file_type() {
1111 2 : Ok(t) => if t.is_dir() {
1112 2 : std::fs::remove_dir_all(entry_path)
1113 : } else {
1114 0 : std::fs::remove_file(entry_path)
1115 : }
1116 2 : .or_else(fs_ext::ignore_not_found),
1117 0 : Err(e) => Err(e),
1118 : } {
1119 0 : tracing::warn!("Failed to purge stale timeline dentry {entry_path}: {e}");
1120 2 : }
1121 6 : }
1122 : }
1123 :
1124 104 : Ok(())
1125 104 : }
1126 :
1127 : /// Get sum of all remote timelines sizes
1128 : ///
1129 : /// This function relies on the index_part instead of listing the remote storage
1130 0 : pub fn remote_size(&self) -> u64 {
1131 0 : let mut size = 0;
1132 :
1133 0 : for timeline in self.list_timelines() {
1134 0 : if let Some(remote_client) = &timeline.remote_client {
1135 0 : size += remote_client.get_remote_physical_size();
1136 0 : }
1137 : }
1138 :
1139 0 : size
1140 0 : }
1141 :
1142 12 : #[instrument(skip_all, fields(timeline_id=%timeline_id))]
1143 : async fn load_remote_timeline(
1144 : &self,
1145 : timeline_id: TimelineId,
1146 : index_part: IndexPart,
1147 : remote_metadata: TimelineMetadata,
1148 : resources: TimelineResources,
1149 : ctx: &RequestContext,
1150 : ) -> anyhow::Result<()> {
1151 : span::debug_assert_current_span_has_tenant_id();
1152 :
1153 6 : info!("downloading index file for timeline {}", timeline_id);
1154 : tokio::fs::create_dir_all(self.conf.timeline_path(&self.tenant_shard_id, &timeline_id))
1155 : .await
1156 : .context("Failed to create new timeline directory")?;
1157 :
1158 : let ancestor = if let Some(ancestor_id) = remote_metadata.ancestor_timeline() {
1159 : let timelines = self.timelines.lock().unwrap();
1160 : Some(Arc::clone(timelines.get(&ancestor_id).ok_or_else(
1161 0 : || {
1162 0 : anyhow::anyhow!(
1163 0 : "cannot find ancestor timeline {ancestor_id} for timeline {timeline_id}"
1164 0 : )
1165 0 : },
1166 : )?))
1167 : } else {
1168 : None
1169 : };
1170 :
1171 : self.timeline_init_and_sync(
1172 : timeline_id,
1173 : resources,
1174 : Some(index_part),
1175 : remote_metadata,
1176 : ancestor,
1177 : ctx,
1178 : )
1179 : .await
1180 : }
1181 :
1182 : /// Create a placeholder Tenant object for a broken tenant
1183 0 : pub fn create_broken_tenant(
1184 0 : conf: &'static PageServerConf,
1185 0 : tenant_shard_id: TenantShardId,
1186 0 : reason: String,
1187 0 : ) -> Arc<Tenant> {
1188 0 : Arc::new(Tenant::new(
1189 0 : TenantState::Broken {
1190 0 : reason,
1191 0 : backtrace: String::new(),
1192 0 : },
1193 0 : conf,
1194 0 : AttachedTenantConf::try_from(LocationConf::default()).unwrap(),
1195 0 : // Shard identity isn't meaningful for a broken tenant: it's just a placeholder
1196 0 : // to occupy the slot for this TenantShardId.
1197 0 : ShardIdentity::broken(tenant_shard_id.shard_number, tenant_shard_id.shard_count),
1198 0 : None,
1199 0 : tenant_shard_id,
1200 0 : None,
1201 0 : DeletionQueueClient::broken(),
1202 0 : ))
1203 0 : }
1204 :
1205 104 : async fn load_timeline_metadata(
1206 104 : self: &Arc<Tenant>,
1207 104 : timeline_ids: HashSet<TimelineId>,
1208 104 : remote_storage: &GenericRemoteStorage,
1209 104 : cancel: CancellationToken,
1210 104 : ) -> anyhow::Result<HashMap<TimelineId, TimelinePreload>> {
1211 104 : let mut part_downloads = JoinSet::new();
1212 110 : for timeline_id in timeline_ids {
1213 6 : let client = RemoteTimelineClient::new(
1214 6 : remote_storage.clone(),
1215 6 : self.deletion_queue_client.clone(),
1216 6 : self.conf,
1217 6 : self.tenant_shard_id,
1218 6 : timeline_id,
1219 6 : self.generation,
1220 6 : );
1221 6 : let cancel_clone = cancel.clone();
1222 6 : part_downloads.spawn(
1223 6 : async move {
1224 6 : debug!("starting index part download");
1225 :
1226 16 : let index_part = client.download_index_file(&cancel_clone).await;
1227 :
1228 6 : debug!("finished index part download");
1229 :
1230 6 : Result::<_, anyhow::Error>::Ok(TimelinePreload {
1231 6 : client,
1232 6 : timeline_id,
1233 6 : index_part,
1234 6 : })
1235 6 : }
1236 6 : .map(move |res| {
1237 6 : res.with_context(|| format!("download index part for timeline {timeline_id}"))
1238 6 : })
1239 6 : .instrument(info_span!("download_index_part", %timeline_id)),
1240 : );
1241 : }
1242 :
1243 104 : let mut timeline_preloads: HashMap<TimelineId, TimelinePreload> = HashMap::new();
1244 :
1245 110 : loop {
1246 115 : tokio::select!(
1247 110 : next = part_downloads.join_next() => {
1248 : match next {
1249 : Some(result) => {
1250 : let preload_result = result.context("join preload task")?;
1251 : let preload = preload_result?;
1252 : timeline_preloads.insert(preload.timeline_id, preload);
1253 : },
1254 : None => {
1255 : break;
1256 : }
1257 : }
1258 : },
1259 : _ = cancel.cancelled() => {
1260 : anyhow::bail!("Cancelled while waiting for remote index download")
1261 : }
1262 110 : )
1263 110 : }
1264 :
1265 104 : Ok(timeline_preloads)
1266 104 : }
1267 :
1268 4 : pub(crate) fn tenant_shard_id(&self) -> TenantShardId {
1269 4 : self.tenant_shard_id
1270 4 : }
1271 :
1272 : /// Get Timeline handle for given Neon timeline ID.
1273 : /// This function is idempotent. It doesn't change internal state in any way.
1274 234 : pub fn get_timeline(
1275 234 : &self,
1276 234 : timeline_id: TimelineId,
1277 234 : active_only: bool,
1278 234 : ) -> Result<Arc<Timeline>, GetTimelineError> {
1279 234 : let timelines_accessor = self.timelines.lock().unwrap();
1280 234 : let timeline = timelines_accessor
1281 234 : .get(&timeline_id)
1282 234 : .ok_or(GetTimelineError::NotFound {
1283 234 : tenant_id: self.tenant_shard_id,
1284 234 : timeline_id,
1285 234 : })?;
1286 :
1287 232 : if active_only && !timeline.is_active() {
1288 0 : Err(GetTimelineError::NotActive {
1289 0 : tenant_id: self.tenant_shard_id,
1290 0 : timeline_id,
1291 0 : state: timeline.current_state(),
1292 0 : })
1293 : } else {
1294 232 : Ok(Arc::clone(timeline))
1295 : }
1296 234 : }
1297 :
1298 : /// Lists timelines the tenant contains.
1299 : /// Up to tenant's implementation to omit certain timelines that ar not considered ready for use.
1300 0 : pub fn list_timelines(&self) -> Vec<Arc<Timeline>> {
1301 0 : self.timelines
1302 0 : .lock()
1303 0 : .unwrap()
1304 0 : .values()
1305 0 : .map(Arc::clone)
1306 0 : .collect()
1307 0 : }
1308 :
1309 0 : pub fn list_timeline_ids(&self) -> Vec<TimelineId> {
1310 0 : self.timelines.lock().unwrap().keys().cloned().collect()
1311 0 : }
1312 :
1313 : /// This is used to create the initial 'main' timeline during bootstrapping,
1314 : /// or when importing a new base backup. The caller is expected to load an
1315 : /// initial image of the datadir to the new timeline after this.
1316 : ///
1317 : /// Until that happens, the on-disk state is invalid (disk_consistent_lsn=Lsn(0))
1318 : /// and the timeline will fail to load at a restart.
1319 : ///
1320 : /// For tests, use `DatadirModification::init_empty_test_timeline` + `commit` to setup the
1321 : /// minimum amount of keys required to get a writable timeline.
1322 : /// (Without it, `put` might fail due to `repartition` failing.)
1323 96 : pub(crate) async fn create_empty_timeline(
1324 96 : &self,
1325 96 : new_timeline_id: TimelineId,
1326 96 : initdb_lsn: Lsn,
1327 96 : pg_version: u32,
1328 96 : _ctx: &RequestContext,
1329 96 : ) -> anyhow::Result<UninitializedTimeline> {
1330 96 : anyhow::ensure!(
1331 96 : self.is_active(),
1332 0 : "Cannot create empty timelines on inactive tenant"
1333 : );
1334 :
1335 : // Protect against concurrent attempts to use this TimelineId
1336 96 : let create_guard = self.create_timeline_create_guard(new_timeline_id)?;
1337 :
1338 94 : let new_metadata = TimelineMetadata::new(
1339 94 : // Initialize disk_consistent LSN to 0, The caller must import some data to
1340 94 : // make it valid, before calling finish_creation()
1341 94 : Lsn(0),
1342 94 : None,
1343 94 : None,
1344 94 : Lsn(0),
1345 94 : initdb_lsn,
1346 94 : initdb_lsn,
1347 94 : pg_version,
1348 94 : );
1349 94 : self.prepare_new_timeline(
1350 94 : new_timeline_id,
1351 94 : &new_metadata,
1352 94 : create_guard,
1353 94 : initdb_lsn,
1354 94 : None,
1355 94 : )
1356 0 : .await
1357 96 : }
1358 :
1359 : /// Helper for unit tests to create an empty timeline.
1360 : ///
1361 : /// The timeline is has state value `Active` but its background loops are not running.
1362 : // This makes the various functions which anyhow::ensure! for Active state work in tests.
1363 : // Our current tests don't need the background loops.
1364 : #[cfg(test)]
1365 88 : pub async fn create_test_timeline(
1366 88 : &self,
1367 88 : new_timeline_id: TimelineId,
1368 88 : initdb_lsn: Lsn,
1369 88 : pg_version: u32,
1370 88 : ctx: &RequestContext,
1371 88 : ) -> anyhow::Result<Arc<Timeline>> {
1372 88 : let uninit_tl = self
1373 88 : .create_empty_timeline(new_timeline_id, initdb_lsn, pg_version, ctx)
1374 0 : .await?;
1375 88 : let tline = uninit_tl.raw_timeline().expect("we just created it");
1376 88 : assert_eq!(tline.get_last_record_lsn(), Lsn(0));
1377 :
1378 : // Setup minimum keys required for the timeline to be usable.
1379 88 : let mut modification = tline.begin_modification(initdb_lsn);
1380 88 : modification
1381 88 : .init_empty_test_timeline()
1382 88 : .context("init_empty_test_timeline")?;
1383 88 : modification
1384 88 : .commit(ctx)
1385 88 : .await
1386 88 : .context("commit init_empty_test_timeline modification")?;
1387 :
1388 : // Flush to disk so that uninit_tl's check for valid disk_consistent_lsn passes.
1389 88 : tline.maybe_spawn_flush_loop();
1390 88 : tline.freeze_and_flush().await.context("freeze_and_flush")?;
1391 :
1392 : // Make sure the freeze_and_flush reaches remote storage.
1393 88 : tline
1394 88 : .remote_client
1395 88 : .as_ref()
1396 88 : .unwrap()
1397 88 : .wait_completion()
1398 84 : .await
1399 88 : .unwrap();
1400 :
1401 88 : let tl = uninit_tl.finish_creation()?;
1402 : // The non-test code would call tl.activate() here.
1403 88 : tl.set_state(TimelineState::Active);
1404 88 : Ok(tl)
1405 88 : }
1406 :
1407 : /// Create a new timeline.
1408 : ///
1409 : /// Returns the new timeline ID and reference to its Timeline object.
1410 : ///
1411 : /// If the caller specified the timeline ID to use (`new_timeline_id`), and timeline with
1412 : /// the same timeline ID already exists, returns CreateTimelineError::AlreadyExists.
1413 : #[allow(clippy::too_many_arguments)]
1414 0 : pub(crate) async fn create_timeline(
1415 0 : self: &Arc<Tenant>,
1416 0 : new_timeline_id: TimelineId,
1417 0 : ancestor_timeline_id: Option<TimelineId>,
1418 0 : mut ancestor_start_lsn: Option<Lsn>,
1419 0 : pg_version: u32,
1420 0 : load_existing_initdb: Option<TimelineId>,
1421 0 : broker_client: storage_broker::BrokerClientChannel,
1422 0 : ctx: &RequestContext,
1423 0 : ) -> Result<Arc<Timeline>, CreateTimelineError> {
1424 0 : if !self.is_active() {
1425 0 : if matches!(self.current_state(), TenantState::Stopping { .. }) {
1426 0 : return Err(CreateTimelineError::ShuttingDown);
1427 : } else {
1428 0 : return Err(CreateTimelineError::Other(anyhow::anyhow!(
1429 0 : "Cannot create timelines on inactive tenant"
1430 0 : )));
1431 : }
1432 0 : }
1433 :
1434 0 : let _gate = self
1435 0 : .gate
1436 0 : .enter()
1437 0 : .map_err(|_| CreateTimelineError::ShuttingDown)?;
1438 :
1439 : // Get exclusive access to the timeline ID: this ensures that it does not already exist,
1440 : // and that no other creation attempts will be allowed in while we are working.
1441 0 : let create_guard = match self.create_timeline_create_guard(new_timeline_id) {
1442 0 : Ok(m) => m,
1443 : Err(TimelineExclusionError::AlreadyCreating) => {
1444 : // Creation is in progress, we cannot create it again, and we cannot
1445 : // check if this request matches the existing one, so caller must try
1446 : // again later.
1447 0 : return Err(CreateTimelineError::AlreadyCreating);
1448 : }
1449 0 : Err(TimelineExclusionError::Other(e)) => {
1450 0 : return Err(CreateTimelineError::Other(e));
1451 : }
1452 0 : Err(TimelineExclusionError::AlreadyExists(existing)) => {
1453 0 : debug!("timeline {new_timeline_id} already exists");
1454 :
1455 : // Idempotency: creating the same timeline twice is not an error, unless
1456 : // the second creation has different parameters.
1457 0 : if existing.get_ancestor_timeline_id() != ancestor_timeline_id
1458 0 : || existing.pg_version != pg_version
1459 0 : || (ancestor_start_lsn.is_some()
1460 0 : && ancestor_start_lsn != Some(existing.get_ancestor_lsn()))
1461 : {
1462 0 : return Err(CreateTimelineError::Conflict);
1463 0 : }
1464 :
1465 0 : if let Some(remote_client) = existing.remote_client.as_ref() {
1466 : // Wait for uploads to complete, so that when we return Ok, the timeline
1467 : // is known to be durable on remote storage. Just like we do at the end of
1468 : // this function, after we have created the timeline ourselves.
1469 : //
1470 : // We only really care that the initial version of `index_part.json` has
1471 : // been uploaded. That's enough to remember that the timeline
1472 : // exists. However, there is no function to wait specifically for that so
1473 : // we just wait for all in-progress uploads to finish.
1474 0 : remote_client
1475 0 : .wait_completion()
1476 0 : .await
1477 0 : .context("wait for timeline uploads to complete")?;
1478 0 : }
1479 :
1480 0 : return Ok(existing);
1481 : }
1482 : };
1483 :
1484 0 : pausable_failpoint!("timeline-creation-after-uninit");
1485 :
1486 0 : let loaded_timeline = match ancestor_timeline_id {
1487 0 : Some(ancestor_timeline_id) => {
1488 0 : let ancestor_timeline = self
1489 0 : .get_timeline(ancestor_timeline_id, false)
1490 0 : .context("Cannot branch off the timeline that's not present in pageserver")?;
1491 :
1492 : // instead of waiting around, just deny the request because ancestor is not yet
1493 : // ready for other purposes either.
1494 0 : if !ancestor_timeline.is_active() {
1495 0 : return Err(CreateTimelineError::AncestorNotActive);
1496 0 : }
1497 :
1498 0 : if let Some(lsn) = ancestor_start_lsn.as_mut() {
1499 0 : *lsn = lsn.align();
1500 0 :
1501 0 : let ancestor_ancestor_lsn = ancestor_timeline.get_ancestor_lsn();
1502 0 : if ancestor_ancestor_lsn > *lsn {
1503 : // can we safely just branch from the ancestor instead?
1504 0 : return Err(CreateTimelineError::AncestorLsn(anyhow::anyhow!(
1505 0 : "invalid start lsn {} for ancestor timeline {}: less than timeline ancestor lsn {}",
1506 0 : lsn,
1507 0 : ancestor_timeline_id,
1508 0 : ancestor_ancestor_lsn,
1509 0 : )));
1510 0 : }
1511 0 :
1512 0 : // Wait for the WAL to arrive and be processed on the parent branch up
1513 0 : // to the requested branch point. The repository code itself doesn't
1514 0 : // require it, but if we start to receive WAL on the new timeline,
1515 0 : // decoding the new WAL might need to look up previous pages, relation
1516 0 : // sizes etc. and that would get confused if the previous page versions
1517 0 : // are not in the repository yet.
1518 0 : ancestor_timeline
1519 0 : .wait_lsn(*lsn, timeline::WaitLsnWaiter::Tenant, ctx)
1520 0 : .await
1521 0 : .map_err(|e| match e {
1522 0 : e @ (WaitLsnError::Timeout(_) | WaitLsnError::BadState) => {
1523 0 : CreateTimelineError::AncestorLsn(anyhow::anyhow!(e))
1524 : }
1525 0 : WaitLsnError::Shutdown => CreateTimelineError::ShuttingDown,
1526 0 : })?;
1527 0 : }
1528 :
1529 0 : self.branch_timeline(
1530 0 : &ancestor_timeline,
1531 0 : new_timeline_id,
1532 0 : ancestor_start_lsn,
1533 0 : create_guard,
1534 0 : ctx,
1535 0 : )
1536 0 : .await?
1537 : }
1538 : None => {
1539 0 : self.bootstrap_timeline(
1540 0 : new_timeline_id,
1541 0 : pg_version,
1542 0 : load_existing_initdb,
1543 0 : create_guard,
1544 0 : ctx,
1545 0 : )
1546 0 : .await?
1547 : }
1548 : };
1549 :
1550 : // At this point we have dropped our guard on [`Self::timelines_creating`], and
1551 : // the timeline is visible in [`Self::timelines`], but it is _not_ durable yet. We must
1552 : // not send a success to the caller until it is. The same applies to handling retries,
1553 : // see the handling of [`TimelineExclusionError::AlreadyExists`] above.
1554 0 : if let Some(remote_client) = loaded_timeline.remote_client.as_ref() {
1555 0 : let kind = ancestor_timeline_id
1556 0 : .map(|_| "branched")
1557 0 : .unwrap_or("bootstrapped");
1558 0 : remote_client.wait_completion().await.with_context(|| {
1559 0 : format!("wait for {} timeline initial uploads to complete", kind)
1560 0 : })?;
1561 0 : }
1562 :
1563 0 : loaded_timeline.activate(self.clone(), broker_client, None, ctx);
1564 0 :
1565 0 : Ok(loaded_timeline)
1566 0 : }
1567 :
1568 0 : pub(crate) async fn delete_timeline(
1569 0 : self: Arc<Self>,
1570 0 : timeline_id: TimelineId,
1571 0 : ) -> Result<(), DeleteTimelineError> {
1572 0 : DeleteTimelineFlow::run(&self, timeline_id, false).await?;
1573 :
1574 0 : Ok(())
1575 0 : }
1576 :
1577 : /// perform one garbage collection iteration, removing old data files from disk.
1578 : /// this function is periodically called by gc task.
1579 : /// also it can be explicitly requested through page server api 'do_gc' command.
1580 : ///
1581 : /// `target_timeline_id` specifies the timeline to GC, or None for all.
1582 : ///
1583 : /// The `horizon` an `pitr` parameters determine how much WAL history needs to be retained.
1584 : /// Also known as the retention period, or the GC cutoff point. `horizon` specifies
1585 : /// the amount of history, as LSN difference from current latest LSN on each timeline.
1586 : /// `pitr` specifies the same as a time difference from the current time. The effective
1587 : /// GC cutoff point is determined conservatively by either `horizon` and `pitr`, whichever
1588 : /// requires more history to be retained.
1589 : //
1590 8 : pub async fn gc_iteration(
1591 8 : &self,
1592 8 : target_timeline_id: Option<TimelineId>,
1593 8 : horizon: u64,
1594 8 : pitr: Duration,
1595 8 : cancel: &CancellationToken,
1596 8 : ctx: &RequestContext,
1597 8 : ) -> anyhow::Result<GcResult> {
1598 8 : // Don't start doing work during shutdown
1599 8 : if let TenantState::Stopping { .. } = self.current_state() {
1600 0 : return Ok(GcResult::default());
1601 8 : }
1602 8 :
1603 8 : // there is a global allowed_error for this
1604 8 : anyhow::ensure!(
1605 8 : self.is_active(),
1606 0 : "Cannot run GC iteration on inactive tenant"
1607 : );
1608 :
1609 : {
1610 8 : let conf = self.tenant_conf.load();
1611 8 :
1612 8 : if !conf.location.may_delete_layers_hint() {
1613 0 : info!("Skipping GC in location state {:?}", conf.location);
1614 0 : return Ok(GcResult::default());
1615 8 : }
1616 8 : }
1617 8 :
1618 8 : self.gc_iteration_internal(target_timeline_id, horizon, pitr, cancel, ctx)
1619 0 : .await
1620 8 : }
1621 :
1622 : /// Perform one compaction iteration.
1623 : /// This function is periodically called by compactor task.
1624 : /// Also it can be explicitly requested per timeline through page server
1625 : /// api's 'compact' command.
1626 0 : async fn compaction_iteration(
1627 0 : &self,
1628 0 : cancel: &CancellationToken,
1629 0 : ctx: &RequestContext,
1630 0 : ) -> anyhow::Result<(), timeline::CompactionError> {
1631 0 : // Don't start doing work during shutdown, or when broken, we do not need those in the logs
1632 0 : if !self.is_active() {
1633 0 : return Ok(());
1634 0 : }
1635 0 :
1636 0 : {
1637 0 : let conf = self.tenant_conf.load();
1638 0 : if !conf.location.may_delete_layers_hint() || !conf.location.may_upload_layers_hint() {
1639 0 : info!("Skipping compaction in location state {:?}", conf.location);
1640 0 : return Ok(());
1641 0 : }
1642 0 : }
1643 0 :
1644 0 : // Scan through the hashmap and collect a list of all the timelines,
1645 0 : // while holding the lock. Then drop the lock and actually perform the
1646 0 : // compactions. We don't want to block everything else while the
1647 0 : // compaction runs.
1648 0 : let timelines_to_compact = {
1649 0 : let timelines = self.timelines.lock().unwrap();
1650 0 : let timelines_to_compact = timelines
1651 0 : .iter()
1652 0 : .filter_map(|(timeline_id, timeline)| {
1653 0 : if timeline.is_active() {
1654 0 : Some((*timeline_id, timeline.clone()))
1655 : } else {
1656 0 : None
1657 : }
1658 0 : })
1659 0 : .collect::<Vec<_>>();
1660 0 : drop(timelines);
1661 0 : timelines_to_compact
1662 : };
1663 :
1664 0 : for (timeline_id, timeline) in &timelines_to_compact {
1665 0 : timeline
1666 0 : .compact(cancel, EnumSet::empty(), ctx)
1667 0 : .instrument(info_span!("compact_timeline", %timeline_id))
1668 0 : .await?;
1669 : }
1670 :
1671 0 : Ok(())
1672 0 : }
1673 :
1674 118 : pub fn current_state(&self) -> TenantState {
1675 118 : self.state.borrow().clone()
1676 118 : }
1677 :
1678 104 : pub fn is_active(&self) -> bool {
1679 104 : self.current_state() == TenantState::Active
1680 104 : }
1681 :
1682 0 : pub fn generation(&self) -> Generation {
1683 0 : self.generation
1684 0 : }
1685 :
1686 0 : pub(crate) fn wal_redo_manager_status(&self) -> Option<WalRedoManagerStatus> {
1687 0 : self.walredo_mgr.as_ref().and_then(|mgr| mgr.status())
1688 0 : }
1689 :
1690 : /// Changes tenant status to active, unless shutdown was already requested.
1691 : ///
1692 : /// `background_jobs_can_start` is an optional barrier set to a value during pageserver startup
1693 : /// to delay background jobs. Background jobs can be started right away when None is given.
1694 0 : fn activate(
1695 0 : self: &Arc<Self>,
1696 0 : broker_client: BrokerClientChannel,
1697 0 : background_jobs_can_start: Option<&completion::Barrier>,
1698 0 : ctx: &RequestContext,
1699 0 : ) {
1700 0 : span::debug_assert_current_span_has_tenant_id();
1701 0 :
1702 0 : let mut activating = false;
1703 0 : self.state.send_modify(|current_state| {
1704 0 : use pageserver_api::models::ActivatingFrom;
1705 0 : match &*current_state {
1706 : TenantState::Activating(_) | TenantState::Active | TenantState::Broken { .. } | TenantState::Stopping { .. } => {
1707 0 : panic!("caller is responsible for calling activate() only on Loading / Attaching tenants, got {state:?}", state = current_state);
1708 : }
1709 0 : TenantState::Loading => {
1710 0 : *current_state = TenantState::Activating(ActivatingFrom::Loading);
1711 0 : }
1712 0 : TenantState::Attaching => {
1713 0 : *current_state = TenantState::Activating(ActivatingFrom::Attaching);
1714 0 : }
1715 : }
1716 0 : debug!(tenant_id = %self.tenant_shard_id.tenant_id, shard_id = %self.tenant_shard_id.shard_slug(), "Activating tenant");
1717 0 : activating = true;
1718 0 : // Continue outside the closure. We need to grab timelines.lock()
1719 0 : // and we plan to turn it into a tokio::sync::Mutex in a future patch.
1720 0 : });
1721 0 :
1722 0 : if activating {
1723 0 : let timelines_accessor = self.timelines.lock().unwrap();
1724 0 : let timelines_to_activate = timelines_accessor
1725 0 : .values()
1726 0 : .filter(|timeline| !(timeline.is_broken() || timeline.is_stopping()));
1727 0 :
1728 0 : // Spawn gc and compaction loops. The loops will shut themselves
1729 0 : // down when they notice that the tenant is inactive.
1730 0 : tasks::start_background_loops(self, background_jobs_can_start);
1731 0 :
1732 0 : let mut activated_timelines = 0;
1733 :
1734 0 : for timeline in timelines_to_activate {
1735 0 : timeline.activate(
1736 0 : self.clone(),
1737 0 : broker_client.clone(),
1738 0 : background_jobs_can_start,
1739 0 : ctx,
1740 0 : );
1741 0 : activated_timelines += 1;
1742 0 : }
1743 :
1744 0 : self.state.send_modify(move |current_state| {
1745 0 : assert!(
1746 0 : matches!(current_state, TenantState::Activating(_)),
1747 0 : "set_stopping and set_broken wait for us to leave Activating state",
1748 : );
1749 0 : *current_state = TenantState::Active;
1750 0 :
1751 0 : let elapsed = self.constructed_at.elapsed();
1752 0 : let total_timelines = timelines_accessor.len();
1753 0 :
1754 0 : // log a lot of stuff, because some tenants sometimes suffer from user-visible
1755 0 : // times to activate. see https://github.com/neondatabase/neon/issues/4025
1756 0 : info!(
1757 0 : since_creation_millis = elapsed.as_millis(),
1758 0 : tenant_id = %self.tenant_shard_id.tenant_id,
1759 0 : shard_id = %self.tenant_shard_id.shard_slug(),
1760 0 : activated_timelines,
1761 0 : total_timelines,
1762 0 : post_state = <&'static str>::from(&*current_state),
1763 0 : "activation attempt finished"
1764 0 : );
1765 :
1766 0 : TENANT.activation.observe(elapsed.as_secs_f64());
1767 0 : });
1768 0 : }
1769 0 : }
1770 :
1771 : /// Shutdown the tenant and join all of the spawned tasks.
1772 : ///
1773 : /// The method caters for all use-cases:
1774 : /// - pageserver shutdown (freeze_and_flush == true)
1775 : /// - detach + ignore (freeze_and_flush == false)
1776 : ///
1777 : /// This will attempt to shutdown even if tenant is broken.
1778 : ///
1779 : /// `shutdown_progress` is a [`completion::Barrier`] for the shutdown initiated by this call.
1780 : /// If the tenant is already shutting down, we return a clone of the first shutdown call's
1781 : /// `Barrier` as an `Err`. This not-first caller can use the returned barrier to join with
1782 : /// the ongoing shutdown.
1783 6 : async fn shutdown(
1784 6 : &self,
1785 6 : shutdown_progress: completion::Barrier,
1786 6 : shutdown_mode: timeline::ShutdownMode,
1787 6 : ) -> Result<(), completion::Barrier> {
1788 6 : span::debug_assert_current_span_has_tenant_id();
1789 :
1790 : // Set tenant (and its timlines) to Stoppping state.
1791 : //
1792 : // Since we can only transition into Stopping state after activation is complete,
1793 : // run it in a JoinSet so all tenants have a chance to stop before we get SIGKILLed.
1794 : //
1795 : // Transitioning tenants to Stopping state has a couple of non-obvious side effects:
1796 : // 1. Lock out any new requests to the tenants.
1797 : // 2. Signal cancellation to WAL receivers (we wait on it below).
1798 : // 3. Signal cancellation for other tenant background loops.
1799 : // 4. ???
1800 : //
1801 : // The waiting for the cancellation is not done uniformly.
1802 : // We certainly wait for WAL receivers to shut down.
1803 : // That is necessary so that no new data comes in before the freeze_and_flush.
1804 : // But the tenant background loops are joined-on in our caller.
1805 : // It's mesed up.
1806 : // we just ignore the failure to stop
1807 :
1808 : // If we're still attaching, fire the cancellation token early to drop out: this
1809 : // will prevent us flushing, but ensures timely shutdown if some I/O during attach
1810 : // is very slow.
1811 6 : if matches!(self.current_state(), TenantState::Attaching) {
1812 0 : self.cancel.cancel();
1813 6 : }
1814 :
1815 6 : match self.set_stopping(shutdown_progress, false, false).await {
1816 6 : Ok(()) => {}
1817 0 : Err(SetStoppingError::Broken) => {
1818 0 : // assume that this is acceptable
1819 0 : }
1820 0 : Err(SetStoppingError::AlreadyStopping(other)) => {
1821 0 : // give caller the option to wait for this this shutdown
1822 0 : info!("Tenant::shutdown: AlreadyStopping");
1823 0 : return Err(other);
1824 : }
1825 : };
1826 :
1827 6 : let mut js = tokio::task::JoinSet::new();
1828 6 : {
1829 6 : let timelines = self.timelines.lock().unwrap();
1830 6 : timelines.values().for_each(|timeline| {
1831 6 : let timeline = Arc::clone(timeline);
1832 6 : let timeline_id = timeline.timeline_id;
1833 6 : let span = tracing::info_span!("timeline_shutdown", %timeline_id, ?shutdown_mode);
1834 15 : js.spawn(async move { timeline.shutdown(shutdown_mode).instrument(span).await });
1835 6 : })
1836 6 : };
1837 6 : // test_long_timeline_create_then_tenant_delete is leaning on this message
1838 6 : tracing::info!("Waiting for timelines...");
1839 12 : while let Some(res) = js.join_next().await {
1840 0 : match res {
1841 6 : Ok(()) => {}
1842 0 : Err(je) if je.is_cancelled() => unreachable!("no cancelling used"),
1843 0 : Err(je) if je.is_panic() => { /* logged already */ }
1844 0 : Err(je) => warn!("unexpected JoinError: {je:?}"),
1845 : }
1846 : }
1847 :
1848 : // We cancel the Tenant's cancellation token _after_ the timelines have all shut down. This permits
1849 : // them to continue to do work during their shutdown methods, e.g. flushing data.
1850 6 : tracing::debug!("Cancelling CancellationToken");
1851 6 : self.cancel.cancel();
1852 6 :
1853 6 : // shutdown all tenant and timeline tasks: gc, compaction, page service
1854 6 : // No new tasks will be started for this tenant because it's in `Stopping` state.
1855 6 : //
1856 6 : // this will additionally shutdown and await all timeline tasks.
1857 6 : tracing::debug!("Waiting for tasks...");
1858 6 : task_mgr::shutdown_tasks(None, Some(self.tenant_shard_id), None).await;
1859 :
1860 : // Wait for any in-flight operations to complete
1861 6 : self.gate.close().await;
1862 :
1863 6 : remove_tenant_metrics(&self.tenant_shard_id);
1864 6 :
1865 6 : Ok(())
1866 6 : }
1867 :
1868 : /// Change tenant status to Stopping, to mark that it is being shut down.
1869 : ///
1870 : /// This function waits for the tenant to become active if it isn't already, before transitioning it into Stopping state.
1871 : ///
1872 : /// This function is not cancel-safe!
1873 : ///
1874 : /// `allow_transition_from_loading` is needed for the special case of loading task deleting the tenant.
1875 : /// `allow_transition_from_attaching` is needed for the special case of attaching deleted tenant.
1876 6 : async fn set_stopping(
1877 6 : &self,
1878 6 : progress: completion::Barrier,
1879 6 : allow_transition_from_loading: bool,
1880 6 : allow_transition_from_attaching: bool,
1881 6 : ) -> Result<(), SetStoppingError> {
1882 6 : let mut rx = self.state.subscribe();
1883 6 :
1884 6 : // cannot stop before we're done activating, so wait out until we're done activating
1885 6 : rx.wait_for(|state| match state {
1886 0 : TenantState::Attaching if allow_transition_from_attaching => true,
1887 : TenantState::Activating(_) | TenantState::Attaching => {
1888 0 : info!(
1889 0 : "waiting for {} to turn Active|Broken|Stopping",
1890 0 : <&'static str>::from(state)
1891 0 : );
1892 0 : false
1893 : }
1894 0 : TenantState::Loading => allow_transition_from_loading,
1895 6 : TenantState::Active | TenantState::Broken { .. } | TenantState::Stopping { .. } => true,
1896 6 : })
1897 0 : .await
1898 6 : .expect("cannot drop self.state while on a &self method");
1899 6 :
1900 6 : // we now know we're done activating, let's see whether this task is the winner to transition into Stopping
1901 6 : let mut err = None;
1902 6 : let stopping = self.state.send_if_modified(|current_state| match current_state {
1903 : TenantState::Activating(_) => {
1904 0 : unreachable!("1we ensured above that we're done with activation, and, there is no re-activation")
1905 : }
1906 : TenantState::Attaching => {
1907 0 : if !allow_transition_from_attaching {
1908 0 : unreachable!("2we ensured above that we're done with activation, and, there is no re-activation")
1909 0 : };
1910 0 : *current_state = TenantState::Stopping { progress };
1911 0 : true
1912 : }
1913 : TenantState::Loading => {
1914 0 : if !allow_transition_from_loading {
1915 0 : unreachable!("3we ensured above that we're done with activation, and, there is no re-activation")
1916 0 : };
1917 0 : *current_state = TenantState::Stopping { progress };
1918 0 : true
1919 : }
1920 : TenantState::Active => {
1921 : // FIXME: due to time-of-check vs time-of-use issues, it can happen that new timelines
1922 : // are created after the transition to Stopping. That's harmless, as the Timelines
1923 : // won't be accessible to anyone afterwards, because the Tenant is in Stopping state.
1924 6 : *current_state = TenantState::Stopping { progress };
1925 6 : // Continue stopping outside the closure. We need to grab timelines.lock()
1926 6 : // and we plan to turn it into a tokio::sync::Mutex in a future patch.
1927 6 : true
1928 : }
1929 0 : TenantState::Broken { reason, .. } => {
1930 0 : info!(
1931 0 : "Cannot set tenant to Stopping state, it is in Broken state due to: {reason}"
1932 0 : );
1933 0 : err = Some(SetStoppingError::Broken);
1934 0 : false
1935 : }
1936 0 : TenantState::Stopping { progress } => {
1937 0 : info!("Tenant is already in Stopping state");
1938 0 : err = Some(SetStoppingError::AlreadyStopping(progress.clone()));
1939 0 : false
1940 : }
1941 6 : });
1942 6 : match (stopping, err) {
1943 6 : (true, None) => {} // continue
1944 0 : (false, Some(err)) => return Err(err),
1945 0 : (true, Some(_)) => unreachable!(
1946 0 : "send_if_modified closure must error out if not transitioning to Stopping"
1947 0 : ),
1948 0 : (false, None) => unreachable!(
1949 0 : "send_if_modified closure must return true if transitioning to Stopping"
1950 0 : ),
1951 : }
1952 :
1953 6 : let timelines_accessor = self.timelines.lock().unwrap();
1954 6 : let not_broken_timelines = timelines_accessor
1955 6 : .values()
1956 6 : .filter(|timeline| !timeline.is_broken());
1957 12 : for timeline in not_broken_timelines {
1958 6 : timeline.set_state(TimelineState::Stopping);
1959 6 : }
1960 6 : Ok(())
1961 6 : }
1962 :
1963 : /// Method for tenant::mgr to transition us into Broken state in case of a late failure in
1964 : /// `remove_tenant_from_memory`
1965 : ///
1966 : /// This function waits for the tenant to become active if it isn't already, before transitioning it into Stopping state.
1967 : ///
1968 : /// In tests, we also use this to set tenants to Broken state on purpose.
1969 0 : pub(crate) async fn set_broken(&self, reason: String) {
1970 0 : let mut rx = self.state.subscribe();
1971 0 :
1972 0 : // The load & attach routines own the tenant state until it has reached `Active`.
1973 0 : // So, wait until it's done.
1974 0 : rx.wait_for(|state| match state {
1975 : TenantState::Activating(_) | TenantState::Loading | TenantState::Attaching => {
1976 0 : info!(
1977 0 : "waiting for {} to turn Active|Broken|Stopping",
1978 0 : <&'static str>::from(state)
1979 0 : );
1980 0 : false
1981 : }
1982 0 : TenantState::Active | TenantState::Broken { .. } | TenantState::Stopping { .. } => true,
1983 0 : })
1984 0 : .await
1985 0 : .expect("cannot drop self.state while on a &self method");
1986 0 :
1987 0 : // we now know we're done activating, let's see whether this task is the winner to transition into Broken
1988 0 : self.set_broken_no_wait(reason)
1989 0 : }
1990 :
1991 0 : pub(crate) fn set_broken_no_wait(&self, reason: impl Display) {
1992 0 : let reason = reason.to_string();
1993 0 : self.state.send_modify(|current_state| {
1994 0 : match *current_state {
1995 : TenantState::Activating(_) | TenantState::Loading | TenantState::Attaching => {
1996 0 : unreachable!("we ensured above that we're done with activation, and, there is no re-activation")
1997 : }
1998 : TenantState::Active => {
1999 0 : if cfg!(feature = "testing") {
2000 0 : warn!("Changing Active tenant to Broken state, reason: {}", reason);
2001 0 : *current_state = TenantState::broken_from_reason(reason);
2002 : } else {
2003 0 : unreachable!("not allowed to call set_broken on Active tenants in non-testing builds")
2004 : }
2005 : }
2006 : TenantState::Broken { .. } => {
2007 0 : warn!("Tenant is already in Broken state");
2008 : }
2009 : // This is the only "expected" path, any other path is a bug.
2010 : TenantState::Stopping { .. } => {
2011 0 : warn!(
2012 0 : "Marking Stopping tenant as Broken state, reason: {}",
2013 0 : reason
2014 0 : );
2015 0 : *current_state = TenantState::broken_from_reason(reason);
2016 : }
2017 : }
2018 0 : });
2019 0 : }
2020 :
2021 0 : pub fn subscribe_for_state_updates(&self) -> watch::Receiver<TenantState> {
2022 0 : self.state.subscribe()
2023 0 : }
2024 :
2025 : /// The activate_now semaphore is initialized with zero units. As soon as
2026 : /// we add a unit, waiters will be able to acquire a unit and proceed.
2027 0 : pub(crate) fn activate_now(&self) {
2028 0 : self.activate_now_sem.add_permits(1);
2029 0 : }
2030 :
2031 0 : pub(crate) async fn wait_to_become_active(
2032 0 : &self,
2033 0 : timeout: Duration,
2034 0 : ) -> Result<(), GetActiveTenantError> {
2035 0 : let mut receiver = self.state.subscribe();
2036 0 : loop {
2037 0 : let current_state = receiver.borrow_and_update().clone();
2038 0 : match current_state {
2039 : TenantState::Loading | TenantState::Attaching | TenantState::Activating(_) => {
2040 : // in these states, there's a chance that we can reach ::Active
2041 0 : self.activate_now();
2042 0 : match timeout_cancellable(timeout, &self.cancel, receiver.changed()).await {
2043 0 : Ok(r) => {
2044 0 : r.map_err(
2045 0 : |_e: tokio::sync::watch::error::RecvError|
2046 : // Tenant existed but was dropped: report it as non-existent
2047 0 : GetActiveTenantError::NotFound(GetTenantError::NotFound(self.tenant_shard_id.tenant_id))
2048 0 : )?
2049 : }
2050 : Err(TimeoutCancellableError::Cancelled) => {
2051 0 : return Err(GetActiveTenantError::Cancelled);
2052 : }
2053 : Err(TimeoutCancellableError::Timeout) => {
2054 0 : return Err(GetActiveTenantError::WaitForActiveTimeout {
2055 0 : latest_state: Some(self.current_state()),
2056 0 : wait_time: timeout,
2057 0 : });
2058 : }
2059 : }
2060 : }
2061 : TenantState::Active { .. } => {
2062 0 : return Ok(());
2063 : }
2064 0 : TenantState::Broken { reason, .. } => {
2065 0 : // This is fatal, and reported distinctly from the general case of "will never be active" because
2066 0 : // it's logically a 500 to external API users (broken is always a bug).
2067 0 : return Err(GetActiveTenantError::Broken(reason));
2068 : }
2069 : TenantState::Stopping { .. } => {
2070 : // There's no chance the tenant can transition back into ::Active
2071 0 : return Err(GetActiveTenantError::WillNotBecomeActive(current_state));
2072 : }
2073 : }
2074 : }
2075 0 : }
2076 :
2077 0 : pub(crate) fn get_attach_mode(&self) -> AttachmentMode {
2078 0 : self.tenant_conf.load().location.attach_mode
2079 0 : }
2080 :
2081 : /// For API access: generate a LocationConfig equivalent to the one that would be used to
2082 : /// create a Tenant in the same state. Do not use this in hot paths: it's for relatively
2083 : /// rare external API calls, like a reconciliation at startup.
2084 0 : pub(crate) fn get_location_conf(&self) -> models::LocationConfig {
2085 0 : let conf = self.tenant_conf.load();
2086 :
2087 0 : let location_config_mode = match conf.location.attach_mode {
2088 0 : AttachmentMode::Single => models::LocationConfigMode::AttachedSingle,
2089 0 : AttachmentMode::Multi => models::LocationConfigMode::AttachedMulti,
2090 0 : AttachmentMode::Stale => models::LocationConfigMode::AttachedStale,
2091 : };
2092 :
2093 : // We have a pageserver TenantConf, we need the API-facing TenantConfig.
2094 0 : let tenant_config: models::TenantConfig = conf.tenant_conf.clone().into();
2095 0 :
2096 0 : models::LocationConfig {
2097 0 : mode: location_config_mode,
2098 0 : generation: self.generation.into(),
2099 0 : secondary_conf: None,
2100 0 : shard_number: self.shard_identity.number.0,
2101 0 : shard_count: self.shard_identity.count.literal(),
2102 0 : shard_stripe_size: self.shard_identity.stripe_size.0,
2103 0 : tenant_conf: tenant_config,
2104 0 : }
2105 0 : }
2106 :
2107 0 : pub(crate) fn get_tenant_shard_id(&self) -> &TenantShardId {
2108 0 : &self.tenant_shard_id
2109 0 : }
2110 :
2111 0 : pub(crate) fn get_shard_stripe_size(&self) -> ShardStripeSize {
2112 0 : self.shard_identity.stripe_size
2113 0 : }
2114 :
2115 0 : pub(crate) fn get_generation(&self) -> Generation {
2116 0 : self.generation
2117 0 : }
2118 :
2119 : /// This function partially shuts down the tenant (it shuts down the Timelines) and is fallible,
2120 : /// and can leave the tenant in a bad state if it fails. The caller is responsible for
2121 : /// resetting this tenant to a valid state if we fail.
2122 0 : pub(crate) async fn split_prepare(
2123 0 : &self,
2124 0 : child_shards: &Vec<TenantShardId>,
2125 0 : ) -> anyhow::Result<()> {
2126 0 : let timelines = self.timelines.lock().unwrap().clone();
2127 0 : for timeline in timelines.values() {
2128 0 : let Some(tl_client) = &timeline.remote_client else {
2129 0 : anyhow::bail!("Remote storage is mandatory");
2130 : };
2131 :
2132 0 : let Some(remote_storage) = &self.remote_storage else {
2133 0 : anyhow::bail!("Remote storage is mandatory");
2134 : };
2135 :
2136 : // We do not block timeline creation/deletion during splits inside the pageserver: it is up to higher levels
2137 : // to ensure that they do not start a split if currently in the process of doing these.
2138 :
2139 : // Upload an index from the parent: this is partly to provide freshness for the
2140 : // child tenants that will copy it, and partly for general ease-of-debugging: there will
2141 : // always be a parent shard index in the same generation as we wrote the child shard index.
2142 0 : tl_client.schedule_index_upload_for_file_changes()?;
2143 0 : tl_client.wait_completion().await?;
2144 :
2145 : // Shut down the timeline's remote client: this means that the indices we write
2146 : // for child shards will not be invalidated by the parent shard deleting layers.
2147 0 : tl_client.shutdown().await;
2148 :
2149 : // Download methods can still be used after shutdown, as they don't flow through the remote client's
2150 : // queue. In principal the RemoteTimelineClient could provide this without downloading it, but this
2151 : // operation is rare, so it's simpler to just download it (and robustly guarantees that the index
2152 : // we use here really is the remotely persistent one).
2153 0 : let result = tl_client
2154 0 : .download_index_file(&self.cancel)
2155 0 : .instrument(info_span!("download_index_file", tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug(), timeline_id=%timeline.timeline_id))
2156 0 : .await?;
2157 0 : let index_part = match result {
2158 : MaybeDeletedIndexPart::Deleted(_) => {
2159 0 : anyhow::bail!("Timeline deletion happened concurrently with split")
2160 : }
2161 0 : MaybeDeletedIndexPart::IndexPart(p) => p,
2162 : };
2163 :
2164 0 : for child_shard in child_shards {
2165 0 : upload_index_part(
2166 0 : remote_storage,
2167 0 : child_shard,
2168 0 : &timeline.timeline_id,
2169 0 : self.generation,
2170 0 : &index_part,
2171 0 : &self.cancel,
2172 0 : )
2173 0 : .await?;
2174 : }
2175 : }
2176 :
2177 0 : Ok(())
2178 0 : }
2179 : }
2180 :
2181 : /// Given a Vec of timelines and their ancestors (timeline_id, ancestor_id),
2182 : /// perform a topological sort, so that the parent of each timeline comes
2183 : /// before the children.
2184 : /// E extracts the ancestor from T
2185 : /// This allows for T to be different. It can be TimelineMetadata, can be Timeline itself, etc.
2186 104 : fn tree_sort_timelines<T, E>(
2187 104 : timelines: HashMap<TimelineId, T>,
2188 104 : extractor: E,
2189 104 : ) -> anyhow::Result<Vec<(TimelineId, T)>>
2190 104 : where
2191 104 : E: Fn(&T) -> Option<TimelineId>,
2192 104 : {
2193 104 : let mut result = Vec::with_capacity(timelines.len());
2194 104 :
2195 104 : let mut now = Vec::with_capacity(timelines.len());
2196 104 : // (ancestor, children)
2197 104 : let mut later: HashMap<TimelineId, Vec<(TimelineId, T)>> =
2198 104 : HashMap::with_capacity(timelines.len());
2199 :
2200 110 : for (timeline_id, value) in timelines {
2201 6 : if let Some(ancestor_id) = extractor(&value) {
2202 2 : let children = later.entry(ancestor_id).or_default();
2203 2 : children.push((timeline_id, value));
2204 4 : } else {
2205 4 : now.push((timeline_id, value));
2206 4 : }
2207 : }
2208 :
2209 110 : while let Some((timeline_id, metadata)) = now.pop() {
2210 6 : result.push((timeline_id, metadata));
2211 : // All children of this can be loaded now
2212 6 : if let Some(mut children) = later.remove(&timeline_id) {
2213 2 : now.append(&mut children);
2214 4 : }
2215 : }
2216 :
2217 : // All timelines should be visited now. Unless there were timelines with missing ancestors.
2218 104 : if !later.is_empty() {
2219 0 : for (missing_id, orphan_ids) in later {
2220 0 : for (orphan_id, _) in orphan_ids {
2221 0 : error!("could not load timeline {orphan_id} because its ancestor timeline {missing_id} could not be loaded");
2222 : }
2223 : }
2224 0 : bail!("could not load tenant because some timelines are missing ancestors");
2225 104 : }
2226 104 :
2227 104 : Ok(result)
2228 104 : }
2229 :
2230 : impl Tenant {
2231 0 : pub fn tenant_specific_overrides(&self) -> TenantConfOpt {
2232 0 : self.tenant_conf.load().tenant_conf.clone()
2233 0 : }
2234 :
2235 0 : pub fn effective_config(&self) -> TenantConf {
2236 0 : self.tenant_specific_overrides()
2237 0 : .merge(self.conf.default_tenant_conf.clone())
2238 0 : }
2239 :
2240 0 : pub fn get_checkpoint_distance(&self) -> u64 {
2241 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2242 0 : tenant_conf
2243 0 : .checkpoint_distance
2244 0 : .unwrap_or(self.conf.default_tenant_conf.checkpoint_distance)
2245 0 : }
2246 :
2247 0 : pub fn get_checkpoint_timeout(&self) -> Duration {
2248 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2249 0 : tenant_conf
2250 0 : .checkpoint_timeout
2251 0 : .unwrap_or(self.conf.default_tenant_conf.checkpoint_timeout)
2252 0 : }
2253 :
2254 0 : pub fn get_compaction_target_size(&self) -> u64 {
2255 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2256 0 : tenant_conf
2257 0 : .compaction_target_size
2258 0 : .unwrap_or(self.conf.default_tenant_conf.compaction_target_size)
2259 0 : }
2260 :
2261 0 : pub fn get_compaction_period(&self) -> Duration {
2262 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2263 0 : tenant_conf
2264 0 : .compaction_period
2265 0 : .unwrap_or(self.conf.default_tenant_conf.compaction_period)
2266 0 : }
2267 :
2268 0 : pub fn get_compaction_threshold(&self) -> usize {
2269 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2270 0 : tenant_conf
2271 0 : .compaction_threshold
2272 0 : .unwrap_or(self.conf.default_tenant_conf.compaction_threshold)
2273 0 : }
2274 :
2275 0 : pub fn get_gc_horizon(&self) -> u64 {
2276 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2277 0 : tenant_conf
2278 0 : .gc_horizon
2279 0 : .unwrap_or(self.conf.default_tenant_conf.gc_horizon)
2280 0 : }
2281 :
2282 0 : pub fn get_gc_period(&self) -> Duration {
2283 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2284 0 : tenant_conf
2285 0 : .gc_period
2286 0 : .unwrap_or(self.conf.default_tenant_conf.gc_period)
2287 0 : }
2288 :
2289 0 : pub fn get_image_creation_threshold(&self) -> usize {
2290 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2291 0 : tenant_conf
2292 0 : .image_creation_threshold
2293 0 : .unwrap_or(self.conf.default_tenant_conf.image_creation_threshold)
2294 0 : }
2295 :
2296 0 : pub fn get_pitr_interval(&self) -> Duration {
2297 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2298 0 : tenant_conf
2299 0 : .pitr_interval
2300 0 : .unwrap_or(self.conf.default_tenant_conf.pitr_interval)
2301 0 : }
2302 :
2303 0 : pub fn get_trace_read_requests(&self) -> bool {
2304 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2305 0 : tenant_conf
2306 0 : .trace_read_requests
2307 0 : .unwrap_or(self.conf.default_tenant_conf.trace_read_requests)
2308 0 : }
2309 :
2310 0 : pub fn get_min_resident_size_override(&self) -> Option<u64> {
2311 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2312 0 : tenant_conf
2313 0 : .min_resident_size_override
2314 0 : .or(self.conf.default_tenant_conf.min_resident_size_override)
2315 0 : }
2316 :
2317 0 : pub fn get_heatmap_period(&self) -> Option<Duration> {
2318 0 : let tenant_conf = self.tenant_conf.load().tenant_conf.clone();
2319 0 : let heatmap_period = tenant_conf
2320 0 : .heatmap_period
2321 0 : .unwrap_or(self.conf.default_tenant_conf.heatmap_period);
2322 0 : if heatmap_period.is_zero() {
2323 0 : None
2324 : } else {
2325 0 : Some(heatmap_period)
2326 : }
2327 0 : }
2328 :
2329 0 : pub fn set_new_tenant_config(&self, new_tenant_conf: TenantConfOpt) {
2330 0 : // Use read-copy-update in order to avoid overwriting the location config
2331 0 : // state if this races with [`Tenant::set_new_location_config`]. Note that
2332 0 : // this race is not possible if both request types come from the storage
2333 0 : // controller (as they should!) because an exclusive op lock is required
2334 0 : // on the storage controller side.
2335 0 : self.tenant_conf.rcu(|inner| {
2336 0 : Arc::new(AttachedTenantConf {
2337 0 : tenant_conf: new_tenant_conf.clone(),
2338 0 : location: inner.location,
2339 0 : })
2340 0 : });
2341 0 :
2342 0 : self.tenant_conf_updated(&new_tenant_conf);
2343 0 : // Don't hold self.timelines.lock() during the notifies.
2344 0 : // There's no risk of deadlock right now, but there could be if we consolidate
2345 0 : // mutexes in struct Timeline in the future.
2346 0 : let timelines = self.list_timelines();
2347 0 : for timeline in timelines {
2348 0 : timeline.tenant_conf_updated(&new_tenant_conf);
2349 0 : }
2350 0 : }
2351 :
2352 0 : pub(crate) fn set_new_location_config(&self, new_conf: AttachedTenantConf) {
2353 0 : let new_tenant_conf = new_conf.tenant_conf.clone();
2354 0 :
2355 0 : self.tenant_conf.store(Arc::new(new_conf));
2356 0 :
2357 0 : self.tenant_conf_updated(&new_tenant_conf);
2358 0 : // Don't hold self.timelines.lock() during the notifies.
2359 0 : // There's no risk of deadlock right now, but there could be if we consolidate
2360 0 : // mutexes in struct Timeline in the future.
2361 0 : let timelines = self.list_timelines();
2362 0 : for timeline in timelines {
2363 0 : timeline.tenant_conf_updated(&new_tenant_conf);
2364 0 : }
2365 0 : }
2366 :
2367 104 : fn get_timeline_get_throttle_config(
2368 104 : psconf: &'static PageServerConf,
2369 104 : overrides: &TenantConfOpt,
2370 104 : ) -> throttle::Config {
2371 104 : overrides
2372 104 : .timeline_get_throttle
2373 104 : .clone()
2374 104 : .unwrap_or(psconf.default_tenant_conf.timeline_get_throttle.clone())
2375 104 : }
2376 :
2377 0 : pub(crate) fn tenant_conf_updated(&self, new_conf: &TenantConfOpt) {
2378 0 : let conf = Self::get_timeline_get_throttle_config(self.conf, new_conf);
2379 0 : self.timeline_get_throttle.reconfigure(conf)
2380 0 : }
2381 :
2382 : /// Helper function to create a new Timeline struct.
2383 : ///
2384 : /// The returned Timeline is in Loading state. The caller is responsible for
2385 : /// initializing any on-disk state, and for inserting the Timeline to the 'timelines'
2386 : /// map.
2387 : ///
2388 : /// `validate_ancestor == false` is used when a timeline is created for deletion
2389 : /// and we might not have the ancestor present anymore which is fine for to be
2390 : /// deleted timelines.
2391 314 : fn create_timeline_struct(
2392 314 : &self,
2393 314 : new_timeline_id: TimelineId,
2394 314 : new_metadata: &TimelineMetadata,
2395 314 : ancestor: Option<Arc<Timeline>>,
2396 314 : resources: TimelineResources,
2397 314 : cause: CreateTimelineCause,
2398 314 : ) -> anyhow::Result<Arc<Timeline>> {
2399 314 : let state = match cause {
2400 : CreateTimelineCause::Load => {
2401 314 : let ancestor_id = new_metadata.ancestor_timeline();
2402 314 : anyhow::ensure!(
2403 314 : ancestor_id == ancestor.as_ref().map(|t| t.timeline_id),
2404 0 : "Timeline's {new_timeline_id} ancestor {ancestor_id:?} was not found"
2405 : );
2406 314 : TimelineState::Loading
2407 : }
2408 0 : CreateTimelineCause::Delete => TimelineState::Stopping,
2409 : };
2410 :
2411 314 : let pg_version = new_metadata.pg_version();
2412 314 :
2413 314 : let timeline = Timeline::new(
2414 314 : self.conf,
2415 314 : Arc::clone(&self.tenant_conf),
2416 314 : new_metadata,
2417 314 : ancestor,
2418 314 : new_timeline_id,
2419 314 : self.tenant_shard_id,
2420 314 : self.generation,
2421 314 : self.shard_identity,
2422 314 : self.walredo_mgr.clone(),
2423 314 : resources,
2424 314 : pg_version,
2425 314 : state,
2426 314 : self.cancel.child_token(),
2427 314 : );
2428 314 :
2429 314 : Ok(timeline)
2430 314 : }
2431 :
2432 : // Allow too_many_arguments because a constructor's argument list naturally grows with the
2433 : // number of attributes in the struct: breaking these out into a builder wouldn't be helpful.
2434 : #[allow(clippy::too_many_arguments)]
2435 104 : fn new(
2436 104 : state: TenantState,
2437 104 : conf: &'static PageServerConf,
2438 104 : attached_conf: AttachedTenantConf,
2439 104 : shard_identity: ShardIdentity,
2440 104 : walredo_mgr: Option<Arc<WalRedoManager>>,
2441 104 : tenant_shard_id: TenantShardId,
2442 104 : remote_storage: Option<GenericRemoteStorage>,
2443 104 : deletion_queue_client: DeletionQueueClient,
2444 104 : ) -> Tenant {
2445 104 : let (state, mut rx) = watch::channel(state);
2446 104 :
2447 104 : tokio::spawn(async move {
2448 99 : // reflect tenant state in metrics:
2449 99 : // - global per tenant state: TENANT_STATE_METRIC
2450 99 : // - "set" of broken tenants: BROKEN_TENANTS_SET
2451 99 : //
2452 99 : // set of broken tenants should not have zero counts so that it remains accessible for
2453 99 : // alerting.
2454 99 :
2455 99 : let tid = tenant_shard_id.to_string();
2456 99 : let shard_id = tenant_shard_id.shard_slug().to_string();
2457 99 : let set_key = &[tid.as_str(), shard_id.as_str()][..];
2458 99 :
2459 105 : fn inspect_state(state: &TenantState) -> ([&'static str; 1], bool) {
2460 105 : ([state.into()], matches!(state, TenantState::Broken { .. }))
2461 105 : }
2462 99 :
2463 99 : let mut tuple = inspect_state(&rx.borrow_and_update());
2464 99 :
2465 99 : let is_broken = tuple.1;
2466 99 : let mut counted_broken = if is_broken {
2467 : // add the id to the set right away, there should not be any updates on the channel
2468 : // after before tenant is removed, if ever
2469 0 : BROKEN_TENANTS_SET.with_label_values(set_key).set(1);
2470 0 : true
2471 : } else {
2472 99 : false
2473 : };
2474 :
2475 105 : loop {
2476 105 : let labels = &tuple.0;
2477 105 : let current = TENANT_STATE_METRIC.with_label_values(labels);
2478 105 : current.inc();
2479 105 :
2480 105 : if rx.changed().await.is_err() {
2481 : // tenant has been dropped
2482 4 : current.dec();
2483 4 : drop(BROKEN_TENANTS_SET.remove_label_values(set_key));
2484 4 : break;
2485 6 : }
2486 6 :
2487 6 : current.dec();
2488 6 : tuple = inspect_state(&rx.borrow_and_update());
2489 6 :
2490 6 : let is_broken = tuple.1;
2491 6 : if is_broken && !counted_broken {
2492 0 : counted_broken = true;
2493 0 : // insert the tenant_id (back) into the set while avoiding needless counter
2494 0 : // access
2495 0 : BROKEN_TENANTS_SET.with_label_values(set_key).set(1);
2496 6 : }
2497 : }
2498 104 : });
2499 104 :
2500 104 : Tenant {
2501 104 : tenant_shard_id,
2502 104 : shard_identity,
2503 104 : generation: attached_conf.location.generation,
2504 104 : conf,
2505 104 : // using now here is good enough approximation to catch tenants with really long
2506 104 : // activation times.
2507 104 : constructed_at: Instant::now(),
2508 104 : timelines: Mutex::new(HashMap::new()),
2509 104 : timelines_creating: Mutex::new(HashSet::new()),
2510 104 : gc_cs: tokio::sync::Mutex::new(()),
2511 104 : walredo_mgr,
2512 104 : remote_storage,
2513 104 : deletion_queue_client,
2514 104 : state,
2515 104 : cached_logical_sizes: tokio::sync::Mutex::new(HashMap::new()),
2516 104 : cached_synthetic_tenant_size: Arc::new(AtomicU64::new(0)),
2517 104 : eviction_task_tenant_state: tokio::sync::Mutex::new(EvictionTaskTenantState::default()),
2518 104 : activate_now_sem: tokio::sync::Semaphore::new(0),
2519 104 : delete_progress: Arc::new(tokio::sync::Mutex::new(DeleteTenantFlow::default())),
2520 104 : cancel: CancellationToken::default(),
2521 104 : gate: Gate::default(),
2522 104 : timeline_get_throttle: Arc::new(throttle::Throttle::new(
2523 104 : Tenant::get_timeline_get_throttle_config(conf, &attached_conf.tenant_conf),
2524 104 : &crate::metrics::tenant_throttling::TIMELINE_GET,
2525 104 : )),
2526 104 : tenant_conf: Arc::new(ArcSwap::from_pointee(attached_conf)),
2527 104 : }
2528 104 : }
2529 :
2530 : /// Locate and load config
2531 0 : pub(super) fn load_tenant_config(
2532 0 : conf: &'static PageServerConf,
2533 0 : tenant_shard_id: &TenantShardId,
2534 0 : ) -> anyhow::Result<LocationConf> {
2535 0 : let legacy_config_path = conf.tenant_config_path(tenant_shard_id);
2536 0 : let config_path = conf.tenant_location_config_path(tenant_shard_id);
2537 0 :
2538 0 : if config_path.exists() {
2539 : // New-style config takes precedence
2540 0 : let deserialized = Self::read_config(&config_path)?;
2541 0 : Ok(toml_edit::de::from_document::<LocationConf>(deserialized)?)
2542 0 : } else if legacy_config_path.exists() {
2543 : // Upgrade path: found an old-style configuration only
2544 0 : let deserialized = Self::read_config(&legacy_config_path)?;
2545 :
2546 0 : let mut tenant_conf = TenantConfOpt::default();
2547 0 : for (key, item) in deserialized.iter() {
2548 0 : match key {
2549 0 : "tenant_config" => {
2550 0 : tenant_conf = TenantConfOpt::try_from(item.to_owned()).context(format!("Failed to parse config from file '{legacy_config_path}' as pageserver config"))?;
2551 : }
2552 0 : _ => bail!(
2553 0 : "config file {legacy_config_path} has unrecognized pageserver option '{key}'"
2554 0 : ),
2555 : }
2556 : }
2557 :
2558 : // Legacy configs are implicitly in attached state, and do not support sharding
2559 0 : Ok(LocationConf::attached_single(
2560 0 : tenant_conf,
2561 0 : Generation::none(),
2562 0 : &models::ShardParameters::default(),
2563 0 : ))
2564 : } else {
2565 : // FIXME If the config file is not found, assume that we're attaching
2566 : // a detached tenant and config is passed via attach command.
2567 : // https://github.com/neondatabase/neon/issues/1555
2568 : // OR: we're loading after incomplete deletion that managed to remove config.
2569 0 : info!(
2570 0 : "tenant config not found in {} or {}",
2571 0 : config_path, legacy_config_path
2572 0 : );
2573 0 : Ok(LocationConf::default())
2574 : }
2575 0 : }
2576 :
2577 0 : fn read_config(path: &Utf8Path) -> anyhow::Result<toml_edit::Document> {
2578 0 : info!("loading tenant configuration from {path}");
2579 :
2580 : // load and parse file
2581 0 : let config = fs::read_to_string(path)
2582 0 : .with_context(|| format!("Failed to load config from path '{path}'"))?;
2583 :
2584 0 : config
2585 0 : .parse::<toml_edit::Document>()
2586 0 : .with_context(|| format!("Failed to parse config from file '{path}' as toml file"))
2587 0 : }
2588 :
2589 0 : #[tracing::instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug()))]
2590 : pub(super) async fn persist_tenant_config(
2591 : conf: &'static PageServerConf,
2592 : tenant_shard_id: &TenantShardId,
2593 : location_conf: &LocationConf,
2594 : ) -> anyhow::Result<()> {
2595 : let legacy_config_path = conf.tenant_config_path(tenant_shard_id);
2596 : let config_path = conf.tenant_location_config_path(tenant_shard_id);
2597 :
2598 : Self::persist_tenant_config_at(
2599 : tenant_shard_id,
2600 : &config_path,
2601 : &legacy_config_path,
2602 : location_conf,
2603 : )
2604 : .await
2605 : }
2606 :
2607 0 : #[tracing::instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug()))]
2608 : pub(super) async fn persist_tenant_config_at(
2609 : tenant_shard_id: &TenantShardId,
2610 : config_path: &Utf8Path,
2611 : legacy_config_path: &Utf8Path,
2612 : location_conf: &LocationConf,
2613 : ) -> anyhow::Result<()> {
2614 : if let LocationMode::Attached(attach_conf) = &location_conf.mode {
2615 : // The modern-style LocationConf config file requires a generation to be set. In case someone
2616 : // is running a pageserver without the infrastructure to set generations, write out the legacy-style
2617 : // config file that only contains TenantConf.
2618 : //
2619 : // This will eventually be removed in https://github.com/neondatabase/neon/issues/5388
2620 :
2621 : if attach_conf.generation.is_none() {
2622 0 : tracing::info!(
2623 0 : "Running without generations, writing legacy-style tenant config file"
2624 0 : );
2625 : Self::persist_tenant_config_legacy(
2626 : tenant_shard_id,
2627 : legacy_config_path,
2628 : &location_conf.tenant_conf,
2629 : )
2630 : .await?;
2631 :
2632 : return Ok(());
2633 : }
2634 : }
2635 :
2636 0 : debug!("persisting tenantconf to {config_path}");
2637 :
2638 : let mut conf_content = r#"# This file contains a specific per-tenant's config.
2639 : # It is read in case of pageserver restart.
2640 : "#
2641 : .to_string();
2642 :
2643 0 : fail::fail_point!("tenant-config-before-write", |_| {
2644 0 : anyhow::bail!("tenant-config-before-write");
2645 0 : });
2646 :
2647 : // Convert the config to a toml file.
2648 : conf_content += &toml_edit::ser::to_string_pretty(&location_conf)?;
2649 :
2650 : let temp_path = path_with_suffix_extension(config_path, TEMP_FILE_SUFFIX);
2651 :
2652 : let tenant_shard_id = *tenant_shard_id;
2653 : let config_path = config_path.to_owned();
2654 : let conf_content = conf_content.into_bytes();
2655 : VirtualFile::crashsafe_overwrite(config_path.clone(), temp_path, conf_content)
2656 : .await
2657 0 : .with_context(|| format!("write tenant {tenant_shard_id} config to {config_path}"))?;
2658 :
2659 : Ok(())
2660 : }
2661 :
2662 0 : #[tracing::instrument(skip_all, fields(tenant_id=%tenant_shard_id.tenant_id, shard_id=%tenant_shard_id.shard_slug()))]
2663 : async fn persist_tenant_config_legacy(
2664 : tenant_shard_id: &TenantShardId,
2665 : target_config_path: &Utf8Path,
2666 : tenant_conf: &TenantConfOpt,
2667 : ) -> anyhow::Result<()> {
2668 0 : debug!("persisting tenantconf to {target_config_path}");
2669 :
2670 : let mut conf_content = r#"# This file contains a specific per-tenant's config.
2671 : # It is read in case of pageserver restart.
2672 :
2673 : [tenant_config]
2674 : "#
2675 : .to_string();
2676 :
2677 : // Convert the config to a toml file.
2678 : conf_content += &toml_edit::ser::to_string(&tenant_conf)?;
2679 :
2680 : let temp_path = path_with_suffix_extension(target_config_path, TEMP_FILE_SUFFIX);
2681 :
2682 : let tenant_shard_id = *tenant_shard_id;
2683 : let target_config_path = target_config_path.to_owned();
2684 : let conf_content = conf_content.into_bytes();
2685 : VirtualFile::crashsafe_overwrite(target_config_path.clone(), temp_path, conf_content)
2686 : .await
2687 0 : .with_context(|| {
2688 0 : format!("write tenant {tenant_shard_id} config to {target_config_path}")
2689 0 : })?;
2690 : Ok(())
2691 : }
2692 :
2693 : //
2694 : // How garbage collection works:
2695 : //
2696 : // +--bar------------->
2697 : // /
2698 : // +----+-----foo---------------->
2699 : // /
2700 : // ----main--+-------------------------->
2701 : // \
2702 : // +-----baz-------->
2703 : //
2704 : //
2705 : // 1. Grab 'gc_cs' mutex to prevent new timelines from being created while Timeline's
2706 : // `gc_infos` are being refreshed
2707 : // 2. Scan collected timelines, and on each timeline, make note of the
2708 : // all the points where other timelines have been branched off.
2709 : // We will refrain from removing page versions at those LSNs.
2710 : // 3. For each timeline, scan all layer files on the timeline.
2711 : // Remove all files for which a newer file exists and which
2712 : // don't cover any branch point LSNs.
2713 : //
2714 : // TODO:
2715 : // - if a relation has a non-incremental persistent layer on a child branch, then we
2716 : // don't need to keep that in the parent anymore. But currently
2717 : // we do.
2718 8 : async fn gc_iteration_internal(
2719 8 : &self,
2720 8 : target_timeline_id: Option<TimelineId>,
2721 8 : horizon: u64,
2722 8 : pitr: Duration,
2723 8 : cancel: &CancellationToken,
2724 8 : ctx: &RequestContext,
2725 8 : ) -> anyhow::Result<GcResult> {
2726 8 : let mut totals: GcResult = Default::default();
2727 8 : let now = Instant::now();
2728 :
2729 8 : let gc_timelines = match self
2730 8 : .refresh_gc_info_internal(target_timeline_id, horizon, pitr, cancel, ctx)
2731 0 : .await
2732 : {
2733 8 : Ok(result) => result,
2734 0 : Err(e) => {
2735 0 : if let Some(PageReconstructError::Cancelled) =
2736 0 : e.downcast_ref::<PageReconstructError>()
2737 : {
2738 : // Handle cancellation
2739 0 : totals.elapsed = now.elapsed();
2740 0 : return Ok(totals);
2741 : } else {
2742 : // Propagate other errors
2743 0 : return Err(e);
2744 : }
2745 : }
2746 : };
2747 :
2748 8 : failpoint_support::sleep_millis_async!("gc_iteration_internal_after_getting_gc_timelines");
2749 :
2750 : // If there is nothing to GC, we don't want any messages in the INFO log.
2751 8 : if !gc_timelines.is_empty() {
2752 8 : info!("{} timelines need GC", gc_timelines.len());
2753 : } else {
2754 0 : debug!("{} timelines need GC", gc_timelines.len());
2755 : }
2756 :
2757 : // Perform GC for each timeline.
2758 : //
2759 : // Note that we don't hold the `Tenant::gc_cs` lock here because we don't want to delay the
2760 : // branch creation task, which requires the GC lock. A GC iteration can run concurrently
2761 : // with branch creation.
2762 : //
2763 : // See comments in [`Tenant::branch_timeline`] for more information about why branch
2764 : // creation task can run concurrently with timeline's GC iteration.
2765 16 : for timeline in gc_timelines {
2766 8 : if task_mgr::is_shutdown_requested() || cancel.is_cancelled() {
2767 : // We were requested to shut down. Stop and return with the progress we
2768 : // made.
2769 0 : break;
2770 8 : }
2771 8 : let result = timeline.gc().await?;
2772 8 : totals += result;
2773 : }
2774 :
2775 8 : totals.elapsed = now.elapsed();
2776 8 : Ok(totals)
2777 8 : }
2778 :
2779 : /// Refreshes the Timeline::gc_info for all timelines, returning the
2780 : /// vector of timelines which have [`Timeline::get_last_record_lsn`] past
2781 : /// [`Tenant::get_gc_horizon`].
2782 : ///
2783 : /// This is usually executed as part of periodic gc, but can now be triggered more often.
2784 0 : pub async fn refresh_gc_info(
2785 0 : &self,
2786 0 : cancel: &CancellationToken,
2787 0 : ctx: &RequestContext,
2788 0 : ) -> anyhow::Result<Vec<Arc<Timeline>>> {
2789 0 : // since this method can now be called at different rates than the configured gc loop, it
2790 0 : // might be that these configuration values get applied faster than what it was previously,
2791 0 : // since these were only read from the gc task.
2792 0 : let horizon = self.get_gc_horizon();
2793 0 : let pitr = self.get_pitr_interval();
2794 0 :
2795 0 : // refresh all timelines
2796 0 : let target_timeline_id = None;
2797 0 :
2798 0 : self.refresh_gc_info_internal(target_timeline_id, horizon, pitr, cancel, ctx)
2799 0 : .await
2800 0 : }
2801 :
2802 8 : async fn refresh_gc_info_internal(
2803 8 : &self,
2804 8 : target_timeline_id: Option<TimelineId>,
2805 8 : horizon: u64,
2806 8 : pitr: Duration,
2807 8 : cancel: &CancellationToken,
2808 8 : ctx: &RequestContext,
2809 8 : ) -> anyhow::Result<Vec<Arc<Timeline>>> {
2810 : // grab mutex to prevent new timelines from being created here.
2811 8 : let gc_cs = self.gc_cs.lock().await;
2812 :
2813 : // Scan all timelines. For each timeline, remember the timeline ID and
2814 : // the branch point where it was created.
2815 8 : let (all_branchpoints, timeline_ids): (BTreeSet<(TimelineId, Lsn)>, _) = {
2816 8 : let timelines = self.timelines.lock().unwrap();
2817 8 : let mut all_branchpoints = BTreeSet::new();
2818 8 : let timeline_ids = {
2819 8 : if let Some(target_timeline_id) = target_timeline_id.as_ref() {
2820 8 : if timelines.get(target_timeline_id).is_none() {
2821 0 : bail!("gc target timeline does not exist")
2822 8 : }
2823 0 : };
2824 :
2825 8 : timelines
2826 8 : .iter()
2827 14 : .map(|(timeline_id, timeline_entry)| {
2828 6 : if let Some(ancestor_timeline_id) =
2829 14 : &timeline_entry.get_ancestor_timeline_id()
2830 : {
2831 : // If target_timeline is specified, we only need to know branchpoints of its children
2832 6 : if let Some(timeline_id) = target_timeline_id {
2833 6 : if ancestor_timeline_id == &timeline_id {
2834 6 : all_branchpoints.insert((
2835 6 : *ancestor_timeline_id,
2836 6 : timeline_entry.get_ancestor_lsn(),
2837 6 : ));
2838 6 : }
2839 : }
2840 : // Collect branchpoints for all timelines
2841 0 : else {
2842 0 : all_branchpoints.insert((
2843 0 : *ancestor_timeline_id,
2844 0 : timeline_entry.get_ancestor_lsn(),
2845 0 : ));
2846 0 : }
2847 8 : }
2848 :
2849 14 : *timeline_id
2850 14 : })
2851 8 : .collect::<Vec<_>>()
2852 8 : };
2853 8 : (all_branchpoints, timeline_ids)
2854 8 : };
2855 8 :
2856 8 : // Ok, we now know all the branch points.
2857 8 : // Update the GC information for each timeline.
2858 8 : let mut gc_timelines = Vec::with_capacity(timeline_ids.len());
2859 22 : for timeline_id in timeline_ids {
2860 : // Timeline is known to be local and loaded.
2861 14 : let timeline = self
2862 14 : .get_timeline(timeline_id, false)
2863 14 : .with_context(|| format!("Timeline {timeline_id} was not found"))?;
2864 :
2865 : // If target_timeline is specified, ignore all other timelines
2866 14 : if let Some(target_timeline_id) = target_timeline_id {
2867 14 : if timeline_id != target_timeline_id {
2868 6 : continue;
2869 8 : }
2870 0 : }
2871 :
2872 8 : if let Some(cutoff) = timeline.get_last_record_lsn().checked_sub(horizon) {
2873 8 : let branchpoints: Vec<Lsn> = all_branchpoints
2874 8 : .range((
2875 8 : Included((timeline_id, Lsn(0))),
2876 8 : Included((timeline_id, Lsn(u64::MAX))),
2877 8 : ))
2878 8 : .map(|&x| x.1)
2879 8 : .collect();
2880 8 : timeline
2881 8 : .update_gc_info(branchpoints, cutoff, pitr, cancel, ctx)
2882 0 : .await?;
2883 :
2884 8 : gc_timelines.push(timeline);
2885 0 : }
2886 : }
2887 8 : drop(gc_cs);
2888 8 : Ok(gc_timelines)
2889 8 : }
2890 :
2891 : /// A substitute for `branch_timeline` for use in unit tests.
2892 : /// The returned timeline will have state value `Active` to make various `anyhow::ensure!()`
2893 : /// calls pass, but, we do not actually call `.activate()` under the hood. So, none of the
2894 : /// timeline background tasks are launched, except the flush loop.
2895 : #[cfg(test)]
2896 216 : async fn branch_timeline_test(
2897 216 : &self,
2898 216 : src_timeline: &Arc<Timeline>,
2899 216 : dst_id: TimelineId,
2900 216 : start_lsn: Option<Lsn>,
2901 216 : ctx: &RequestContext,
2902 216 : ) -> Result<Arc<Timeline>, CreateTimelineError> {
2903 216 : let create_guard = self.create_timeline_create_guard(dst_id).unwrap();
2904 216 : let tl = self
2905 216 : .branch_timeline_impl(src_timeline, dst_id, start_lsn, create_guard, ctx)
2906 4 : .await?;
2907 212 : tl.set_state(TimelineState::Active);
2908 212 : Ok(tl)
2909 216 : }
2910 :
2911 : /// Branch an existing timeline.
2912 : ///
2913 : /// The caller is responsible for activating the returned timeline.
2914 0 : async fn branch_timeline(
2915 0 : &self,
2916 0 : src_timeline: &Arc<Timeline>,
2917 0 : dst_id: TimelineId,
2918 0 : start_lsn: Option<Lsn>,
2919 0 : timeline_create_guard: TimelineCreateGuard<'_>,
2920 0 : ctx: &RequestContext,
2921 0 : ) -> Result<Arc<Timeline>, CreateTimelineError> {
2922 0 : self.branch_timeline_impl(src_timeline, dst_id, start_lsn, timeline_create_guard, ctx)
2923 0 : .await
2924 0 : }
2925 :
2926 216 : async fn branch_timeline_impl(
2927 216 : &self,
2928 216 : src_timeline: &Arc<Timeline>,
2929 216 : dst_id: TimelineId,
2930 216 : start_lsn: Option<Lsn>,
2931 216 : timeline_create_guard: TimelineCreateGuard<'_>,
2932 216 : _ctx: &RequestContext,
2933 216 : ) -> Result<Arc<Timeline>, CreateTimelineError> {
2934 216 : let src_id = src_timeline.timeline_id;
2935 :
2936 : // We will validate our ancestor LSN in this function. Acquire the GC lock so that
2937 : // this check cannot race with GC, and the ancestor LSN is guaranteed to remain
2938 : // valid while we are creating the branch.
2939 216 : let _gc_cs = self.gc_cs.lock().await;
2940 :
2941 : // If no start LSN is specified, we branch the new timeline from the source timeline's last record LSN
2942 216 : let start_lsn = start_lsn.unwrap_or_else(|| {
2943 0 : let lsn = src_timeline.get_last_record_lsn();
2944 0 : info!("branching timeline {dst_id} from timeline {src_id} at last record LSN: {lsn}");
2945 0 : lsn
2946 216 : });
2947 216 :
2948 216 : // Ensure that `start_lsn` is valid, i.e. the LSN is within the PITR
2949 216 : // horizon on the source timeline
2950 216 : //
2951 216 : // We check it against both the planned GC cutoff stored in 'gc_info',
2952 216 : // and the 'latest_gc_cutoff' of the last GC that was performed. The
2953 216 : // planned GC cutoff in 'gc_info' is normally larger than
2954 216 : // 'latest_gc_cutoff_lsn', but beware of corner cases like if you just
2955 216 : // changed the GC settings for the tenant to make the PITR window
2956 216 : // larger, but some of the data was already removed by an earlier GC
2957 216 : // iteration.
2958 216 :
2959 216 : // check against last actual 'latest_gc_cutoff' first
2960 216 : let latest_gc_cutoff_lsn = src_timeline.get_latest_gc_cutoff_lsn();
2961 216 : src_timeline
2962 216 : .check_lsn_is_in_scope(start_lsn, &latest_gc_cutoff_lsn)
2963 216 : .context(format!(
2964 216 : "invalid branch start lsn: less than latest GC cutoff {}",
2965 216 : *latest_gc_cutoff_lsn,
2966 216 : ))
2967 216 : .map_err(CreateTimelineError::AncestorLsn)?;
2968 :
2969 : // and then the planned GC cutoff
2970 : {
2971 212 : let gc_info = src_timeline.gc_info.read().unwrap();
2972 212 : let cutoff = min(gc_info.pitr_cutoff, gc_info.horizon_cutoff);
2973 212 : if start_lsn < cutoff {
2974 0 : return Err(CreateTimelineError::AncestorLsn(anyhow::anyhow!(
2975 0 : "invalid branch start lsn: less than planned GC cutoff {cutoff}"
2976 0 : )));
2977 212 : }
2978 212 : }
2979 212 :
2980 212 : //
2981 212 : // The branch point is valid, and we are still holding the 'gc_cs' lock
2982 212 : // so that GC cannot advance the GC cutoff until we are finished.
2983 212 : // Proceed with the branch creation.
2984 212 : //
2985 212 :
2986 212 : // Determine prev-LSN for the new timeline. We can only determine it if
2987 212 : // the timeline was branched at the current end of the source timeline.
2988 212 : let RecordLsn {
2989 212 : last: src_last,
2990 212 : prev: src_prev,
2991 212 : } = src_timeline.get_last_record_rlsn();
2992 212 : let dst_prev = if src_last == start_lsn {
2993 202 : Some(src_prev)
2994 : } else {
2995 10 : None
2996 : };
2997 :
2998 : // Create the metadata file, noting the ancestor of the new timeline.
2999 : // There is initially no data in it, but all the read-calls know to look
3000 : // into the ancestor.
3001 212 : let metadata = TimelineMetadata::new(
3002 212 : start_lsn,
3003 212 : dst_prev,
3004 212 : Some(src_id),
3005 212 : start_lsn,
3006 212 : *src_timeline.latest_gc_cutoff_lsn.read(), // FIXME: should we hold onto this guard longer?
3007 212 : src_timeline.initdb_lsn,
3008 212 : src_timeline.pg_version,
3009 212 : );
3010 :
3011 212 : let uninitialized_timeline = self
3012 212 : .prepare_new_timeline(
3013 212 : dst_id,
3014 212 : &metadata,
3015 212 : timeline_create_guard,
3016 212 : start_lsn + 1,
3017 212 : Some(Arc::clone(src_timeline)),
3018 212 : )
3019 0 : .await?;
3020 :
3021 212 : let new_timeline = uninitialized_timeline.finish_creation()?;
3022 :
3023 : // Root timeline gets its layers during creation and uploads them along with the metadata.
3024 : // A branch timeline though, when created, can get no writes for some time, hence won't get any layers created.
3025 : // We still need to upload its metadata eagerly: if other nodes `attach` the tenant and miss this timeline, their GC
3026 : // could get incorrect information and remove more layers, than needed.
3027 : // See also https://github.com/neondatabase/neon/issues/3865
3028 212 : if let Some(remote_client) = new_timeline.remote_client.as_ref() {
3029 212 : remote_client
3030 212 : .schedule_index_upload_for_metadata_update(&metadata)
3031 212 : .context("branch initial metadata upload")?;
3032 0 : }
3033 :
3034 212 : Ok(new_timeline)
3035 216 : }
3036 :
3037 : /// For unit tests, make this visible so that other modules can directly create timelines
3038 : #[cfg(test)]
3039 4 : #[tracing::instrument(skip_all, fields(tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug(), %timeline_id))]
3040 : pub(crate) async fn bootstrap_timeline_test(
3041 : &self,
3042 : timeline_id: TimelineId,
3043 : pg_version: u32,
3044 : load_existing_initdb: Option<TimelineId>,
3045 : ctx: &RequestContext,
3046 : ) -> anyhow::Result<Arc<Timeline>> {
3047 : let create_guard = self.create_timeline_create_guard(timeline_id).unwrap();
3048 : self.bootstrap_timeline(
3049 : timeline_id,
3050 : pg_version,
3051 : load_existing_initdb,
3052 : create_guard,
3053 : ctx,
3054 : )
3055 : .await
3056 : }
3057 :
3058 0 : async fn upload_initdb(
3059 0 : &self,
3060 0 : timelines_path: &Utf8PathBuf,
3061 0 : pgdata_path: &Utf8PathBuf,
3062 0 : timeline_id: &TimelineId,
3063 0 : ) -> anyhow::Result<()> {
3064 0 : let Some(storage) = &self.remote_storage else {
3065 : // No remote storage? No upload.
3066 0 : return Ok(());
3067 : };
3068 :
3069 0 : let temp_path = timelines_path.join(format!(
3070 0 : "{INITDB_PATH}.upload-{timeline_id}.{TEMP_FILE_SUFFIX}"
3071 0 : ));
3072 :
3073 0 : scopeguard::defer! {
3074 0 : if let Err(e) = fs::remove_file(&temp_path) {
3075 0 : error!("Failed to remove temporary initdb archive '{temp_path}': {e}");
3076 0 : }
3077 : }
3078 :
3079 0 : let (pgdata_zstd, tar_zst_size) = create_zst_tarball(pgdata_path, &temp_path).await?;
3080 : const INITDB_TAR_ZST_WARN_LIMIT: u64 = 2 * 1024 * 1024;
3081 0 : if tar_zst_size > INITDB_TAR_ZST_WARN_LIMIT {
3082 0 : warn!(
3083 0 : "compressed {temp_path} size of {tar_zst_size} is above limit {INITDB_TAR_ZST_WARN_LIMIT}."
3084 0 : );
3085 0 : }
3086 :
3087 0 : pausable_failpoint!("before-initdb-upload");
3088 :
3089 0 : backoff::retry(
3090 0 : || async {
3091 0 : self::remote_timeline_client::upload_initdb_dir(
3092 0 : storage,
3093 0 : &self.tenant_shard_id.tenant_id,
3094 0 : timeline_id,
3095 0 : pgdata_zstd.try_clone().await?,
3096 0 : tar_zst_size,
3097 0 : &self.cancel,
3098 : )
3099 0 : .await
3100 0 : },
3101 0 : |_| false,
3102 0 : 3,
3103 0 : u32::MAX,
3104 0 : "persist_initdb_tar_zst",
3105 0 : &self.cancel,
3106 0 : )
3107 0 : .await
3108 0 : .ok_or_else(|| anyhow::Error::new(TimeoutOrCancel::Cancel))
3109 0 : .and_then(|x| x)
3110 0 : }
3111 :
3112 : /// - run initdb to init temporary instance and get bootstrap data
3113 : /// - after initialization completes, tar up the temp dir and upload it to S3.
3114 : ///
3115 : /// The caller is responsible for activating the returned timeline.
3116 2 : async fn bootstrap_timeline(
3117 2 : &self,
3118 2 : timeline_id: TimelineId,
3119 2 : pg_version: u32,
3120 2 : load_existing_initdb: Option<TimelineId>,
3121 2 : timeline_create_guard: TimelineCreateGuard<'_>,
3122 2 : ctx: &RequestContext,
3123 2 : ) -> anyhow::Result<Arc<Timeline>> {
3124 2 : // create a `tenant/{tenant_id}/timelines/basebackup-{timeline_id}.{TEMP_FILE_SUFFIX}/`
3125 2 : // temporary directory for basebackup files for the given timeline.
3126 2 :
3127 2 : let timelines_path = self.conf.timelines_path(&self.tenant_shard_id);
3128 2 : let pgdata_path = path_with_suffix_extension(
3129 2 : timelines_path.join(format!("basebackup-{timeline_id}")),
3130 2 : TEMP_FILE_SUFFIX,
3131 2 : );
3132 2 :
3133 2 : // Remove whatever was left from the previous runs: safe because TimelineCreateGuard guarantees
3134 2 : // we won't race with other creations or existent timelines with the same path.
3135 2 : if pgdata_path.exists() {
3136 0 : fs::remove_dir_all(&pgdata_path).with_context(|| {
3137 0 : format!("Failed to remove already existing initdb directory: {pgdata_path}")
3138 0 : })?;
3139 2 : }
3140 :
3141 : // this new directory is very temporary, set to remove it immediately after bootstrap, we don't need it
3142 2 : scopeguard::defer! {
3143 2 : if let Err(e) = fs::remove_dir_all(&pgdata_path) {
3144 : // this is unlikely, but we will remove the directory on pageserver restart or another bootstrap call
3145 0 : error!("Failed to remove temporary initdb directory '{pgdata_path}': {e}");
3146 2 : }
3147 : }
3148 2 : if let Some(existing_initdb_timeline_id) = load_existing_initdb {
3149 2 : let Some(storage) = &self.remote_storage else {
3150 0 : bail!("no storage configured but load_existing_initdb set to {existing_initdb_timeline_id}");
3151 : };
3152 2 : if existing_initdb_timeline_id != timeline_id {
3153 0 : let source_path = &remote_initdb_archive_path(
3154 0 : &self.tenant_shard_id.tenant_id,
3155 0 : &existing_initdb_timeline_id,
3156 0 : );
3157 0 : let dest_path =
3158 0 : &remote_initdb_archive_path(&self.tenant_shard_id.tenant_id, &timeline_id);
3159 0 :
3160 0 : // if this fails, it will get retried by retried control plane requests
3161 0 : storage
3162 0 : .copy_object(source_path, dest_path, &self.cancel)
3163 0 : .await
3164 0 : .context("copy initdb tar")?;
3165 2 : }
3166 2 : let (initdb_tar_zst_path, initdb_tar_zst) =
3167 2 : self::remote_timeline_client::download_initdb_tar_zst(
3168 2 : self.conf,
3169 2 : storage,
3170 2 : &self.tenant_shard_id,
3171 2 : &existing_initdb_timeline_id,
3172 2 : &self.cancel,
3173 2 : )
3174 740 : .await
3175 2 : .context("download initdb tar")?;
3176 :
3177 2 : scopeguard::defer! {
3178 2 : if let Err(e) = fs::remove_file(&initdb_tar_zst_path) {
3179 0 : error!("Failed to remove temporary initdb archive '{initdb_tar_zst_path}': {e}");
3180 2 : }
3181 : }
3182 :
3183 2 : let buf_read =
3184 2 : BufReader::with_capacity(remote_timeline_client::BUFFER_SIZE, initdb_tar_zst);
3185 2 : extract_zst_tarball(&pgdata_path, buf_read)
3186 10324 : .await
3187 2 : .context("extract initdb tar")?;
3188 : } else {
3189 : // Init temporarily repo to get bootstrap data, this creates a directory in the `pgdata_path` path
3190 0 : run_initdb(self.conf, &pgdata_path, pg_version, &self.cancel).await?;
3191 :
3192 : // Upload the created data dir to S3
3193 0 : if self.tenant_shard_id().is_zero() {
3194 0 : self.upload_initdb(&timelines_path, &pgdata_path, &timeline_id)
3195 0 : .await?;
3196 0 : }
3197 : }
3198 2 : let pgdata_lsn = import_datadir::get_lsn_from_controlfile(&pgdata_path)?.align();
3199 2 :
3200 2 : // Import the contents of the data directory at the initial checkpoint
3201 2 : // LSN, and any WAL after that.
3202 2 : // Initdb lsn will be equal to last_record_lsn which will be set after import.
3203 2 : // Because we know it upfront avoid having an option or dummy zero value by passing it to the metadata.
3204 2 : let new_metadata = TimelineMetadata::new(
3205 2 : Lsn(0),
3206 2 : None,
3207 2 : None,
3208 2 : Lsn(0),
3209 2 : pgdata_lsn,
3210 2 : pgdata_lsn,
3211 2 : pg_version,
3212 2 : );
3213 2 : let raw_timeline = self
3214 2 : .prepare_new_timeline(
3215 2 : timeline_id,
3216 2 : &new_metadata,
3217 2 : timeline_create_guard,
3218 2 : pgdata_lsn,
3219 2 : None,
3220 2 : )
3221 0 : .await?;
3222 :
3223 2 : let tenant_shard_id = raw_timeline.owning_tenant.tenant_shard_id;
3224 2 : let unfinished_timeline = raw_timeline.raw_timeline()?;
3225 :
3226 2 : import_datadir::import_timeline_from_postgres_datadir(
3227 2 : unfinished_timeline,
3228 2 : &pgdata_path,
3229 2 : pgdata_lsn,
3230 2 : ctx,
3231 2 : )
3232 12041 : .await
3233 2 : .with_context(|| {
3234 0 : format!("Failed to import pgdatadir for timeline {tenant_shard_id}/{timeline_id}")
3235 2 : })?;
3236 :
3237 : // Flush the new layer files to disk, before we make the timeline as available to
3238 : // the outside world.
3239 : //
3240 : // Flush loop needs to be spawned in order to be able to flush.
3241 2 : unfinished_timeline.maybe_spawn_flush_loop();
3242 2 :
3243 2 : fail::fail_point!("before-checkpoint-new-timeline", |_| {
3244 0 : anyhow::bail!("failpoint before-checkpoint-new-timeline");
3245 2 : });
3246 :
3247 2 : unfinished_timeline
3248 2 : .freeze_and_flush()
3249 2 : .await
3250 2 : .with_context(|| {
3251 0 : format!(
3252 0 : "Failed to flush after pgdatadir import for timeline {tenant_shard_id}/{timeline_id}"
3253 0 : )
3254 2 : })?;
3255 :
3256 : // All done!
3257 2 : let timeline = raw_timeline.finish_creation()?;
3258 :
3259 2 : Ok(timeline)
3260 2 : }
3261 :
3262 : /// Call this before constructing a timeline, to build its required structures
3263 308 : fn build_timeline_resources(&self, timeline_id: TimelineId) -> TimelineResources {
3264 308 : let remote_client = if let Some(remote_storage) = self.remote_storage.as_ref() {
3265 308 : let remote_client = RemoteTimelineClient::new(
3266 308 : remote_storage.clone(),
3267 308 : self.deletion_queue_client.clone(),
3268 308 : self.conf,
3269 308 : self.tenant_shard_id,
3270 308 : timeline_id,
3271 308 : self.generation,
3272 308 : );
3273 308 : Some(remote_client)
3274 : } else {
3275 0 : None
3276 : };
3277 :
3278 308 : TimelineResources {
3279 308 : remote_client,
3280 308 : deletion_queue_client: self.deletion_queue_client.clone(),
3281 308 : timeline_get_throttle: self.timeline_get_throttle.clone(),
3282 308 : }
3283 308 : }
3284 :
3285 : /// Creates intermediate timeline structure and its files.
3286 : ///
3287 : /// An empty layer map is initialized, and new data and WAL can be imported starting
3288 : /// at 'disk_consistent_lsn'. After any initial data has been imported, call
3289 : /// `finish_creation` to insert the Timeline into the timelines map.
3290 308 : async fn prepare_new_timeline<'a>(
3291 308 : &'a self,
3292 308 : new_timeline_id: TimelineId,
3293 308 : new_metadata: &TimelineMetadata,
3294 308 : create_guard: TimelineCreateGuard<'a>,
3295 308 : start_lsn: Lsn,
3296 308 : ancestor: Option<Arc<Timeline>>,
3297 308 : ) -> anyhow::Result<UninitializedTimeline> {
3298 308 : let tenant_shard_id = self.tenant_shard_id;
3299 308 :
3300 308 : let resources = self.build_timeline_resources(new_timeline_id);
3301 308 : if let Some(remote_client) = &resources.remote_client {
3302 308 : remote_client.init_upload_queue_for_empty_remote(new_metadata)?;
3303 0 : }
3304 :
3305 308 : let timeline_struct = self
3306 308 : .create_timeline_struct(
3307 308 : new_timeline_id,
3308 308 : new_metadata,
3309 308 : ancestor,
3310 308 : resources,
3311 308 : CreateTimelineCause::Load,
3312 308 : )
3313 308 : .context("Failed to create timeline data structure")?;
3314 :
3315 308 : timeline_struct.init_empty_layer_map(start_lsn);
3316 :
3317 308 : if let Err(e) = self
3318 308 : .create_timeline_files(&create_guard.timeline_path)
3319 0 : .await
3320 : {
3321 0 : error!("Failed to create initial files for timeline {tenant_shard_id}/{new_timeline_id}, cleaning up: {e:?}");
3322 0 : cleanup_timeline_directory(create_guard);
3323 0 : return Err(e);
3324 308 : }
3325 308 :
3326 308 : debug!(
3327 0 : "Successfully created initial files for timeline {tenant_shard_id}/{new_timeline_id}"
3328 0 : );
3329 :
3330 308 : Ok(UninitializedTimeline::new(
3331 308 : self,
3332 308 : new_timeline_id,
3333 308 : Some((timeline_struct, create_guard)),
3334 308 : ))
3335 308 : }
3336 :
3337 308 : async fn create_timeline_files(&self, timeline_path: &Utf8Path) -> anyhow::Result<()> {
3338 308 : crashsafe::create_dir(timeline_path).context("Failed to create timeline directory")?;
3339 :
3340 308 : fail::fail_point!("after-timeline-dir-creation", |_| {
3341 0 : anyhow::bail!("failpoint after-timeline-dir-creation");
3342 308 : });
3343 :
3344 308 : Ok(())
3345 308 : }
3346 :
3347 : /// Get a guard that provides exclusive access to the timeline directory, preventing
3348 : /// concurrent attempts to create the same timeline.
3349 314 : fn create_timeline_create_guard(
3350 314 : &self,
3351 314 : timeline_id: TimelineId,
3352 314 : ) -> Result<TimelineCreateGuard, TimelineExclusionError> {
3353 314 : let tenant_shard_id = self.tenant_shard_id;
3354 314 :
3355 314 : let timeline_path = self.conf.timeline_path(&tenant_shard_id, &timeline_id);
3356 :
3357 314 : let create_guard = TimelineCreateGuard::new(self, timeline_id, timeline_path.clone())?;
3358 :
3359 : // At this stage, we have got exclusive access to in-memory state for this timeline ID
3360 : // for creation.
3361 : // A timeline directory should never exist on disk already:
3362 : // - a previous failed creation would have cleaned up after itself
3363 : // - a pageserver restart would clean up timeline directories that don't have valid remote state
3364 : //
3365 : // Therefore it is an unexpected internal error to encounter a timeline directory already existing here,
3366 : // this error may indicate a bug in cleanup on failed creations.
3367 312 : if timeline_path.exists() {
3368 0 : return Err(TimelineExclusionError::Other(anyhow::anyhow!(
3369 0 : "Timeline directory already exists! This is a bug."
3370 0 : )));
3371 312 : }
3372 312 :
3373 312 : Ok(create_guard)
3374 314 : }
3375 :
3376 : /// Gathers inputs from all of the timelines to produce a sizing model input.
3377 : ///
3378 : /// Future is cancellation safe. Only one calculation can be running at once per tenant.
3379 0 : #[instrument(skip_all, fields(tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug()))]
3380 : pub async fn gather_size_inputs(
3381 : &self,
3382 : // `max_retention_period` overrides the cutoff that is used to calculate the size
3383 : // (only if it is shorter than the real cutoff).
3384 : max_retention_period: Option<u64>,
3385 : cause: LogicalSizeCalculationCause,
3386 : cancel: &CancellationToken,
3387 : ctx: &RequestContext,
3388 : ) -> anyhow::Result<size::ModelInputs> {
3389 : let logical_sizes_at_once = self
3390 : .conf
3391 : .concurrent_tenant_size_logical_size_queries
3392 : .inner();
3393 :
3394 : // TODO: Having a single mutex block concurrent reads is not great for performance.
3395 : //
3396 : // But the only case where we need to run multiple of these at once is when we
3397 : // request a size for a tenant manually via API, while another background calculation
3398 : // is in progress (which is not a common case).
3399 : //
3400 : // See more for on the issue #2748 condenced out of the initial PR review.
3401 : let mut shared_cache = self.cached_logical_sizes.lock().await;
3402 :
3403 : size::gather_inputs(
3404 : self,
3405 : logical_sizes_at_once,
3406 : max_retention_period,
3407 : &mut shared_cache,
3408 : cause,
3409 : cancel,
3410 : ctx,
3411 : )
3412 : .await
3413 : }
3414 :
3415 : /// Calculate synthetic tenant size and cache the result.
3416 : /// This is periodically called by background worker.
3417 : /// result is cached in tenant struct
3418 0 : #[instrument(skip_all, fields(tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug()))]
3419 : pub async fn calculate_synthetic_size(
3420 : &self,
3421 : cause: LogicalSizeCalculationCause,
3422 : cancel: &CancellationToken,
3423 : ctx: &RequestContext,
3424 : ) -> anyhow::Result<u64> {
3425 : let inputs = self.gather_size_inputs(None, cause, cancel, ctx).await?;
3426 :
3427 : let size = inputs.calculate()?;
3428 :
3429 : self.set_cached_synthetic_size(size);
3430 :
3431 : Ok(size)
3432 : }
3433 :
3434 : /// Cache given synthetic size and update the metric value
3435 0 : pub fn set_cached_synthetic_size(&self, size: u64) {
3436 0 : self.cached_synthetic_tenant_size
3437 0 : .store(size, Ordering::Relaxed);
3438 0 :
3439 0 : // Only shard zero should be calculating synthetic sizes
3440 0 : debug_assert!(self.shard_identity.is_zero());
3441 :
3442 0 : TENANT_SYNTHETIC_SIZE_METRIC
3443 0 : .get_metric_with_label_values(&[&self.tenant_shard_id.tenant_id.to_string()])
3444 0 : .unwrap()
3445 0 : .set(size);
3446 0 : }
3447 :
3448 0 : pub fn cached_synthetic_size(&self) -> u64 {
3449 0 : self.cached_synthetic_tenant_size.load(Ordering::Relaxed)
3450 0 : }
3451 :
3452 : /// Flush any in-progress layers, schedule uploads, and wait for uploads to complete.
3453 : ///
3454 : /// This function can take a long time: callers should wrap it in a timeout if calling
3455 : /// from an external API handler.
3456 : ///
3457 : /// Cancel-safety: cancelling this function may leave I/O running, but such I/O is
3458 : /// still bounded by tenant/timeline shutdown.
3459 0 : #[tracing::instrument(skip_all)]
3460 : pub(crate) async fn flush_remote(&self) -> anyhow::Result<()> {
3461 : let timelines = self.timelines.lock().unwrap().clone();
3462 :
3463 0 : async fn flush_timeline(_gate: GateGuard, timeline: Arc<Timeline>) -> anyhow::Result<()> {
3464 0 : tracing::info!(timeline_id=%timeline.timeline_id, "Flushing...");
3465 0 : timeline.freeze_and_flush().await?;
3466 0 : tracing::info!(timeline_id=%timeline.timeline_id, "Waiting for uploads...");
3467 0 : if let Some(client) = &timeline.remote_client {
3468 0 : client.wait_completion().await?;
3469 0 : }
3470 :
3471 0 : Ok(())
3472 0 : }
3473 :
3474 : // We do not use a JoinSet for these tasks, because we don't want them to be
3475 : // aborted when this function's future is cancelled: they should stay alive
3476 : // holding their GateGuard until they complete, to ensure their I/Os complete
3477 : // before Timeline shutdown completes.
3478 : let mut results = FuturesUnordered::new();
3479 :
3480 : for (_timeline_id, timeline) in timelines {
3481 : // Run each timeline's flush in a task holding the timeline's gate: this
3482 : // means that if this function's future is cancelled, the Timeline shutdown
3483 : // will still wait for any I/O in here to complete.
3484 : let Ok(gate) = timeline.gate.enter() else {
3485 : continue;
3486 : };
3487 0 : let jh = tokio::task::spawn(async move { flush_timeline(gate, timeline).await });
3488 : results.push(jh);
3489 : }
3490 :
3491 : while let Some(r) = results.next().await {
3492 : if let Err(e) = r {
3493 : if !e.is_cancelled() && !e.is_panic() {
3494 0 : tracing::error!("unexpected join error: {e:?}");
3495 : }
3496 : }
3497 : }
3498 :
3499 : // The flushes we did above were just writes, but the Tenant might have had
3500 : // pending deletions as well from recent compaction/gc: we want to flush those
3501 : // as well. This requires flushing the global delete queue. This is cheap
3502 : // because it's typically a no-op.
3503 : match self.deletion_queue_client.flush_execute().await {
3504 : Ok(_) => {}
3505 : Err(DeletionQueueError::ShuttingDown) => {}
3506 : }
3507 :
3508 : Ok(())
3509 : }
3510 :
3511 0 : pub(crate) fn get_tenant_conf(&self) -> TenantConfOpt {
3512 0 : self.tenant_conf.load().tenant_conf.clone()
3513 0 : }
3514 : }
3515 :
3516 : /// Create the cluster temporarily in 'initdbpath' directory inside the repository
3517 : /// to get bootstrap data for timeline initialization.
3518 0 : async fn run_initdb(
3519 0 : conf: &'static PageServerConf,
3520 0 : initdb_target_dir: &Utf8Path,
3521 0 : pg_version: u32,
3522 0 : cancel: &CancellationToken,
3523 0 : ) -> Result<(), InitdbError> {
3524 0 : let initdb_bin_path = conf
3525 0 : .pg_bin_dir(pg_version)
3526 0 : .map_err(InitdbError::Other)?
3527 0 : .join("initdb");
3528 0 : let initdb_lib_dir = conf.pg_lib_dir(pg_version).map_err(InitdbError::Other)?;
3529 0 : info!(
3530 0 : "running {} in {}, libdir: {}",
3531 0 : initdb_bin_path, initdb_target_dir, initdb_lib_dir,
3532 0 : );
3533 :
3534 0 : let _permit = INIT_DB_SEMAPHORE.acquire().await;
3535 :
3536 0 : let initdb_command = tokio::process::Command::new(&initdb_bin_path)
3537 0 : .args(["-D", initdb_target_dir.as_ref()])
3538 0 : .args(["-U", &conf.superuser])
3539 0 : .args(["-E", "utf8"])
3540 0 : .arg("--no-instructions")
3541 0 : .arg("--no-sync")
3542 0 : .env_clear()
3543 0 : .env("LD_LIBRARY_PATH", &initdb_lib_dir)
3544 0 : .env("DYLD_LIBRARY_PATH", &initdb_lib_dir)
3545 0 : .stdin(std::process::Stdio::null())
3546 0 : // stdout invocation produces the same output every time, we don't need it
3547 0 : .stdout(std::process::Stdio::null())
3548 0 : // we would be interested in the stderr output, if there was any
3549 0 : .stderr(std::process::Stdio::piped())
3550 0 : .spawn()?;
3551 :
3552 : // Ideally we'd select here with the cancellation token, but the problem is that
3553 : // we can't safely terminate initdb: it launches processes of its own, and killing
3554 : // initdb doesn't kill them. After we return from this function, we want the target
3555 : // directory to be able to be cleaned up.
3556 : // See https://github.com/neondatabase/neon/issues/6385
3557 0 : let initdb_output = initdb_command.wait_with_output().await?;
3558 0 : if !initdb_output.status.success() {
3559 0 : return Err(InitdbError::Failed(
3560 0 : initdb_output.status,
3561 0 : initdb_output.stderr,
3562 0 : ));
3563 0 : }
3564 0 :
3565 0 : // This isn't true cancellation support, see above. Still return an error to
3566 0 : // excercise the cancellation code path.
3567 0 : if cancel.is_cancelled() {
3568 0 : return Err(InitdbError::Cancelled);
3569 0 : }
3570 0 :
3571 0 : Ok(())
3572 0 : }
3573 :
3574 : /// Dump contents of a layer file to stdout.
3575 0 : pub async fn dump_layerfile_from_path(
3576 0 : path: &Utf8Path,
3577 0 : verbose: bool,
3578 0 : ctx: &RequestContext,
3579 0 : ) -> anyhow::Result<()> {
3580 : use std::os::unix::fs::FileExt;
3581 :
3582 : // All layer files start with a two-byte "magic" value, to identify the kind of
3583 : // file.
3584 0 : let file = File::open(path)?;
3585 0 : let mut header_buf = [0u8; 2];
3586 0 : file.read_exact_at(&mut header_buf, 0)?;
3587 :
3588 0 : match u16::from_be_bytes(header_buf) {
3589 : crate::IMAGE_FILE_MAGIC => {
3590 0 : ImageLayer::new_for_path(path, file)?
3591 0 : .dump(verbose, ctx)
3592 0 : .await?
3593 : }
3594 : crate::DELTA_FILE_MAGIC => {
3595 0 : DeltaLayer::new_for_path(path, file)?
3596 0 : .dump(verbose, ctx)
3597 0 : .await?
3598 : }
3599 0 : magic => bail!("unrecognized magic identifier: {:?}", magic),
3600 : }
3601 :
3602 0 : Ok(())
3603 0 : }
3604 :
3605 : #[cfg(test)]
3606 : pub(crate) mod harness {
3607 : use bytes::{Bytes, BytesMut};
3608 : use once_cell::sync::OnceCell;
3609 : use pageserver_api::models::ShardParameters;
3610 : use pageserver_api::shard::ShardIndex;
3611 : use utils::logging;
3612 :
3613 : use crate::deletion_queue::mock::MockDeletionQueue;
3614 : use crate::walredo::apply_neon;
3615 : use crate::{repository::Key, walrecord::NeonWalRecord};
3616 :
3617 : use super::*;
3618 : use hex_literal::hex;
3619 : use utils::id::TenantId;
3620 :
3621 : pub const TIMELINE_ID: TimelineId =
3622 : TimelineId::from_array(hex!("11223344556677881122334455667788"));
3623 : pub const NEW_TIMELINE_ID: TimelineId =
3624 : TimelineId::from_array(hex!("AA223344556677881122334455667788"));
3625 :
3626 : /// Convenience function to create a page image with given string as the only content
3627 3728307 : pub fn test_img(s: &str) -> Bytes {
3628 3728307 : let mut buf = BytesMut::new();
3629 3728307 : buf.extend_from_slice(s.as_bytes());
3630 3728307 : buf.resize(64, 0);
3631 3728307 :
3632 3728307 : buf.freeze()
3633 3728307 : }
3634 :
3635 : impl From<TenantConf> for TenantConfOpt {
3636 104 : fn from(tenant_conf: TenantConf) -> Self {
3637 104 : Self {
3638 104 : checkpoint_distance: Some(tenant_conf.checkpoint_distance),
3639 104 : checkpoint_timeout: Some(tenant_conf.checkpoint_timeout),
3640 104 : compaction_target_size: Some(tenant_conf.compaction_target_size),
3641 104 : compaction_period: Some(tenant_conf.compaction_period),
3642 104 : compaction_threshold: Some(tenant_conf.compaction_threshold),
3643 104 : compaction_algorithm: Some(tenant_conf.compaction_algorithm),
3644 104 : gc_horizon: Some(tenant_conf.gc_horizon),
3645 104 : gc_period: Some(tenant_conf.gc_period),
3646 104 : image_creation_threshold: Some(tenant_conf.image_creation_threshold),
3647 104 : pitr_interval: Some(tenant_conf.pitr_interval),
3648 104 : walreceiver_connect_timeout: Some(tenant_conf.walreceiver_connect_timeout),
3649 104 : lagging_wal_timeout: Some(tenant_conf.lagging_wal_timeout),
3650 104 : max_lsn_wal_lag: Some(tenant_conf.max_lsn_wal_lag),
3651 104 : trace_read_requests: Some(tenant_conf.trace_read_requests),
3652 104 : eviction_policy: Some(tenant_conf.eviction_policy),
3653 104 : min_resident_size_override: tenant_conf.min_resident_size_override,
3654 104 : evictions_low_residence_duration_metric_threshold: Some(
3655 104 : tenant_conf.evictions_low_residence_duration_metric_threshold,
3656 104 : ),
3657 104 : heatmap_period: Some(tenant_conf.heatmap_period),
3658 104 : lazy_slru_download: Some(tenant_conf.lazy_slru_download),
3659 104 : timeline_get_throttle: Some(tenant_conf.timeline_get_throttle),
3660 104 : image_layer_creation_check_threshold: Some(
3661 104 : tenant_conf.image_layer_creation_check_threshold,
3662 104 : ),
3663 104 : }
3664 104 : }
3665 : }
3666 :
3667 : pub struct TenantHarness {
3668 : pub conf: &'static PageServerConf,
3669 : pub tenant_conf: TenantConf,
3670 : pub tenant_shard_id: TenantShardId,
3671 : pub generation: Generation,
3672 : pub shard: ShardIndex,
3673 : pub remote_storage: GenericRemoteStorage,
3674 : pub remote_fs_dir: Utf8PathBuf,
3675 : pub deletion_queue: MockDeletionQueue,
3676 : }
3677 :
3678 : static LOG_HANDLE: OnceCell<()> = OnceCell::new();
3679 :
3680 110 : pub(crate) fn setup_logging() {
3681 110 : LOG_HANDLE.get_or_init(|| {
3682 110 : logging::init(
3683 110 : logging::LogFormat::Test,
3684 110 : // enable it in case the tests exercise code paths that use
3685 110 : // debug_assert_current_span_has_tenant_and_timeline_id
3686 110 : logging::TracingErrorLayerEnablement::EnableWithRustLogFilter,
3687 110 : logging::Output::Stdout,
3688 110 : )
3689 110 : .expect("Failed to init test logging")
3690 110 : });
3691 110 : }
3692 :
3693 : impl TenantHarness {
3694 104 : pub fn create_custom(
3695 104 : test_name: &'static str,
3696 104 : tenant_conf: TenantConf,
3697 104 : ) -> anyhow::Result<Self> {
3698 104 : setup_logging();
3699 104 :
3700 104 : let repo_dir = PageServerConf::test_repo_dir(test_name);
3701 104 : let _ = fs::remove_dir_all(&repo_dir);
3702 104 : fs::create_dir_all(&repo_dir)?;
3703 :
3704 104 : let conf = PageServerConf::dummy_conf(repo_dir);
3705 104 : // Make a static copy of the config. This can never be free'd, but that's
3706 104 : // OK in a test.
3707 104 : let conf: &'static PageServerConf = Box::leak(Box::new(conf));
3708 104 :
3709 104 : let tenant_id = TenantId::generate();
3710 104 : let tenant_shard_id = TenantShardId::unsharded(tenant_id);
3711 104 : fs::create_dir_all(conf.tenant_path(&tenant_shard_id))?;
3712 104 : fs::create_dir_all(conf.timelines_path(&tenant_shard_id))?;
3713 :
3714 : use remote_storage::{RemoteStorageConfig, RemoteStorageKind};
3715 104 : let remote_fs_dir = conf.workdir.join("localfs");
3716 104 : std::fs::create_dir_all(&remote_fs_dir).unwrap();
3717 104 : let config = RemoteStorageConfig {
3718 104 : storage: RemoteStorageKind::LocalFs(remote_fs_dir.clone()),
3719 104 : timeout: RemoteStorageConfig::DEFAULT_TIMEOUT,
3720 104 : };
3721 104 : let remote_storage = GenericRemoteStorage::from_config(&config).unwrap();
3722 104 : let deletion_queue = MockDeletionQueue::new(Some(remote_storage.clone()));
3723 104 :
3724 104 : Ok(Self {
3725 104 : conf,
3726 104 : tenant_conf,
3727 104 : tenant_shard_id,
3728 104 : generation: Generation::new(0xdeadbeef),
3729 104 : shard: ShardIndex::unsharded(),
3730 104 : remote_storage,
3731 104 : remote_fs_dir,
3732 104 : deletion_queue,
3733 104 : })
3734 104 : }
3735 :
3736 102 : pub fn create(test_name: &'static str) -> anyhow::Result<Self> {
3737 102 : // Disable automatic GC and compaction to make the unit tests more deterministic.
3738 102 : // The tests perform them manually if needed.
3739 102 : let tenant_conf = TenantConf {
3740 102 : gc_period: Duration::ZERO,
3741 102 : compaction_period: Duration::ZERO,
3742 102 : ..TenantConf::default()
3743 102 : };
3744 102 :
3745 102 : Self::create_custom(test_name, tenant_conf)
3746 102 : }
3747 :
3748 18 : pub fn span(&self) -> tracing::Span {
3749 18 : info_span!("TenantHarness", tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug())
3750 18 : }
3751 :
3752 104 : pub(crate) async fn load(&self) -> (Arc<Tenant>, RequestContext) {
3753 104 : let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error);
3754 104 : (
3755 104 : self.do_try_load(&ctx)
3756 20 : .await
3757 104 : .expect("failed to load test tenant"),
3758 104 : ctx,
3759 104 : )
3760 104 : }
3761 :
3762 208 : #[instrument(skip_all, fields(tenant_id=%self.tenant_shard_id.tenant_id, shard_id=%self.tenant_shard_id.shard_slug()))]
3763 : pub(crate) async fn do_try_load(
3764 : &self,
3765 : ctx: &RequestContext,
3766 : ) -> anyhow::Result<Arc<Tenant>> {
3767 : let walredo_mgr = Arc::new(WalRedoManager::from(TestRedoManager));
3768 :
3769 : let tenant = Arc::new(Tenant::new(
3770 : TenantState::Loading,
3771 : self.conf,
3772 : AttachedTenantConf::try_from(LocationConf::attached_single(
3773 : TenantConfOpt::from(self.tenant_conf.clone()),
3774 : self.generation,
3775 : &ShardParameters::default(),
3776 : ))
3777 : .unwrap(),
3778 : // This is a legacy/test code path: sharding isn't supported here.
3779 : ShardIdentity::unsharded(),
3780 : Some(walredo_mgr),
3781 : self.tenant_shard_id,
3782 : Some(self.remote_storage.clone()),
3783 : self.deletion_queue.new_client(),
3784 : ));
3785 :
3786 : let preload = tenant
3787 : .preload(&self.remote_storage, CancellationToken::new())
3788 : .await?;
3789 : tenant.attach(Some(preload), SpawnMode::Eager, ctx).await?;
3790 :
3791 : tenant.state.send_replace(TenantState::Active);
3792 : for timeline in tenant.timelines.lock().unwrap().values() {
3793 : timeline.set_state(TimelineState::Active);
3794 : }
3795 : Ok(tenant)
3796 : }
3797 :
3798 4 : pub fn timeline_path(&self, timeline_id: &TimelineId) -> Utf8PathBuf {
3799 4 : self.conf.timeline_path(&self.tenant_shard_id, timeline_id)
3800 4 : }
3801 : }
3802 :
3803 : // Mock WAL redo manager that doesn't do much
3804 : pub(crate) struct TestRedoManager;
3805 :
3806 : impl TestRedoManager {
3807 : /// # Cancel-Safety
3808 : ///
3809 : /// This method is cancellation-safe.
3810 6 : pub async fn request_redo(
3811 6 : &self,
3812 6 : key: Key,
3813 6 : lsn: Lsn,
3814 6 : base_img: Option<(Lsn, Bytes)>,
3815 6 : records: Vec<(Lsn, NeonWalRecord)>,
3816 6 : _pg_version: u32,
3817 6 : ) -> anyhow::Result<Bytes> {
3818 10 : let records_neon = records.iter().all(|r| apply_neon::can_apply_in_neon(&r.1));
3819 6 : if records_neon {
3820 : // For Neon wal records, we can decode without spawning postgres, so do so.
3821 6 : let base_img = base_img.expect("Neon WAL redo requires base image").1;
3822 6 : let mut page = BytesMut::new();
3823 6 : page.extend_from_slice(&base_img);
3824 16 : for (_record_lsn, record) in records {
3825 10 : apply_neon::apply_in_neon(&record, key, &mut page)?;
3826 : }
3827 6 : Ok(page.freeze())
3828 : } else {
3829 : // We never spawn a postgres walredo process in unit tests: just log what we might have done.
3830 0 : let s = format!(
3831 0 : "redo for {} to get to {}, with {} and {} records",
3832 0 : key,
3833 0 : lsn,
3834 0 : if base_img.is_some() {
3835 0 : "base image"
3836 : } else {
3837 0 : "no base image"
3838 : },
3839 0 : records.len()
3840 0 : );
3841 0 : println!("{s}");
3842 0 :
3843 0 : Ok(test_img(&s))
3844 : }
3845 6 : }
3846 : }
3847 : }
3848 :
3849 : #[cfg(test)]
3850 : mod tests {
3851 : use super::*;
3852 : use crate::keyspace::KeySpaceAccum;
3853 : use crate::repository::{Key, Value};
3854 : use crate::tenant::harness::*;
3855 : use crate::tenant::timeline::CompactFlags;
3856 : use crate::DEFAULT_PG_VERSION;
3857 : use bytes::BytesMut;
3858 : use hex_literal::hex;
3859 : use pageserver_api::keyspace::KeySpace;
3860 : use rand::{thread_rng, Rng};
3861 : use tests::timeline::ShutdownMode;
3862 :
3863 : static TEST_KEY: Lazy<Key> =
3864 18 : Lazy::new(|| Key::from_slice(&hex!("010000000033333333444444445500000001")));
3865 :
3866 : #[tokio::test]
3867 2 : async fn test_basic() -> anyhow::Result<()> {
3868 2 : let (tenant, ctx) = TenantHarness::create("test_basic")?.load().await;
3869 2 : let tline = tenant
3870 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x08), DEFAULT_PG_VERSION, &ctx)
3871 6 : .await?;
3872 2 :
3873 2 : let mut writer = tline.writer().await;
3874 2 : writer
3875 2 : .put(
3876 2 : *TEST_KEY,
3877 2 : Lsn(0x10),
3878 2 : &Value::Image(test_img("foo at 0x10")),
3879 2 : &ctx,
3880 2 : )
3881 2 : .await?;
3882 2 : writer.finish_write(Lsn(0x10));
3883 2 : drop(writer);
3884 2 :
3885 2 : let mut writer = tline.writer().await;
3886 2 : writer
3887 2 : .put(
3888 2 : *TEST_KEY,
3889 2 : Lsn(0x20),
3890 2 : &Value::Image(test_img("foo at 0x20")),
3891 2 : &ctx,
3892 2 : )
3893 2 : .await?;
3894 2 : writer.finish_write(Lsn(0x20));
3895 2 : drop(writer);
3896 2 :
3897 2 : assert_eq!(
3898 2 : tline.get(*TEST_KEY, Lsn(0x10), &ctx).await?,
3899 2 : test_img("foo at 0x10")
3900 2 : );
3901 2 : assert_eq!(
3902 2 : tline.get(*TEST_KEY, Lsn(0x1f), &ctx).await?,
3903 2 : test_img("foo at 0x10")
3904 2 : );
3905 2 : assert_eq!(
3906 2 : tline.get(*TEST_KEY, Lsn(0x20), &ctx).await?,
3907 2 : test_img("foo at 0x20")
3908 2 : );
3909 2 :
3910 2 : Ok(())
3911 2 : }
3912 :
3913 : #[tokio::test]
3914 2 : async fn no_duplicate_timelines() -> anyhow::Result<()> {
3915 2 : let (tenant, ctx) = TenantHarness::create("no_duplicate_timelines")?
3916 2 : .load()
3917 2 : .await;
3918 2 : let _ = tenant
3919 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
3920 6 : .await?;
3921 2 :
3922 2 : match tenant
3923 2 : .create_empty_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
3924 2 : .await
3925 2 : {
3926 2 : Ok(_) => panic!("duplicate timeline creation should fail"),
3927 2 : Err(e) => assert_eq!(e.to_string(), "Already exists".to_string()),
3928 2 : }
3929 2 :
3930 2 : Ok(())
3931 2 : }
3932 :
3933 : /// Convenience function to create a page image with given string as the only content
3934 10 : pub fn test_value(s: &str) -> Value {
3935 10 : let mut buf = BytesMut::new();
3936 10 : buf.extend_from_slice(s.as_bytes());
3937 10 : Value::Image(buf.freeze())
3938 10 : }
3939 :
3940 : ///
3941 : /// Test branch creation
3942 : ///
3943 : #[tokio::test]
3944 2 : async fn test_branch() -> anyhow::Result<()> {
3945 2 : use std::str::from_utf8;
3946 2 :
3947 2 : let (tenant, ctx) = TenantHarness::create("test_branch")?.load().await;
3948 2 : let tline = tenant
3949 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
3950 6 : .await?;
3951 2 : let mut writer = tline.writer().await;
3952 2 :
3953 2 : #[allow(non_snake_case)]
3954 2 : let TEST_KEY_A: Key = Key::from_hex("110000000033333333444444445500000001").unwrap();
3955 2 : #[allow(non_snake_case)]
3956 2 : let TEST_KEY_B: Key = Key::from_hex("110000000033333333444444445500000002").unwrap();
3957 2 :
3958 2 : // Insert a value on the timeline
3959 2 : writer
3960 2 : .put(TEST_KEY_A, Lsn(0x20), &test_value("foo at 0x20"), &ctx)
3961 2 : .await?;
3962 2 : writer
3963 2 : .put(TEST_KEY_B, Lsn(0x20), &test_value("foobar at 0x20"), &ctx)
3964 2 : .await?;
3965 2 : writer.finish_write(Lsn(0x20));
3966 2 :
3967 2 : writer
3968 2 : .put(TEST_KEY_A, Lsn(0x30), &test_value("foo at 0x30"), &ctx)
3969 2 : .await?;
3970 2 : writer.finish_write(Lsn(0x30));
3971 2 : writer
3972 2 : .put(TEST_KEY_A, Lsn(0x40), &test_value("foo at 0x40"), &ctx)
3973 2 : .await?;
3974 2 : writer.finish_write(Lsn(0x40));
3975 2 :
3976 2 : //assert_current_logical_size(&tline, Lsn(0x40));
3977 2 :
3978 2 : // Branch the history, modify relation differently on the new timeline
3979 2 : tenant
3980 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x30)), &ctx)
3981 2 : .await?;
3982 2 : let newtline = tenant
3983 2 : .get_timeline(NEW_TIMELINE_ID, true)
3984 2 : .expect("Should have a local timeline");
3985 2 : let mut new_writer = newtline.writer().await;
3986 2 : new_writer
3987 2 : .put(TEST_KEY_A, Lsn(0x40), &test_value("bar at 0x40"), &ctx)
3988 2 : .await?;
3989 2 : new_writer.finish_write(Lsn(0x40));
3990 2 :
3991 2 : // Check page contents on both branches
3992 2 : assert_eq!(
3993 2 : from_utf8(&tline.get(TEST_KEY_A, Lsn(0x40), &ctx).await?)?,
3994 2 : "foo at 0x40"
3995 2 : );
3996 2 : assert_eq!(
3997 2 : from_utf8(&newtline.get(TEST_KEY_A, Lsn(0x40), &ctx).await?)?,
3998 2 : "bar at 0x40"
3999 2 : );
4000 2 : assert_eq!(
4001 2 : from_utf8(&newtline.get(TEST_KEY_B, Lsn(0x40), &ctx).await?)?,
4002 2 : "foobar at 0x20"
4003 2 : );
4004 2 :
4005 2 : //assert_current_logical_size(&tline, Lsn(0x40));
4006 2 :
4007 2 : Ok(())
4008 2 : }
4009 :
4010 20 : async fn make_some_layers(
4011 20 : tline: &Timeline,
4012 20 : start_lsn: Lsn,
4013 20 : ctx: &RequestContext,
4014 20 : ) -> anyhow::Result<()> {
4015 20 : let mut lsn = start_lsn;
4016 : {
4017 20 : let mut writer = tline.writer().await;
4018 : // Create a relation on the timeline
4019 20 : writer
4020 20 : .put(
4021 20 : *TEST_KEY,
4022 20 : lsn,
4023 20 : &Value::Image(test_img(&format!("foo at {}", lsn))),
4024 20 : ctx,
4025 20 : )
4026 10 : .await?;
4027 20 : writer.finish_write(lsn);
4028 20 : lsn += 0x10;
4029 20 : writer
4030 20 : .put(
4031 20 : *TEST_KEY,
4032 20 : lsn,
4033 20 : &Value::Image(test_img(&format!("foo at {}", lsn))),
4034 20 : ctx,
4035 20 : )
4036 0 : .await?;
4037 20 : writer.finish_write(lsn);
4038 20 : lsn += 0x10;
4039 20 : }
4040 20 : tline.freeze_and_flush().await?;
4041 : {
4042 20 : let mut writer = tline.writer().await;
4043 20 : writer
4044 20 : .put(
4045 20 : *TEST_KEY,
4046 20 : lsn,
4047 20 : &Value::Image(test_img(&format!("foo at {}", lsn))),
4048 20 : ctx,
4049 20 : )
4050 10 : .await?;
4051 20 : writer.finish_write(lsn);
4052 20 : lsn += 0x10;
4053 20 : writer
4054 20 : .put(
4055 20 : *TEST_KEY,
4056 20 : lsn,
4057 20 : &Value::Image(test_img(&format!("foo at {}", lsn))),
4058 20 : ctx,
4059 20 : )
4060 0 : .await?;
4061 20 : writer.finish_write(lsn);
4062 20 : }
4063 20 : tline.freeze_and_flush().await
4064 20 : }
4065 :
4066 : #[tokio::test]
4067 2 : async fn test_prohibit_branch_creation_on_garbage_collected_data() -> anyhow::Result<()> {
4068 2 : let (tenant, ctx) =
4069 2 : TenantHarness::create("test_prohibit_branch_creation_on_garbage_collected_data")?
4070 2 : .load()
4071 2 : .await;
4072 2 : let tline = tenant
4073 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4074 6 : .await?;
4075 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4076 2 :
4077 2 : // this removes layers before lsn 40 (50 minus 10), so there are two remaining layers, image and delta for 31-50
4078 2 : // FIXME: this doesn't actually remove any layer currently, given how the flushing
4079 2 : // and compaction works. But it does set the 'cutoff' point so that the cross check
4080 2 : // below should fail.
4081 2 : tenant
4082 2 : .gc_iteration(
4083 2 : Some(TIMELINE_ID),
4084 2 : 0x10,
4085 2 : Duration::ZERO,
4086 2 : &CancellationToken::new(),
4087 2 : &ctx,
4088 2 : )
4089 2 : .await?;
4090 2 :
4091 2 : // try to branch at lsn 25, should fail because we already garbage collected the data
4092 2 : match tenant
4093 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x25)), &ctx)
4094 2 : .await
4095 2 : {
4096 2 : Ok(_) => panic!("branching should have failed"),
4097 2 : Err(err) => {
4098 2 : let CreateTimelineError::AncestorLsn(err) = err else {
4099 2 : panic!("wrong error type")
4100 2 : };
4101 2 : assert!(err.to_string().contains("invalid branch start lsn"));
4102 2 : assert!(err
4103 2 : .source()
4104 2 : .unwrap()
4105 2 : .to_string()
4106 2 : .contains("we might've already garbage collected needed data"))
4107 2 : }
4108 2 : }
4109 2 :
4110 2 : Ok(())
4111 2 : }
4112 :
4113 : #[tokio::test]
4114 2 : async fn test_prohibit_branch_creation_on_pre_initdb_lsn() -> anyhow::Result<()> {
4115 2 : let (tenant, ctx) =
4116 2 : TenantHarness::create("test_prohibit_branch_creation_on_pre_initdb_lsn")?
4117 2 : .load()
4118 2 : .await;
4119 2 :
4120 2 : let tline = tenant
4121 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x50), DEFAULT_PG_VERSION, &ctx)
4122 6 : .await?;
4123 2 : // try to branch at lsn 0x25, should fail because initdb lsn is 0x50
4124 2 : match tenant
4125 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x25)), &ctx)
4126 2 : .await
4127 2 : {
4128 2 : Ok(_) => panic!("branching should have failed"),
4129 2 : Err(err) => {
4130 2 : let CreateTimelineError::AncestorLsn(err) = err else {
4131 2 : panic!("wrong error type");
4132 2 : };
4133 2 : assert!(&err.to_string().contains("invalid branch start lsn"));
4134 2 : assert!(&err
4135 2 : .source()
4136 2 : .unwrap()
4137 2 : .to_string()
4138 2 : .contains("is earlier than latest GC horizon"));
4139 2 : }
4140 2 : }
4141 2 :
4142 2 : Ok(())
4143 2 : }
4144 :
4145 : /*
4146 : // FIXME: This currently fails to error out. Calling GC doesn't currently
4147 : // remove the old value, we'd need to work a little harder
4148 : #[tokio::test]
4149 : async fn test_prohibit_get_for_garbage_collected_data() -> anyhow::Result<()> {
4150 : let repo =
4151 : RepoHarness::create("test_prohibit_get_for_garbage_collected_data")?
4152 : .load();
4153 :
4154 : let tline = repo.create_empty_timeline(TIMELINE_ID, Lsn(0), DEFAULT_PG_VERSION)?;
4155 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4156 :
4157 : repo.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO)?;
4158 : let latest_gc_cutoff_lsn = tline.get_latest_gc_cutoff_lsn();
4159 : assert!(*latest_gc_cutoff_lsn > Lsn(0x25));
4160 : match tline.get(*TEST_KEY, Lsn(0x25)) {
4161 : Ok(_) => panic!("request for page should have failed"),
4162 : Err(err) => assert!(err.to_string().contains("not found at")),
4163 : }
4164 : Ok(())
4165 : }
4166 : */
4167 :
4168 : #[tokio::test]
4169 2 : async fn test_get_branchpoints_from_an_inactive_timeline() -> anyhow::Result<()> {
4170 2 : let (tenant, ctx) =
4171 2 : TenantHarness::create("test_get_branchpoints_from_an_inactive_timeline")?
4172 2 : .load()
4173 2 : .await;
4174 2 : let tline = tenant
4175 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4176 6 : .await?;
4177 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4178 2 :
4179 2 : tenant
4180 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x40)), &ctx)
4181 2 : .await?;
4182 2 : let newtline = tenant
4183 2 : .get_timeline(NEW_TIMELINE_ID, true)
4184 2 : .expect("Should have a local timeline");
4185 2 :
4186 6 : make_some_layers(newtline.as_ref(), Lsn(0x60), &ctx).await?;
4187 2 :
4188 2 : tline.set_broken("test".to_owned());
4189 2 :
4190 2 : tenant
4191 2 : .gc_iteration(
4192 2 : Some(TIMELINE_ID),
4193 2 : 0x10,
4194 2 : Duration::ZERO,
4195 2 : &CancellationToken::new(),
4196 2 : &ctx,
4197 2 : )
4198 2 : .await?;
4199 2 :
4200 2 : // The branchpoints should contain all timelines, even ones marked
4201 2 : // as Broken.
4202 2 : {
4203 2 : let branchpoints = &tline.gc_info.read().unwrap().retain_lsns;
4204 2 : assert_eq!(branchpoints.len(), 1);
4205 2 : assert_eq!(branchpoints[0], Lsn(0x40));
4206 2 : }
4207 2 :
4208 2 : // You can read the key from the child branch even though the parent is
4209 2 : // Broken, as long as you don't need to access data from the parent.
4210 2 : assert_eq!(
4211 4 : newtline.get(*TEST_KEY, Lsn(0x70), &ctx).await?,
4212 2 : test_img(&format!("foo at {}", Lsn(0x70)))
4213 2 : );
4214 2 :
4215 2 : // This needs to traverse to the parent, and fails.
4216 2 : let err = newtline.get(*TEST_KEY, Lsn(0x50), &ctx).await.unwrap_err();
4217 2 : assert!(err
4218 2 : .to_string()
4219 2 : .contains("will not become active. Current state: Broken"));
4220 2 :
4221 2 : Ok(())
4222 2 : }
4223 :
4224 : #[tokio::test]
4225 2 : async fn test_retain_data_in_parent_which_is_needed_for_child() -> anyhow::Result<()> {
4226 2 : let (tenant, ctx) =
4227 2 : TenantHarness::create("test_retain_data_in_parent_which_is_needed_for_child")?
4228 2 : .load()
4229 2 : .await;
4230 2 : let tline = tenant
4231 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4232 5 : .await?;
4233 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4234 2 :
4235 2 : tenant
4236 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x40)), &ctx)
4237 2 : .await?;
4238 2 : let newtline = tenant
4239 2 : .get_timeline(NEW_TIMELINE_ID, true)
4240 2 : .expect("Should have a local timeline");
4241 2 : // this removes layers before lsn 40 (50 minus 10), so there are two remaining layers, image and delta for 31-50
4242 2 : tenant
4243 2 : .gc_iteration(
4244 2 : Some(TIMELINE_ID),
4245 2 : 0x10,
4246 2 : Duration::ZERO,
4247 2 : &CancellationToken::new(),
4248 2 : &ctx,
4249 2 : )
4250 2 : .await?;
4251 4 : assert!(newtline.get(*TEST_KEY, Lsn(0x25), &ctx).await.is_ok());
4252 2 :
4253 2 : Ok(())
4254 2 : }
4255 : #[tokio::test]
4256 2 : async fn test_parent_keeps_data_forever_after_branching() -> anyhow::Result<()> {
4257 2 : let (tenant, ctx) =
4258 2 : TenantHarness::create("test_parent_keeps_data_forever_after_branching")?
4259 2 : .load()
4260 2 : .await;
4261 2 : let tline = tenant
4262 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4263 6 : .await?;
4264 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4265 2 :
4266 2 : tenant
4267 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x40)), &ctx)
4268 2 : .await?;
4269 2 : let newtline = tenant
4270 2 : .get_timeline(NEW_TIMELINE_ID, true)
4271 2 : .expect("Should have a local timeline");
4272 2 :
4273 6 : make_some_layers(newtline.as_ref(), Lsn(0x60), &ctx).await?;
4274 2 :
4275 2 : // run gc on parent
4276 2 : tenant
4277 2 : .gc_iteration(
4278 2 : Some(TIMELINE_ID),
4279 2 : 0x10,
4280 2 : Duration::ZERO,
4281 2 : &CancellationToken::new(),
4282 2 : &ctx,
4283 2 : )
4284 2 : .await?;
4285 2 :
4286 2 : // Check that the data is still accessible on the branch.
4287 2 : assert_eq!(
4288 7 : newtline.get(*TEST_KEY, Lsn(0x50), &ctx).await?,
4289 2 : test_img(&format!("foo at {}", Lsn(0x40)))
4290 2 : );
4291 2 :
4292 2 : Ok(())
4293 2 : }
4294 :
4295 : #[tokio::test]
4296 2 : async fn timeline_load() -> anyhow::Result<()> {
4297 2 : const TEST_NAME: &str = "timeline_load";
4298 2 : let harness = TenantHarness::create(TEST_NAME)?;
4299 2 : {
4300 2 : let (tenant, ctx) = harness.load().await;
4301 2 : let tline = tenant
4302 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x7000), DEFAULT_PG_VERSION, &ctx)
4303 6 : .await?;
4304 6 : make_some_layers(tline.as_ref(), Lsn(0x8000), &ctx).await?;
4305 2 : // so that all uploads finish & we can call harness.load() below again
4306 2 : tenant
4307 2 : .shutdown(Default::default(), ShutdownMode::FreezeAndFlush)
4308 2 : .instrument(harness.span())
4309 2 : .await
4310 2 : .ok()
4311 2 : .unwrap();
4312 2 : }
4313 2 :
4314 10 : let (tenant, _ctx) = harness.load().await;
4315 2 : tenant
4316 2 : .get_timeline(TIMELINE_ID, true)
4317 2 : .expect("cannot load timeline");
4318 2 :
4319 2 : Ok(())
4320 2 : }
4321 :
4322 : #[tokio::test]
4323 2 : async fn timeline_load_with_ancestor() -> anyhow::Result<()> {
4324 2 : const TEST_NAME: &str = "timeline_load_with_ancestor";
4325 2 : let harness = TenantHarness::create(TEST_NAME)?;
4326 2 : // create two timelines
4327 2 : {
4328 2 : let (tenant, ctx) = harness.load().await;
4329 2 : let tline = tenant
4330 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4331 6 : .await?;
4332 2 :
4333 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4334 2 :
4335 2 : let child_tline = tenant
4336 2 : .branch_timeline_test(&tline, NEW_TIMELINE_ID, Some(Lsn(0x40)), &ctx)
4337 2 : .await?;
4338 2 : child_tline.set_state(TimelineState::Active);
4339 2 :
4340 2 : let newtline = tenant
4341 2 : .get_timeline(NEW_TIMELINE_ID, true)
4342 2 : .expect("Should have a local timeline");
4343 2 :
4344 6 : make_some_layers(newtline.as_ref(), Lsn(0x60), &ctx).await?;
4345 2 :
4346 2 : // so that all uploads finish & we can call harness.load() below again
4347 2 : tenant
4348 2 : .shutdown(Default::default(), ShutdownMode::FreezeAndFlush)
4349 2 : .instrument(harness.span())
4350 3 : .await
4351 2 : .ok()
4352 2 : .unwrap();
4353 2 : }
4354 2 :
4355 2 : // check that both of them are initially unloaded
4356 10 : let (tenant, _ctx) = harness.load().await;
4357 2 :
4358 2 : // check that both, child and ancestor are loaded
4359 2 : let _child_tline = tenant
4360 2 : .get_timeline(NEW_TIMELINE_ID, true)
4361 2 : .expect("cannot get child timeline loaded");
4362 2 :
4363 2 : let _ancestor_tline = tenant
4364 2 : .get_timeline(TIMELINE_ID, true)
4365 2 : .expect("cannot get ancestor timeline loaded");
4366 2 :
4367 2 : Ok(())
4368 2 : }
4369 :
4370 : #[tokio::test]
4371 2 : async fn delta_layer_dumping() -> anyhow::Result<()> {
4372 2 : use storage_layer::AsLayerDesc;
4373 2 : let (tenant, ctx) = TenantHarness::create("test_layer_dumping")?.load().await;
4374 2 : let tline = tenant
4375 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4376 6 : .await?;
4377 6 : make_some_layers(tline.as_ref(), Lsn(0x20), &ctx).await?;
4378 2 :
4379 2 : let layer_map = tline.layers.read().await;
4380 2 : let level0_deltas = layer_map
4381 2 : .layer_map()
4382 2 : .get_level0_deltas()?
4383 2 : .into_iter()
4384 4 : .map(|desc| layer_map.get_from_desc(&desc))
4385 2 : .collect::<Vec<_>>();
4386 2 :
4387 2 : assert!(!level0_deltas.is_empty());
4388 2 :
4389 6 : for delta in level0_deltas {
4390 2 : // Ensure we are dumping a delta layer here
4391 4 : assert!(delta.layer_desc().is_delta);
4392 8 : delta.dump(true, &ctx).await.unwrap();
4393 2 : }
4394 2 :
4395 2 : Ok(())
4396 2 : }
4397 :
4398 : #[tokio::test]
4399 2 : async fn test_images() -> anyhow::Result<()> {
4400 2 : let (tenant, ctx) = TenantHarness::create("test_images")?.load().await;
4401 2 : let tline = tenant
4402 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x08), DEFAULT_PG_VERSION, &ctx)
4403 6 : .await?;
4404 2 :
4405 2 : let mut writer = tline.writer().await;
4406 2 : writer
4407 2 : .put(
4408 2 : *TEST_KEY,
4409 2 : Lsn(0x10),
4410 2 : &Value::Image(test_img("foo at 0x10")),
4411 2 : &ctx,
4412 2 : )
4413 2 : .await?;
4414 2 : writer.finish_write(Lsn(0x10));
4415 2 : drop(writer);
4416 2 :
4417 2 : tline.freeze_and_flush().await?;
4418 2 : tline
4419 2 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4420 2 : .await?;
4421 2 :
4422 2 : let mut writer = tline.writer().await;
4423 2 : writer
4424 2 : .put(
4425 2 : *TEST_KEY,
4426 2 : Lsn(0x20),
4427 2 : &Value::Image(test_img("foo at 0x20")),
4428 2 : &ctx,
4429 2 : )
4430 2 : .await?;
4431 2 : writer.finish_write(Lsn(0x20));
4432 2 : drop(writer);
4433 2 :
4434 2 : tline.freeze_and_flush().await?;
4435 2 : tline
4436 2 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4437 2 : .await?;
4438 2 :
4439 2 : let mut writer = tline.writer().await;
4440 2 : writer
4441 2 : .put(
4442 2 : *TEST_KEY,
4443 2 : Lsn(0x30),
4444 2 : &Value::Image(test_img("foo at 0x30")),
4445 2 : &ctx,
4446 2 : )
4447 2 : .await?;
4448 2 : writer.finish_write(Lsn(0x30));
4449 2 : drop(writer);
4450 2 :
4451 2 : tline.freeze_and_flush().await?;
4452 2 : tline
4453 2 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4454 2 : .await?;
4455 2 :
4456 2 : let mut writer = tline.writer().await;
4457 2 : writer
4458 2 : .put(
4459 2 : *TEST_KEY,
4460 2 : Lsn(0x40),
4461 2 : &Value::Image(test_img("foo at 0x40")),
4462 2 : &ctx,
4463 2 : )
4464 2 : .await?;
4465 2 : writer.finish_write(Lsn(0x40));
4466 2 : drop(writer);
4467 2 :
4468 2 : tline.freeze_and_flush().await?;
4469 2 : tline
4470 2 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4471 2 : .await?;
4472 2 :
4473 2 : assert_eq!(
4474 4 : tline.get(*TEST_KEY, Lsn(0x10), &ctx).await?,
4475 2 : test_img("foo at 0x10")
4476 2 : );
4477 2 : assert_eq!(
4478 3 : tline.get(*TEST_KEY, Lsn(0x1f), &ctx).await?,
4479 2 : test_img("foo at 0x10")
4480 2 : );
4481 2 : assert_eq!(
4482 2 : tline.get(*TEST_KEY, Lsn(0x20), &ctx).await?,
4483 2 : test_img("foo at 0x20")
4484 2 : );
4485 2 : assert_eq!(
4486 4 : tline.get(*TEST_KEY, Lsn(0x30), &ctx).await?,
4487 2 : test_img("foo at 0x30")
4488 2 : );
4489 2 : assert_eq!(
4490 4 : tline.get(*TEST_KEY, Lsn(0x40), &ctx).await?,
4491 2 : test_img("foo at 0x40")
4492 2 : );
4493 2 :
4494 2 : Ok(())
4495 2 : }
4496 :
4497 6 : async fn bulk_insert_compact_gc(
4498 6 : timeline: Arc<Timeline>,
4499 6 : ctx: &RequestContext,
4500 6 : mut lsn: Lsn,
4501 6 : repeat: usize,
4502 6 : key_count: usize,
4503 6 : ) -> anyhow::Result<()> {
4504 6 : let mut test_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
4505 6 : let mut blknum = 0;
4506 6 :
4507 6 : // Enforce that key range is monotonously increasing
4508 6 : let mut keyspace = KeySpaceAccum::new();
4509 6 :
4510 6 : for _ in 0..repeat {
4511 300 : for _ in 0..key_count {
4512 3000000 : test_key.field6 = blknum;
4513 3000000 : let mut writer = timeline.writer().await;
4514 3000000 : writer
4515 3000000 : .put(
4516 3000000 : test_key,
4517 3000000 : lsn,
4518 3000000 : &Value::Image(test_img(&format!("{} at {}", blknum, lsn))),
4519 3000000 : ctx,
4520 3000000 : )
4521 92067 : .await?;
4522 3000000 : writer.finish_write(lsn);
4523 3000000 : drop(writer);
4524 3000000 :
4525 3000000 : keyspace.add_key(test_key);
4526 3000000 :
4527 3000000 : lsn = Lsn(lsn.0 + 0x10);
4528 3000000 : blknum += 1;
4529 : }
4530 :
4531 300 : let cutoff = timeline.get_last_record_lsn();
4532 300 :
4533 300 : timeline
4534 300 : .update_gc_info(
4535 300 : Vec::new(),
4536 300 : cutoff,
4537 300 : Duration::ZERO,
4538 300 : &CancellationToken::new(),
4539 300 : ctx,
4540 300 : )
4541 0 : .await?;
4542 300 : timeline.freeze_and_flush().await?;
4543 300 : timeline
4544 300 : .compact(&CancellationToken::new(), EnumSet::empty(), ctx)
4545 72771 : .await?;
4546 300 : timeline.gc().await?;
4547 : }
4548 :
4549 6 : Ok(())
4550 6 : }
4551 :
4552 : //
4553 : // Insert 1000 key-value pairs with increasing keys, flush, compact, GC.
4554 : // Repeat 50 times.
4555 : //
4556 : #[tokio::test]
4557 2 : async fn test_bulk_insert() -> anyhow::Result<()> {
4558 2 : let harness = TenantHarness::create("test_bulk_insert")?;
4559 2 : let (tenant, ctx) = harness.load().await;
4560 2 : let tline = tenant
4561 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x08), DEFAULT_PG_VERSION, &ctx)
4562 6 : .await?;
4563 2 :
4564 2 : let lsn = Lsn(0x10);
4565 58861 : bulk_insert_compact_gc(tline.clone(), &ctx, lsn, 50, 10000).await?;
4566 2 :
4567 2 : Ok(())
4568 2 : }
4569 :
4570 : // Test the vectored get real implementation against a simple sequential implementation.
4571 : //
4572 : // The test generates a keyspace by repeatedly flushing the in-memory layer and compacting.
4573 : // Projected to 2D the key space looks like below. Lsn grows upwards on the Y axis and keys
4574 : // grow to the right on the X axis.
4575 : // [Delta]
4576 : // [Delta]
4577 : // [Delta]
4578 : // [Delta]
4579 : // ------------ Image ---------------
4580 : //
4581 : // After layer generation we pick the ranges to query as follows:
4582 : // 1. The beginning of each delta layer
4583 : // 2. At the seam between two adjacent delta layers
4584 : //
4585 : // There's one major downside to this test: delta layers only contains images,
4586 : // so the search can stop at the first delta layer and doesn't traverse any deeper.
4587 : #[tokio::test]
4588 2 : async fn test_get_vectored() -> anyhow::Result<()> {
4589 2 : let harness = TenantHarness::create("test_get_vectored")?;
4590 2 : let (tenant, ctx) = harness.load().await;
4591 2 : let tline = tenant
4592 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x08), DEFAULT_PG_VERSION, &ctx)
4593 6 : .await?;
4594 2 :
4595 2 : let lsn = Lsn(0x10);
4596 58861 : bulk_insert_compact_gc(tline.clone(), &ctx, lsn, 50, 10000).await?;
4597 2 :
4598 2 : let guard = tline.layers.read().await;
4599 2 : guard.layer_map().dump(true, &ctx).await?;
4600 2 :
4601 2 : let mut reads = Vec::new();
4602 2 : let mut prev = None;
4603 12 : guard.layer_map().iter_historic_layers().for_each(|desc| {
4604 12 : if !desc.is_delta() {
4605 2 : prev = Some(desc.clone());
4606 2 : return;
4607 10 : }
4608 10 :
4609 10 : let start = desc.key_range.start;
4610 10 : let end = desc
4611 10 : .key_range
4612 10 : .start
4613 10 : .add(Timeline::MAX_GET_VECTORED_KEYS.try_into().unwrap());
4614 10 : reads.push(KeySpace {
4615 10 : ranges: vec![start..end],
4616 10 : });
4617 2 :
4618 10 : if let Some(prev) = &prev {
4619 10 : if !prev.is_delta() {
4620 10 : return;
4621 2 : }
4622 0 :
4623 0 : let first_range = Key {
4624 0 : field6: prev.key_range.end.field6 - 4,
4625 0 : ..prev.key_range.end
4626 0 : }..prev.key_range.end;
4627 0 :
4628 0 : let second_range = desc.key_range.start..Key {
4629 0 : field6: desc.key_range.start.field6 + 4,
4630 0 : ..desc.key_range.start
4631 0 : };
4632 0 :
4633 0 : reads.push(KeySpace {
4634 0 : ranges: vec![first_range, second_range],
4635 0 : });
4636 2 : };
4637 2 :
4638 2 : prev = Some(desc.clone());
4639 12 : });
4640 2 :
4641 2 : drop(guard);
4642 2 :
4643 2 : // Pick a big LSN such that we query over all the changes.
4644 2 : let reads_lsn = Lsn(u64::MAX - 1);
4645 2 :
4646 12 : for read in reads {
4647 10 : info!("Doing vectored read on {:?}", read);
4648 2 :
4649 26 : let vectored_res = tline.get_vectored_impl(read.clone(), reads_lsn, &ctx).await;
4650 10 : tline
4651 10 : .validate_get_vectored_impl(&vectored_res, read, reads_lsn, &ctx)
4652 20 : .await;
4653 2 : }
4654 2 :
4655 2 : Ok(())
4656 2 : }
4657 :
4658 : // Test that vectored get handles layer gaps correctly
4659 : // by advancing into the next ancestor timeline if required.
4660 : //
4661 : // The test generates timelines that look like the diagram below.
4662 : // We leave a gap in one of the L1 layers at `gap_at_key` (`/` in the diagram).
4663 : // The reconstruct data for that key lies in the ancestor timeline (`X` in the diagram).
4664 : //
4665 : // ```
4666 : //-------------------------------+
4667 : // ... |
4668 : // [ L1 ] |
4669 : // [ / L1 ] | Child Timeline
4670 : // ... |
4671 : // ------------------------------+
4672 : // [ X L1 ] | Parent Timeline
4673 : // ------------------------------+
4674 : // ```
4675 : #[tokio::test]
4676 2 : async fn test_get_vectored_key_gap() -> anyhow::Result<()> {
4677 2 : let tenant_conf = TenantConf {
4678 2 : // Make compaction deterministic
4679 2 : gc_period: Duration::ZERO,
4680 2 : compaction_period: Duration::ZERO,
4681 2 : // Encourage creation of L1 layers
4682 2 : checkpoint_distance: 16 * 1024,
4683 2 : compaction_target_size: 8 * 1024,
4684 2 : ..TenantConf::default()
4685 2 : };
4686 2 :
4687 2 : let harness = TenantHarness::create_custom("test_get_vectored_key_gap", tenant_conf)?;
4688 2 : let (tenant, ctx) = harness.load().await;
4689 2 :
4690 2 : let mut current_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
4691 2 : let gap_at_key = current_key.add(100);
4692 2 : let mut current_lsn = Lsn(0x10);
4693 2 :
4694 2 : const KEY_COUNT: usize = 10_000;
4695 2 :
4696 2 : let timeline_id = TimelineId::generate();
4697 2 : let current_timeline = tenant
4698 2 : .create_test_timeline(timeline_id, current_lsn, DEFAULT_PG_VERSION, &ctx)
4699 5 : .await?;
4700 2 :
4701 2 : current_lsn += 0x100;
4702 2 :
4703 2 : let mut writer = current_timeline.writer().await;
4704 2 : writer
4705 2 : .put(
4706 2 : gap_at_key,
4707 2 : current_lsn,
4708 2 : &Value::Image(test_img(&format!("{} at {}", gap_at_key, current_lsn))),
4709 2 : &ctx,
4710 2 : )
4711 2 : .await?;
4712 2 : writer.finish_write(current_lsn);
4713 2 : drop(writer);
4714 2 :
4715 2 : let mut latest_lsns = HashMap::new();
4716 2 : latest_lsns.insert(gap_at_key, current_lsn);
4717 2 :
4718 2 : current_timeline.freeze_and_flush().await?;
4719 2 :
4720 2 : let child_timeline_id = TimelineId::generate();
4721 2 :
4722 2 : tenant
4723 2 : .branch_timeline_test(
4724 2 : ¤t_timeline,
4725 2 : child_timeline_id,
4726 2 : Some(current_lsn),
4727 2 : &ctx,
4728 2 : )
4729 2 : .await?;
4730 2 : let child_timeline = tenant
4731 2 : .get_timeline(child_timeline_id, true)
4732 2 : .expect("Should have the branched timeline");
4733 2 :
4734 20002 : for i in 0..KEY_COUNT {
4735 20000 : if current_key == gap_at_key {
4736 2 : current_key = current_key.next();
4737 2 : continue;
4738 19998 : }
4739 19998 :
4740 19998 : current_lsn += 0x10;
4741 2 :
4742 19998 : let mut writer = child_timeline.writer().await;
4743 19998 : writer
4744 19998 : .put(
4745 19998 : current_key,
4746 19998 : current_lsn,
4747 19998 : &Value::Image(test_img(&format!("{} at {}", current_key, current_lsn))),
4748 19998 : &ctx,
4749 19998 : )
4750 581 : .await?;
4751 19998 : writer.finish_write(current_lsn);
4752 19998 : drop(writer);
4753 19998 :
4754 19998 : latest_lsns.insert(current_key, current_lsn);
4755 19998 : current_key = current_key.next();
4756 19998 :
4757 19998 : // Flush every now and then to encourage layer file creation.
4758 19998 : if i % 500 == 0 {
4759 40 : child_timeline.freeze_and_flush().await?;
4760 19958 : }
4761 2 : }
4762 2 :
4763 2 : child_timeline.freeze_and_flush().await?;
4764 2 : let mut flags = EnumSet::new();
4765 2 : flags.insert(CompactFlags::ForceRepartition);
4766 2 : child_timeline
4767 2 : .compact(&CancellationToken::new(), flags, &ctx)
4768 1123 : .await?;
4769 2 :
4770 2 : let key_near_end = {
4771 2 : let mut tmp = current_key;
4772 2 : tmp.field6 -= 10;
4773 2 : tmp
4774 2 : };
4775 2 :
4776 2 : let key_near_gap = {
4777 2 : let mut tmp = gap_at_key;
4778 2 : tmp.field6 -= 10;
4779 2 : tmp
4780 2 : };
4781 2 :
4782 2 : let read = KeySpace {
4783 2 : ranges: vec![key_near_gap..gap_at_key.next(), key_near_end..current_key],
4784 2 : };
4785 2 : let results = child_timeline
4786 2 : .get_vectored_impl(read.clone(), current_lsn, &ctx)
4787 15 : .await?;
4788 2 :
4789 44 : for (key, img_res) in results {
4790 42 : let expected = test_img(&format!("{} at {}", key, latest_lsns[&key]));
4791 42 : assert_eq!(img_res?, expected);
4792 2 : }
4793 2 :
4794 2 : Ok(())
4795 2 : }
4796 :
4797 : #[tokio::test]
4798 2 : async fn test_random_updates() -> anyhow::Result<()> {
4799 2 : let harness = TenantHarness::create("test_random_updates")?;
4800 2 : let (tenant, ctx) = harness.load().await;
4801 2 : let tline = tenant
4802 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4803 4 : .await?;
4804 2 :
4805 2 : const NUM_KEYS: usize = 1000;
4806 2 :
4807 2 : let mut test_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
4808 2 :
4809 2 : let mut keyspace = KeySpaceAccum::new();
4810 2 :
4811 2 : // Track when each page was last modified. Used to assert that
4812 2 : // a read sees the latest page version.
4813 2 : let mut updated = [Lsn(0); NUM_KEYS];
4814 2 :
4815 2 : let mut lsn = Lsn(0x10);
4816 2 : #[allow(clippy::needless_range_loop)]
4817 2002 : for blknum in 0..NUM_KEYS {
4818 2000 : lsn = Lsn(lsn.0 + 0x10);
4819 2000 : test_key.field6 = blknum as u32;
4820 2000 : let mut writer = tline.writer().await;
4821 2000 : writer
4822 2000 : .put(
4823 2000 : test_key,
4824 2000 : lsn,
4825 2000 : &Value::Image(test_img(&format!("{} at {}", blknum, lsn))),
4826 2000 : &ctx,
4827 2000 : )
4828 59 : .await?;
4829 2000 : writer.finish_write(lsn);
4830 2000 : updated[blknum] = lsn;
4831 2000 : drop(writer);
4832 2000 :
4833 2000 : keyspace.add_key(test_key);
4834 2 : }
4835 2 :
4836 102 : for _ in 0..50 {
4837 100100 : for _ in 0..NUM_KEYS {
4838 100000 : lsn = Lsn(lsn.0 + 0x10);
4839 100000 : let blknum = thread_rng().gen_range(0..NUM_KEYS);
4840 100000 : test_key.field6 = blknum as u32;
4841 100000 : let mut writer = tline.writer().await;
4842 100000 : writer
4843 100000 : .put(
4844 100000 : test_key,
4845 100000 : lsn,
4846 100000 : &Value::Image(test_img(&format!("{} at {}", blknum, lsn))),
4847 100000 : &ctx,
4848 100000 : )
4849 3103 : .await?;
4850 100000 : writer.finish_write(lsn);
4851 100000 : drop(writer);
4852 100000 : updated[blknum] = lsn;
4853 2 : }
4854 2 :
4855 2 : // Read all the blocks
4856 100000 : for (blknum, last_lsn) in updated.iter().enumerate() {
4857 100000 : test_key.field6 = blknum as u32;
4858 100000 : assert_eq!(
4859 100000 : tline.get(test_key, lsn, &ctx).await?,
4860 100000 : test_img(&format!("{} at {}", blknum, last_lsn))
4861 2 : );
4862 2 : }
4863 2 :
4864 2 : // Perform a cycle of flush, compact, and GC
4865 100 : let cutoff = tline.get_last_record_lsn();
4866 100 : tline
4867 100 : .update_gc_info(
4868 100 : Vec::new(),
4869 100 : cutoff,
4870 100 : Duration::ZERO,
4871 100 : &CancellationToken::new(),
4872 100 : &ctx,
4873 100 : )
4874 2 : .await?;
4875 101 : tline.freeze_and_flush().await?;
4876 100 : tline
4877 100 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4878 2608 : .await?;
4879 100 : tline.gc().await?;
4880 2 : }
4881 2 :
4882 2 : Ok(())
4883 2 : }
4884 :
4885 : #[tokio::test]
4886 2 : async fn test_traverse_branches() -> anyhow::Result<()> {
4887 2 : let (tenant, ctx) = TenantHarness::create("test_traverse_branches")?
4888 2 : .load()
4889 2 : .await;
4890 2 : let mut tline = tenant
4891 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4892 6 : .await?;
4893 2 :
4894 2 : const NUM_KEYS: usize = 1000;
4895 2 :
4896 2 : let mut test_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
4897 2 :
4898 2 : let mut keyspace = KeySpaceAccum::new();
4899 2 :
4900 2 : // Track when each page was last modified. Used to assert that
4901 2 : // a read sees the latest page version.
4902 2 : let mut updated = [Lsn(0); NUM_KEYS];
4903 2 :
4904 2 : let mut lsn = Lsn(0x10);
4905 2 : #[allow(clippy::needless_range_loop)]
4906 2002 : for blknum in 0..NUM_KEYS {
4907 2000 : lsn = Lsn(lsn.0 + 0x10);
4908 2000 : test_key.field6 = blknum as u32;
4909 2000 : let mut writer = tline.writer().await;
4910 2000 : writer
4911 2000 : .put(
4912 2000 : test_key,
4913 2000 : lsn,
4914 2000 : &Value::Image(test_img(&format!("{} at {}", blknum, lsn))),
4915 2000 : &ctx,
4916 2000 : )
4917 59 : .await?;
4918 2000 : writer.finish_write(lsn);
4919 2000 : updated[blknum] = lsn;
4920 2000 : drop(writer);
4921 2000 :
4922 2000 : keyspace.add_key(test_key);
4923 2 : }
4924 2 :
4925 102 : for _ in 0..50 {
4926 100 : let new_tline_id = TimelineId::generate();
4927 100 : tenant
4928 100 : .branch_timeline_test(&tline, new_tline_id, Some(lsn), &ctx)
4929 2 : .await?;
4930 100 : tline = tenant
4931 100 : .get_timeline(new_tline_id, true)
4932 100 : .expect("Should have the branched timeline");
4933 2 :
4934 100100 : for _ in 0..NUM_KEYS {
4935 100000 : lsn = Lsn(lsn.0 + 0x10);
4936 100000 : let blknum = thread_rng().gen_range(0..NUM_KEYS);
4937 100000 : test_key.field6 = blknum as u32;
4938 100000 : let mut writer = tline.writer().await;
4939 100000 : writer
4940 100000 : .put(
4941 100000 : test_key,
4942 100000 : lsn,
4943 100000 : &Value::Image(test_img(&format!("{} at {}", blknum, lsn))),
4944 100000 : &ctx,
4945 100000 : )
4946 3088 : .await?;
4947 100000 : println!("updating {} at {}", blknum, lsn);
4948 100000 : writer.finish_write(lsn);
4949 100000 : drop(writer);
4950 100000 : updated[blknum] = lsn;
4951 2 : }
4952 2 :
4953 2 : // Read all the blocks
4954 100000 : for (blknum, last_lsn) in updated.iter().enumerate() {
4955 100000 : test_key.field6 = blknum as u32;
4956 100000 : assert_eq!(
4957 100000 : tline.get(test_key, lsn, &ctx).await?,
4958 100000 : test_img(&format!("{} at {}", blknum, last_lsn))
4959 2 : );
4960 2 : }
4961 2 :
4962 2 : // Perform a cycle of flush, compact, and GC
4963 100 : let cutoff = tline.get_last_record_lsn();
4964 100 : tline
4965 100 : .update_gc_info(
4966 100 : Vec::new(),
4967 100 : cutoff,
4968 100 : Duration::ZERO,
4969 100 : &CancellationToken::new(),
4970 100 : &ctx,
4971 100 : )
4972 2 : .await?;
4973 100 : tline.freeze_and_flush().await?;
4974 100 : tline
4975 100 : .compact(&CancellationToken::new(), EnumSet::empty(), &ctx)
4976 13204 : .await?;
4977 100 : tline.gc().await?;
4978 2 : }
4979 2 :
4980 2 : Ok(())
4981 2 : }
4982 :
4983 : #[tokio::test]
4984 2 : async fn test_traverse_ancestors() -> anyhow::Result<()> {
4985 2 : let (tenant, ctx) = TenantHarness::create("test_traverse_ancestors")?
4986 2 : .load()
4987 2 : .await;
4988 2 : let mut tline = tenant
4989 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
4990 6 : .await?;
4991 2 :
4992 2 : const NUM_KEYS: usize = 100;
4993 2 : const NUM_TLINES: usize = 50;
4994 2 :
4995 2 : let mut test_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
4996 2 : // Track page mutation lsns across different timelines.
4997 2 : let mut updated = [[Lsn(0); NUM_KEYS]; NUM_TLINES];
4998 2 :
4999 2 : let mut lsn = Lsn(0x10);
5000 2 :
5001 2 : #[allow(clippy::needless_range_loop)]
5002 102 : for idx in 0..NUM_TLINES {
5003 100 : let new_tline_id = TimelineId::generate();
5004 100 : tenant
5005 100 : .branch_timeline_test(&tline, new_tline_id, Some(lsn), &ctx)
5006 2 : .await?;
5007 100 : tline = tenant
5008 100 : .get_timeline(new_tline_id, true)
5009 100 : .expect("Should have the branched timeline");
5010 2 :
5011 10100 : for _ in 0..NUM_KEYS {
5012 10000 : lsn = Lsn(lsn.0 + 0x10);
5013 10000 : let blknum = thread_rng().gen_range(0..NUM_KEYS);
5014 10000 : test_key.field6 = blknum as u32;
5015 10000 : let mut writer = tline.writer().await;
5016 10000 : writer
5017 10000 : .put(
5018 10000 : test_key,
5019 10000 : lsn,
5020 10000 : &Value::Image(test_img(&format!("{} {} at {}", idx, blknum, lsn))),
5021 10000 : &ctx,
5022 10000 : )
5023 318 : .await?;
5024 10000 : println!("updating [{}][{}] at {}", idx, blknum, lsn);
5025 10000 : writer.finish_write(lsn);
5026 10000 : drop(writer);
5027 10000 : updated[idx][blknum] = lsn;
5028 2 : }
5029 2 : }
5030 2 :
5031 2 : // Read pages from leaf timeline across all ancestors.
5032 100 : for (idx, lsns) in updated.iter().enumerate() {
5033 10000 : for (blknum, lsn) in lsns.iter().enumerate() {
5034 2 : // Skip empty mutations.
5035 10000 : if lsn.0 == 0 {
5036 3635 : continue;
5037 6365 : }
5038 6365 : println!("checking [{idx}][{blknum}] at {lsn}");
5039 6365 : test_key.field6 = blknum as u32;
5040 6365 : assert_eq!(
5041 6365 : tline.get(test_key, *lsn, &ctx).await?,
5042 6365 : test_img(&format!("{idx} {blknum} at {lsn}"))
5043 2 : );
5044 2 : }
5045 2 : }
5046 2 : Ok(())
5047 2 : }
5048 :
5049 : #[tokio::test]
5050 2 : async fn test_write_at_initdb_lsn_takes_optimization_code_path() -> anyhow::Result<()> {
5051 2 : let (tenant, ctx) = TenantHarness::create("test_empty_test_timeline_is_usable")?
5052 2 : .load()
5053 2 : .await;
5054 2 :
5055 2 : let initdb_lsn = Lsn(0x20);
5056 2 : let utline = tenant
5057 2 : .create_empty_timeline(TIMELINE_ID, initdb_lsn, DEFAULT_PG_VERSION, &ctx)
5058 2 : .await?;
5059 2 : let tline = utline.raw_timeline().unwrap();
5060 2 :
5061 2 : // Spawn flush loop now so that we can set the `expect_initdb_optimization`
5062 2 : tline.maybe_spawn_flush_loop();
5063 2 :
5064 2 : // Make sure the timeline has the minimum set of required keys for operation.
5065 2 : // The only operation you can always do on an empty timeline is to `put` new data.
5066 2 : // Except if you `put` at `initdb_lsn`.
5067 2 : // In that case, there's an optimization to directly create image layers instead of delta layers.
5068 2 : // It uses `repartition()`, which assumes some keys to be present.
5069 2 : // Let's make sure the test timeline can handle that case.
5070 2 : {
5071 2 : let mut state = tline.flush_loop_state.lock().unwrap();
5072 2 : assert_eq!(
5073 2 : timeline::FlushLoopState::Running {
5074 2 : expect_initdb_optimization: false,
5075 2 : initdb_optimization_count: 0,
5076 2 : },
5077 2 : *state
5078 2 : );
5079 2 : *state = timeline::FlushLoopState::Running {
5080 2 : expect_initdb_optimization: true,
5081 2 : initdb_optimization_count: 0,
5082 2 : };
5083 2 : }
5084 2 :
5085 2 : // Make writes at the initdb_lsn. When we flush it below, it should be handled by the optimization.
5086 2 : // As explained above, the optimization requires some keys to be present.
5087 2 : // As per `create_empty_timeline` documentation, use init_empty to set them.
5088 2 : // This is what `create_test_timeline` does, by the way.
5089 2 : let mut modification = tline.begin_modification(initdb_lsn);
5090 2 : modification
5091 2 : .init_empty_test_timeline()
5092 2 : .context("init_empty_test_timeline")?;
5093 2 : modification
5094 2 : .commit(&ctx)
5095 2 : .await
5096 2 : .context("commit init_empty_test_timeline modification")?;
5097 2 :
5098 2 : // Do the flush. The flush code will check the expectations that we set above.
5099 2 : tline.freeze_and_flush().await?;
5100 2 :
5101 2 : // assert freeze_and_flush exercised the initdb optimization
5102 2 : {
5103 2 : let state = tline.flush_loop_state.lock().unwrap();
5104 2 : let timeline::FlushLoopState::Running {
5105 2 : expect_initdb_optimization,
5106 2 : initdb_optimization_count,
5107 2 : } = *state
5108 2 : else {
5109 2 : panic!("unexpected state: {:?}", *state);
5110 2 : };
5111 2 : assert!(expect_initdb_optimization);
5112 2 : assert!(initdb_optimization_count > 0);
5113 2 : }
5114 2 : Ok(())
5115 2 : }
5116 :
5117 : #[tokio::test]
5118 2 : async fn test_create_guard_crash() -> anyhow::Result<()> {
5119 2 : let name = "test_create_guard_crash";
5120 2 : let harness = TenantHarness::create(name)?;
5121 2 : {
5122 2 : let (tenant, ctx) = harness.load().await;
5123 2 : let tline = tenant
5124 2 : .create_empty_timeline(TIMELINE_ID, Lsn(0), DEFAULT_PG_VERSION, &ctx)
5125 2 : .await?;
5126 2 : // Leave the timeline ID in [`Tenant::timelines_creating`] to exclude attempting to create it again
5127 2 : let raw_tline = tline.raw_timeline().unwrap();
5128 2 : raw_tline
5129 2 : .shutdown(super::timeline::ShutdownMode::Hard)
5130 2 : .instrument(info_span!("test_shutdown", tenant_id=%raw_tline.tenant_shard_id, shard_id=%raw_tline.tenant_shard_id.shard_slug(), timeline_id=%TIMELINE_ID))
5131 2 : .await;
5132 2 : std::mem::forget(tline);
5133 2 : }
5134 2 :
5135 2 : let (tenant, _) = harness.load().await;
5136 2 : match tenant.get_timeline(TIMELINE_ID, false) {
5137 2 : Ok(_) => panic!("timeline should've been removed during load"),
5138 2 : Err(e) => {
5139 2 : assert_eq!(
5140 2 : e,
5141 2 : GetTimelineError::NotFound {
5142 2 : tenant_id: tenant.tenant_shard_id,
5143 2 : timeline_id: TIMELINE_ID,
5144 2 : }
5145 2 : )
5146 2 : }
5147 2 : }
5148 2 :
5149 2 : assert!(!harness
5150 2 : .conf
5151 2 : .timeline_path(&tenant.tenant_shard_id, &TIMELINE_ID)
5152 2 : .exists());
5153 2 :
5154 2 : Ok(())
5155 2 : }
5156 :
5157 : #[tokio::test]
5158 2 : async fn test_read_at_max_lsn() -> anyhow::Result<()> {
5159 2 : let harness = TenantHarness::create("test_read_at_max_lsn")?;
5160 2 : let (tenant, ctx) = harness.load().await;
5161 2 : let tline = tenant
5162 2 : .create_test_timeline(TIMELINE_ID, Lsn(0x08), DEFAULT_PG_VERSION, &ctx)
5163 6 : .await?;
5164 2 :
5165 2 : let lsn = Lsn(0x10);
5166 58861 : bulk_insert_compact_gc(tline.clone(), &ctx, lsn, 50, 10000).await?;
5167 2 :
5168 2 : let test_key = Key::from_hex("010000000033333333444444445500000000").unwrap();
5169 2 : let read_lsn = Lsn(u64::MAX - 1);
5170 2 :
5171 6 : assert!(tline.get(test_key, read_lsn, &ctx).await.is_ok());
5172 2 :
5173 2 : Ok(())
5174 2 : }
5175 : }
|