Skip to main content

mito2/region/
utils.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
15use 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/// A loader for loading metadata from a region dir.
38#[derive(Debug, Clone)]
39pub struct RegionMetadataLoader {
40    config: Arc<MitoConfig>,
41    object_store_manager: ObjectStoreManagerRef,
42    lease_enabled: bool,
43}
44
45impl RegionMetadataLoader {
46    /// Creates a new `RegionMetadataLoader`.
47    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    /// Loads the metadata of the region from the region dir.
60    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    /// Loads the manifest of the region from the region dir.
70    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(&region_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/// A copier for copying files from a region to another region.
93#[derive(Debug, Clone)]
94pub struct RegionFileCopier {
95    access_layer: AccessLayerRef,
96}
97
98/// A descriptor for a file.
99#[derive(Debug, Clone, Copy)]
100pub enum FileDescriptor {
101    /// An index file.
102    Index {
103        file_id: FileId,
104        version: IndexVersion,
105        size: u64,
106    },
107    /// A data file.
108    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
135/// Builds the source and target file paths for a given file descriptor.
136///
137/// # Arguments
138///
139/// * `source_region_id`: The ID of the source region.
140/// * `target_region_id`: The ID of the target region.
141/// * `file_id`: The ID of the file.
142///
143/// # Returns
144///
145/// A tuple containing the source and target file paths.
146fn 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    /// Copies files from a source region to a target region.
174    ///
175    /// # Arguments
176    ///
177    /// * `source_region_id`: The ID of the source region.
178    /// * `target_region_id`: The ID of the target region.
179    /// * `file_ids`: The IDs of the files to copy.
180    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    /// Cleans the copied files from the target region.
253    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}