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 : }
|