1use 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#[derive(Debug, Clone)]
28pub(crate) struct SstVersion {
29 levels: LevelMetaArray,
31}
32
33pub(crate) type SstVersionRef = Arc<SstVersion>;
34
35impl SstVersion {
36 pub(crate) fn new() -> SstVersion {
38 SstVersion {
39 levels: new_level_meta_vec(),
40 }
41 }
42
43 pub(crate) fn levels(&self) -> &[LevelMeta] {
45 &self.levels
46 }
47
48 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 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 if f.index_id().version > new_index_version {
69 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 *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 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 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 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 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 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 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
182type LevelMetaArray = [LevelMeta; MAX_LEVEL as usize];
185
186#[derive(Clone)]
188pub struct LevelMeta {
189 pub level: Level,
191 pub files: HashMap<FileId, FileHandle>,
193}
194
195impl LevelMeta {
196 pub(crate) fn new(level: Level) -> LevelMeta {
198 LevelMeta {
199 level,
200 files: HashMap::new(),
201 }
202 }
203
204 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() }
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 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}