1use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18use common_telemetry::debug;
19use dashmap::{DashMap, Entry};
20use store_api::storage::{FileRef, FileRefsManifest, RegionId};
21
22use crate::error::Result;
23use crate::metrics::GC_REF_FILE_CNT;
24use crate::region::MitoRegionRef;
25use crate::sst::file::FileMeta;
26
27#[derive(Debug, Clone, Default)]
30pub struct RegionFileRefs {
31 pub files: HashMap<FileRef, usize>,
33}
34
35#[derive(Debug)]
40pub struct FileReferenceManager {
41 node_id: Option<u64>,
43 files_per_region: DashMap<RegionId, RegionFileRefs>,
45 gc_enabled: bool,
48}
49
50pub type FileReferenceManagerRef = Arc<FileReferenceManager>;
51
52impl FileReferenceManager {
53 pub fn new(node_id: Option<u64>) -> Self {
54 Self {
55 node_id,
56 files_per_region: Default::default(),
57 gc_enabled: false,
58 }
59 }
60
61 pub fn with_gc_enabled(node_id: Option<u64>, gc_enabled: bool) -> Self {
63 Self {
64 node_id,
65 files_per_region: Default::default(),
66 gc_enabled,
67 }
68 }
69
70 pub fn is_gc_enabled(&self) -> bool {
76 self.gc_enabled
77 }
78
79 fn ref_file_set(&self, region_id: RegionId) -> Option<HashSet<FileRef>> {
80 let file_refs = self.files_per_region.get(®ion_id)?.clone();
82
83 if file_refs.files.is_empty() {
84 return Some(HashSet::new());
87 }
88
89 let ref_file_set: HashSet<FileRef> = file_refs.files.keys().cloned().collect();
90
91 debug!(
92 "Get file refs for region {}, node {:?}, {} files",
93 region_id,
94 self.node_id,
95 ref_file_set.len(),
96 );
97
98 Some(ref_file_set)
99 }
100
101 pub(crate) async fn get_snapshot_of_file_refs(
106 &self,
107 query_regions_for_mem: Vec<MitoRegionRef>,
108 dst_region_to_src_regions: Vec<(MitoRegionRef, HashSet<RegionId>)>,
109 ) -> Result<FileRefsManifest> {
110 let mut ref_files = HashMap::new();
111 for region_id in query_regions_for_mem.iter().map(|r| r.region_id()) {
113 if let Some(files) = self.ref_file_set(region_id) {
114 ref_files.insert(region_id, files);
115 }
116 }
117
118 let mut manifest_version = HashMap::new();
119
120 let mut cross_region_refs = HashMap::new();
121
122 for (dst_region, src_regions) in &dst_region_to_src_regions {
124 let manifest = dst_region.manifest_ctx.manifest().await;
125 for meta in manifest.files.values() {
126 if src_regions.contains(&meta.region_id) {
127 cross_region_refs
128 .entry(meta.region_id)
129 .or_insert_with(HashSet::new)
130 .insert(dst_region.region_id());
131 ref_files
135 .entry(meta.region_id)
136 .or_insert_with(HashSet::new)
137 .insert(FileRef::new(
138 meta.region_id,
139 meta.file_id,
140 meta.index_version(),
141 ));
142 }
143 }
144 manifest_version.insert(dst_region.region_id(), manifest.manifest_version);
146 }
147
148 for r in &query_regions_for_mem {
149 let manifest = r.manifest_ctx.manifest().await;
150 ref_files.entry(r.region_id()).and_modify(|refs| {
152 *refs = std::mem::take(refs)
153 .into_iter()
154 .filter(|f| !manifest.files.contains_key(&f.file_id))
155 .collect();
156 });
157 manifest_version.insert(r.region_id(), manifest.manifest_version);
158 }
159
160 Ok(FileRefsManifest {
162 file_refs: ref_files,
163 manifest_version,
164 cross_region_refs,
165 })
166 }
167
168 pub fn add_file(&self, file_meta: &FileMeta) {
172 let region_id = file_meta.region_id;
173 let mut is_new = false;
174 {
175 let file_ref = FileRef::new(
176 file_meta.region_id,
177 file_meta.file_id,
178 file_meta.index_version(),
179 );
180 self.files_per_region
181 .entry(region_id)
182 .and_modify(|refs| {
183 refs.files
184 .entry(file_ref.clone())
185 .and_modify(|count| *count += 1)
186 .or_insert_with(|| {
187 is_new = true;
188 1
189 });
190 })
191 .or_insert_with(|| RegionFileRefs {
192 files: HashMap::from_iter([(file_ref, 1)]),
193 });
194 }
195 if is_new {
196 GC_REF_FILE_CNT.inc();
197 }
198 }
199
200 pub fn remove_file(&self, file_meta: &FileMeta) {
203 let region_id = file_meta.region_id;
204 let file_ref = FileRef::new(region_id, file_meta.file_id, file_meta.index_version());
205
206 let mut remove_table_entry = false;
207 let mut remove_file_ref = false;
208 let mut file_cnt = 0;
209
210 let region_ref = self.files_per_region.entry(region_id).and_modify(|refs| {
211 let entry = refs.files.entry(file_ref.clone()).and_modify(|count| {
212 if *count > 0 {
213 *count -= 1;
214 }
215 if *count == 0 {
216 remove_file_ref = true;
217 }
218 });
219 if let std::collections::hash_map::Entry::Occupied(o) = entry
220 && remove_file_ref
221 {
222 o.remove_entry();
223 }
224
225 file_cnt = refs.files.len();
226
227 if refs.files.is_empty() {
228 remove_table_entry = true;
229 }
230 });
231
232 if let Entry::Occupied(o) = region_ref
233 && remove_table_entry
234 {
235 o.remove_entry();
236 }
237 if remove_file_ref {
238 GC_REF_FILE_CNT.dec();
239 }
240 }
241
242 pub fn node_id(&self) -> Option<u64> {
243 self.node_id
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use std::num::NonZeroU64;
250
251 use smallvec::SmallVec;
252 use store_api::storage::{FileId, RegionId};
253
254 use super::*;
255 use crate::sst::file::{ColumnIndexMetadata, FileMeta, FileTimeRange, IndexType, RegionFileId};
256
257 #[tokio::test]
258 async fn test_file_ref_mgr() {
259 common_telemetry::init_default_ut_logging();
260
261 let sst_file_id = RegionFileId::new(RegionId::new(0, 0), FileId::random());
262
263 let file_ref_mgr = FileReferenceManager::new(None);
264
265 let file_meta = FileMeta {
266 region_id: sst_file_id.region_id(),
267 file_id: sst_file_id.file_id(),
268 time_range: FileTimeRange::default(),
269 level: 0,
270 file_size: 4096,
271 max_row_group_uncompressed_size: 4096,
272 available_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
273 indexes: vec![ColumnIndexMetadata {
274 column_id: 0,
275 created_indexes: SmallVec::from_iter([IndexType::InvertedIndex]),
276 ..Default::default()
277 }],
278 index_file_size: 4096,
279 index_version: 0,
280 num_rows: 1024,
281 num_row_groups: 1,
282 sequence: NonZeroU64::new(4096),
283 partition_expr: None,
284 num_series: 0,
285 ..Default::default()
286 };
287
288 file_ref_mgr.add_file(&file_meta);
289
290 assert_eq!(
291 file_ref_mgr
292 .files_per_region
293 .get(&file_meta.region_id)
294 .unwrap()
295 .files,
296 HashMap::from_iter([(
297 FileRef::new(
298 file_meta.region_id,
299 file_meta.file_id,
300 file_meta.index_version()
301 ),
302 1
303 )])
304 );
305
306 file_ref_mgr.add_file(&file_meta);
307
308 let expected_region_ref_manifest = HashSet::from_iter([FileRef::new(
309 file_meta.region_id,
310 file_meta.file_id,
311 file_meta.index_version(),
312 )]);
313
314 assert_eq!(
315 file_ref_mgr.ref_file_set(file_meta.region_id).unwrap(),
316 expected_region_ref_manifest
317 );
318
319 assert_eq!(
320 file_ref_mgr
321 .files_per_region
322 .get(&file_meta.region_id)
323 .unwrap()
324 .files,
325 HashMap::from_iter([(
326 FileRef::new(
327 file_meta.region_id,
328 file_meta.file_id,
329 file_meta.index_version()
330 ),
331 2
332 )])
333 );
334
335 assert_eq!(
336 file_ref_mgr.ref_file_set(file_meta.region_id).unwrap(),
337 expected_region_ref_manifest
338 );
339
340 file_ref_mgr.remove_file(&file_meta);
341
342 assert_eq!(
343 file_ref_mgr
344 .files_per_region
345 .get(&file_meta.region_id)
346 .unwrap()
347 .files,
348 HashMap::from_iter([(
349 FileRef::new(
350 file_meta.region_id,
351 file_meta.file_id,
352 file_meta.index_version()
353 ),
354 1
355 )])
356 );
357
358 assert_eq!(
359 file_ref_mgr.ref_file_set(file_meta.region_id).unwrap(),
360 expected_region_ref_manifest
361 );
362
363 file_ref_mgr.remove_file(&file_meta);
364
365 assert!(
366 file_ref_mgr
367 .files_per_region
368 .get(&file_meta.region_id)
369 .is_none(),
370 "{:?}",
371 file_ref_mgr.files_per_region
372 );
373
374 assert!(
375 file_ref_mgr.ref_file_set(file_meta.region_id).is_none(),
376 "{:?}",
377 file_ref_mgr.files_per_region
378 );
379 }
380}