1use std::sync::Arc;
16use std::time::Instant;
17
18use common_base::readable_size::ReadableSize;
19use common_telemetry::{debug, error, info};
20use futures::future::try_join_all;
21use object_store::manager::ObjectStoreManagerRef;
22use snafu::{ResultExt, ensure};
23use store_api::metadata::RegionMetadataRef;
24use store_api::region_request::PathType;
25use store_api::storage::{FileId, IndexVersion, RegionId};
26
27use crate::access_layer::AccessLayerRef;
28use crate::config::MitoConfig;
29use crate::error::{self, InvalidSourceAndTargetRegionSnafu, Result};
30use crate::manifest::action::RegionManifest;
31use crate::manifest::manager::{RegionManifestManager, RegionManifestOptions};
32use crate::region::opener::get_object_store;
33use crate::region::options::RegionOptions;
34use crate::sst::file::{RegionFileId, RegionIndexId};
35use crate::sst::location;
36
37#[derive(Debug, Clone)]
39pub struct RegionMetadataLoader {
40 config: Arc<MitoConfig>,
41 object_store_manager: ObjectStoreManagerRef,
42 lease_enabled: bool,
43}
44
45impl RegionMetadataLoader {
46 pub fn new(
48 config: Arc<MitoConfig>,
49 object_store_manager: ObjectStoreManagerRef,
50 lease_enabled: bool,
51 ) -> Self {
52 Self {
53 config,
54 object_store_manager,
55 lease_enabled,
56 }
57 }
58
59 pub async fn load(
61 &self,
62 region_dir: &str,
63 region_options: &RegionOptions,
64 ) -> Result<Option<RegionMetadataRef>> {
65 let manifest = self.load_manifest(region_dir, region_options).await?;
66 Ok(manifest.map(|m| m.metadata.clone()))
67 }
68
69 pub async fn load_manifest(
71 &self,
72 region_dir: &str,
73 region_options: &RegionOptions,
74 ) -> Result<Option<Arc<RegionManifest>>> {
75 let object_store = get_object_store(®ion_options.storage, &self.object_store_manager)?;
76 let region_manifest_options =
77 RegionManifestOptions::new(&self.config, region_dir, &object_store)
78 .with_conditional_publication(
79 region_options.requires_conditional_manifest_publication(self.lease_enabled),
80 );
81 let Some(manifest_manager) =
82 RegionManifestManager::open(region_manifest_options, &Default::default()).await?
83 else {
84 return Ok(None);
85 };
86
87 let manifest = manifest_manager.manifest();
88 Ok(Some(manifest))
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct RegionFileCopier {
95 access_layer: AccessLayerRef,
96}
97
98#[derive(Debug, Clone, Copy)]
100pub enum FileDescriptor {
101 Index {
103 file_id: FileId,
104 version: IndexVersion,
105 size: u64,
106 },
107 Data { file_id: FileId, size: u64 },
109}
110
111impl FileDescriptor {
112 pub fn size(&self) -> u64 {
113 match self {
114 FileDescriptor::Index { size, .. } => *size,
115 FileDescriptor::Data { size, .. } => *size,
116 }
117 }
118
119 fn file_path(&self, region_id: RegionId, table_dir: &str, path_type: PathType) -> String {
120 match *self {
121 FileDescriptor::Index {
122 file_id, version, ..
123 } => location::index_file_path(
124 table_dir,
125 RegionIndexId::new(RegionFileId::new(region_id, file_id), version),
126 path_type,
127 ),
128 FileDescriptor::Data { file_id, .. } => {
129 location::sst_file_path(table_dir, RegionFileId::new(region_id, file_id), path_type)
130 }
131 }
132 }
133}
134
135fn build_copy_file_paths(
147 source_region_id: RegionId,
148 target_region_id: RegionId,
149 file_descriptor: FileDescriptor,
150 table_dir: &str,
151 path_type: PathType,
152) -> (String, String) {
153 (
154 file_descriptor.file_path(source_region_id, table_dir, path_type),
155 file_descriptor.file_path(target_region_id, table_dir, path_type),
156 )
157}
158
159fn build_delete_file_path(
160 target_region_id: RegionId,
161 file_descriptor: FileDescriptor,
162 table_dir: &str,
163 path_type: PathType,
164) -> String {
165 file_descriptor.file_path(target_region_id, table_dir, path_type)
166}
167
168impl RegionFileCopier {
169 pub fn new(access_layer: AccessLayerRef) -> Self {
170 Self { access_layer }
171 }
172
173 pub async fn copy_files(
181 &self,
182 source_region_id: RegionId,
183 target_region_id: RegionId,
184 file_ids: Vec<FileDescriptor>,
185 parallelism: usize,
186 ) -> Result<()> {
187 ensure!(
188 source_region_id.table_id() == target_region_id.table_id(),
189 InvalidSourceAndTargetRegionSnafu {
190 source_region_id,
191 target_region_id,
192 },
193 );
194 let table_dir = self.access_layer.table_dir();
195 let path_type = self.access_layer.path_type();
196 let object_store = self.access_layer.object_store();
197
198 info!(
199 "Copying {} files from region {} to region {}",
200 file_ids.len(),
201 source_region_id,
202 target_region_id
203 );
204 debug!(
205 "Copying files: {:?} from region {} to region {}",
206 file_ids, source_region_id, target_region_id
207 );
208 let mut tasks = Vec::with_capacity(parallelism);
209 for skip in 0..parallelism {
210 let target_file_ids = file_ids.iter().skip(skip).step_by(parallelism).copied();
211 let object_store = object_store.clone();
212 tasks.push(async move {
213 for file_desc in target_file_ids {
214 let (source_path, target_path) = build_copy_file_paths(
215 source_region_id,
216 target_region_id,
217 file_desc,
218 table_dir,
219 path_type,
220 );
221 let now = Instant::now();
222 object_store
223 .copy(&source_path, &target_path)
224 .await
225 .inspect_err(
226 |e| error!(e; "Failed to copy file {} to {}", source_path, target_path),
227 )
228 .context(error::OpenDalSnafu)?;
229 let file_size = ReadableSize(file_desc.size());
230 info!(
231 "Copied file {} to {}, file size: {}, elapsed: {:?}",
232 source_path,
233 target_path,
234 file_size,
235 now.elapsed(),
236 );
237 }
238
239 Ok(())
240 });
241 }
242
243 if let Err(err) = try_join_all(tasks).await {
244 error!(err; "Failed to copy files from region {} to region {}", source_region_id, target_region_id);
245 self.clean_target_region(target_region_id, file_ids).await;
246 return Err(err);
247 }
248
249 Ok(())
250 }
251
252 async fn clean_target_region(&self, target_region_id: RegionId, file_ids: Vec<FileDescriptor>) {
254 let table_dir = self.access_layer.table_dir();
255 let path_type = self.access_layer.path_type();
256 let object_store = self.access_layer.object_store();
257 let delete_file_path = file_ids
258 .into_iter()
259 .map(|file_descriptor| {
260 build_delete_file_path(target_region_id, file_descriptor, table_dir, path_type)
261 })
262 .collect::<Vec<_>>();
263 debug!(
264 "Deleting files: {:?} after failed to copy files to target region {}",
265 delete_file_path, target_region_id
266 );
267 if let Err(err) = object_store.delete_iter(delete_file_path).await {
268 error!(err; "Failed to delete files from region {}", target_region_id);
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 #[cfg(feature = "hdfs-object-store")]
276 use object_store::ObjectStore;
277 #[cfg(feature = "hdfs-object-store")]
278 use object_store::layers::HdfsCompatibilityLayer;
279 #[cfg(feature = "hdfs-object-store")]
280 use object_store::services::Fs;
281
282 use super::*;
283 #[cfg(feature = "hdfs-object-store")]
284 use crate::access_layer::AccessLayer;
285 #[cfg(feature = "hdfs-object-store")]
286 use crate::sst::index::intermediate::IntermediateManager;
287 #[cfg(feature = "hdfs-object-store")]
288 use crate::sst::index::puffin_manager::PuffinManagerFactory;
289
290 #[test]
291 fn test_build_copy_file_paths() {
292 common_telemetry::init_default_ut_logging();
293 let file_id = FileId::random();
294 let source_region_id = RegionId::new(1, 1);
295 let target_region_id = RegionId::new(1, 2);
296 let file_descriptor = FileDescriptor::Data { file_id, size: 100 };
297 let table_dir = "/table_dir";
298 let path_type = PathType::Bare;
299 let (source_path, target_path) = build_copy_file_paths(
300 source_region_id,
301 target_region_id,
302 file_descriptor,
303 table_dir,
304 path_type,
305 );
306 assert_eq!(
307 source_path,
308 format!("/table_dir/1_0000000001/{}.parquet", file_id)
309 );
310 assert_eq!(
311 target_path,
312 format!("/table_dir/1_0000000002/{}.parquet", file_id)
313 );
314
315 let version = 1;
316 let file_descriptor = FileDescriptor::Index {
317 file_id,
318 version,
319 size: 100,
320 };
321 let (source_path, target_path) = build_copy_file_paths(
322 source_region_id,
323 target_region_id,
324 file_descriptor,
325 table_dir,
326 path_type,
327 );
328 assert_eq!(
329 source_path,
330 format!(
331 "/table_dir/1_0000000001/index/{}.{}.puffin",
332 file_id, version
333 )
334 );
335 assert_eq!(
336 target_path,
337 format!(
338 "/table_dir/1_0000000002/index/{}.{}.puffin",
339 file_id, version
340 )
341 );
342 }
343
344 #[test]
345 fn test_build_delete_file_path() {
346 common_telemetry::init_default_ut_logging();
347 let file_id = FileId::random();
348 let target_region_id = RegionId::new(1, 2);
349 let table_dir = "/table_dir";
350 let path_type = PathType::Bare;
351
352 let file_descriptor = FileDescriptor::Data { file_id, size: 100 };
353 let path = build_delete_file_path(target_region_id, file_descriptor, table_dir, path_type);
354 assert_eq!(path, format!("/table_dir/1_0000000002/{}.parquet", file_id));
355
356 let file_descriptor = FileDescriptor::Index {
357 file_id,
358 version: 1,
359 size: 100,
360 };
361 let path = build_delete_file_path(target_region_id, file_descriptor, table_dir, path_type);
362 assert_eq!(
363 path,
364 format!("/table_dir/1_0000000002/index/{}.1.puffin", file_id)
365 );
366 }
367
368 #[cfg(feature = "hdfs-object-store")]
369 #[tokio::test]
370 async fn test_copy_region_files_with_hdfs_fallback() {
371 let (temp_dir, puffin_manager) =
372 PuffinManagerFactory::new_for_test_async("hdfs-copy-region").await;
373 let intermediate_manager = IntermediateManager::init_fs(temp_dir.path().to_string_lossy())
374 .await
375 .unwrap();
376 let storage_dir = temp_dir.path().join("storage");
377 std::fs::create_dir(&storage_dir).unwrap();
378 let object_store = ObjectStore::new(Fs::default().root(storage_dir.to_str().unwrap()))
379 .unwrap()
380 .layer(HdfsCompatibilityLayer::new_for_test());
381 let access_layer = Arc::new(AccessLayer::new(
382 "table_dir",
383 PathType::Bare,
384 object_store.clone(),
385 puffin_manager,
386 intermediate_manager,
387 ));
388 let copier = RegionFileCopier::new(access_layer);
389 let source_region_id = RegionId::new(1, 1);
390 let target_region_id = RegionId::new(1, 2);
391 let file_id = FileId::random();
392 let descriptor = FileDescriptor::Data { file_id, size: 8 };
393 let (source_path, target_path) = build_copy_file_paths(
394 source_region_id,
395 target_region_id,
396 descriptor,
397 "table_dir",
398 PathType::Bare,
399 );
400 object_store.write(&source_path, "contents").await.unwrap();
401
402 copier
403 .copy_files(source_region_id, target_region_id, vec![descriptor], 1)
404 .await
405 .unwrap();
406
407 assert_eq!(
408 b"contents",
409 object_store
410 .read(&target_path)
411 .await
412 .unwrap()
413 .to_bytes()
414 .as_ref()
415 );
416 }
417}