LCOV - code coverage report
Current view: top level - pageserver/src/tenant/timeline - init.rs (source / functions) Coverage Total Hit
Test: b9d67f908f91f00e353a27440ba89f642a869959.info Lines: 51.8 % 112 58
Test Date: 2024-11-19 21:44:13 Functions: 45.5 % 11 5

            Line data    Source code
       1              : use crate::{
       2              :     is_temporary,
       3              :     tenant::{
       4              :         ephemeral_file::is_ephemeral_file,
       5              :         remote_timeline_client::{
       6              :             self,
       7              :             index::{IndexPart, LayerFileMetadata},
       8              :         },
       9              :         storage_layer::LayerName,
      10              :     },
      11              : };
      12              : use anyhow::Context;
      13              : use camino::{Utf8Path, Utf8PathBuf};
      14              : use std::{
      15              :     collections::{hash_map, HashMap},
      16              :     str::FromStr,
      17              : };
      18              : use utils::lsn::Lsn;
      19              : 
      20              : /// Identified files in the timeline directory.
      21              : pub(super) enum Discovered {
      22              :     /// The only one we care about
      23              :     Layer(LayerName, LocalLayerFileMetadata),
      24              :     /// Old ephmeral files from previous launches, should be removed
      25              :     Ephemeral(String),
      26              :     /// Old temporary timeline files, unsure what these really are, should be removed
      27              :     Temporary(String),
      28              :     /// Temporary on-demand download files, should be removed
      29              :     TemporaryDownload(String),
      30              :     /// Backup file from previously future layers
      31              :     IgnoredBackup(Utf8PathBuf),
      32              :     /// Unrecognized, warn about these
      33              :     Unknown(String),
      34              : }
      35              : 
      36              : /// Scans the timeline directory for interesting files.
      37            6 : pub(super) fn scan_timeline_dir(path: &Utf8Path) -> anyhow::Result<Vec<Discovered>> {
      38            6 :     let mut ret = Vec::new();
      39              : 
      40           16 :     for direntry in path.read_dir_utf8()? {
      41           16 :         let direntry = direntry?;
      42           16 :         let file_name = direntry.file_name().to_string();
      43              : 
      44           16 :         let discovered = match LayerName::from_str(&file_name) {
      45           16 :             Ok(file_name) => {
      46           16 :                 let file_size = direntry.metadata()?.len();
      47           16 :                 Discovered::Layer(
      48           16 :                     file_name,
      49           16 :                     LocalLayerFileMetadata::new(direntry.path().to_owned(), file_size),
      50           16 :                 )
      51              :             }
      52              :             Err(_) => {
      53            0 :                 if file_name.ends_with(".old") {
      54              :                     // ignore these
      55            0 :                     Discovered::IgnoredBackup(direntry.path().to_owned())
      56            0 :                 } else if remote_timeline_client::is_temp_download_file(direntry.path()) {
      57            0 :                     Discovered::TemporaryDownload(file_name)
      58            0 :                 } else if is_ephemeral_file(&file_name) {
      59            0 :                     Discovered::Ephemeral(file_name)
      60            0 :                 } else if is_temporary(direntry.path()) {
      61            0 :                     Discovered::Temporary(file_name)
      62              :                 } else {
      63            0 :                     Discovered::Unknown(file_name)
      64              :                 }
      65              :             }
      66              :         };
      67              : 
      68           16 :         ret.push(discovered);
      69              :     }
      70              : 
      71            6 :     Ok(ret)
      72            6 : }
      73              : 
      74              : /// Whereas `LayerFileMetadata` describes the metadata we would store in remote storage,
      75              : /// this structure extends it with metadata describing the layer's presence in local storage.
      76              : #[derive(Clone, Debug)]
      77              : pub(super) struct LocalLayerFileMetadata {
      78              :     pub(super) file_size: u64,
      79              :     pub(super) local_path: Utf8PathBuf,
      80              : }
      81              : 
      82              : impl LocalLayerFileMetadata {
      83           16 :     pub fn new(local_path: Utf8PathBuf, file_size: u64) -> Self {
      84           16 :         Self {
      85           16 :             local_path,
      86           16 :             file_size,
      87           16 :         }
      88           16 :     }
      89              : }
      90              : 
      91              : /// For a layer that is present in remote metadata, this type describes how to handle
      92              : /// it during startup: it is either Resident (and we have some metadata about a local file),
      93              : /// or it is Evicted (and we only have remote metadata).
      94              : #[derive(Clone, Debug)]
      95              : pub(super) enum Decision {
      96              :     /// The layer is not present locally.
      97              :     Evicted(LayerFileMetadata),
      98              :     /// The layer is present locally, and metadata matches: we may hook up this layer to the
      99              :     /// existing file in local storage.
     100              :     Resident {
     101              :         local: LocalLayerFileMetadata,
     102              :         remote: LayerFileMetadata,
     103              :     },
     104              : }
     105              : 
     106              : /// A layer needs to be left out of the layer map.
     107              : #[derive(Debug)]
     108              : pub(super) enum DismissedLayer {
     109              :     /// The related layer is is in future compared to disk_consistent_lsn, it must not be loaded.
     110              :     Future {
     111              :         /// `None` if the layer is only known through [`IndexPart`].
     112              :         local: Option<LocalLayerFileMetadata>,
     113              :     },
     114              :     /// The layer only exists locally.
     115              :     ///
     116              :     /// In order to make crash safe updates to layer map, we must dismiss layers which are only
     117              :     /// found locally or not yet included in the remote `index_part.json`.
     118              :     LocalOnly(LocalLayerFileMetadata),
     119              : 
     120              :     /// The layer exists in remote storage but the local layer's metadata (e.g. file size)
     121              :     /// does not match it
     122              :     BadMetadata(LocalLayerFileMetadata),
     123              : }
     124              : 
     125              : /// Merges local discoveries and remote [`IndexPart`] to a collection of decisions.
     126            6 : pub(super) fn reconcile(
     127            6 :     local_layers: Vec<(LayerName, LocalLayerFileMetadata)>,
     128            6 :     index_part: &IndexPart,
     129            6 :     disk_consistent_lsn: Lsn,
     130            6 : ) -> Vec<(LayerName, Result<Decision, DismissedLayer>)> {
     131            6 :     let mut result = Vec::new();
     132            6 : 
     133            6 :     let mut remote_layers = HashMap::new();
     134              : 
     135              :     // Construct Decisions for layers that are found locally, if they're in remote metadata.  Otherwise
     136              :     // construct DismissedLayers to get rid of them.
     137           22 :     for (layer_name, local_metadata) in local_layers {
     138           16 :         let Some(remote_metadata) = index_part.layer_metadata.get(&layer_name) else {
     139            0 :             result.push((layer_name, Err(DismissedLayer::LocalOnly(local_metadata))));
     140            0 :             continue;
     141              :         };
     142              : 
     143           16 :         if remote_metadata.file_size != local_metadata.file_size {
     144            0 :             result.push((layer_name, Err(DismissedLayer::BadMetadata(local_metadata))));
     145            0 :             continue;
     146           16 :         }
     147           16 : 
     148           16 :         remote_layers.insert(
     149           16 :             layer_name,
     150           16 :             Decision::Resident {
     151           16 :                 local: local_metadata,
     152           16 :                 remote: remote_metadata.clone(),
     153           16 :             },
     154           16 :         );
     155              :     }
     156              : 
     157              :     // Construct Decision for layers that were not found locally
     158            6 :     index_part
     159            6 :         .layer_metadata
     160            6 :         .iter()
     161           16 :         .for_each(|(name, metadata)| {
     162           16 :             if let hash_map::Entry::Vacant(entry) = remote_layers.entry(name.clone()) {
     163            0 :                 entry.insert(Decision::Evicted(metadata.clone()));
     164           16 :             }
     165           16 :         });
     166            6 : 
     167            6 :     // For layers that were found in authoritative remote metadata, apply a final check that they are within
     168            6 :     // the disk_consistent_lsn.
     169           16 :     result.extend(remote_layers.into_iter().map(|(name, decision)| {
     170           16 :         if name.is_in_future(disk_consistent_lsn) {
     171            0 :             match decision {
     172            0 :                 Decision::Evicted(_remote) => (name, Err(DismissedLayer::Future { local: None })),
     173              :                 Decision::Resident {
     174            0 :                     local,
     175            0 :                     remote: _remote,
     176            0 :                 } => (name, Err(DismissedLayer::Future { local: Some(local) })),
     177              :             }
     178              :         } else {
     179           16 :             (name, Ok(decision))
     180              :         }
     181           16 :     }));
     182            6 : 
     183            6 :     result
     184            6 : }
     185              : 
     186            0 : pub(super) fn cleanup(path: &Utf8Path, kind: &str) -> anyhow::Result<()> {
     187            0 :     let file_name = path.file_name().expect("must be file path");
     188            0 :     tracing::debug!(kind, ?file_name, "cleaning up");
     189            0 :     std::fs::remove_file(path).with_context(|| format!("failed to remove {kind} at {path}"))
     190            0 : }
     191              : 
     192            0 : pub(super) fn cleanup_local_file_for_remote(local: &LocalLayerFileMetadata) -> anyhow::Result<()> {
     193            0 :     let local_size = local.file_size;
     194            0 :     let path = &local.local_path;
     195            0 :     let file_name = path.file_name().expect("must be file path");
     196            0 :     tracing::warn!(
     197            0 :         "removing local file {file_name:?} because it has unexpected length {local_size};"
     198              :     );
     199              : 
     200            0 :     std::fs::remove_file(path).with_context(|| format!("failed to remove layer at {path}"))
     201            0 : }
     202              : 
     203            0 : pub(super) fn cleanup_future_layer(
     204            0 :     path: &Utf8Path,
     205            0 :     name: &LayerName,
     206            0 :     disk_consistent_lsn: Lsn,
     207            0 : ) -> anyhow::Result<()> {
     208            0 :     // future image layers are allowed to be produced always for not yet flushed to disk
     209            0 :     // lsns stored in InMemoryLayer.
     210            0 :     let kind = name.kind();
     211            0 :     tracing::info!("found future {kind} layer {name} disk_consistent_lsn is {disk_consistent_lsn}");
     212            0 :     std::fs::remove_file(path)?;
     213            0 :     Ok(())
     214            0 : }
     215              : 
     216            0 : pub(super) fn cleanup_local_only_file(
     217            0 :     name: &LayerName,
     218            0 :     local: &LocalLayerFileMetadata,
     219            0 : ) -> anyhow::Result<()> {
     220            0 :     let kind = name.kind();
     221            0 :     tracing::info!(
     222            0 :         "found local-only {kind} layer {name} size {}",
     223              :         local.file_size
     224              :     );
     225            0 :     std::fs::remove_file(&local.local_path)?;
     226            0 :     Ok(())
     227            0 : }
        

Generated by: LCOV version 2.1-beta