Skip to main content

mito2/manifest/
manager.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::sync::atomic::{AtomicU64, Ordering};
17use std::time::Instant;
18
19use common_datasource::compression::CompressionType;
20use common_telemetry::{debug, info};
21use futures::TryStreamExt;
22use object_store::ObjectStore;
23use snafu::{OptionExt, ResultExt, ensure};
24use store_api::metadata::RegionMetadataRef;
25use store_api::{MAX_VERSION, MIN_VERSION, ManifestVersion};
26
27use crate::cache::manifest_cache::ManifestCache;
28use crate::config::MitoConfig;
29use crate::error::{
30    self, InstallManifestToSnafu, NoCheckpointSnafu, NoManifestsSnafu, RegionStoppedSnafu, Result,
31};
32use crate::manifest::action::{
33    RegionChange, RegionCheckpoint, RegionEdit, RegionManifest, RegionManifestBuilder,
34    RegionMetaAction, RegionMetaActionList, RemovedFile,
35};
36use crate::manifest::checkpointer::Checkpointer;
37use crate::manifest::storage::{
38    ManifestObjectStore, file_version, is_checkpoint_file, is_delta_file, list_start_after,
39    manifest_compress_type, manifest_dir,
40};
41use crate::metrics::MANIFEST_OP_ELAPSED;
42use crate::region::{ManifestStats, RegionLeaderState, RegionRoleState};
43use crate::sst::FormatType;
44
45/// Options for [RegionManifestManager].
46#[derive(Debug, Clone)]
47pub struct RegionManifestOptions {
48    /// Directory to store manifest.
49    pub manifest_dir: String,
50    pub object_store: ObjectStore,
51    pub compress_type: CompressionType,
52    /// Interval of version ([ManifestVersion](store_api::manifest::ManifestVersion)) between two checkpoints.
53    /// Set to 0 to disable checkpoint.
54    pub checkpoint_distance: u64,
55    pub remove_file_options: RemoveFileOptions,
56    /// Optional cache for manifest files.
57    pub manifest_cache: Option<ManifestCache>,
58}
59
60impl RegionManifestOptions {
61    /// Creates a new [RegionManifestOptions] with the given region directory, object store, and configuration.
62    pub fn new(config: &MitoConfig, region_dir: &str, object_store: &ObjectStore) -> Self {
63        RegionManifestOptions {
64            manifest_dir: manifest_dir(region_dir),
65            object_store: object_store.clone(),
66            // We don't allow users to set the compression algorithm as we use it as a file suffix.
67            // Currently, the manifest storage doesn't have good support for changing compression algorithms.
68            compress_type: manifest_compress_type(config.compress_manifest),
69            checkpoint_distance: config.manifest_checkpoint_distance,
70            remove_file_options: RemoveFileOptions {
71                enable_gc: config.gc.enable,
72            },
73            manifest_cache: None,
74        }
75    }
76}
77
78/// Options for updating `removed_files` field in [RegionManifest].
79#[derive(Debug, Clone)]
80#[cfg_attr(any(test, feature = "test"), derive(Default))]
81pub struct RemoveFileOptions {
82    /// Whether GC is enabled. If not, the removed files should always be empty when persisting manifest.
83    pub enable_gc: bool,
84}
85
86// rewrite note:
87// trait Checkpoint -> struct RegionCheckpoint
88// trait MetaAction -> struct RegionMetaActionList
89// trait MetaActionIterator -> struct MetaActionIteratorImpl
90
91#[cfg_attr(doc, aquamarine::aquamarine)]
92/// Manage region's manifest. Provide APIs to access (create/modify/recover) region's persisted
93/// metadata.
94///
95/// ```mermaid
96/// classDiagram
97/// class RegionManifestManager {
98///     -ManifestObjectStore store
99///     -RegionManifestOptions options
100///     -RegionManifest manifest
101///     +new() RegionManifestManager
102///     +open() Option~RegionManifestManager~
103///     +stop()
104///     +update(RegionMetaActionList action_list) ManifestVersion
105///     +manifest() RegionManifest
106/// }
107/// class ManifestObjectStore {
108///     -ObjectStore object_store
109/// }
110/// class RegionChange {
111///     -RegionMetadataRef metadata
112/// }
113/// class RegionEdit {
114///     -VersionNumber region_version
115///     -Vec~FileMeta~ files_to_add
116///     -Vec~FileMeta~ files_to_remove
117///     -SequenceNumber flushed_sequence
118/// }
119/// class RegionRemove {
120///     -RegionId region_id
121/// }
122/// RegionManifestManager o-- ManifestObjectStore
123/// RegionManifestManager o-- RegionManifest
124/// RegionManifestManager o-- RegionManifestOptions
125/// RegionManifestManager -- RegionMetaActionList
126/// RegionManifestManager -- RegionCheckpoint
127/// ManifestObjectStore o-- ObjectStore
128/// RegionMetaActionList o-- RegionMetaAction
129/// RegionMetaAction o-- ProtocolAction
130/// RegionMetaAction o-- RegionChange
131/// RegionMetaAction o-- RegionEdit
132/// RegionMetaAction o-- RegionRemove
133/// RegionChange o-- RegionMetadata
134/// RegionEdit o-- FileMeta
135///
136/// class RegionManifest {
137///     -RegionMetadataRef metadata
138///     -HashMap&lt;FileId, FileMeta&gt; files
139///     -ManifestVersion manifest_version
140/// }
141/// class RegionMetadata
142/// class FileMeta
143/// RegionManifest o-- RegionMetadata
144/// RegionManifest o-- FileMeta
145///
146/// class RegionCheckpoint {
147///     -ManifestVersion last_version
148///     -Option~RegionManifest~ checkpoint
149/// }
150/// RegionCheckpoint o-- RegionManifest
151/// ```
152#[derive(Debug)]
153pub struct RegionManifestManager {
154    store: ManifestObjectStore,
155    last_version: Arc<AtomicU64>,
156    checkpointer: Checkpointer,
157    manifest: Arc<RegionManifest>,
158    // Staging manifest is used to store the manifest of the staging region before it becomes available.
159    // It is initially inherited from the previous manifest(i.e., `self.manifest`).
160    // When the staging manifest becomes available, it will be used to construct the new manifest.
161    staging_manifest: Option<Arc<RegionManifest>>,
162    stats: ManifestStats,
163    stopped: bool,
164}
165
166impl RegionManifestManager {
167    /// Constructs a region's manifest and persist it.
168    pub async fn new(
169        metadata: RegionMetadataRef,
170        flushed_entry_id: u64,
171        options: RegionManifestOptions,
172        sst_format: FormatType,
173        stats: &ManifestStats,
174    ) -> Result<Self> {
175        // construct storage
176        let mut store = ManifestObjectStore::new(
177            &options.manifest_dir,
178            options.object_store.clone(),
179            options.compress_type,
180            stats.total_manifest_size.clone(),
181            options.manifest_cache.clone(),
182        );
183        let manifest_version = stats.manifest_version.clone();
184
185        info!(
186            "Creating region manifest in {} with metadata {:?}, flushed_entry_id: {}",
187            options.manifest_dir, metadata, flushed_entry_id
188        );
189
190        let version = MIN_VERSION;
191        let mut manifest_builder = RegionManifestBuilder::default();
192        // set the initial metadata.
193        manifest_builder.apply_change(
194            version,
195            RegionChange {
196                metadata: metadata.clone(),
197                sst_format,
198                append_mode: None,
199            },
200        );
201        let manifest = manifest_builder.try_build()?;
202        let region_id = metadata.region_id;
203
204        debug!(
205            "Build region manifest in {}, manifest: {:?}",
206            options.manifest_dir, manifest
207        );
208
209        let mut actions = vec![RegionMetaAction::Change(RegionChange {
210            metadata,
211            sst_format,
212            append_mode: None,
213        })];
214        if flushed_entry_id > 0 {
215            actions.push(RegionMetaAction::Edit(RegionEdit {
216                files_to_add: vec![],
217                files_to_remove: vec![],
218                timestamp_ms: None,
219                compaction_time_window: None,
220                flushed_entry_id: Some(flushed_entry_id),
221                flushed_sequence: None,
222                committed_sequence: None,
223            }));
224        }
225
226        // Persist region change.
227        let action_list = RegionMetaActionList::new(actions);
228
229        // New region is not in staging mode.
230        // TODO(ruihang): add staging mode support if needed.
231        store.save(version, &action_list.encode()?, false).await?;
232
233        let checkpointer = Checkpointer::new(region_id, options, store.clone(), MIN_VERSION);
234        manifest_version.store(version, Ordering::Relaxed);
235        manifest
236            .removed_files
237            .update_file_removed_cnt_to_stats(stats);
238        Ok(Self {
239            store,
240            last_version: manifest_version,
241            checkpointer,
242            manifest: Arc::new(manifest),
243            staging_manifest: None,
244            stats: stats.clone(),
245            stopped: false,
246        })
247    }
248
249    /// Opens an existing manifest.
250    ///
251    /// Returns `Ok(None)` if no such manifest.
252    pub async fn open(
253        options: RegionManifestOptions,
254        stats: &ManifestStats,
255    ) -> Result<Option<Self>> {
256        let _t = MANIFEST_OP_ELAPSED
257            .with_label_values(&["open"])
258            .start_timer();
259        let open_start = Instant::now();
260
261        // construct storage
262        let mut store = ManifestObjectStore::new(
263            &options.manifest_dir,
264            options.object_store.clone(),
265            options.compress_type,
266            stats.total_manifest_size.clone(),
267            options.manifest_cache.clone(),
268        );
269        let manifest_version = stats.manifest_version.clone();
270
271        // recover from storage
272        // construct manifest builder
273        // calculate the manifest size from the latest checkpoint
274        let mut version = MIN_VERSION;
275        let checkpoint = Self::last_checkpoint(&mut store).await?;
276        let last_checkpoint_version = checkpoint
277            .as_ref()
278            .map(|(checkpoint, _)| checkpoint.last_version)
279            .unwrap_or(MIN_VERSION);
280        let mut manifest_builder = if let Some((checkpoint, _)) = checkpoint {
281            info!(
282                "Recover region manifest {} from checkpoint version {}",
283                options.manifest_dir, checkpoint.last_version
284            );
285            version = version.max(checkpoint.last_version + 1);
286            RegionManifestBuilder::with_checkpoint(checkpoint.checkpoint)
287        } else {
288            info!(
289                "Checkpoint not found in {}, build manifest from scratch",
290                options.manifest_dir
291            );
292            RegionManifestBuilder::default()
293        };
294
295        let replay_start_version = version;
296        info!(
297            "Replaying region manifest {} from version {}, last checkpoint version: {}",
298            options.manifest_dir, replay_start_version, last_checkpoint_version,
299        );
300
301        // apply actions from storage
302        let manifests = store.fetch_manifests(version, MAX_VERSION).await?;
303        let replayed_deltas = manifests.len();
304
305        for (manifest_version, raw_action_list) in manifests {
306            let action_list = RegionMetaActionList::decode(&raw_action_list)?;
307            // set manifest size after last checkpoint
308            store.set_delta_file_size(manifest_version, raw_action_list.len() as u64);
309            for action in action_list.actions {
310                match action {
311                    RegionMetaAction::Change(action) => {
312                        manifest_builder.apply_change(manifest_version, action);
313                    }
314                    RegionMetaAction::PartitionExprChange(action) => {
315                        manifest_builder.apply_partition_expr_change(manifest_version, action);
316                    }
317                    RegionMetaAction::Edit(action) => {
318                        manifest_builder.apply_edit(manifest_version, action);
319                    }
320                    RegionMetaAction::Remove(_) => {
321                        debug!(
322                            "Unhandled action in {}, action: {:?}",
323                            options.manifest_dir, action
324                        );
325                    }
326                    RegionMetaAction::Truncate(action) => {
327                        manifest_builder.apply_truncate(manifest_version, action);
328                    }
329                }
330            }
331        }
332
333        // set the initial metadata if necessary
334        if !manifest_builder.contains_metadata() {
335            debug!("No region manifest in {}", options.manifest_dir);
336            return Ok(None);
337        }
338
339        let manifest = manifest_builder.try_build()?;
340        debug!(
341            "Recovered region manifest from {}, manifest: {:?}",
342            options.manifest_dir, manifest
343        );
344        let version = manifest.manifest_version;
345
346        let manifest_dir = options.manifest_dir.clone();
347        let checkpointer = Checkpointer::new(
348            manifest.metadata.region_id,
349            options,
350            store.clone(),
351            last_checkpoint_version,
352        );
353        manifest_version.store(version, Ordering::Relaxed);
354        manifest
355            .removed_files
356            .update_file_removed_cnt_to_stats(stats);
357        info!(
358            "Opened region manifest {}, region_id: {}, start_version: {}, last_checkpoint_version: {}, replayed_deltas: {}, final_version: {}, cost: {:?}",
359            manifest_dir,
360            manifest.metadata.region_id,
361            replay_start_version,
362            last_checkpoint_version,
363            replayed_deltas,
364            version,
365            open_start.elapsed(),
366        );
367        Ok(Some(Self {
368            store,
369            last_version: manifest_version,
370            checkpointer,
371            manifest: Arc::new(manifest),
372            // TODO(weny): open the staging manifest if exists.
373            staging_manifest: None,
374            stats: stats.clone(),
375            stopped: false,
376        }))
377    }
378
379    /// Stops the manager.
380    pub async fn stop(&mut self) {
381        self.stopped = true;
382    }
383
384    /// Returns whether the manager has stopped accepting updates.
385    pub(crate) fn is_stopped(&self) -> bool {
386        self.stopped
387    }
388
389    /// Installs the manifest changes from the current version to the target version (inclusive).
390    ///
391    /// Returns installed version.
392    /// **Note**: This function is not guaranteed to install the target version strictly.
393    /// The installed version may be greater than the target version.
394    pub async fn install_manifest_to(
395        &mut self,
396        target_version: ManifestVersion,
397    ) -> Result<ManifestVersion> {
398        let _t = MANIFEST_OP_ELAPSED
399            .with_label_values(&["install_manifest_to"])
400            .start_timer();
401
402        let last_version = self.last_version();
403        // Case 1: If the target version is less than the current version, return the current version.
404        if last_version >= target_version {
405            debug!(
406                "Target version {} is less than or equal to the current version {}, region: {}, skip install",
407                target_version, last_version, self.manifest.metadata.region_id
408            );
409            return Ok(last_version);
410        }
411
412        ensure!(
413            !self.stopped,
414            RegionStoppedSnafu {
415                region_id: self.manifest.metadata.region_id,
416            }
417        );
418
419        let region_id = self.manifest.metadata.region_id;
420        // Fetches manifests from the last version strictly.
421        let mut manifests = self
422            .store
423            // Invariant: last_version < target_version.
424            .fetch_manifests_strict_from(last_version + 1, target_version + 1, region_id)
425            .await?;
426
427        // Case 2: No manifests in range: [current_version+1, target_version+1)
428        //
429        // |---------Has been deleted------------|     [Checkpoint Version]...[Latest Version]
430        //                                                                    [Leader region]
431        // [Current Version]......[Target Version]
432        // [Follower region]
433        if manifests.is_empty() {
434            info!(
435                "Manifests are not strict from {}, region: {}, tries to install the last checkpoint",
436                last_version, self.manifest.metadata.region_id
437            );
438            let last_version = self.install_last_checkpoint().await?;
439            // Case 2.1: If the installed checkpoint version is greater than or equal to the target version, return the last version.
440            if last_version >= target_version {
441                return Ok(last_version);
442            }
443
444            // Fetches manifests from the installed version strictly.
445            manifests = self
446                .store
447                // Invariant: last_version < target_version.
448                .fetch_manifests_strict_from(last_version + 1, target_version + 1, region_id)
449                .await?;
450        }
451
452        if manifests.is_empty() {
453            return NoManifestsSnafu {
454                region_id: self.manifest.metadata.region_id,
455                start_version: last_version + 1,
456                end_version: target_version + 1,
457                last_version,
458            }
459            .fail();
460        }
461
462        debug_assert_eq!(manifests.first().unwrap().0, last_version + 1);
463        let mut manifest_builder =
464            RegionManifestBuilder::with_checkpoint(Some(self.manifest.as_ref().clone()));
465
466        for (manifest_version, raw_action_list) in manifests {
467            self.store
468                .set_delta_file_size(manifest_version, raw_action_list.len() as u64);
469            let action_list = RegionMetaActionList::decode(&raw_action_list)?;
470            for action in action_list.actions {
471                match action {
472                    RegionMetaAction::Change(action) => {
473                        manifest_builder.apply_change(manifest_version, action);
474                    }
475                    RegionMetaAction::PartitionExprChange(action) => {
476                        manifest_builder.apply_partition_expr_change(manifest_version, action);
477                    }
478                    RegionMetaAction::Edit(action) => {
479                        manifest_builder.apply_edit(manifest_version, action);
480                    }
481                    RegionMetaAction::Remove(_) => {
482                        debug!(
483                            "Unhandled action for region {}, action: {:?}",
484                            self.manifest.metadata.region_id, action
485                        );
486                    }
487                    RegionMetaAction::Truncate(action) => {
488                        manifest_builder.apply_truncate(manifest_version, action);
489                    }
490                }
491            }
492        }
493
494        let new_manifest = manifest_builder.try_build()?;
495        ensure!(
496            new_manifest.manifest_version >= target_version,
497            InstallManifestToSnafu {
498                region_id: self.manifest.metadata.region_id,
499                target_version,
500                available_version: new_manifest.manifest_version,
501                last_version,
502            }
503        );
504
505        let version = self.last_version();
506        new_manifest
507            .removed_files
508            .update_file_removed_cnt_to_stats(&self.stats);
509        self.manifest = Arc::new(new_manifest);
510        let last_version = self.set_version(self.manifest.manifest_version);
511        info!(
512            "Install manifest changes from {} to {}, region: {}",
513            version, last_version, self.manifest.metadata.region_id
514        );
515
516        Ok(last_version)
517    }
518
519    /// Installs the last checkpoint.
520    pub(crate) async fn install_last_checkpoint(&mut self) -> Result<ManifestVersion> {
521        let last_version = self.last_version();
522        let Some((checkpoint, checkpoint_size)) = Self::last_checkpoint(&mut self.store).await?
523        else {
524            return NoCheckpointSnafu {
525                region_id: self.manifest.metadata.region_id,
526                last_version,
527            }
528            .fail();
529        };
530        self.store.reset_manifest_size();
531        self.store
532            .set_checkpoint_file_size(checkpoint.last_version, checkpoint_size);
533        let builder = RegionManifestBuilder::with_checkpoint(checkpoint.checkpoint);
534        let manifest = builder.try_build()?;
535        let last_version = self.set_version(manifest.manifest_version);
536        manifest
537            .removed_files
538            .update_file_removed_cnt_to_stats(&self.stats);
539        self.manifest = Arc::new(manifest);
540        info!(
541            "Installed region manifest from checkpoint: {}, region: {}",
542            checkpoint.last_version, self.manifest.metadata.region_id
543        );
544
545        Ok(last_version)
546    }
547
548    /// Updates the manifest. Returns the current manifest version number.
549    pub async fn update(
550        &mut self,
551        action_list: RegionMetaActionList,
552        is_staging: bool,
553    ) -> Result<ManifestVersion> {
554        let _t = MANIFEST_OP_ELAPSED
555            .with_label_values(&["update"])
556            .start_timer();
557
558        ensure!(
559            !self.stopped,
560            RegionStoppedSnafu {
561                region_id: self.manifest.metadata.region_id,
562            }
563        );
564
565        let version = self.increase_version();
566        self.store
567            .save(version, &action_list.encode()?, is_staging)
568            .await?;
569
570        // For a staging region, the manifest is initially inherited from the previous manifest(i.e., `self.manifest`).
571        // When the staging manifest becomes available, it will be used to construct the new manifest.
572        let mut manifest_builder =
573            if is_staging && let Some(staging_manifest) = self.staging_manifest.as_ref() {
574                RegionManifestBuilder::with_checkpoint(Some(staging_manifest.as_ref().clone()))
575            } else {
576                RegionManifestBuilder::with_checkpoint(Some(self.manifest.as_ref().clone()))
577            };
578
579        for action in action_list.actions {
580            match action {
581                RegionMetaAction::Change(action) => {
582                    manifest_builder.apply_change(version, action);
583                }
584                RegionMetaAction::PartitionExprChange(action) => {
585                    manifest_builder.apply_partition_expr_change(version, action);
586                }
587                RegionMetaAction::Edit(action) => {
588                    manifest_builder.apply_edit(version, action);
589                }
590                RegionMetaAction::Remove(_) => {
591                    debug!(
592                        "Unhandled action for region {}, action: {:?}",
593                        self.manifest.metadata.region_id, action
594                    );
595                }
596                RegionMetaAction::Truncate(action) => {
597                    manifest_builder.apply_truncate(version, action);
598                }
599            }
600        }
601
602        if is_staging {
603            let new_manifest = manifest_builder.try_build()?;
604            self.staging_manifest = Some(Arc::new(new_manifest));
605
606            info!(
607                "Skipping checkpoint for region {} in staging mode, manifest version: {}",
608                self.manifest.metadata.region_id, self.manifest.manifest_version
609            );
610        } else {
611            let new_manifest = manifest_builder.try_build()?;
612            new_manifest
613                .removed_files
614                .update_file_removed_cnt_to_stats(&self.stats);
615            let updated_manifest = self
616                .checkpointer
617                .update_manifest_removed_files(new_manifest)?;
618            self.manifest = Arc::new(updated_manifest);
619            self.checkpointer
620                .maybe_do_checkpoint(self.manifest.as_ref());
621        }
622
623        Ok(version)
624    }
625
626    /// Clear deleted files from manifest's `removed_files` field without update version. Notice if datanode exit before checkpoint then new manifest by open region may still contain these deleted files, which is acceptable for gc process.
627    pub fn clear_deleted_files(&mut self, deleted_files: Vec<RemovedFile>) {
628        let mut manifest = (*self.manifest()).clone();
629        manifest.removed_files.clear_deleted_files(deleted_files);
630        self.set_manifest(Arc::new(manifest));
631    }
632
633    pub(crate) fn set_manifest(&mut self, manifest: Arc<RegionManifest>) {
634        self.manifest = manifest;
635    }
636
637    /// Retrieves the current [RegionManifest].
638    pub fn manifest(&self) -> Arc<RegionManifest> {
639        self.manifest.clone()
640    }
641
642    /// Retrieves the current [RegionManifest].
643    pub fn staging_manifest(&self) -> Option<Arc<RegionManifest>> {
644        self.staging_manifest.clone()
645    }
646
647    /// Returns total manifest size.
648    pub fn manifest_usage(&self) -> u64 {
649        self.store.total_manifest_size()
650    }
651
652    /// Returns true if a newer version manifest file is found.
653    ///
654    /// It is typically used in read-only regions to catch up with manifest.
655    /// It doesn't lock the manifest directory in the object store so the result
656    /// may be inaccurate if there are concurrent writes.
657    pub async fn has_update(&self) -> Result<bool> {
658        let last_version = self.last_version();
659
660        // Skip older files at the object-store layer. Files for `v == last_version`
661        // may still appear (`{path}{v:020}` sorts before `{path}{v:020}.json`) but
662        // they are filtered out below by the `version > last_version` check.
663        let start_after = list_start_after(self.store.manifest_dir(), last_version);
664        let streamer = self
665            .store
666            .manifest_lister(false, Some(&start_after))
667            .await?
668            .context(error::EmptyManifestDirSnafu {
669                manifest_dir: self.store.manifest_dir(),
670            })?;
671
672        let need_update = streamer
673            .try_any(|entry| async move {
674                let file_name = entry.name();
675                if is_delta_file(file_name) || is_checkpoint_file(file_name) {
676                    let version = file_version(file_name);
677                    if version > last_version {
678                        return true;
679                    }
680                }
681                false
682            })
683            .await
684            .context(error::OpenDalSnafu)?;
685
686        Ok(need_update)
687    }
688
689    /// Increases last version and returns the increased version.
690    fn increase_version(&mut self) -> ManifestVersion {
691        let previous = self.last_version.fetch_add(1, Ordering::Relaxed);
692        previous + 1
693    }
694
695    /// Sets the last version.
696    fn set_version(&mut self, version: ManifestVersion) -> ManifestVersion {
697        self.last_version.store(version, Ordering::Relaxed);
698        version
699    }
700
701    pub fn last_version(&self) -> ManifestVersion {
702        self.last_version.load(Ordering::Relaxed)
703    }
704
705    /// Fetches the last [RegionCheckpoint] from storage.
706    ///
707    /// If the checkpoint is not found, returns `None`.
708    /// Otherwise, returns the checkpoint and the size of the checkpoint.
709    pub(crate) async fn last_checkpoint(
710        store: &mut ManifestObjectStore,
711    ) -> Result<Option<(RegionCheckpoint, u64)>> {
712        let last_checkpoint = store.load_last_checkpoint().await?;
713
714        if let Some((_, bytes)) = last_checkpoint {
715            let checkpoint = RegionCheckpoint::decode(&bytes)?;
716            Ok(Some((checkpoint, bytes.len() as u64)))
717        } else {
718            Ok(None)
719        }
720    }
721
722    pub fn store(&self) -> ManifestObjectStore {
723        self.store.clone()
724    }
725
726    #[cfg(test)]
727    pub(crate) fn checkpointer(&self) -> &Checkpointer {
728        &self.checkpointer
729    }
730
731    /// Merge all staged manifest actions into a single action list ready for submission.
732    /// This collects all staging manifests, applies them sequentially, and returns the merged actions.
733    pub(crate) async fn merge_staged_actions(
734        &mut self,
735        region_state: RegionRoleState,
736    ) -> Result<Option<RegionMetaActionList>> {
737        // Only merge if we're in staging mode
738        if region_state != RegionRoleState::Leader(RegionLeaderState::Staging) {
739            return Ok(None);
740        }
741
742        // Fetch all staging manifests
743        let staging_manifests = self.store.fetch_staging_manifests().await?;
744
745        if staging_manifests.is_empty() {
746            info!(
747                "No staging manifests to merge for region {}",
748                self.manifest.metadata.region_id
749            );
750            return Ok(None);
751        }
752
753        info!(
754            "Merging {} staging manifests for region {}",
755            staging_manifests.len(),
756            self.manifest.metadata.region_id
757        );
758
759        // Start with current manifest state as the base
760        let mut merged_actions = Vec::new();
761        let mut latest_version = self.last_version();
762
763        // Apply all staging actions in order
764        for (manifest_version, raw_action_list) in staging_manifests {
765            let action_list = RegionMetaActionList::decode(&raw_action_list)?;
766
767            for action in action_list.actions {
768                merged_actions.push(action);
769            }
770
771            latest_version = latest_version.max(manifest_version);
772        }
773
774        if merged_actions.is_empty() {
775            return Ok(None);
776        }
777
778        info!(
779            "Successfully merged {} actions from staging manifests for region {}, latest version: {}",
780            merged_actions.len(),
781            self.manifest.metadata.region_id,
782            latest_version
783        );
784
785        Ok(Some(RegionMetaActionList::new(merged_actions)))
786    }
787
788    /// Unsets the staging manifest.
789    pub(crate) fn unset_staging_manifest(&mut self) {
790        self.staging_manifest = None;
791    }
792
793    /// Clear all staging manifests.
794    pub(crate) async fn clear_staging_manifest_and_dir(&mut self) -> Result<()> {
795        self.staging_manifest = None;
796        self.store.clear_staging_manifests().await?;
797        info!(
798            "Cleared all staging manifests for region {}",
799            self.manifest.metadata.region_id
800        );
801        Ok(())
802    }
803}
804
805#[cfg(test)]
806impl RegionManifestManager {
807    fn validate_manifest(&self, expect: &RegionMetadataRef, last_version: ManifestVersion) {
808        let manifest = self.manifest();
809        assert_eq!(manifest.metadata, *expect);
810        assert_eq!(self.manifest.manifest_version, self.last_version());
811        assert_eq!(last_version, self.last_version());
812    }
813}
814
815#[cfg(test)]
816mod test {
817    use std::time::Duration;
818
819    use api::v1::SemanticType;
820    use common_datasource::compression::CompressionType;
821    use common_test_util::temp_dir::create_temp_dir;
822    use datatypes::prelude::ConcreteDataType;
823    use datatypes::schema::ColumnSchema;
824    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
825
826    use super::*;
827    use crate::manifest::action::{RegionChange, RegionEdit};
828    use crate::manifest::tests::utils::basic_region_metadata;
829    use crate::test_util::TestEnv;
830
831    #[tokio::test]
832    async fn create_manifest_manager() {
833        let metadata = Arc::new(basic_region_metadata());
834        let env = TestEnv::new().await;
835        let manager = env
836            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
837            .await
838            .unwrap()
839            .unwrap();
840
841        manager.validate_manifest(&metadata, 0);
842    }
843
844    #[tokio::test]
845    async fn open_manifest_manager() {
846        let env = TestEnv::new().await;
847        // Try to opens an empty manifest.
848        assert!(
849            env.create_manifest_manager(CompressionType::Uncompressed, 10, None)
850                .await
851                .unwrap()
852                .is_none()
853        );
854
855        // Creates a manifest.
856        let metadata = Arc::new(basic_region_metadata());
857        let mut manager = env
858            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
859            .await
860            .unwrap()
861            .unwrap();
862        // Stops it.
863        manager.stop().await;
864
865        // Open it.
866        let manager = env
867            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
868            .await
869            .unwrap()
870            .unwrap();
871
872        manager.validate_manifest(&metadata, 0);
873    }
874
875    #[tokio::test]
876    async fn manifest_with_partition_expr_roundtrip() {
877        let env = TestEnv::new().await;
878        let expr_json =
879            r#"{"Expr":{"lhs":{"Column":"a"},"op":"GtEq","rhs":{"Value":{"UInt32":10}}}}"#;
880        let mut metadata = basic_region_metadata();
881        metadata.set_partition_expr(Some(expr_json.to_string()));
882        let metadata = Arc::new(metadata);
883        let mut manager = env
884            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
885            .await
886            .unwrap()
887            .unwrap();
888
889        // persisted manifest should contain the same partition_expr JSON
890        let manifest = manager.manifest();
891        assert_eq!(manifest.metadata.partition_expr.as_deref(), Some(expr_json));
892
893        manager.stop().await;
894
895        // Reopen and check again
896        let manager = env
897            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
898            .await
899            .unwrap()
900            .unwrap();
901        let manifest = manager.manifest();
902        assert_eq!(manifest.metadata.partition_expr.as_deref(), Some(expr_json));
903    }
904
905    #[tokio::test]
906    async fn region_change_add_column() {
907        let metadata = Arc::new(basic_region_metadata());
908        let env = TestEnv::new().await;
909        let mut manager = env
910            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
911            .await
912            .unwrap()
913            .unwrap();
914
915        let mut new_metadata_builder = RegionMetadataBuilder::from_existing((*metadata).clone());
916        new_metadata_builder.push_column_metadata(ColumnMetadata {
917            column_schema: ColumnSchema::new("val2", ConcreteDataType::float64_datatype(), false),
918            semantic_type: SemanticType::Field,
919            column_id: 252,
920        });
921        let new_metadata = Arc::new(new_metadata_builder.build().unwrap());
922
923        let action_list =
924            RegionMetaActionList::with_action(RegionMetaAction::Change(RegionChange {
925                metadata: new_metadata.clone(),
926                sst_format: FormatType::PrimaryKey,
927                append_mode: None,
928            }));
929
930        let current_version = manager.update(action_list, false).await.unwrap();
931        assert_eq!(current_version, 1);
932        manager.validate_manifest(&new_metadata, 1);
933
934        // Reopen the manager.
935        manager.stop().await;
936        let manager = env
937            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
938            .await
939            .unwrap()
940            .unwrap();
941        manager.validate_manifest(&new_metadata, 1);
942    }
943
944    /// Just for test, refer to wal_dir_usage in src/store-api/src/logstore.rs.
945    async fn manifest_dir_usage(path: &str) -> u64 {
946        let mut size = 0;
947        let mut read_dir = tokio::fs::read_dir(path).await.unwrap();
948        while let Ok(dir_entry) = read_dir.next_entry().await {
949            let Some(entry) = dir_entry else {
950                break;
951            };
952            if entry.file_type().await.unwrap().is_file() {
953                let file_name = entry.file_name().into_string().unwrap();
954                if file_name.contains(".checkpoint") || file_name.contains(".json") {
955                    let file_size = entry.metadata().await.unwrap().len() as usize;
956                    debug!("File: {file_name:?}, size: {file_size}");
957                    size += file_size;
958                }
959            }
960        }
961        size as u64
962    }
963
964    #[tokio::test]
965    async fn test_manifest_size() {
966        let metadata = Arc::new(basic_region_metadata());
967        let data_home = create_temp_dir("");
968        let data_home_path = data_home.path().to_str().unwrap().to_string();
969        let env = TestEnv::with_data_home(data_home).await;
970
971        let manifest_dir = format!("{}/manifest", data_home_path);
972
973        let mut manager = env
974            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
975            .await
976            .unwrap()
977            .unwrap();
978
979        let mut new_metadata_builder = RegionMetadataBuilder::from_existing((*metadata).clone());
980        new_metadata_builder.push_column_metadata(ColumnMetadata {
981            column_schema: ColumnSchema::new("val2", ConcreteDataType::float64_datatype(), false),
982            semantic_type: SemanticType::Field,
983            column_id: 252,
984        });
985        let new_metadata = Arc::new(new_metadata_builder.build().unwrap());
986
987        let action_list =
988            RegionMetaActionList::with_action(RegionMetaAction::Change(RegionChange {
989                metadata: new_metadata.clone(),
990                sst_format: FormatType::PrimaryKey,
991                append_mode: None,
992            }));
993
994        let current_version = manager.update(action_list, false).await.unwrap();
995        assert_eq!(current_version, 1);
996        manager.validate_manifest(&new_metadata, 1);
997
998        // get manifest size
999        let manifest_size = manager.manifest_usage();
1000        assert_eq!(manifest_size, manifest_dir_usage(&manifest_dir).await);
1001
1002        // update 10 times nop_action to trigger checkpoint
1003        for _ in 0..10 {
1004            manager
1005                .update(
1006                    RegionMetaActionList::new(vec![RegionMetaAction::Edit(RegionEdit {
1007                        files_to_add: vec![],
1008                        files_to_remove: vec![],
1009                        timestamp_ms: None,
1010                        compaction_time_window: None,
1011                        flushed_entry_id: None,
1012                        flushed_sequence: None,
1013                        committed_sequence: None,
1014                    })]),
1015                    false,
1016                )
1017                .await
1018                .unwrap();
1019        }
1020
1021        while manager.checkpointer.is_doing_checkpoint() {
1022            tokio::time::sleep(Duration::from_millis(10)).await;
1023        }
1024
1025        // check manifest size again
1026        let manifest_size = manager.manifest_usage();
1027        assert_eq!(manifest_size, manifest_dir_usage(&manifest_dir).await);
1028
1029        // Reopen the manager,
1030        // we just calculate the size from the latest checkpoint file
1031        manager.stop().await;
1032        let manager = env
1033            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
1034            .await
1035            .unwrap()
1036            .unwrap();
1037        manager.validate_manifest(&new_metadata, 11);
1038
1039        // get manifest size again
1040        let manifest_size = manager.manifest_usage();
1041        assert_eq!(manifest_size, 1397);
1042    }
1043}