TLA 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::LayerFileName,
10 : Generation,
11 : },
12 : METADATA_FILE_NAME,
13 : };
14 : use anyhow::Context;
15 : use camino::Utf8Path;
16 : use std::{collections::HashMap, str::FromStr};
17 : use utils::lsn::Lsn;
18 :
19 : /// Identified files in the timeline directory.
20 : pub(super) enum Discovered {
21 : /// The only one we care about
22 : Layer(LayerFileName, u64),
23 : /// Old ephmeral files from previous launches, should be removed
24 : Ephemeral(String),
25 : /// Old temporary timeline files, unsure what these really are, should be removed
26 : Temporary(String),
27 : /// Temporary on-demand download files, should be removed
28 : TemporaryDownload(String),
29 : /// "metadata" file we persist locally and include in `index_part.json`
30 : Metadata,
31 : /// Backup file from previously future layers
32 : IgnoredBackup,
33 : /// Unrecognized, warn about these
34 : Unknown(String),
35 : }
36 :
37 : /// Scans the timeline directory for interesting files.
38 CBC 314 : pub(super) fn scan_timeline_dir(path: &Utf8Path) -> anyhow::Result<Vec<Discovered>> {
39 314 : let mut ret = Vec::new();
40 :
41 5014 : for direntry in path.read_dir_utf8()? {
42 5014 : let direntry = direntry?;
43 5014 : let file_name = direntry.file_name().to_string();
44 :
45 5014 : let discovered = match LayerFileName::from_str(&file_name) {
46 4615 : Ok(file_name) => {
47 4615 : let file_size = direntry.metadata()?.len();
48 4615 : Discovered::Layer(file_name, file_size)
49 : }
50 : Err(_) => {
51 399 : if file_name == METADATA_FILE_NAME {
52 314 : Discovered::Metadata
53 85 : } else if file_name.ends_with(".old") {
54 : // ignore these
55 UBC 0 : Discovered::IgnoredBackup
56 CBC 85 : } else if remote_timeline_client::is_temp_download_file(direntry.path()) {
57 UBC 0 : Discovered::TemporaryDownload(file_name)
58 CBC 85 : } else if is_ephemeral_file(&file_name) {
59 77 : Discovered::Ephemeral(file_name)
60 8 : } else if is_temporary(direntry.path()) {
61 8 : Discovered::Temporary(file_name)
62 : } else {
63 UBC 0 : Discovered::Unknown(file_name)
64 : }
65 : }
66 : };
67 :
68 CBC 5014 : ret.push(discovered);
69 : }
70 :
71 314 : Ok(ret)
72 314 : }
73 :
74 : /// Decision on what to do with a layer file after considering its local and remote metadata.
75 UBC 0 : #[derive(Clone, Debug)]
76 : pub(super) enum Decision {
77 : /// The layer is not present locally.
78 : Evicted(LayerFileMetadata),
79 : /// The layer is present locally, but local metadata does not match remote; we must
80 : /// delete it and treat it as evicted.
81 : UseRemote {
82 : local: LayerFileMetadata,
83 : remote: LayerFileMetadata,
84 : },
85 : /// The layer is present locally, and metadata matches.
86 : UseLocal(LayerFileMetadata),
87 : }
88 :
89 : /// A layer needs to be left out of the layer map.
90 0 : #[derive(Debug)]
91 : pub(super) enum DismissedLayer {
92 : /// The related layer is is in future compared to disk_consistent_lsn, it must not be loaded.
93 : Future {
94 : /// The local metadata. `None` if the layer is only known through [`IndexPart`].
95 : local: Option<LayerFileMetadata>,
96 : },
97 : /// The layer only exists locally.
98 : ///
99 : /// In order to make crash safe updates to layer map, we must dismiss layers which are only
100 : /// found locally or not yet included in the remote `index_part.json`.
101 : LocalOnly(LayerFileMetadata),
102 : }
103 :
104 : /// Merges local discoveries and remote [`IndexPart`] to a collection of decisions.
105 CBC 314 : pub(super) fn reconcile(
106 314 : discovered: Vec<(LayerFileName, u64)>,
107 314 : index_part: Option<&IndexPart>,
108 314 : disk_consistent_lsn: Lsn,
109 314 : generation: Generation,
110 314 : ) -> Vec<(LayerFileName, Result<Decision, DismissedLayer>)> {
111 314 : use Decision::*;
112 314 :
113 314 : // name => (local, remote)
114 314 : type Collected = HashMap<LayerFileName, (Option<LayerFileMetadata>, Option<LayerFileMetadata>)>;
115 314 :
116 314 : let mut discovered = discovered
117 314 : .into_iter()
118 4615 : .map(|(name, file_size)| {
119 4615 : (
120 4615 : name,
121 4615 : // The generation here will be corrected to match IndexPart in the merge below, unless
122 4615 : // it is not in IndexPart, in which case using our current generation makes sense
123 4615 : // because it will be uploaded in this generation.
124 4615 : (Some(LayerFileMetadata::new(file_size, generation)), None),
125 4615 : )
126 4615 : })
127 314 : .collect::<Collected>();
128 314 :
129 314 : // merge any index_part information, when available
130 314 : index_part
131 314 : .as_ref()
132 314 : .map(|ip| ip.layer_metadata.iter())
133 314 : .into_iter()
134 314 : .flatten()
135 8078 : .map(|(name, metadata)| (name, LayerFileMetadata::from(metadata)))
136 314 : .for_each(|(name, metadata)| {
137 8078 : if let Some(existing) = discovered.get_mut(name) {
138 4347 : existing.1 = Some(metadata);
139 4347 : } else {
140 3731 : discovered.insert(name.to_owned(), (None, Some(metadata)));
141 3731 : }
142 8078 : });
143 314 :
144 314 : discovered
145 314 : .into_iter()
146 8346 : .map(|(name, (local, remote))| {
147 8346 : let decision = if name.is_in_future(disk_consistent_lsn) {
148 7 : Err(DismissedLayer::Future { local })
149 : } else {
150 8339 : match (local, remote) {
151 4347 : (Some(local), Some(remote)) if local != remote => {
152 1768 : Ok(UseRemote { local, remote })
153 : }
154 2579 : (Some(x), Some(_)) => Ok(UseLocal(x)),
155 3731 : (None, Some(x)) => Ok(Evicted(x)),
156 261 : (Some(x), None) => Err(DismissedLayer::LocalOnly(x)),
157 : (None, None) => {
158 UBC 0 : unreachable!("there must not be any non-local non-remote files")
159 : }
160 : }
161 : };
162 :
163 CBC 8346 : (name, decision)
164 8346 : })
165 314 : .collect::<Vec<_>>()
166 314 : }
167 :
168 85 : pub(super) fn cleanup(path: &Utf8Path, kind: &str) -> anyhow::Result<()> {
169 85 : let file_name = path.file_name().expect("must be file path");
170 85 : tracing::debug!(kind, ?file_name, "cleaning up");
171 85 : std::fs::remove_file(path).with_context(|| format!("failed to remove {kind} at {path}"))
172 85 : }
173 :
174 1 : pub(super) fn cleanup_local_file_for_remote(
175 1 : path: &Utf8Path,
176 1 : local: &LayerFileMetadata,
177 1 : remote: &LayerFileMetadata,
178 1 : ) -> anyhow::Result<()> {
179 1 : let local_size = local.file_size();
180 1 : let remote_size = remote.file_size();
181 1 :
182 1 : let file_name = path.file_name().expect("must be file path");
183 1 : tracing::warn!("removing local file {file_name:?} because it has unexpected length {local_size}; length in remote index is {remote_size}");
184 1 : if let Err(err) = crate::tenant::timeline::rename_to_backup(path) {
185 UBC 0 : assert!(
186 0 : path.exists(),
187 0 : "we would leave the local_layer without a file if this does not hold: {path}",
188 : );
189 0 : Err(err)
190 : } else {
191 CBC 1 : Ok(())
192 : }
193 1 : }
194 :
195 7 : pub(super) fn cleanup_future_layer(
196 7 : path: &Utf8Path,
197 7 : name: &LayerFileName,
198 7 : disk_consistent_lsn: Lsn,
199 7 : ) -> anyhow::Result<()> {
200 7 : // future image layers are allowed to be produced always for not yet flushed to disk
201 7 : // lsns stored in InMemoryLayer.
202 7 : let kind = name.kind();
203 7 : tracing::info!("found future {kind} layer {name} disk_consistent_lsn is {disk_consistent_lsn}");
204 7 : std::fs::remove_file(path)?;
205 7 : Ok(())
206 7 : }
207 :
208 261 : pub(super) fn cleanup_local_only_file(
209 261 : path: &Utf8Path,
210 261 : name: &LayerFileName,
211 261 : local: &LayerFileMetadata,
212 261 : ) -> anyhow::Result<()> {
213 261 : let kind = name.kind();
214 261 : tracing::info!("found local-only {kind} layer {name}, metadata {local:?}");
215 261 : std::fs::remove_file(path)?;
216 261 : Ok(())
217 261 : }
|