Line data Source code
1 : //! This module contains global `(tenant_id, timeline_id)` -> `Arc<Timeline>` mapping.
2 : //! All timelines should always be present in this map, this is done by loading them
3 : //! all from the disk on startup and keeping them in memory.
4 :
5 : use crate::defaults::DEFAULT_EVICTION_CONCURRENCY;
6 : use crate::rate_limit::RateLimiter;
7 : use crate::state::TimelinePersistentState;
8 : use crate::timeline::{get_tenant_dir, get_timeline_dir, Timeline, TimelineError};
9 : use crate::timelines_set::TimelinesSet;
10 : use crate::wal_storage::Storage;
11 : use crate::{control_file, wal_storage, SafeKeeperConf};
12 : use anyhow::{bail, Context, Result};
13 : use camino::Utf8PathBuf;
14 : use camino_tempfile::Utf8TempDir;
15 : use safekeeper_api::membership::Configuration;
16 : use safekeeper_api::models::SafekeeperUtilization;
17 : use safekeeper_api::ServerInfo;
18 : use serde::Serialize;
19 : use std::collections::HashMap;
20 : use std::str::FromStr;
21 : use std::sync::atomic::Ordering;
22 : use std::sync::{Arc, Mutex};
23 : use std::time::{Duration, Instant};
24 : use tokio::fs;
25 : use tracing::*;
26 : use utils::crashsafe::{durable_rename, fsync_async_opt};
27 : use utils::id::{TenantId, TenantTimelineId, TimelineId};
28 : use utils::lsn::Lsn;
29 :
30 : // Timeline entry in the global map: either a ready timeline, or mark that it is
31 : // being created.
32 : #[derive(Clone)]
33 : enum GlobalMapTimeline {
34 : CreationInProgress,
35 : Timeline(Arc<Timeline>),
36 : }
37 :
38 : struct GlobalTimelinesState {
39 : timelines: HashMap<TenantTimelineId, GlobalMapTimeline>,
40 :
41 : // A tombstone indicates this timeline used to exist has been deleted. These are used to prevent
42 : // on-demand timeline creation from recreating deleted timelines. This is only soft-enforced, as
43 : // this map is dropped on restart.
44 : tombstones: HashMap<TenantTimelineId, Instant>,
45 :
46 : conf: Arc<SafeKeeperConf>,
47 : broker_active_set: Arc<TimelinesSet>,
48 : global_rate_limiter: RateLimiter,
49 : }
50 :
51 : impl GlobalTimelinesState {
52 : /// Get dependencies for a timeline constructor.
53 0 : fn get_dependencies(&self) -> (Arc<SafeKeeperConf>, Arc<TimelinesSet>, RateLimiter) {
54 0 : (
55 0 : self.conf.clone(),
56 0 : self.broker_active_set.clone(),
57 0 : self.global_rate_limiter.clone(),
58 0 : )
59 0 : }
60 :
61 : /// Get timeline from the map. Returns error if timeline doesn't exist or
62 : /// creation is in progress.
63 0 : fn get(&self, ttid: &TenantTimelineId) -> Result<Arc<Timeline>, TimelineError> {
64 0 : match self.timelines.get(ttid).cloned() {
65 0 : Some(GlobalMapTimeline::Timeline(tli)) => Ok(tli),
66 : Some(GlobalMapTimeline::CreationInProgress) => {
67 0 : Err(TimelineError::CreationInProgress(*ttid))
68 : }
69 0 : None => Err(TimelineError::NotFound(*ttid)),
70 : }
71 0 : }
72 :
73 0 : fn delete(&mut self, ttid: TenantTimelineId) {
74 0 : self.timelines.remove(&ttid);
75 0 : self.tombstones.insert(ttid, Instant::now());
76 0 : }
77 : }
78 :
79 : /// A struct used to manage access to the global timelines map.
80 : pub struct GlobalTimelines {
81 : state: Mutex<GlobalTimelinesState>,
82 : }
83 :
84 : impl GlobalTimelines {
85 : /// Create a new instance of the global timelines map.
86 0 : pub fn new(conf: Arc<SafeKeeperConf>) -> Self {
87 0 : Self {
88 0 : state: Mutex::new(GlobalTimelinesState {
89 0 : timelines: HashMap::new(),
90 0 : tombstones: HashMap::new(),
91 0 : conf,
92 0 : broker_active_set: Arc::new(TimelinesSet::default()),
93 0 : global_rate_limiter: RateLimiter::new(1, 1),
94 0 : }),
95 0 : }
96 0 : }
97 :
98 : /// Inject dependencies needed for the timeline constructors and load all timelines to memory.
99 0 : pub async fn init(&self) -> Result<()> {
100 0 : // clippy isn't smart enough to understand that drop(state) releases the
101 0 : // lock, so use explicit block
102 0 : let tenants_dir = {
103 0 : let mut state = self.state.lock().unwrap();
104 0 : state.global_rate_limiter = RateLimiter::new(
105 0 : state.conf.partial_backup_concurrency,
106 0 : DEFAULT_EVICTION_CONCURRENCY,
107 0 : );
108 0 :
109 0 : // Iterate through all directories and load tenants for all directories
110 0 : // named as a valid tenant_id.
111 0 : state.conf.workdir.clone()
112 0 : };
113 0 : let mut tenant_count = 0;
114 0 : for tenants_dir_entry in std::fs::read_dir(&tenants_dir)
115 0 : .with_context(|| format!("failed to list tenants dir {}", tenants_dir))?
116 : {
117 0 : match &tenants_dir_entry {
118 0 : Ok(tenants_dir_entry) => {
119 0 : if let Ok(tenant_id) =
120 0 : TenantId::from_str(tenants_dir_entry.file_name().to_str().unwrap_or(""))
121 : {
122 0 : tenant_count += 1;
123 0 : self.load_tenant_timelines(tenant_id).await?;
124 0 : }
125 : }
126 0 : Err(e) => error!(
127 0 : "failed to list tenants dir entry {:?} in directory {}, reason: {:?}",
128 : tenants_dir_entry, tenants_dir, e
129 : ),
130 : }
131 : }
132 :
133 0 : info!(
134 0 : "found {} tenants directories, successfully loaded {} timelines",
135 0 : tenant_count,
136 0 : self.state.lock().unwrap().timelines.len()
137 : );
138 0 : Ok(())
139 0 : }
140 :
141 : /// Loads all timelines for the given tenant to memory. Returns fs::read_dir
142 : /// errors if any.
143 : ///
144 : /// It is async, but self.state lock is sync and there is no important
145 : /// reason to make it async (it is always held for a short while), so we
146 : /// just lock and unlock it for each timeline -- this function is called
147 : /// during init when nothing else is running, so this is fine.
148 0 : async fn load_tenant_timelines(&self, tenant_id: TenantId) -> Result<()> {
149 0 : let (conf, broker_active_set, partial_backup_rate_limiter) = {
150 0 : let state = self.state.lock().unwrap();
151 0 : state.get_dependencies()
152 0 : };
153 0 :
154 0 : let timelines_dir = get_tenant_dir(&conf, &tenant_id);
155 0 : for timelines_dir_entry in std::fs::read_dir(&timelines_dir)
156 0 : .with_context(|| format!("failed to list timelines dir {}", timelines_dir))?
157 : {
158 0 : match &timelines_dir_entry {
159 0 : Ok(timeline_dir_entry) => {
160 0 : if let Ok(timeline_id) =
161 0 : TimelineId::from_str(timeline_dir_entry.file_name().to_str().unwrap_or(""))
162 : {
163 0 : let ttid = TenantTimelineId::new(tenant_id, timeline_id);
164 0 : match Timeline::load_timeline(conf.clone(), ttid) {
165 0 : Ok(tli) => {
166 0 : let mut shared_state = tli.write_shared_state().await;
167 0 : self.state
168 0 : .lock()
169 0 : .unwrap()
170 0 : .timelines
171 0 : .insert(ttid, GlobalMapTimeline::Timeline(tli.clone()));
172 0 : tli.bootstrap(
173 0 : &mut shared_state,
174 0 : &conf,
175 0 : broker_active_set.clone(),
176 0 : partial_backup_rate_limiter.clone(),
177 0 : );
178 : }
179 : // If we can't load a timeline, it's most likely because of a corrupted
180 : // directory. We will log an error and won't allow to delete/recreate
181 : // this timeline. The only way to fix this timeline is to repair manually
182 : // and restart the safekeeper.
183 0 : Err(e) => error!(
184 0 : "failed to load timeline {} for tenant {}, reason: {:?}",
185 : timeline_id, tenant_id, e
186 : ),
187 : }
188 0 : }
189 : }
190 0 : Err(e) => error!(
191 0 : "failed to list timelines dir entry {:?} in directory {}, reason: {:?}",
192 : timelines_dir_entry, timelines_dir, e
193 : ),
194 : }
195 : }
196 :
197 0 : Ok(())
198 0 : }
199 :
200 : /// Get the number of timelines in the map.
201 0 : pub fn timelines_count(&self) -> usize {
202 0 : self.state.lock().unwrap().timelines.len()
203 0 : }
204 :
205 : /// Get the global safekeeper config.
206 0 : pub fn get_global_config(&self) -> Arc<SafeKeeperConf> {
207 0 : self.state.lock().unwrap().conf.clone()
208 0 : }
209 :
210 0 : pub fn get_global_broker_active_set(&self) -> Arc<TimelinesSet> {
211 0 : self.state.lock().unwrap().broker_active_set.clone()
212 0 : }
213 :
214 : /// Create a new timeline with the given id. If the timeline already exists, returns
215 : /// an existing timeline.
216 0 : pub(crate) async fn create(
217 0 : &self,
218 0 : ttid: TenantTimelineId,
219 0 : mconf: Configuration,
220 0 : server_info: ServerInfo,
221 0 : start_lsn: Lsn,
222 0 : commit_lsn: Lsn,
223 0 : ) -> Result<Arc<Timeline>> {
224 0 : let (conf, _, _) = {
225 0 : let state = self.state.lock().unwrap();
226 0 : if let Ok(timeline) = state.get(&ttid) {
227 : // Timeline already exists, return it.
228 0 : return Ok(timeline);
229 0 : }
230 0 :
231 0 : if state.tombstones.contains_key(&ttid) {
232 0 : anyhow::bail!("Timeline {ttid} is deleted, refusing to recreate");
233 0 : }
234 0 :
235 0 : state.get_dependencies()
236 0 : };
237 0 :
238 0 : info!("creating new timeline {}", ttid);
239 :
240 : // Do on disk initialization in tmp dir.
241 0 : let (_tmp_dir, tmp_dir_path) = create_temp_timeline_dir(&conf, ttid).await?;
242 :
243 : // TODO: currently we create only cfile. It would be reasonable to
244 : // immediately initialize first WAL segment as well.
245 0 : let state = TimelinePersistentState::new(&ttid, mconf, server_info, start_lsn, commit_lsn)?;
246 0 : control_file::FileStorage::create_new(&tmp_dir_path, state, conf.no_sync).await?;
247 0 : let timeline = self.load_temp_timeline(ttid, &tmp_dir_path, true).await?;
248 0 : Ok(timeline)
249 0 : }
250 :
251 : /// Move timeline from a temp directory to the main storage, and load it to
252 : /// the global map. Creating timeline in this way ensures atomicity: rename
253 : /// is atomic, so either move of the whole datadir succeeds or it doesn't,
254 : /// but corrupted data dir shouldn't be possible.
255 : ///
256 : /// We'd like to avoid holding map lock while doing IO, so it's a 3 step
257 : /// process:
258 : /// 1) check the global map that timeline doesn't exist and mark that we're
259 : /// creating it;
260 : /// 2) move the directory and load the timeline
261 : /// 3) take lock again and insert the timeline into the global map.
262 0 : pub async fn load_temp_timeline(
263 0 : &self,
264 0 : ttid: TenantTimelineId,
265 0 : tmp_path: &Utf8PathBuf,
266 0 : check_tombstone: bool,
267 0 : ) -> Result<Arc<Timeline>> {
268 : // Check for existence and mark that we're creating it.
269 0 : let (conf, broker_active_set, partial_backup_rate_limiter) = {
270 0 : let mut state = self.state.lock().unwrap();
271 0 : match state.timelines.get(&ttid) {
272 : Some(GlobalMapTimeline::CreationInProgress) => {
273 0 : bail!(TimelineError::CreationInProgress(ttid));
274 : }
275 : Some(GlobalMapTimeline::Timeline(_)) => {
276 0 : bail!(TimelineError::AlreadyExists(ttid));
277 : }
278 0 : _ => {}
279 0 : }
280 0 : if check_tombstone {
281 0 : if state.tombstones.contains_key(&ttid) {
282 0 : anyhow::bail!("timeline {ttid} is deleted, refusing to recreate");
283 0 : }
284 : } else {
285 : // We may be have been asked to load a timeline that was previously deleted (e.g. from `pull_timeline.rs`). We trust
286 : // that the human doing this manual intervention knows what they are doing, and remove its tombstone.
287 0 : if state.tombstones.remove(&ttid).is_some() {
288 0 : warn!("un-deleted timeline {ttid}");
289 0 : }
290 : }
291 0 : state
292 0 : .timelines
293 0 : .insert(ttid, GlobalMapTimeline::CreationInProgress);
294 0 : state.get_dependencies()
295 0 : };
296 0 :
297 0 : // Do the actual move and reflect the result in the map.
298 0 : match GlobalTimelines::install_temp_timeline(ttid, tmp_path, conf.clone()).await {
299 0 : Ok(timeline) => {
300 0 : let mut timeline_shared_state = timeline.write_shared_state().await;
301 0 : let mut state = self.state.lock().unwrap();
302 0 : assert!(matches!(
303 0 : state.timelines.get(&ttid),
304 : Some(GlobalMapTimeline::CreationInProgress)
305 : ));
306 :
307 0 : state
308 0 : .timelines
309 0 : .insert(ttid, GlobalMapTimeline::Timeline(timeline.clone()));
310 0 : drop(state);
311 0 : timeline.bootstrap(
312 0 : &mut timeline_shared_state,
313 0 : &conf,
314 0 : broker_active_set,
315 0 : partial_backup_rate_limiter,
316 0 : );
317 0 : drop(timeline_shared_state);
318 0 : Ok(timeline)
319 : }
320 0 : Err(e) => {
321 0 : // Init failed, remove the marker from the map
322 0 : let mut state = self.state.lock().unwrap();
323 0 : assert!(matches!(
324 0 : state.timelines.get(&ttid),
325 : Some(GlobalMapTimeline::CreationInProgress)
326 : ));
327 0 : state.timelines.remove(&ttid);
328 0 : Err(e)
329 : }
330 : }
331 0 : }
332 :
333 : /// Main part of load_temp_timeline: do the move and load.
334 0 : async fn install_temp_timeline(
335 0 : ttid: TenantTimelineId,
336 0 : tmp_path: &Utf8PathBuf,
337 0 : conf: Arc<SafeKeeperConf>,
338 0 : ) -> Result<Arc<Timeline>> {
339 0 : let tenant_path = get_tenant_dir(conf.as_ref(), &ttid.tenant_id);
340 0 : let timeline_path = get_timeline_dir(conf.as_ref(), &ttid);
341 0 :
342 0 : // We must have already checked that timeline doesn't exist in the map,
343 0 : // but there might be existing datadir: if timeline is corrupted it is
344 0 : // not loaded. We don't want to overwrite such a dir, so check for its
345 0 : // existence.
346 0 : match fs::metadata(&timeline_path).await {
347 : Ok(_) => {
348 : // Timeline directory exists on disk, we should leave state unchanged
349 : // and return error.
350 0 : bail!(TimelineError::Invalid(ttid));
351 : }
352 0 : Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
353 0 : Err(e) => {
354 0 : return Err(e.into());
355 : }
356 : }
357 :
358 0 : info!(
359 0 : "moving timeline {} from {} to {}",
360 : ttid, tmp_path, timeline_path
361 : );
362 :
363 : // Now it is safe to move the timeline directory to the correct
364 : // location. First, create tenant directory. Ignore error if it already
365 : // exists.
366 0 : if let Err(e) = tokio::fs::create_dir(&tenant_path).await {
367 0 : if e.kind() != std::io::ErrorKind::AlreadyExists {
368 0 : return Err(e.into());
369 0 : }
370 0 : }
371 : // fsync it
372 0 : fsync_async_opt(&tenant_path, !conf.no_sync).await?;
373 : // and its creation
374 0 : fsync_async_opt(&conf.workdir, !conf.no_sync).await?;
375 :
376 : // Do the move.
377 0 : durable_rename(tmp_path, &timeline_path, !conf.no_sync).await?;
378 :
379 0 : Timeline::load_timeline(conf, ttid)
380 0 : }
381 :
382 : /// Get a timeline from the global map. If it's not present, it doesn't exist on disk,
383 : /// or was corrupted and couldn't be loaded on startup. Returned timeline is always valid,
384 : /// i.e. loaded in memory and not cancelled.
385 0 : pub(crate) fn get(&self, ttid: TenantTimelineId) -> Result<Arc<Timeline>, TimelineError> {
386 0 : let tli_res = {
387 0 : let state = self.state.lock().unwrap();
388 0 : state.get(&ttid)
389 0 : };
390 0 : match tli_res {
391 0 : Ok(tli) => {
392 0 : if tli.is_cancelled() {
393 0 : return Err(TimelineError::Cancelled(ttid));
394 0 : }
395 0 : Ok(tli)
396 : }
397 0 : _ => tli_res,
398 : }
399 0 : }
400 :
401 : /// Returns all timelines. This is used for background timeline processes.
402 0 : pub fn get_all(&self) -> Vec<Arc<Timeline>> {
403 0 : let global_lock = self.state.lock().unwrap();
404 0 : global_lock
405 0 : .timelines
406 0 : .values()
407 0 : .filter_map(|t| match t {
408 0 : GlobalMapTimeline::Timeline(t) => {
409 0 : if t.is_cancelled() {
410 0 : None
411 : } else {
412 0 : Some(t.clone())
413 : }
414 : }
415 0 : _ => None,
416 0 : })
417 0 : .collect()
418 0 : }
419 :
420 : /// Returns statistics about timeline counts
421 0 : pub fn get_timeline_counts(&self) -> SafekeeperUtilization {
422 0 : let global_lock = self.state.lock().unwrap();
423 0 : let timeline_count = global_lock
424 0 : .timelines
425 0 : .values()
426 0 : .filter(|t| match t {
427 0 : GlobalMapTimeline::CreationInProgress => false,
428 0 : GlobalMapTimeline::Timeline(t) => !t.is_cancelled(),
429 0 : })
430 0 : .count() as u64;
431 0 : SafekeeperUtilization { timeline_count }
432 0 : }
433 :
434 : /// Returns all timelines belonging to a given tenant. Used for deleting all timelines of a tenant,
435 : /// and that's why it can return cancelled timelines, to retry deleting them.
436 0 : fn get_all_for_tenant(&self, tenant_id: TenantId) -> Vec<Arc<Timeline>> {
437 0 : let global_lock = self.state.lock().unwrap();
438 0 : global_lock
439 0 : .timelines
440 0 : .values()
441 0 : .filter_map(|t| match t {
442 0 : GlobalMapTimeline::Timeline(t) => Some(t.clone()),
443 0 : _ => None,
444 0 : })
445 0 : .filter(|t| t.ttid.tenant_id == tenant_id)
446 0 : .collect()
447 0 : }
448 :
449 : /// Cancels timeline, then deletes the corresponding data directory.
450 : /// If only_local, doesn't remove WAL segments in remote storage.
451 0 : pub(crate) async fn delete(
452 0 : &self,
453 0 : ttid: &TenantTimelineId,
454 0 : only_local: bool,
455 0 : ) -> Result<TimelineDeleteForceResult> {
456 0 : let tli_res = {
457 0 : let state = self.state.lock().unwrap();
458 0 :
459 0 : if state.tombstones.contains_key(ttid) {
460 : // Presence of a tombstone guarantees that a previous deletion has completed and there is no work to do.
461 0 : info!("Timeline {ttid} was already deleted");
462 0 : return Ok(TimelineDeleteForceResult {
463 0 : dir_existed: false,
464 0 : was_active: false,
465 0 : });
466 0 : }
467 0 :
468 0 : state.get(ttid)
469 : };
470 :
471 0 : let result = match tli_res {
472 0 : Ok(timeline) => {
473 0 : let was_active = timeline.broker_active.load(Ordering::Relaxed);
474 0 :
475 0 : info!("deleting timeline {}, only_local={}", ttid, only_local);
476 0 : timeline.shutdown().await;
477 :
478 : // Take a lock and finish the deletion holding this mutex.
479 0 : let mut shared_state = timeline.write_shared_state().await;
480 :
481 0 : let dir_existed = timeline.delete(&mut shared_state, only_local).await?;
482 :
483 0 : Ok(TimelineDeleteForceResult {
484 0 : dir_existed,
485 0 : was_active, // TODO: we probably should remove this field
486 0 : })
487 : }
488 : Err(_) => {
489 : // Timeline is not memory, but it may still exist on disk in broken state.
490 0 : let dir_path = get_timeline_dir(self.state.lock().unwrap().conf.as_ref(), ttid);
491 0 : let dir_existed = delete_dir(dir_path)?;
492 :
493 0 : Ok(TimelineDeleteForceResult {
494 0 : dir_existed,
495 0 : was_active: false,
496 0 : })
497 : }
498 : };
499 :
500 : // Finalize deletion, by dropping Timeline objects and storing smaller tombstones. The tombstones
501 : // are used to prevent still-running computes from re-creating the same timeline when they send data,
502 : // and to speed up repeated deletion calls by avoiding re-listing objects.
503 0 : self.state.lock().unwrap().delete(*ttid);
504 0 :
505 0 : result
506 0 : }
507 :
508 : /// Deactivates and deletes all timelines for the tenant. Returns map of all timelines which
509 : /// the tenant had, `true` if a timeline was active. There may be a race if new timelines are
510 : /// created simultaneously. In that case the function will return error and the caller should
511 : /// retry tenant deletion again later.
512 : ///
513 : /// If only_local, doesn't remove WAL segments in remote storage.
514 0 : pub async fn delete_force_all_for_tenant(
515 0 : &self,
516 0 : tenant_id: &TenantId,
517 0 : only_local: bool,
518 0 : ) -> Result<HashMap<TenantTimelineId, TimelineDeleteForceResult>> {
519 0 : info!("deleting all timelines for tenant {}", tenant_id);
520 0 : let to_delete = self.get_all_for_tenant(*tenant_id);
521 0 :
522 0 : let mut err = None;
523 0 :
524 0 : let mut deleted = HashMap::new();
525 0 : for tli in &to_delete {
526 0 : match self.delete(&tli.ttid, only_local).await {
527 0 : Ok(result) => {
528 0 : deleted.insert(tli.ttid, result);
529 0 : }
530 0 : Err(e) => {
531 0 : error!("failed to delete timeline {}: {}", tli.ttid, e);
532 : // Save error to return later.
533 0 : err = Some(e);
534 : }
535 : }
536 : }
537 :
538 : // If there was an error, return it.
539 0 : if let Some(e) = err {
540 0 : return Err(e);
541 0 : }
542 0 :
543 0 : // There may be broken timelines on disk, so delete the whole tenant dir as well.
544 0 : // Note that we could concurrently create new timelines while we were deleting them,
545 0 : // so the directory may be not empty. In this case timelines will have bad state
546 0 : // and timeline background jobs can panic.
547 0 : delete_dir(get_tenant_dir(
548 0 : self.state.lock().unwrap().conf.as_ref(),
549 0 : tenant_id,
550 0 : ))?;
551 :
552 0 : Ok(deleted)
553 0 : }
554 :
555 0 : pub fn housekeeping(&self, tombstone_ttl: &Duration) {
556 0 : let mut state = self.state.lock().unwrap();
557 0 :
558 0 : // We keep tombstones long enough to have a good chance of preventing rogue computes from re-creating deleted
559 0 : // timelines. If a compute kept running for longer than this TTL (or across a safekeeper restart) then they
560 0 : // may recreate a deleted timeline.
561 0 : let now = Instant::now();
562 0 : state
563 0 : .tombstones
564 0 : .retain(|_, v| now.duration_since(*v) < *tombstone_ttl);
565 0 : }
566 : }
567 :
568 : #[derive(Clone, Copy, Serialize)]
569 : pub struct TimelineDeleteForceResult {
570 : pub dir_existed: bool,
571 : pub was_active: bool,
572 : }
573 :
574 : /// Deletes directory and it's contents. Returns false if directory does not exist.
575 0 : fn delete_dir(path: Utf8PathBuf) -> Result<bool> {
576 0 : match std::fs::remove_dir_all(path) {
577 0 : Ok(_) => Ok(true),
578 0 : Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
579 0 : Err(e) => Err(e.into()),
580 : }
581 0 : }
582 :
583 : /// Create temp directory for a new timeline. It needs to be located on the same
584 : /// filesystem as the rest of the timelines. It will be automatically deleted when
585 : /// Utf8TempDir goes out of scope.
586 0 : pub async fn create_temp_timeline_dir(
587 0 : conf: &SafeKeeperConf,
588 0 : ttid: TenantTimelineId,
589 0 : ) -> Result<(Utf8TempDir, Utf8PathBuf)> {
590 0 : let temp_base = conf.workdir.join("tmp");
591 0 :
592 0 : tokio::fs::create_dir_all(&temp_base).await?;
593 :
594 0 : let tli_dir = camino_tempfile::Builder::new()
595 0 : .suffix("_temptli")
596 0 : .prefix(&format!("{}_{}_", ttid.tenant_id, ttid.timeline_id))
597 0 : .tempdir_in(temp_base)?;
598 :
599 0 : let tli_dir_path = tli_dir.path().to_path_buf();
600 0 :
601 0 : Ok((tli_dir, tli_dir_path))
602 0 : }
603 :
604 : /// Do basic validation of a temp timeline, before moving it to the global map.
605 0 : pub async fn validate_temp_timeline(
606 0 : conf: &SafeKeeperConf,
607 0 : ttid: TenantTimelineId,
608 0 : path: &Utf8PathBuf,
609 0 : ) -> Result<(Lsn, Lsn)> {
610 0 : let control_path = path.join("safekeeper.control");
611 :
612 0 : let control_store = control_file::FileStorage::load_control_file(control_path)?;
613 0 : if control_store.server.wal_seg_size == 0 {
614 0 : bail!("wal_seg_size is not set");
615 0 : }
616 :
617 0 : let wal_store = wal_storage::PhysicalStorage::new(&ttid, path, &control_store, conf.no_sync)?;
618 :
619 0 : let commit_lsn = control_store.commit_lsn;
620 0 : let flush_lsn = wal_store.flush_lsn();
621 0 :
622 0 : Ok((commit_lsn, flush_lsn))
623 0 : }
|