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)]
29struct RegionFileRefs {
30 files: HashMap<FileRef, usize>,
32}
33
34#[derive(Debug)]
36pub struct FileReferenceManager {
37 node_id: Option<u64>,
39 files_per_region: DashMap<RegionId, RegionFileRefs>,
40 gc_enabled: bool,
43}
44
45pub type FileReferenceManagerRef = Arc<FileReferenceManager>;
46
47impl FileReferenceManager {
48 pub fn new(node_id: Option<u64>) -> Self {
49 Self {
50 node_id,
51 files_per_region: Default::default(),
52 gc_enabled: false,
53 }
54 }
55
56 pub fn with_gc_enabled(node_id: Option<u64>, gc_enabled: bool) -> Self {
58 Self {
59 node_id,
60 files_per_region: Default::default(),
61 gc_enabled,
62 }
63 }
64
65 pub fn is_gc_enabled(&self) -> bool {
67 self.gc_enabled
68 }
69
70 fn ref_file_set(&self, region_id: RegionId) -> Option<HashSet<FileRef>> {
71 let region_refs = self.files_per_region.get(®ion_id)?.clone();
72 let refs: HashSet<_> = region_refs.files.keys().cloned().collect();
73 debug!(
74 "Get file refs for region {}, node {:?}, {} files",
75 region_id,
76 self.node_id,
77 region_refs.files.len(),
78 );
79 Some(refs)
80 }
81
82 pub(crate) async fn get_snapshot_of_file_refs(
84 &self,
85 query_regions_for_mem: Vec<MitoRegionRef>,
86 dst_region_to_src_regions: Vec<(MitoRegionRef, HashSet<RegionId>)>,
87 ) -> Result<FileRefsManifest> {
88 let region_ids: HashSet<_> = query_regions_for_mem
91 .iter()
92 .map(|r| r.region_id())
93 .chain(
94 dst_region_to_src_regions
95 .iter()
96 .flat_map(|(_, sources)| sources.iter().copied()),
97 )
98 .collect();
99 let mut ref_files = HashMap::new();
100 for region_id in region_ids {
101 if let Some(files) = self.ref_file_set(region_id) {
102 ref_files.insert(region_id, files);
103 }
104 }
105
106 let mut manifest_version = HashMap::new();
107 let mut cross_region_refs = HashMap::new();
108
109 for (dst_region, src_regions) in &dst_region_to_src_regions {
110 let manifest = dst_region.manifest_ctx.manifest().await;
111 for meta in manifest.files.values() {
112 if src_regions.contains(&meta.region_id) {
113 cross_region_refs
114 .entry(meta.region_id)
115 .or_insert_with(HashSet::new)
116 .insert(dst_region.region_id());
117 ref_files
120 .entry(meta.region_id)
121 .or_insert_with(HashSet::new)
122 .insert(FileRef::new(
123 meta.region_id,
124 meta.file_id,
125 meta.index_version(),
126 ));
127 }
128 }
129 manifest_version.insert(dst_region.region_id(), manifest.manifest_version);
130 }
131
132 for r in &query_regions_for_mem {
133 let manifest = r.manifest_ctx.manifest().await;
134 ref_files.entry(r.region_id()).and_modify(|refs| {
137 refs.retain(|f| {
138 !manifest
139 .files
140 .get(&f.file_id)
141 .is_some_and(|meta| meta.index_version() == f.index_version)
142 });
143 });
144 manifest_version.insert(r.region_id(), manifest.manifest_version);
145 }
146
147 Ok(FileRefsManifest {
148 file_refs: ref_files,
149 manifest_version,
150 cross_region_refs,
151 })
152 }
153
154 pub(crate) fn register_file(&self, file_meta: &FileMeta) {
156 let mut region = self
157 .files_per_region
158 .entry(file_meta.region_id)
159 .or_default();
160 let file_ref = FileRef::new(
161 file_meta.region_id,
162 file_meta.file_id,
163 file_meta.index_version(),
164 );
165 let count = region.files.entry(file_ref).or_insert_with(|| {
166 GC_REF_FILE_CNT.inc();
167 0
168 });
169 *count += 1;
170 }
171
172 pub(crate) fn remove_file(&self, file_meta: &FileMeta) {
174 let file_ref = FileRef::new(
175 file_meta.region_id,
176 file_meta.file_id,
177 file_meta.index_version(),
178 );
179 if let Entry::Occupied(mut region) = self.files_per_region.entry(file_meta.region_id) {
180 if let std::collections::hash_map::Entry::Occupied(mut file) =
181 region.get_mut().files.entry(file_ref)
182 {
183 *file.get_mut() -= 1;
184 if *file.get() == 0 {
185 file.remove();
186 GC_REF_FILE_CNT.dec();
187 }
188 }
189 if region.get().files.is_empty() {
190 region.remove();
191 }
192 }
193 }
194
195 pub fn node_id(&self) -> Option<u64> {
196 self.node_id
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use store_api::storage::FileId;
203
204 use super::*;
205 use crate::sst::file::IndexType;
206
207 #[test]
208 fn test_index_view_references_release_generations_independently() {
209 let manager = FileReferenceManager::new(None);
210 let meta = FileMeta {
211 region_id: RegionId::new(1, 1),
212 file_id: FileId::random(),
213 available_indexes: smallvec::smallvec![IndexType::InvertedIndex],
214 ..Default::default()
215 };
216 let updated = FileMeta {
217 index_version: 2,
218 ..meta.clone()
219 };
220 manager.register_file(&meta);
221 manager.register_file(&meta);
222 manager.register_file(&updated);
223 let expected: HashSet<_> = [0, 2]
224 .into_iter()
225 .map(|version| FileRef::new(meta.region_id, meta.file_id, Some(version)))
226 .collect();
227 assert_eq!(manager.ref_file_set(meta.region_id).unwrap(), expected);
228
229 manager.remove_file(&meta);
231 assert_eq!(manager.ref_file_set(meta.region_id).unwrap(), expected);
232 manager.remove_file(&meta);
233 assert_eq!(
234 manager.ref_file_set(meta.region_id).unwrap(),
235 HashSet::from([FileRef::new(meta.region_id, meta.file_id, Some(2))])
236 );
237 manager.remove_file(&updated);
238 assert!(manager.ref_file_set(meta.region_id).is_none());
239 }
240}