LCOV - code coverage report
Current view: top level - pageserver/src/tenant/timeline - init.rs (source / functions) Coverage Total Hit
Test: b4ae4c4857f9ef3e144e982a35ee23bc84c71983.info Lines: 49.6 % 119 59
Test Date: 2024-10-22 22:13:45 Functions: 41.7 % 12 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: Option<&IndexPart>,
     129            6 :     disk_consistent_lsn: Lsn,
     130            6 : ) -> Vec<(LayerName, Result<Decision, DismissedLayer>)> {
     131            6 :     let Some(index_part) = index_part else {
     132              :         // If we have no remote metadata, no local layer files are considered valid to load
     133            0 :         return local_layers
     134            0 :             .into_iter()
     135            0 :             .map(|(layer_name, local_metadata)| {
     136            0 :                 (layer_name, Err(DismissedLayer::LocalOnly(local_metadata)))
     137            0 :             })
     138            0 :             .collect();
     139              :     };
     140              : 
     141            6 :     let mut result = Vec::new();
     142            6 : 
     143            6 :     let mut remote_layers = HashMap::new();
     144              : 
     145              :     // Construct Decisions for layers that are found locally, if they're in remote metadata.  Otherwise
     146              :     // construct DismissedLayers to get rid of them.
     147           22 :     for (layer_name, local_metadata) in local_layers {
     148           16 :         let Some(remote_metadata) = index_part.layer_metadata.get(&layer_name) else {
     149            0 :             result.push((layer_name, Err(DismissedLayer::LocalOnly(local_metadata))));
     150            0 :             continue;
     151              :         };
     152              : 
     153           16 :         if remote_metadata.file_size != local_metadata.file_size {
     154            0 :             result.push((layer_name, Err(DismissedLayer::BadMetadata(local_metadata))));
     155            0 :             continue;
     156           16 :         }
     157           16 : 
     158           16 :         remote_layers.insert(
     159           16 :             layer_name,
     160           16 :             Decision::Resident {
     161           16 :                 local: local_metadata,
     162           16 :                 remote: remote_metadata.clone(),
     163           16 :             },
     164           16 :         );
     165              :     }
     166              : 
     167              :     // Construct Decision for layers that were not found locally
     168            6 :     index_part
     169            6 :         .layer_metadata
     170            6 :         .iter()
     171           16 :         .for_each(|(name, metadata)| {
     172           16 :             if let hash_map::Entry::Vacant(entry) = remote_layers.entry(name.clone()) {
     173            0 :                 entry.insert(Decision::Evicted(metadata.clone()));
     174           16 :             }
     175           16 :         });
     176            6 : 
     177            6 :     // For layers that were found in authoritative remote metadata, apply a final check that they are within
     178            6 :     // the disk_consistent_lsn.
     179           16 :     result.extend(remote_layers.into_iter().map(|(name, decision)| {
     180           16 :         if name.is_in_future(disk_consistent_lsn) {
     181            0 :             match decision {
     182            0 :                 Decision::Evicted(_remote) => (name, Err(DismissedLayer::Future { local: None })),
     183              :                 Decision::Resident {
     184            0 :                     local,
     185            0 :                     remote: _remote,
     186            0 :                 } => (name, Err(DismissedLayer::Future { local: Some(local) })),
     187              :             }
     188              :         } else {
     189           16 :             (name, Ok(decision))
     190              :         }
     191           16 :     }));
     192            6 : 
     193            6 :     result
     194            6 : }
     195              : 
     196            0 : pub(super) fn cleanup(path: &Utf8Path, kind: &str) -> anyhow::Result<()> {
     197            0 :     let file_name = path.file_name().expect("must be file path");
     198            0 :     tracing::debug!(kind, ?file_name, "cleaning up");
     199            0 :     std::fs::remove_file(path).with_context(|| format!("failed to remove {kind} at {path}"))
     200            0 : }
     201              : 
     202            0 : pub(super) fn cleanup_local_file_for_remote(local: &LocalLayerFileMetadata) -> anyhow::Result<()> {
     203            0 :     let local_size = local.file_size;
     204            0 :     let path = &local.local_path;
     205            0 :     let file_name = path.file_name().expect("must be file path");
     206            0 :     tracing::warn!(
     207            0 :         "removing local file {file_name:?} because it has unexpected length {local_size};"
     208              :     );
     209              : 
     210            0 :     std::fs::remove_file(path).with_context(|| format!("failed to remove layer at {path}"))
     211            0 : }
     212              : 
     213            0 : pub(super) fn cleanup_future_layer(
     214            0 :     path: &Utf8Path,
     215            0 :     name: &LayerName,
     216            0 :     disk_consistent_lsn: Lsn,
     217            0 : ) -> anyhow::Result<()> {
     218            0 :     // future image layers are allowed to be produced always for not yet flushed to disk
     219            0 :     // lsns stored in InMemoryLayer.
     220            0 :     let kind = name.kind();
     221            0 :     tracing::info!("found future {kind} layer {name} disk_consistent_lsn is {disk_consistent_lsn}");
     222            0 :     std::fs::remove_file(path)?;
     223            0 :     Ok(())
     224            0 : }
     225              : 
     226            0 : pub(super) fn cleanup_local_only_file(
     227            0 :     name: &LayerName,
     228            0 :     local: &LocalLayerFileMetadata,
     229            0 : ) -> anyhow::Result<()> {
     230            0 :     let kind = name.kind();
     231            0 :     tracing::info!(
     232            0 :         "found local-only {kind} layer {name} size {}",
     233              :         local.file_size
     234              :     );
     235            0 :     std::fs::remove_file(&local.local_path)?;
     236            0 :     Ok(())
     237            0 : }
        

Generated by: LCOV version 2.1-beta