Remove [Un]Loaded timeline code (#2359)

This commit is contained in:
Kirill Bulatov
2022-09-01 16:22:22 +03:00
committed by Kirill Bulatov
parent f78a542cba
commit 2db20e5587
12 changed files with 290 additions and 476 deletions

View File

@@ -8,7 +8,6 @@ use utils::{
};
// These enums are used in the API response fields.
use crate::repository::LocalTimelineState;
use crate::tenant_mgr::TenantState;
#[serde_as]
@@ -133,7 +132,6 @@ pub struct LocalTimelineInfo {
pub current_physical_size: Option<u64>, // is None when timeline is Unloaded
pub current_logical_size_non_incremental: Option<u64>,
pub current_physical_size_non_incremental: Option<u64>,
pub timeline_state: LocalTimelineState,
pub wal_source_connstr: Option<String>,
#[serde_as(as = "Option<DisplayFromStr>")]

View File

@@ -11,8 +11,7 @@ use super::models::{
StatusResponse, TenantConfigRequest, TenantCreateRequest, TenantCreateResponse, TenantInfo,
TimelineCreateRequest,
};
use crate::layered_repository::{metadata::TimelineMetadata, Timeline};
use crate::repository::{LocalTimelineState, RepositoryTimeline};
use crate::layered_repository::Timeline;
use crate::storage_sync;
use crate::storage_sync::index::{RemoteIndex, RemoteTimeline};
use crate::tenant_config::TenantConfOpt;
@@ -74,7 +73,7 @@ fn get_config(request: &Request<Body>) -> &'static PageServerConf {
// Helper functions to construct a LocalTimelineInfo struct for a timeline
fn local_timeline_info_from_loaded_timeline(
fn local_timeline_info_from_timeline(
timeline: &Arc<Timeline>,
include_non_incremental_logical_size: bool,
include_non_incremental_physical_size: bool,
@@ -105,7 +104,6 @@ fn local_timeline_info_from_loaded_timeline(
last_record_lsn,
prev_record_lsn: Some(timeline.get_prev_record_lsn()),
latest_gc_cutoff_lsn: *timeline.get_latest_gc_cutoff_lsn(),
timeline_state: LocalTimelineState::Loaded,
current_logical_size: Some(
timeline
.get_current_logical_size()
@@ -129,61 +127,20 @@ fn local_timeline_info_from_loaded_timeline(
Ok(info)
}
fn local_timeline_info_from_unloaded_timeline(metadata: &TimelineMetadata) -> LocalTimelineInfo {
LocalTimelineInfo {
ancestor_timeline_id: metadata.ancestor_timeline(),
ancestor_lsn: {
match metadata.ancestor_lsn() {
Lsn(0) => None,
lsn @ Lsn(_) => Some(lsn),
}
},
disk_consistent_lsn: metadata.disk_consistent_lsn(),
last_record_lsn: metadata.disk_consistent_lsn(),
prev_record_lsn: metadata.prev_record_lsn(),
latest_gc_cutoff_lsn: metadata.latest_gc_cutoff_lsn(),
timeline_state: LocalTimelineState::Unloaded,
current_logical_size: None,
current_physical_size: None,
current_logical_size_non_incremental: None,
current_physical_size_non_incremental: None,
wal_source_connstr: None,
last_received_msg_lsn: None,
last_received_msg_ts: None,
}
}
fn local_timeline_info_from_repo_timeline(
repo_timeline: &RepositoryTimeline<Timeline>,
include_non_incremental_logical_size: bool,
include_non_incremental_physical_size: bool,
) -> anyhow::Result<LocalTimelineInfo> {
match repo_timeline {
RepositoryTimeline::Loaded(timeline) => local_timeline_info_from_loaded_timeline(
timeline,
include_non_incremental_logical_size,
include_non_incremental_physical_size,
),
RepositoryTimeline::Unloaded { metadata } => {
Ok(local_timeline_info_from_unloaded_timeline(metadata))
}
}
}
fn list_local_timelines(
tenant_id: ZTenantId,
include_non_incremental_logical_size: bool,
include_non_incremental_physical_size: bool,
) -> Result<Vec<(ZTimelineId, LocalTimelineInfo)>> {
let repo = tenant_mgr::get_repository_for_tenant(tenant_id)
.with_context(|| format!("Failed to get repo for tenant {}", tenant_id))?;
.with_context(|| format!("Failed to get repo for tenant {tenant_id}"))?;
let repo_timelines = repo.list_timelines();
let mut local_timeline_info = Vec::with_capacity(repo_timelines.len());
for (timeline_id, repository_timeline) in repo_timelines {
local_timeline_info.push((
timeline_id,
local_timeline_info_from_repo_timeline(
local_timeline_info_from_timeline(
&repository_timeline,
include_non_incremental_logical_size,
include_non_incremental_physical_size,
@@ -214,12 +171,12 @@ async fn timeline_create_handler(mut request: Request<Body>) -> Result<Response<
request_data.ancestor_timeline_id.map(ZTimelineId::from),
request_data.ancestor_start_lsn,
) {
Ok(Some((new_timeline_id, new_timeline))) => {
Ok(Some(new_timeline)) => {
// Created. Construct a TimelineInfo for it.
let local_info = local_timeline_info_from_loaded_timeline(&new_timeline, false, false)?;
let local_info = local_timeline_info_from_timeline(&new_timeline, false, false)?;
Ok(Some(TimelineInfo {
tenant_id,
timeline_id: new_timeline_id,
timeline_id: new_timeline.timeline_id,
local: Some(local_info),
remote: None,
}))
@@ -311,7 +268,7 @@ async fn timeline_detail_handler(request: Request<Body>) -> Result<Response<Body
repo.get_timeline(timeline_id)
.as_ref()
.map(|timeline| {
local_timeline_info_from_repo_timeline(
local_timeline_info_from_timeline(
timeline,
include_non_incremental_logical_size,
include_non_incremental_physical_size,

View File

@@ -16,6 +16,7 @@ use tracing::*;
use utils::zid::ZTenantTimelineId;
use std::cmp::min;
use std::collections::hash_map;
use std::collections::hash_map::Entry;
use std::collections::BTreeSet;
use std::collections::HashMap;
@@ -24,7 +25,9 @@ use std::fs::File;
use std::num::NonZeroU64;
use std::ops::Bound::Included;
use std::path::Path;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::Arc;
use std::sync::MutexGuard;
use std::sync::{Mutex, RwLock};
use std::time::{Duration, Instant};
use self::metadata::{metadata_path, TimelineMetadata};
@@ -32,7 +35,7 @@ use crate::config::PageServerConf;
use crate::storage_sync::index::RemoteIndex;
use crate::tenant_config::{TenantConf, TenantConfOpt};
use crate::repository::{GcResult, RepositoryTimeline};
use crate::repository::GcResult;
use crate::tenant_mgr::LocalTimelineUpdate;
use crate::thread_mgr;
use crate::walredo::WalRedoManager;
@@ -61,7 +64,6 @@ mod storage_layer;
mod timeline;
use storage_layer::Layer;
use timeline::LayeredTimelineEntry;
pub use timeline::Timeline;
@@ -103,7 +105,7 @@ pub struct Repository {
tenant_conf: Arc<RwLock<TenantConfOpt>>,
tenant_id: ZTenantId,
timelines: Mutex<HashMap<ZTimelineId, LayeredTimelineEntry>>,
timelines: Mutex<HashMap<ZTimelineId, Arc<Timeline>>>,
// This mutex prevents creation of new timelines during GC.
// Adding yet another mutex (in addition to `timelines`) is needed because holding
// `timelines` mutex during all GC iteration (especially with enforced checkpoint)
@@ -126,37 +128,18 @@ pub struct Repository {
impl Repository {
/// Get Timeline handle for given zenith timeline ID.
/// This function is idempotent. It doesn't change internal state in any way.
pub fn get_timeline(&self, timelineid: ZTimelineId) -> Option<RepositoryTimeline<Timeline>> {
self.timelines
.lock()
.unwrap()
.get(&timelineid)
.cloned()
.map(RepositoryTimeline::from)
}
/// Get Timeline handle for locally available timeline. Load it into memory if it is not loaded.
pub fn get_timeline_load(&self, timeline_id: ZTimelineId) -> Result<Arc<Timeline>> {
let mut timelines = self.timelines.lock().unwrap();
match self.get_timeline_load_internal(timeline_id, &mut timelines)? {
Some(local_loaded_timeline) => Ok(local_loaded_timeline),
None => anyhow::bail!("cannot get local timeline, unknown timeline id: {timeline_id}"),
}
pub fn get_timeline(&self, timeline_id: ZTimelineId) -> Option<Arc<Timeline>> {
self.timelines.lock().unwrap().get(&timeline_id).cloned()
}
/// Lists timelines the repository contains.
/// Up to repository's implementation to omit certain timelines that ar not considered ready for use.
pub fn list_timelines(&self) -> Vec<(ZTimelineId, RepositoryTimeline<Timeline>)> {
pub fn list_timelines(&self) -> Vec<(ZTimelineId, Arc<Timeline>)> {
self.timelines
.lock()
.unwrap()
.iter()
.map(|(timeline_id, timeline_entry)| {
(
*timeline_id,
RepositoryTimeline::from(timeline_entry.clone()),
)
})
.map(|(timeline_id, timeline_entry)| (*timeline_id, Arc::clone(timeline_entry)))
.collect()
}
@@ -164,16 +147,18 @@ impl Repository {
/// Initdb lsn is provided for timeline impl to be able to perform checks for some operations against it.
pub fn create_empty_timeline(
&self,
timeline_id: ZTimelineId,
new_timeline_id: ZTimelineId,
initdb_lsn: Lsn,
) -> Result<Arc<Timeline>> {
// XXX: keep the lock to avoid races during timeline creation
let mut timelines = self.timelines.lock().unwrap();
let vacant_timeline_entry = match timelines.entry(timeline_id) {
Entry::Occupied(_) => bail!("Timeline already exists"),
Entry::Vacant(vacant_entry) => vacant_entry,
};
let timeline_path = self.conf.timeline_path(&timeline_id, &self.tenant_id);
anyhow::ensure!(
timelines.get(&new_timeline_id).is_none(),
"Timeline {new_timeline_id} already exists"
);
let timeline_path = self.conf.timeline_path(&new_timeline_id, &self.tenant_id);
if timeline_path.exists() {
bail!("Timeline directory already exists, but timeline is missing in repository map. This is a bug.")
}
@@ -181,31 +166,25 @@ impl Repository {
// Create the timeline directory, and write initial metadata to file.
crashsafe_dir::create_dir_all(timeline_path)?;
let metadata = TimelineMetadata::new(Lsn(0), None, None, Lsn(0), initdb_lsn, initdb_lsn);
save_metadata(self.conf, timeline_id, self.tenant_id, &metadata, true)?;
let timeline = Timeline::new(
let new_metadata =
TimelineMetadata::new(Lsn(0), None, None, Lsn(0), initdb_lsn, initdb_lsn);
save_metadata(
self.conf,
Arc::clone(&self.tenant_conf),
metadata,
None,
timeline_id,
new_timeline_id,
self.tenant_id,
Arc::clone(&self.walredo_mgr),
self.upload_layers,
);
timeline.layers.write().unwrap().next_open_layer_at = Some(initdb_lsn);
&new_metadata,
true,
)?;
// Insert if not exists
let timeline = Arc::new(timeline);
vacant_timeline_entry.insert(LayeredTimelineEntry::Loaded(Arc::clone(&timeline)));
let new_timeline =
self.initialize_new_timeline(new_timeline_id, new_metadata, &mut timelines)?;
new_timeline.layers.write().unwrap().next_open_layer_at = Some(initdb_lsn);
crate::tenant_mgr::try_send_timeline_update(LocalTimelineUpdate::Attach {
id: ZTenantTimelineId::new(self.tenant_id(), timeline_id),
timeline: Arc::clone(&timeline),
});
if let hash_map::Entry::Vacant(v) = timelines.entry(new_timeline_id) {
v.insert(Arc::clone(&new_timeline));
}
Ok(timeline)
Ok(new_timeline)
}
/// Branch a timeline
@@ -214,7 +193,7 @@ impl Repository {
src: ZTimelineId,
dst: ZTimelineId,
start_lsn: Option<Lsn>,
) -> Result<()> {
) -> Result<Arc<Timeline>> {
// We need to hold this lock to prevent GC from starting at the same time. GC scans the directory to learn
// about timelines, so otherwise a race condition is possible, where we create new timeline and GC
// concurrently removes data that is needed by the new timeline.
@@ -229,12 +208,12 @@ impl Repository {
// Step 2 is to avoid initializing the new branch using data removed by past GC iterations
// or in-queue GC iterations.
// XXX: keep the lock to avoid races during timeline creation
let mut timelines = self.timelines.lock().unwrap();
let src_timeline = self
.get_timeline_load_internal(src, &mut timelines)
let src_timeline = timelines
.get(&src)
// message about timeline being remote is one .context up in the stack
.context("failed to load timeline for branching")?
.ok_or_else(|| anyhow::anyhow!("unknown timeline id: {}", &src))?;
.ok_or_else(|| anyhow::anyhow!("unknown timeline id: {src}"))?;
let latest_gc_cutoff_lsn = src_timeline.get_latest_gc_cutoff_lsn();
@@ -252,7 +231,7 @@ impl Repository {
.check_lsn_is_in_scope(start_lsn, &latest_gc_cutoff_lsn)
.context(format!(
"invalid branch start lsn: less than latest GC cutoff {}",
*latest_gc_cutoff_lsn
*latest_gc_cutoff_lsn,
))?;
{
let gc_info = src_timeline.gc_info.read().unwrap();
@@ -293,11 +272,13 @@ impl Repository {
);
crashsafe_dir::create_dir_all(self.conf.timeline_path(&dst, &self.tenant_id))?;
save_metadata(self.conf, dst, self.tenant_id, &metadata, true)?;
timelines.insert(dst, LayeredTimelineEntry::Unloaded { id: dst, metadata });
info!("branched timeline {} from {} at {}", dst, src, start_lsn);
let new_timeline = self.initialize_new_timeline(dst, metadata, &mut timelines)?;
timelines.insert(dst, Arc::clone(&new_timeline));
Ok(())
info!("branched timeline {dst} from {src} at {start_lsn}");
Ok(new_timeline)
}
/// perform one garbage collection iteration, removing old data files from disk.
@@ -346,14 +327,7 @@ impl Repository {
for (timelineid, timeline) in &timelines_to_compact {
let _entered =
info_span!("compact", timeline = %timelineid, tenant = %self.tenant_id).entered();
match timeline {
LayeredTimelineEntry::Loaded(timeline) => {
timeline.compact()?;
}
LayeredTimelineEntry::Unloaded { .. } => {
debug!("Cannot compact remote timeline {}", timelineid)
}
}
timeline.compact()?;
}
Ok(())
@@ -371,15 +345,7 @@ impl Repository {
let timelines = self.timelines.lock().unwrap();
let timelines_to_compact = timelines
.iter()
// filter to get only loaded timelines
.filter_map(|(timelineid, entry)| match entry {
LayeredTimelineEntry::Loaded(timeline) => Some((timelineid, timeline)),
LayeredTimelineEntry::Unloaded { .. } => {
debug!("Skipping checkpoint for unloaded timeline {}", timelineid);
None
}
})
.map(|(timelineid, timeline)| (*timelineid, timeline.clone()))
.map(|(timelineid, timeline)| (*timelineid, Arc::clone(timeline)))
.collect::<Vec<_>>();
drop(timelines);
@@ -403,7 +369,7 @@ impl Repository {
// because detach removes files, which will break child branches
let children_exist = timelines
.iter()
.any(|(_, entry)| entry.ancestor_timeline_id() == Some(timeline_id));
.any(|(_, entry)| entry.get_ancestor_timeline_id() == Some(timeline_id));
ensure!(
!children_exist,
@@ -431,19 +397,36 @@ impl Repository {
Ok(())
}
/// Updates timeline based on the `TimelineSyncStatusUpdate`, received from the remote storage synchronization.
/// See [`crate::remote_storage`] for more details about the synchronization.
pub fn attach_timeline(&self, timeline_id: ZTimelineId) -> Result<()> {
debug!("attach timeline_id: {}", timeline_id,);
match self.timelines.lock().unwrap().entry(timeline_id) {
Entry::Occupied(_) => bail!("We completed a download for a timeline that already exists in repository. This is a bug."),
Entry::Vacant(entry) => {
// we need to get metadata of a timeline, another option is to pass it along with Downloaded status
let metadata = load_metadata(self.conf, timeline_id, self.tenant_id).context("failed to load local metadata")?;
// finally we make newly downloaded timeline visible to repository
entry.insert(LayeredTimelineEntry::Unloaded { id: timeline_id, metadata })
},
pub fn init_attach_timelines(
&self,
timelines: Vec<(ZTimelineId, TimelineMetadata)>,
) -> anyhow::Result<()> {
let sorted_timelines = if timelines.len() == 1 {
timelines
} else if !timelines.is_empty() {
tree_sort_timelines(timelines)?
} else {
warn!("No timelines to attach received");
return Ok(());
};
let mut timelines_accessor = self.timelines.lock().unwrap();
for (timeline_id, metadata) in sorted_timelines {
let timeline = self
.initialize_new_timeline(timeline_id, metadata, &mut timelines_accessor)
.with_context(|| format!("Failed to initialize timeline {timeline_id}"))?;
match timelines_accessor.entry(timeline.timeline_id) {
hash_map::Entry::Occupied(_) => anyhow::bail!(
"Found freshly initialized timeline {} in the tenant map",
timeline.timeline_id
),
hash_map::Entry::Vacant(v) => {
v.insert(timeline);
}
}
}
Ok(())
}
@@ -453,6 +436,49 @@ impl Repository {
}
}
/// Given a Vec of timelines and their ancestors (timeline_id, ancestor_id),
/// perform a topological sort, so that the parent of each timeline comes
/// before the children.
fn tree_sort_timelines(
timelines: Vec<(ZTimelineId, TimelineMetadata)>,
) -> Result<Vec<(ZTimelineId, TimelineMetadata)>> {
let mut result = Vec::with_capacity(timelines.len());
let mut now = Vec::with_capacity(timelines.len());
// (ancestor, children)
let mut later: HashMap<ZTimelineId, Vec<(ZTimelineId, TimelineMetadata)>> =
HashMap::with_capacity(timelines.len());
for (timeline_id, metadata) in timelines {
if let Some(ancestor_id) = metadata.ancestor_timeline() {
let children = later.entry(ancestor_id).or_default();
children.push((timeline_id, metadata));
} else {
now.push((timeline_id, metadata));
}
}
while let Some((timeline_id, metadata)) = now.pop() {
result.push((timeline_id, metadata));
// All children of this can be loaded now
if let Some(mut children) = later.remove(&timeline_id) {
now.append(&mut children);
}
}
// All timelines should be visited now. Unless there were timelines with missing ancestors.
if !later.is_empty() {
for (missing_id, orphan_ids) in later {
for (orphan_id, _) in orphan_ids {
error!("could not load timeline {orphan_id} because its ancestor timeline {missing_id} could not be loaded");
}
}
bail!("could not load tenant because some timelines are missing ancestors");
}
Ok(result)
}
/// Private functions
impl Repository {
pub fn get_checkpoint_distance(&self) -> u64 {
@@ -548,87 +574,49 @@ impl Repository {
Ok(())
}
// Implementation of the public `get_timeline_load` function.
// Differences from the public:
// * interface in that the caller must already hold the mutex on the 'timelines' hashmap.
fn get_timeline_load_internal(
fn initialize_new_timeline(
&self,
timeline_id: ZTimelineId,
timelines: &mut HashMap<ZTimelineId, LayeredTimelineEntry>,
) -> anyhow::Result<Option<Arc<Timeline>>> {
Ok(match timelines.get(&timeline_id) {
Some(entry) => match entry {
LayeredTimelineEntry::Loaded(local_timeline) => {
debug!("timeline {timeline_id} found loaded into memory");
Some(Arc::clone(local_timeline))
}
LayeredTimelineEntry::Unloaded { .. } => {
debug!(
"timeline {timeline_id} found on a local disk, but not loaded into the memory, loading"
);
let timeline = self.load_local_timeline(timeline_id, timelines)?;
let was_loaded = timelines.insert(
timeline_id,
LayeredTimelineEntry::Loaded(Arc::clone(&timeline)),
);
ensure!(
was_loaded.is_none()
|| matches!(was_loaded, Some(LayeredTimelineEntry::Unloaded { .. })),
"assertion failure, inserted wrong timeline in an incorrect state"
);
Some(timeline)
}
},
None => {
debug!("timeline {timeline_id} not found");
None
}
})
}
fn load_local_timeline(
&self,
timeline_id: ZTimelineId,
timelines: &mut HashMap<ZTimelineId, LayeredTimelineEntry>,
new_timeline_id: ZTimelineId,
new_metadata: TimelineMetadata,
timelines: &mut MutexGuard<HashMap<ZTimelineId, Arc<Timeline>>>,
) -> anyhow::Result<Arc<Timeline>> {
let metadata = load_metadata(self.conf, timeline_id, self.tenant_id)
.context("failed to load metadata")?;
let disk_consistent_lsn = metadata.disk_consistent_lsn();
let ancestor = match new_metadata.ancestor_timeline() {
Some(ancestor_timeline_id) => Some(
timelines
.get(&ancestor_timeline_id)
.cloned()
.with_context(|| {
format!(
"Timeline's {new_timeline_id} ancestor {ancestor_timeline_id} was not found"
)
})?,
),
None => None,
};
let ancestor = metadata
.ancestor_timeline()
.map(|ancestor_timeline_id| {
trace!("loading {timeline_id}'s ancestor {}", &ancestor_timeline_id);
self.get_timeline_load_internal(ancestor_timeline_id, timelines)
})
.transpose()
.context("cannot load ancestor timeline")?
.flatten()
.map(LayeredTimelineEntry::Loaded);
let _enter = info_span!("loading local timeline").entered();
let new_disk_consistent_lsn = new_metadata.disk_consistent_lsn();
let timeline = Timeline::new(
let new_timeline = Arc::new(Timeline::new(
self.conf,
Arc::clone(&self.tenant_conf),
metadata,
new_metadata,
ancestor,
timeline_id,
new_timeline_id,
self.tenant_id,
Arc::clone(&self.walredo_mgr),
self.upload_layers,
);
timeline
.load_layer_map(disk_consistent_lsn)
));
new_timeline
.load_layer_map(new_disk_consistent_lsn)
.context("failed to load layermap")?;
let timeline = Arc::new(timeline);
crate::tenant_mgr::try_send_timeline_update(LocalTimelineUpdate::Attach {
id: ZTenantTimelineId::new(self.tenant_id(), timeline_id),
timeline: Arc::clone(&timeline),
id: ZTenantTimelineId::new(self.tenant_id(), new_timeline_id),
timeline: Arc::clone(&new_timeline),
});
Ok(timeline)
Ok(new_timeline)
}
pub fn new(
@@ -775,18 +763,20 @@ impl Repository {
// This is unresolved question for now, how to do gc in presence of remote timelines
// especially when this is combined with branching.
// Somewhat related: https://github.com/zenithdb/zenith/issues/999
if let Some(ancestor_timeline_id) = &timeline_entry.ancestor_timeline_id() {
if let Some(ancestor_timeline_id) = &timeline_entry.get_ancestor_timeline_id() {
// If target_timeline is specified, we only need to know branchpoints of its children
if let Some(timelineid) = target_timeline_id {
if ancestor_timeline_id == &timelineid {
all_branchpoints
.insert((*ancestor_timeline_id, timeline_entry.ancestor_lsn()));
all_branchpoints.insert((
*ancestor_timeline_id,
timeline_entry.get_ancestor_lsn(),
));
}
}
// Collect branchpoints for all timelines
else {
all_branchpoints
.insert((*ancestor_timeline_id, timeline_entry.ancestor_lsn()));
.insert((*ancestor_timeline_id, timeline_entry.get_ancestor_lsn()));
}
}
@@ -801,7 +791,9 @@ impl Repository {
let mut gc_timelines = Vec::with_capacity(timeline_ids.len());
for timeline_id in timeline_ids {
// Timeline is known to be local and loaded.
let timeline = self.get_timeline_load(timeline_id)?;
let timeline = self
.get_timeline(timeline_id)
.with_context(|| format!("Timeline {timeline_id} was not found"))?;
// If target_timeline is specified, ignore all other timelines
if let Some(target_timelineid) = target_timeline_id {
@@ -1031,20 +1023,21 @@ pub mod repo_harness {
false,
);
// populate repo with locally available timelines
let mut timelines_to_load = Vec::new();
for timeline_dir_entry in fs::read_dir(self.conf.timelines_path(&self.tenant_id))
.expect("should be able to read timelines dir")
{
let timeline_dir_entry = timeline_dir_entry.unwrap();
let timeline_dir_entry = timeline_dir_entry?;
let timeline_id: ZTimelineId = timeline_dir_entry
.path()
.file_name()
.unwrap()
.to_string_lossy()
.parse()
.unwrap();
repo.attach_timeline(timeline_id)?;
.parse()?;
let timeline_metadata = load_metadata(self.conf, timeline_id, self.tenant_id)?;
timelines_to_load.push((timeline_id, timeline_metadata));
}
repo.init_attach_timelines(timelines_to_load)?;
Ok(repo)
}
@@ -1127,7 +1120,10 @@ mod tests {
match repo.create_empty_timeline(TIMELINE_ID, Lsn(0)) {
Ok(_) => panic!("duplicate timeline creation should fail"),
Err(e) => assert_eq!(e.to_string(), "Timeline already exists"),
Err(e) => assert_eq!(
e.to_string(),
format!("Timeline {TIMELINE_ID} already exists")
),
}
Ok(())
@@ -1170,7 +1166,7 @@ mod tests {
// Branch the history, modify relation differently on the new timeline
repo.branch_timeline(TIMELINE_ID, NEW_TIMELINE_ID, Some(Lsn(0x30)))?;
let newtline = repo
.get_timeline_load(NEW_TIMELINE_ID)
.get_timeline(NEW_TIMELINE_ID)
.expect("Should have a local timeline");
let new_writer = newtline.writer();
new_writer.put(TEST_KEY_A, Lsn(0x40), &test_value("bar at 0x40"))?;
@@ -1318,7 +1314,7 @@ mod tests {
repo.branch_timeline(TIMELINE_ID, NEW_TIMELINE_ID, Some(Lsn(0x40)))?;
let newtline = repo
.get_timeline_load(NEW_TIMELINE_ID)
.get_timeline(NEW_TIMELINE_ID)
.expect("Should have a local timeline");
// this removes layers before lsn 40 (50 minus 10), so there are two remaining layers, image and delta for 31-50
repo.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO, false)?;
@@ -1334,7 +1330,7 @@ mod tests {
repo.branch_timeline(TIMELINE_ID, NEW_TIMELINE_ID, Some(Lsn(0x40)))?;
let newtline = repo
.get_timeline_load(NEW_TIMELINE_ID)
.get_timeline(NEW_TIMELINE_ID)
.expect("Should have a local timeline");
make_some_layers(newtline.as_ref(), Lsn(0x60))?;
@@ -1363,17 +1359,8 @@ mod tests {
}
let repo = harness.load();
let tline = repo
.get_timeline(TIMELINE_ID)
repo.get_timeline(TIMELINE_ID)
.expect("cannot load timeline");
assert!(matches!(tline, RepositoryTimeline::Unloaded { .. }));
assert!(repo.get_timeline_load(TIMELINE_ID).is_ok());
let tline = repo
.get_timeline(TIMELINE_ID)
.expect("cannot load timeline");
assert!(matches!(tline, RepositoryTimeline::Loaded(_)));
Ok(())
}
@@ -1393,7 +1380,7 @@ mod tests {
repo.branch_timeline(TIMELINE_ID, NEW_TIMELINE_ID, Some(Lsn(0x40)))?;
let newtline = repo
.get_timeline_load(NEW_TIMELINE_ID)
.get_timeline(NEW_TIMELINE_ID)
.expect("Should have a local timeline");
make_some_layers(newtline.as_ref(), Lsn(0x60))?;
@@ -1402,28 +1389,15 @@ mod tests {
// check that both of them are initially unloaded
let repo = harness.load();
{
let tline = repo.get_timeline(TIMELINE_ID).expect("cannot get timeline");
assert!(matches!(tline, RepositoryTimeline::Unloaded { .. }));
let tline = repo
.get_timeline(NEW_TIMELINE_ID)
.expect("cannot get timeline");
assert!(matches!(tline, RepositoryTimeline::Unloaded { .. }));
}
// load only child timeline
let _ = repo
.get_timeline_load(NEW_TIMELINE_ID)
.expect("cannot load timeline");
// check that both, child and ancestor are loaded
let tline = repo
let _child_tline = repo
.get_timeline(NEW_TIMELINE_ID)
.expect("cannot get timeline");
assert!(matches!(tline, RepositoryTimeline::Loaded(_)));
.expect("cannot get child timeline loaded");
let tline = repo.get_timeline(TIMELINE_ID).expect("cannot get timeline");
assert!(matches!(tline, RepositoryTimeline::Loaded(_)));
let _ancestor_tline = repo
.get_timeline(TIMELINE_ID)
.expect("cannot get ancestor timeline loaded");
Ok(())
}
@@ -1447,7 +1421,9 @@ mod tests {
std::fs::write(metadata_path, metadata_bytes)?;
let err = harness.try_load().err().expect("should fail");
assert_eq!(err.to_string(), "failed to load local metadata");
assert!(err
.to_string()
.starts_with("Failed to parse metadata bytes from path"));
let mut found_error_message = false;
let mut err_source = err.source();
@@ -1663,7 +1639,9 @@ mod tests {
for _ in 0..50 {
let new_tline_id = ZTimelineId::generate();
repo.branch_timeline(tline_id, new_tline_id, Some(lsn))?;
tline = repo.get_timeline_load(new_tline_id)?;
tline = repo
.get_timeline(new_tline_id)
.expect("Should have the branched timeline");
tline_id = new_tline_id;
for _ in 0..NUM_KEYS {
@@ -1722,7 +1700,9 @@ mod tests {
for idx in 0..NUM_TLINES {
let new_tline_id = ZTimelineId::generate();
repo.branch_timeline(tline_id, new_tline_id, Some(lsn))?;
tline = repo.get_timeline_load(new_tline_id)?;
tline = repo
.get_timeline(new_tline_id)
.expect("Should have the branched timeline");
tline_id = new_tline_id;
for _ in 0..NUM_KEYS {
@@ -1749,11 +1729,11 @@ mod tests {
if lsn.0 == 0 {
continue;
}
println!("chekcking [{}][{}] at {}", idx, blknum, lsn);
println!("checking [{idx}][{blknum}] at {lsn}");
test_key.field6 = blknum as u32;
assert_eq!(
tline.get(test_key, *lsn)?,
TEST_IMG(&format!("{} {} at {}", idx, blknum, lsn))
TEST_IMG(&format!("{idx} {blknum} at {lsn}"))
);
}
}

View File

@@ -50,7 +50,7 @@ use utils::{
zid::{ZTenantId, ZTimelineId},
};
use crate::repository::{GcResult, RepositoryTimeline};
use crate::repository::GcResult;
use crate::repository::{Key, Value};
use crate::thread_mgr;
use crate::walreceiver::IS_WAL_RECEIVER;
@@ -164,72 +164,6 @@ static PERSISTENT_BYTES_WRITTEN: Lazy<IntCounter> = Lazy::new(|| {
.expect("failed to define a metric")
});
#[derive(Clone)]
pub enum LayeredTimelineEntry {
Loaded(Arc<Timeline>),
Unloaded {
id: ZTimelineId,
metadata: TimelineMetadata,
},
}
impl LayeredTimelineEntry {
fn timeline_id(&self) -> ZTimelineId {
match self {
LayeredTimelineEntry::Loaded(timeline) => timeline.timeline_id,
LayeredTimelineEntry::Unloaded { id, .. } => *id,
}
}
pub fn ancestor_timeline_id(&self) -> Option<ZTimelineId> {
match self {
LayeredTimelineEntry::Loaded(timeline) => {
timeline.ancestor_timeline.as_ref().map(|t| t.timeline_id())
}
LayeredTimelineEntry::Unloaded { metadata, .. } => metadata.ancestor_timeline(),
}
}
pub fn ancestor_lsn(&self) -> Lsn {
match self {
LayeredTimelineEntry::Loaded(timeline) => timeline.ancestor_lsn,
LayeredTimelineEntry::Unloaded { metadata, .. } => metadata.ancestor_lsn(),
}
}
fn ensure_loaded(&self) -> anyhow::Result<&Arc<Timeline>> {
match self {
LayeredTimelineEntry::Loaded(timeline) => Ok(timeline),
LayeredTimelineEntry::Unloaded { .. } => {
anyhow::bail!("timeline is unloaded")
}
}
}
pub fn layer_removal_guard(&self) -> Result<Option<MutexGuard<()>>, anyhow::Error> {
match self {
LayeredTimelineEntry::Loaded(timeline) => timeline
.layer_removal_cs
.try_lock()
.map_err(|e| anyhow::anyhow!("cannot lock compaction critical section {e}"))
.map(Some),
LayeredTimelineEntry::Unloaded { .. } => Ok(None),
}
}
}
impl From<LayeredTimelineEntry> for RepositoryTimeline<Timeline> {
fn from(entry: LayeredTimelineEntry) -> Self {
match entry {
LayeredTimelineEntry::Loaded(timeline) => RepositoryTimeline::Loaded(timeline as _),
LayeredTimelineEntry::Unloaded { metadata, .. } => {
RepositoryTimeline::Unloaded { metadata }
}
}
}
}
struct TimelineMetrics {
pub reconstruct_time_histo: Histogram,
pub materialized_page_cache_hit_counter: GenericCounter<AtomicU64>,
@@ -342,7 +276,7 @@ pub struct Timeline {
// Parent timeline that this timeline was branched from, and the LSN
// of the branch point.
ancestor_timeline: Option<LayeredTimelineEntry>,
ancestor_timeline: Option<Arc<Timeline>>,
ancestor_lsn: Lsn,
// Metrics
@@ -566,7 +500,7 @@ impl Timeline {
pub fn get_ancestor_timeline_id(&self) -> Option<ZTimelineId> {
self.ancestor_timeline
.as_ref()
.map(LayeredTimelineEntry::timeline_id)
.map(|ancestor| ancestor.timeline_id)
}
/// Lock and get timeline's GC cuttof
@@ -781,7 +715,7 @@ impl Timeline {
conf: &'static PageServerConf,
tenant_conf: Arc<RwLock<TenantConfOpt>>,
metadata: TimelineMetadata,
ancestor: Option<LayeredTimelineEntry>,
ancestor: Option<Arc<Timeline>>,
timeline_id: ZTimelineId,
tenant_id: ZTenantId,
walredo_mgr: Arc<dyn WalRedoManager + Send + Sync>,
@@ -938,6 +872,12 @@ impl Timeline {
Ok(())
}
pub fn layer_removal_guard(&self) -> Result<MutexGuard<()>, anyhow::Error> {
self.layer_removal_cs
.try_lock()
.map_err(|e| anyhow::anyhow!("cannot lock compaction critical section {e}"))
}
/// Retrieve current logical size of the timeline.
///
/// The size could be lagging behind the actual number, in case
@@ -1204,24 +1144,13 @@ impl Timeline {
}
fn get_ancestor_timeline(&self) -> Result<Arc<Timeline>> {
let ancestor = self
.ancestor_timeline
.as_ref()
.with_context(|| {
format!(
"Ancestor is missing. Timeline id: {} Ancestor id {:?}",
self.timeline_id,
self.get_ancestor_timeline_id(),
)
})?
.ensure_loaded()
.with_context(|| {
format!(
"Ancestor timeline is not loaded. Timeline id: {} Ancestor id {:?}",
self.timeline_id,
self.get_ancestor_timeline_id(),
)
})?;
let ancestor = self.ancestor_timeline.as_ref().with_context(|| {
format!(
"Ancestor is missing. Timeline id: {} Ancestor id {:?}",
self.timeline_id,
self.get_ancestor_timeline_id(),
)
})?;
Ok(Arc::clone(ancestor))
}
@@ -1251,7 +1180,9 @@ impl Timeline {
layer = Arc::clone(open_layer);
} else {
// No writeable layer yet. Create one.
let start_lsn = layers.next_open_layer_at.unwrap();
let start_lsn = layers
.next_open_layer_at
.context("No next open layer found")?;
trace!(
"creating layer for write at {}/{} for record at {}",
@@ -1496,7 +1427,7 @@ impl Timeline {
let ancestor_timelineid = self
.ancestor_timeline
.as_ref()
.map(LayeredTimelineEntry::timeline_id);
.map(|ancestor| ancestor.timeline_id);
let metadata = TimelineMetadata::new(
disk_consistent_lsn,

View File

@@ -457,18 +457,18 @@ impl PageServerHandler {
fn handle_pagerequests(
&self,
pgb: &mut PostgresBackend,
timelineid: ZTimelineId,
tenantid: ZTenantId,
timeline_id: ZTimelineId,
tenant_id: ZTenantId,
) -> anyhow::Result<()> {
let _enter = info_span!("pagestream", timeline = %timelineid, tenant = %tenantid).entered();
let _enter =
info_span!("pagestream", timeline = %timeline_id, tenant = %tenant_id).entered();
// NOTE: pagerequests handler exits when connection is closed,
// so there is no need to reset the association
thread_mgr::associate_with(Some(tenantid), Some(timelineid));
thread_mgr::associate_with(Some(tenant_id), Some(timeline_id));
// Check that the timeline exists
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Cannot load local timeline")?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
/* switch client to COPYBOTH */
pgb.write_message(&BeMessage::CopyBothResponse)?;
@@ -488,8 +488,8 @@ impl PageServerHandler {
};
let zenith_fe_msg = PagestreamFeMessage::parse(copy_data_bytes)?;
let tenant_id = tenantid.to_string();
let timeline_id = timelineid.to_string();
let tenant_id = tenant_id.to_string();
let timeline_id = timeline_id.to_string();
let response = match zenith_fe_msg {
PagestreamFeMessage::Exists(req) => SMGR_QUERY_TIME
@@ -599,7 +599,9 @@ impl PageServerHandler {
info_span!("import wal", timeline = %timeline_id, tenant = %tenant_id).entered();
let repo = tenant_mgr::get_repository_for_tenant(tenant_id)?;
let timeline = repo.get_timeline_load(timeline_id)?;
let timeline = repo
.get_timeline(timeline_id)
.with_context(|| format!("Timeline {timeline_id} was not found"))?;
ensure!(timeline.get_last_record_lsn() == start_lsn);
// TODO leave clean state on error. For now you can use detach to clean
@@ -762,19 +764,18 @@ impl PageServerHandler {
fn handle_basebackup_request(
&self,
pgb: &mut PostgresBackend,
timelineid: ZTimelineId,
timeline_id: ZTimelineId,
lsn: Option<Lsn>,
prev_lsn: Option<Lsn>,
tenantid: ZTenantId,
tenant_id: ZTenantId,
full_backup: bool,
) -> anyhow::Result<()> {
let span = info_span!("basebackup", timeline = %timelineid, tenant = %tenantid, lsn = field::Empty);
let span = info_span!("basebackup", timeline = %timeline_id, tenant = %tenant_id, lsn = field::Empty);
let _enter = span.enter();
info!("starting");
// check that the timeline exists
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Cannot load local timeline")?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
let latest_gc_cutoff_lsn = timeline.get_latest_gc_cutoff_lsn();
if let Some(lsn) = lsn {
timeline
@@ -906,12 +907,11 @@ impl postgres_backend::Handler for PageServerHandler {
"invalid param number for get_last_record_rlsn command"
);
let tenantid = ZTenantId::from_str(params[0])?;
let timelineid = ZTimelineId::from_str(params[1])?;
let tenant_id = ZTenantId::from_str(params[0])?;
let timeline_id = ZTimelineId::from_str(params[1])?;
self.check_permission(Some(tenantid))?;
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Cannot load local timeline")?;
self.check_permission(Some(tenant_id))?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
let end_of_timeline = timeline.get_last_record_rlsn();
@@ -1134,10 +1134,9 @@ impl postgres_backend::Handler for PageServerHandler {
.captures(query_string)
.with_context(|| format!("Invalid compact: '{}'", query_string))?;
let tenantid = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timelineid = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Couldn't load timeline")?;
let tenant_id = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timeline_id = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
timeline.compact()?;
pgb.write_message_noflush(&SINGLE_COL_ROWDESC)?
@@ -1152,11 +1151,9 @@ impl postgres_backend::Handler for PageServerHandler {
.captures(query_string)
.with_context(|| format!("invalid checkpoint command: '{}'", query_string))?;
let tenantid = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timelineid = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Cannot load local timeline")?;
let tenant_id = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timeline_id = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
// Checkpoint the timeline and also compact it (due to `CheckpointConfig::Forced`).
timeline.checkpoint(CheckpointConfig::Forced)?;
@@ -1172,10 +1169,9 @@ impl postgres_backend::Handler for PageServerHandler {
.captures(query_string)
.with_context(|| format!("invalid get_lsn_by_timestamp: '{}'", query_string))?;
let tenantid = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timelineid = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = tenant_mgr::get_local_timeline_with_load(tenantid, timelineid)
.context("Cannot load local timeline")?;
let tenant_id = ZTenantId::from_str(caps.get(1).unwrap().as_str())?;
let timeline_id = ZTimelineId::from_str(caps.get(2).unwrap().as_str())?;
let timeline = get_local_timeline(tenant_id, timeline_id)?;
let timestamp = humantime::parse_rfc3339(caps.get(3).unwrap().as_str())?;
let timestamp_pg = to_pg_timestamp(timestamp);
@@ -1201,6 +1197,15 @@ impl postgres_backend::Handler for PageServerHandler {
}
}
fn get_local_timeline(tenant_id: ZTenantId, timeline_id: ZTimelineId) -> Result<Arc<Timeline>> {
tenant_mgr::get_repository_for_tenant(tenant_id)
.and_then(|repo| {
repo.get_timeline(timeline_id)
.context("No timeline in tenant's repository")
})
.with_context(|| format!("Could not get timeline {timeline_id} in tenant {tenant_id}"))
}
///
/// A std::io::Write implementation that wraps all data written to it in CopyData
/// messages.

View File

@@ -1,4 +1,3 @@
use crate::layered_repository::metadata::TimelineMetadata;
use crate::walrecord::ZenithWalRecord;
use anyhow::{bail, Result};
use byteorder::{ByteOrder, BE};
@@ -6,7 +5,6 @@ use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{AddAssign, Range};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
@@ -175,30 +173,6 @@ impl Value {
}
}
/// A timeline, that belongs to the current repository.
pub enum RepositoryTimeline<T> {
/// Timeline, with its files present locally in pageserver's working directory.
/// Loaded into pageserver's memory and ready to be used.
Loaded(Arc<T>),
/// All the data is available locally, but not loaded into memory, so loading have to be done before actually using the timeline
Unloaded {
// It is ok to keep metadata here, because it is not changed when timeline is unloaded.
// FIXME can s3 sync actually change it? It can change it when timeline is in awaiting download state.
// but we currently do not download something for the timeline once it is local (even if there are new checkpoints) is it correct?
// also it is not that good to keep TimelineMetadata here, because it is layered repo implementation detail
metadata: TimelineMetadata,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LocalTimelineState {
// timeline is loaded into memory (with layer map and all the bits),
Loaded,
// timeline is on disk locally and ready to be loaded into memory.
Unloaded,
}
///
/// Result of performing GC
///

View File

@@ -903,8 +903,10 @@ fn storage_sync_loop(
"Sync loop step completed, {} new tenant state update(s)",
updated_tenants.len()
);
let mut sync_status_updates: HashMap<ZTenantId, HashSet<ZTimelineId>> =
HashMap::new();
let mut timelines_to_attach: HashMap<
ZTenantId,
Vec<(ZTimelineId, TimelineMetadata)>,
> = HashMap::new();
let index_accessor = runtime.block_on(index.read());
for tenant_id in updated_tenants {
let tenant_entry = match index_accessor.tenant_entry(&tenant_id) {
@@ -930,13 +932,18 @@ fn storage_sync_loop(
// and register them all at once in a repository for download
// to be submitted in a single operation to repository
// so it can apply them at once to internal timeline map.
sync_status_updates
.insert(tenant_id, tenant_entry.keys().copied().collect());
timelines_to_attach.insert(
tenant_id,
tenant_entry
.iter()
.map(|(&id, entry)| (id, entry.metadata.clone()))
.collect(),
);
}
}
drop(index_accessor);
// Batch timeline download registration to ensure that the external registration code won't block any running tasks before.
attach_downloaded_tenants(conf, &index, sync_status_updates);
attach_downloaded_tenants(conf, &index, timelines_to_attach);
}
}
ControlFlow::Break(()) => {

View File

@@ -3,6 +3,7 @@
use crate::config::PageServerConf;
use crate::http::models::TenantInfo;
use crate::layered_repository::metadata::TimelineMetadata;
use crate::layered_repository::{load_metadata, Repository, Timeline};
use crate::storage_sync::index::{RemoteIndex, RemoteTimelineIndex};
use crate::storage_sync::{self, LocalTimelineInitStatus, SyncStartupData};
@@ -14,7 +15,7 @@ use anyhow::Context;
use remote_storage::GenericRemoteStorage;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use tokio::sync::mpsc;
@@ -192,7 +193,7 @@ impl std::fmt::Debug for LocalTimelineUpdate {
pub fn attach_downloaded_tenants(
conf: &'static PageServerConf,
remote_index: &RemoteIndex,
sync_status_updates: HashMap<ZTenantId, HashSet<ZTimelineId>>,
sync_status_updates: HashMap<ZTenantId, Vec<(ZTimelineId, TimelineMetadata)>>,
) {
if sync_status_updates.is_empty() {
debug!("No sync status updates to apply");
@@ -212,11 +213,9 @@ pub fn attach_downloaded_tenants(
continue;
}
};
match attach_downloaded_tenant(&repo, downloaded_timelines) {
Ok(()) => info!("successfully applied sync status updates for tenant {tenant_id}"),
Err(e) => error!(
"Failed to apply timeline sync timeline status updates for tenant {tenant_id}: {e:?}"
),
match repo.init_attach_timelines(downloaded_timelines) {
Ok(()) => info!("successfully loaded local timelines for tenant {tenant_id}"),
Err(e) => error!("Failed to load local timelines for tenant {tenant_id}: {e:?}"),
}
}
}
@@ -371,15 +370,6 @@ pub fn get_repository_for_tenant(tenant_id: ZTenantId) -> anyhow::Result<Arc<Rep
Ok(Arc::clone(&tenant.repo))
}
/// Retrieves local timeline for tenant.
/// Loads it into memory if it is not already loaded.
pub fn get_local_timeline_with_load(
tenant_id: ZTenantId,
timeline_id: ZTimelineId,
) -> anyhow::Result<Arc<Timeline>> {
get_repository_for_tenant(tenant_id)?.get_timeline_load(timeline_id)
}
pub fn delete_timeline(tenant_id: ZTenantId, timeline_id: ZTimelineId) -> anyhow::Result<()> {
// Start with the shutdown of timeline tasks (this shuts down the walreceiver)
// It is important that we do not take locks here, and do not check whether the timeline exists
@@ -499,7 +489,7 @@ fn check_broken_timeline(
conf: &'static PageServerConf,
tenant_id: ZTenantId,
timeline_id: ZTimelineId,
) -> anyhow::Result<()> {
) -> anyhow::Result<TimelineMetadata> {
let metadata =
load_metadata(conf, timeline_id, tenant_id).context("failed to load metadata")?;
@@ -509,7 +499,7 @@ fn check_broken_timeline(
anyhow::bail!("Timeline {timeline_id} has a zero disk consistent LSN.");
}
Ok(())
Ok(metadata)
}
/// Note: all timelines are attached at once if and only if all of them are locally complete
@@ -519,14 +509,14 @@ fn init_local_repository(
local_timeline_init_statuses: HashMap<ZTimelineId, LocalTimelineInitStatus>,
remote_index: &RemoteIndex,
) -> anyhow::Result<(), anyhow::Error> {
let mut timelines_to_attach = HashSet::new();
let mut timelines_to_attach = Vec::new();
for (timeline_id, init_status) in local_timeline_init_statuses {
match init_status {
LocalTimelineInitStatus::LocallyComplete => {
debug!("timeline {timeline_id} for tenant {tenant_id} is locally complete, registering it in repository");
check_broken_timeline(conf, tenant_id, timeline_id)
let metadata = check_broken_timeline(conf, tenant_id, timeline_id)
.context("found broken timeline")?;
timelines_to_attach.insert(timeline_id);
timelines_to_attach.push((timeline_id, metadata));
}
LocalTimelineInitStatus::NeedsSync => {
debug!(
@@ -545,32 +535,8 @@ fn init_local_repository(
// Lets fail here loudly to be on the safe side.
// XXX: It may be a better api to actually distinguish between repository startup
// and processing of newly downloaded timelines.
attach_downloaded_tenant(&repo, timelines_to_attach)
.with_context(|| format!("Failed to bootstrap timelines for tenant {tenant_id}"))?;
Ok(())
}
fn attach_downloaded_tenant(
repo: &Repository,
downloaded_timelines: HashSet<ZTimelineId>,
) -> anyhow::Result<()> {
// first, register timeline metadata to ensure ancestors will be found later during layer load
for &timeline_id in &downloaded_timelines {
repo.attach_timeline(timeline_id).with_context(|| {
format!("Failed to load timeline {timeline_id} into in-memory repository")
})?;
}
// and then load its layers in memory
for timeline_id in downloaded_timelines {
repo.get_timeline_load(timeline_id).with_context(|| {
format!(
"Failed to register add local timeline for tenant {}",
repo.tenant_id(),
)
})?;
}
repo.init_attach_timelines(timelines_to_attach)
.with_context(|| format!("Failed to init local timelines for tenant {tenant_id}"))?;
Ok(())
}

View File

@@ -108,7 +108,7 @@ fn bootstrap_timeline(
tenantid: ZTenantId,
tli: ZTimelineId,
repo: &Repository,
) -> Result<()> {
) -> Result<Arc<Timeline>> {
let initdb_path = conf
.tenant_path(&tenantid)
.join(format!("tmp-timeline-{}", tli));
@@ -141,7 +141,7 @@ fn bootstrap_timeline(
// Remove temp dir. We don't need it anymore
fs::remove_dir_all(pgdata_path)?;
Ok(())
Ok(timeline)
}
///
@@ -159,7 +159,7 @@ pub(crate) fn create_timeline(
new_timeline_id: Option<ZTimelineId>,
ancestor_timeline_id: Option<ZTimelineId>,
mut ancestor_start_lsn: Option<Lsn>,
) -> Result<Option<(ZTimelineId, Arc<Timeline>)>> {
) -> Result<Option<Arc<Timeline>>> {
let new_timeline_id = new_timeline_id.unwrap_or_else(ZTimelineId::generate);
let repo = tenant_mgr::get_repository_for_tenant(tenant_id)?;
@@ -168,11 +168,11 @@ pub(crate) fn create_timeline(
return Ok(None);
}
match ancestor_timeline_id {
let loaded_timeline = match ancestor_timeline_id {
Some(ancestor_timeline_id) => {
let ancestor_timeline = repo
.get_timeline_load(ancestor_timeline_id)
.context("Cannot branch off the timeline that's not present locally")?;
.get_timeline(ancestor_timeline_id)
.context("Cannot branch off the timeline that's not present in pageserver")?;
if let Some(lsn) = ancestor_start_lsn.as_mut() {
// Wait for the WAL to arrive and be processed on the parent branch up
@@ -201,8 +201,5 @@ pub(crate) fn create_timeline(
None => bootstrap_timeline(conf, tenant_id, new_timeline_id, repo.as_ref())?,
};
// load the timeline into memory
let loaded_timeline = tenant_mgr::get_local_timeline_with_load(tenant_id, new_timeline_id)?;
Ok(Some((new_timeline_id, loaded_timeline)))
Ok(Some(loaded_timeline))
}

View File

@@ -132,7 +132,7 @@ pub async fn handle_walreceiver_connection(
let (repo, timeline) = tokio::task::spawn_blocking(move || {
let repo = tenant_mgr::get_repository_for_tenant(tenant_id)
.with_context(|| format!("no repository found for tenant {tenant_id}"))?;
let timeline = tenant_mgr::get_local_timeline_with_load(tenant_id, timeline_id)
let timeline = repo.get_timeline(timeline_id)
.with_context(|| {
format!("local timeline {timeline_id} not found for tenant {tenant_id}")
})?;

View File

@@ -68,9 +68,11 @@ def test_broken_timeline(neon_env_builder: NeonEnvBuilder):
# But all others are broken
# First timeline would fail instantly due to corrupt metadata file
# First timeline would not get loaded into pageserver due to corrupt metadata file
(_tenant, _timeline, pg) = tenant_timelines[1]
with pytest.raises(Exception, match="Cannot load local timeline") as err:
with pytest.raises(
Exception, match=f"Could not get timeline {timeline1} in tenant {tenant1}"
) as err:
pg.start()
log.info(f"compute startup failed eagerly for timeline with corrupt metadata: {err}")

View File

@@ -93,10 +93,7 @@ def check_client(client: NeonPageserverHttpClient, initial_tenant: ZTenantId):
assert ZTenantId(timeline_details["tenant_id"]) == tenant_id
assert ZTimelineId(timeline_details["timeline_id"]) == timeline_id
local_timeline_details = timeline_details.get("local")
assert local_timeline_details is not None
assert local_timeline_details["timeline_state"] == "Loaded"
assert timeline_details.get("local") is not None
def test_pageserver_http_get_wal_receiver_not_found(neon_simple_env: NeonEnv):