Skip to main content

mito2/sst/
version.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! SST version.
16use std::collections::HashMap;
17use std::fmt;
18use std::sync::Arc;
19
20use common_time::{TimeToLive, Timestamp};
21use store_api::storage::{FileId, RegionId};
22
23use crate::sst::file::{FileHandle, FileMeta, Level, MAX_LEVEL};
24use crate::sst::file_purger::FilePurgerRef;
25
26/// A version of all SSTs in a region.
27#[derive(Debug, Clone)]
28pub(crate) struct SstVersion {
29    /// SST metadata organized by levels.
30    levels: LevelMetaArray,
31}
32
33pub(crate) type SstVersionRef = Arc<SstVersion>;
34
35impl SstVersion {
36    /// Returns a new [SstVersion].
37    pub(crate) fn new() -> SstVersion {
38        SstVersion {
39            levels: new_level_meta_vec(),
40        }
41    }
42
43    /// Returns a slice to metadatas of all levels.
44    pub(crate) fn levels(&self) -> &[LevelMeta] {
45        &self.levels
46    }
47
48    /// Add files to the version. If a file with the same `file_id` already exists,
49    /// it will be overwritten with the new file.
50    ///
51    /// # Panics
52    /// Panics if level of [FileMeta] is greater than [MAX_LEVEL].
53    pub(crate) fn add_files(
54        &mut self,
55        file_purger: FilePurgerRef,
56        files_to_add: impl Iterator<Item = FileMeta>,
57    ) {
58        for file in files_to_add {
59            let level = file.level;
60            let new_index_version = file.index_version;
61            // If the file already exists, then we should only replace the handle when the index is outdated.
62            self.levels[level as usize]
63                .files
64                .entry(file.file_id)
65                .and_modify(|f| {
66                    if *f.meta_ref() == file || f.meta_ref().is_index_up_to_date(&file) {
67                        // same file meta or current file handle's index is up-to-date, skip adding
68                        if f.index_id().version > new_index_version {
69                            // what does it mean for us to see older index version?
70                            common_telemetry::warn!(
71                                "Adding file with older index version, existing: {:?}, new: {:?}, ignoring new file",
72                                f.meta_ref(),
73                                file
74                            );
75                        }
76                    } else {
77                        // include case like old file have no index or index is outdated
78                        *f = FileHandle::new(file.clone(), file_purger.clone());
79                    }
80                })
81                .or_insert_with(|| {
82                    FileHandle::new(file.clone(), file_purger.clone())
83                });
84        }
85    }
86
87    /// Remove files from the version.
88    ///
89    /// # Panics
90    /// Panics if level of [FileMeta] is greater than [MAX_LEVEL].
91    pub(crate) fn remove_files(&mut self, files_to_remove: impl Iterator<Item = FileMeta>) {
92        for file in files_to_remove {
93            let level = file.level;
94            if let Some(handle) = self.levels[level as usize].files.remove(&file.file_id) {
95                handle.mark_deleted();
96            }
97        }
98    }
99
100    /// Marks all SSTs in this version as deleted.
101    pub(crate) fn mark_all_deleted(&self) {
102        for level_meta in &self.levels {
103            for file_handle in level_meta.files.values() {
104                file_handle.mark_deleted();
105            }
106        }
107    }
108
109    /// Returns the number of rows in SST files owned by `region_id`.
110    ///
111    /// Rows from SST files referenced from other regions, for example after
112    /// repartition, are not counted.
113    /// For historical reasons, the result is not precise for old SST files.
114    pub(crate) fn owned_num_rows(&self, region_id: RegionId) -> u64 {
115        self.levels
116            .iter()
117            .map(|level_meta| {
118                level_meta
119                    .files
120                    .values()
121                    .filter(|file_handle| file_handle.region_id() == region_id)
122                    .map(|file_handle| {
123                        let meta = file_handle.meta_ref();
124                        meta.num_rows
125                    })
126                    .sum::<u64>()
127            })
128            .sum()
129    }
130
131    /// Returns the number of SST files owned by `region_id`.
132    pub(crate) fn owned_num_files(&self, region_id: RegionId) -> u64 {
133        self.levels
134            .iter()
135            .map(|level_meta| {
136                level_meta
137                    .files
138                    .values()
139                    .filter(|file_handle| file_handle.region_id() == region_id)
140                    .count() as u64
141            })
142            .sum()
143    }
144
145    /// Returns the space occupied by SST data files owned by `region_id`.
146    pub(crate) fn owned_sst_usage(&self, region_id: RegionId) -> u64 {
147        self.levels
148            .iter()
149            .map(|level_meta| {
150                level_meta
151                    .files
152                    .values()
153                    .filter(|file_handle| file_handle.region_id() == region_id)
154                    .map(|file_handle| {
155                        let meta = file_handle.meta_ref();
156                        meta.file_size
157                    })
158                    .sum::<u64>()
159            })
160            .sum()
161    }
162
163    /// Returns the space occupied by SST index files owned by `region_id`.
164    pub(crate) fn owned_index_usage(&self, region_id: RegionId) -> u64 {
165        self.levels
166            .iter()
167            .map(|level_meta| {
168                level_meta
169                    .files
170                    .values()
171                    .filter(|file_handle| file_handle.region_id() == region_id)
172                    .map(|file_handle| {
173                        let meta = file_handle.meta_ref();
174                        meta.index_file_size
175                    })
176                    .sum::<u64>()
177            })
178            .sum()
179    }
180}
181
182// We only has fixed number of level, so we use array to hold elements. This implementation
183// detail of LevelMetaArray should not be exposed to users of [LevelMetas].
184type LevelMetaArray = [LevelMeta; MAX_LEVEL as usize];
185
186/// Metadata of files in the same SST level.
187#[derive(Clone)]
188pub struct LevelMeta {
189    /// Level number.
190    pub level: Level,
191    /// Handles of SSTs in this level.
192    pub files: HashMap<FileId, FileHandle>,
193}
194
195impl LevelMeta {
196    /// Returns an empty meta of specific `level`.
197    pub(crate) fn new(level: Level) -> LevelMeta {
198        LevelMeta {
199            level,
200            files: HashMap::new(),
201        }
202    }
203
204    /// Returns expired SSTs from current level.
205    pub fn get_expired_files(&self, now: &Timestamp, ttl: &TimeToLive) -> Vec<FileHandle> {
206        self.files
207            .values()
208            .filter(|v| {
209                let (_, end) = v.time_range();
210
211                match ttl.is_expired(&end, now) {
212                    Ok(expired) => expired,
213                    Err(e) => {
214                        common_telemetry::error!(e; "Failed to calculate region TTL expire time");
215                        false
216                    }
217                }
218            })
219            .cloned()
220            .collect()
221    }
222
223    pub fn files(&self) -> impl Iterator<Item = &FileHandle> {
224        self.files.values()
225    }
226}
227
228impl fmt::Debug for LevelMeta {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        f.debug_struct("LevelMeta")
231            .field("level", &self.level)
232            .field("files", &self.files.keys())
233            .finish()
234    }
235}
236
237fn new_level_meta_vec() -> LevelMetaArray {
238    (0u8..MAX_LEVEL)
239        .map(LevelMeta::new)
240        .collect::<Vec<_>>()
241        .try_into()
242        .unwrap() // safety: LevelMetaArray is a fixed length array with length MAX_LEVEL
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use crate::test_util::new_noop_file_purger;
249
250    #[test]
251    fn test_add_files() {
252        let purger = new_noop_file_purger();
253
254        let files = (1..=3)
255            .map(|_| FileMeta {
256                file_id: FileId::random(),
257                ..Default::default()
258            })
259            .collect::<Vec<_>>();
260
261        let mut version = SstVersion::new();
262        // files[1] is added multiple times, and that's ok.
263        version.add_files(purger.clone(), files[..=1].iter().cloned());
264        version.add_files(purger, files[1..].iter().cloned());
265
266        let added_files = &version.levels()[0].files;
267        assert_eq!(added_files.len(), 3);
268        files.iter().for_each(|f| {
269            assert!(added_files.contains_key(&f.file_id));
270        });
271    }
272
273    #[test]
274    fn test_usage_only_counts_owned_files() {
275        let purger = new_noop_file_purger();
276        let region_id = RegionId::new(1, 1);
277        let other_region_id = RegionId::new(1, 2);
278
279        let files = [
280            FileMeta {
281                region_id,
282                file_id: FileId::random(),
283                file_size: 100,
284                index_file_size: 10,
285                num_rows: 1,
286                ..Default::default()
287            },
288            FileMeta {
289                region_id,
290                file_id: FileId::random(),
291                file_size: 200,
292                index_file_size: 20,
293                num_rows: 2,
294                ..Default::default()
295            },
296            FileMeta {
297                region_id: other_region_id,
298                file_id: FileId::random(),
299                file_size: 300,
300                index_file_size: 30,
301                num_rows: 3,
302                ..Default::default()
303            },
304        ];
305
306        let mut version = SstVersion::new();
307        version.add_files(purger, files.iter().cloned());
308
309        assert_eq!(3, version.owned_num_rows(region_id));
310        assert_eq!(2, version.owned_num_files(region_id));
311        assert_eq!(300, version.owned_sst_usage(region_id));
312        assert_eq!(30, version.owned_index_usage(region_id));
313        assert_eq!(3, version.owned_num_rows(other_region_id));
314        assert_eq!(1, version.owned_num_files(other_region_id));
315        assert_eq!(300, version.owned_sst_usage(other_region_id));
316        assert_eq!(30, version.owned_index_usage(other_region_id));
317    }
318}