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        self.update_inner(action_list, is_staging, !is_staging)
555            .await
556    }
557
558    /// Updates the normal manifest without starting a checkpoint.
559    ///
560    /// This is only used while a leader is downgrading. The final flush still
561    /// publishes its manifest edit, but must not start cleanup after the
562    /// downgrade checkpoint barrier.
563    pub(crate) async fn update_normal_without_checkpoint(
564        &mut self,
565        action_list: RegionMetaActionList,
566    ) -> Result<ManifestVersion> {
567        self.update_inner(action_list, false, false).await
568    }
569
570    async fn update_inner(
571        &mut self,
572        action_list: RegionMetaActionList,
573        is_staging: bool,
574        allow_checkpoint: bool,
575    ) -> Result<ManifestVersion> {
576        let _t = MANIFEST_OP_ELAPSED
577            .with_label_values(&["update"])
578            .start_timer();
579
580        ensure!(
581            !self.stopped,
582            RegionStoppedSnafu {
583                region_id: self.manifest.metadata.region_id,
584            }
585        );
586
587        let version = self.increase_version();
588        self.store
589            .save(version, &action_list.encode()?, is_staging)
590            .await?;
591
592        // For a staging region, the manifest is initially inherited from the previous manifest(i.e., `self.manifest`).
593        // When the staging manifest becomes available, it will be used to construct the new manifest.
594        let mut manifest_builder =
595            if is_staging && let Some(staging_manifest) = self.staging_manifest.as_ref() {
596                RegionManifestBuilder::with_checkpoint(Some(staging_manifest.as_ref().clone()))
597            } else {
598                RegionManifestBuilder::with_checkpoint(Some(self.manifest.as_ref().clone()))
599            };
600
601        for action in action_list.actions {
602            match action {
603                RegionMetaAction::Change(action) => {
604                    manifest_builder.apply_change(version, action);
605                }
606                RegionMetaAction::PartitionExprChange(action) => {
607                    manifest_builder.apply_partition_expr_change(version, action);
608                }
609                RegionMetaAction::Edit(action) => {
610                    manifest_builder.apply_edit(version, action);
611                }
612                RegionMetaAction::Remove(_) => {
613                    debug!(
614                        "Unhandled action for region {}, action: {:?}",
615                        self.manifest.metadata.region_id, action
616                    );
617                }
618                RegionMetaAction::Truncate(action) => {
619                    manifest_builder.apply_truncate(version, action);
620                }
621            }
622        }
623
624        if is_staging {
625            let new_manifest = manifest_builder.try_build()?;
626            self.staging_manifest = Some(Arc::new(new_manifest));
627
628            info!(
629                "Skipping checkpoint for region {} in staging mode, manifest version: {}",
630                self.manifest.metadata.region_id, self.manifest.manifest_version
631            );
632        } else {
633            let new_manifest = manifest_builder.try_build()?;
634            new_manifest
635                .removed_files
636                .update_file_removed_cnt_to_stats(&self.stats);
637            let updated_manifest = self
638                .checkpointer
639                .update_manifest_removed_files(new_manifest)?;
640            self.manifest = Arc::new(updated_manifest);
641            if allow_checkpoint {
642                self.checkpointer
643                    .maybe_do_checkpoint(self.manifest.as_ref())
644                    .await;
645            }
646        }
647
648        Ok(version)
649    }
650
651    /// Waits for an in-flight checkpoint to finish, including its cleanup.
652    pub(crate) async fn wait_for_pending_checkpoint(&mut self) {
653        self.checkpointer.wait_for_pending_checkpoint().await;
654    }
655
656    /// 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.
657    pub fn clear_deleted_files(&mut self, deleted_files: Vec<RemovedFile>) {
658        let mut manifest = (*self.manifest()).clone();
659        manifest.removed_files.clear_deleted_files(deleted_files);
660        self.set_manifest(Arc::new(manifest));
661    }
662
663    pub(crate) fn set_manifest(&mut self, manifest: Arc<RegionManifest>) {
664        self.manifest = manifest;
665    }
666
667    /// Retrieves the current [RegionManifest].
668    pub fn manifest(&self) -> Arc<RegionManifest> {
669        self.manifest.clone()
670    }
671
672    /// Retrieves the current [RegionManifest].
673    pub fn staging_manifest(&self) -> Option<Arc<RegionManifest>> {
674        self.staging_manifest.clone()
675    }
676
677    /// Returns total manifest size.
678    pub fn manifest_usage(&self) -> u64 {
679        self.store.total_manifest_size()
680    }
681
682    /// Returns true if a newer version manifest file is found.
683    ///
684    /// It is typically used in read-only regions to catch up with manifest.
685    /// It doesn't lock the manifest directory in the object store so the result
686    /// may be inaccurate if there are concurrent writes.
687    pub async fn has_update(&self) -> Result<bool> {
688        let last_version = self.last_version();
689
690        // Skip older files at the object-store layer. Files for `v == last_version`
691        // may still appear (`{path}{v:020}` sorts before `{path}{v:020}.json`) but
692        // they are filtered out below by the `version > last_version` check.
693        let start_after = list_start_after(self.store.manifest_dir(), last_version);
694        let streamer = self
695            .store
696            .manifest_lister(false, Some(&start_after))
697            .await?
698            .context(error::EmptyManifestDirSnafu {
699                manifest_dir: self.store.manifest_dir(),
700            })?;
701
702        let need_update = streamer
703            .try_any(|entry| async move {
704                let file_name = entry.name();
705                if is_delta_file(file_name) || is_checkpoint_file(file_name) {
706                    let version = file_version(file_name);
707                    if version > last_version {
708                        return true;
709                    }
710                }
711                false
712            })
713            .await
714            .context(error::OpenDalSnafu)?;
715
716        Ok(need_update)
717    }
718
719    /// Increases last version and returns the increased version.
720    fn increase_version(&mut self) -> ManifestVersion {
721        let previous = self.last_version.fetch_add(1, Ordering::Relaxed);
722        previous + 1
723    }
724
725    /// Sets the last version.
726    fn set_version(&mut self, version: ManifestVersion) -> ManifestVersion {
727        self.last_version.store(version, Ordering::Relaxed);
728        version
729    }
730
731    pub fn last_version(&self) -> ManifestVersion {
732        self.last_version.load(Ordering::Relaxed)
733    }
734
735    /// Fetches the last [RegionCheckpoint] from storage.
736    ///
737    /// If the checkpoint is not found, returns `None`.
738    /// Otherwise, returns the checkpoint and the size of the checkpoint.
739    pub(crate) async fn last_checkpoint(
740        store: &mut ManifestObjectStore,
741    ) -> Result<Option<(RegionCheckpoint, u64)>> {
742        let last_checkpoint = store.load_last_checkpoint().await?;
743
744        if let Some((_, bytes)) = last_checkpoint {
745            let checkpoint = RegionCheckpoint::decode(&bytes)?;
746            Ok(Some((checkpoint, bytes.len() as u64)))
747        } else {
748            Ok(None)
749        }
750    }
751
752    pub fn store(&self) -> ManifestObjectStore {
753        self.store.clone()
754    }
755
756    #[cfg(test)]
757    pub(crate) fn checkpointer(&self) -> &Checkpointer {
758        &self.checkpointer
759    }
760
761    /// Merge all staged manifest actions into a single action list ready for submission.
762    /// This collects all staging manifests, applies them sequentially, and returns the merged actions.
763    pub(crate) async fn merge_staged_actions(
764        &mut self,
765        region_state: RegionRoleState,
766    ) -> Result<Option<RegionMetaActionList>> {
767        // Only merge if we're in staging mode
768        if region_state != RegionRoleState::Leader(RegionLeaderState::Staging) {
769            return Ok(None);
770        }
771
772        // Fetch all staging manifests
773        let staging_manifests = self.store.fetch_staging_manifests().await?;
774
775        if staging_manifests.is_empty() {
776            info!(
777                "No staging manifests to merge for region {}",
778                self.manifest.metadata.region_id
779            );
780            return Ok(None);
781        }
782
783        info!(
784            "Merging {} staging manifests for region {}",
785            staging_manifests.len(),
786            self.manifest.metadata.region_id
787        );
788
789        // Start with current manifest state as the base
790        let mut merged_actions = Vec::new();
791        let mut latest_version = self.last_version();
792
793        // Apply all staging actions in order
794        for (manifest_version, raw_action_list) in staging_manifests {
795            let action_list = RegionMetaActionList::decode(&raw_action_list)?;
796
797            for action in action_list.actions {
798                merged_actions.push(action);
799            }
800
801            latest_version = latest_version.max(manifest_version);
802        }
803
804        if merged_actions.is_empty() {
805            return Ok(None);
806        }
807
808        info!(
809            "Successfully merged {} actions from staging manifests for region {}, latest version: {}",
810            merged_actions.len(),
811            self.manifest.metadata.region_id,
812            latest_version
813        );
814
815        Ok(Some(RegionMetaActionList::new(merged_actions)))
816    }
817
818    /// Unsets the staging manifest.
819    pub(crate) fn unset_staging_manifest(&mut self) {
820        self.staging_manifest = None;
821    }
822
823    /// Clear all staging manifests.
824    pub(crate) async fn clear_staging_manifest_and_dir(&mut self) -> Result<()> {
825        self.staging_manifest = None;
826        self.store.clear_staging_manifests().await?;
827        info!(
828            "Cleared all staging manifests for region {}",
829            self.manifest.metadata.region_id
830        );
831        Ok(())
832    }
833}
834
835#[cfg(test)]
836impl RegionManifestManager {
837    fn validate_manifest(&self, expect: &RegionMetadataRef, last_version: ManifestVersion) {
838        let manifest = self.manifest();
839        assert_eq!(manifest.metadata, *expect);
840        assert_eq!(self.manifest.manifest_version, self.last_version());
841        assert_eq!(last_version, self.last_version());
842    }
843}
844
845#[cfg(test)]
846mod test {
847    use std::time::Duration;
848
849    use api::v1::SemanticType;
850    use common_datasource::compression::CompressionType;
851    use common_test_util::temp_dir::create_temp_dir;
852    use datatypes::prelude::ConcreteDataType;
853    use datatypes::schema::ColumnSchema;
854    use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
855
856    use super::*;
857    use crate::manifest::action::{RegionChange, RegionEdit};
858    use crate::manifest::tests::utils::basic_region_metadata;
859    use crate::test_util::TestEnv;
860
861    #[tokio::test]
862    async fn create_manifest_manager() {
863        let metadata = Arc::new(basic_region_metadata());
864        let env = TestEnv::new().await;
865        let manager = env
866            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
867            .await
868            .unwrap()
869            .unwrap();
870
871        manager.validate_manifest(&metadata, 0);
872    }
873
874    #[tokio::test]
875    async fn open_manifest_manager() {
876        let env = TestEnv::new().await;
877        // Try to opens an empty manifest.
878        assert!(
879            env.create_manifest_manager(CompressionType::Uncompressed, 10, None)
880                .await
881                .unwrap()
882                .is_none()
883        );
884
885        // Creates a manifest.
886        let metadata = Arc::new(basic_region_metadata());
887        let mut manager = env
888            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
889            .await
890            .unwrap()
891            .unwrap();
892        // Stops it.
893        manager.stop().await;
894
895        // Open it.
896        let manager = env
897            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
898            .await
899            .unwrap()
900            .unwrap();
901
902        manager.validate_manifest(&metadata, 0);
903    }
904
905    #[tokio::test]
906    async fn manifest_with_partition_expr_roundtrip() {
907        let env = TestEnv::new().await;
908        let expr_json =
909            r#"{"Expr":{"lhs":{"Column":"a"},"op":"GtEq","rhs":{"Value":{"UInt32":10}}}}"#;
910        let mut metadata = basic_region_metadata();
911        metadata.set_partition_expr(Some(expr_json.to_string()));
912        let metadata = Arc::new(metadata);
913        let mut manager = env
914            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
915            .await
916            .unwrap()
917            .unwrap();
918
919        // persisted manifest should contain the same partition_expr JSON
920        let manifest = manager.manifest();
921        assert_eq!(manifest.metadata.partition_expr.as_deref(), Some(expr_json));
922
923        manager.stop().await;
924
925        // Reopen and check again
926        let manager = env
927            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
928            .await
929            .unwrap()
930            .unwrap();
931        let manifest = manager.manifest();
932        assert_eq!(manifest.metadata.partition_expr.as_deref(), Some(expr_json));
933    }
934
935    #[tokio::test]
936    async fn region_change_add_column() {
937        let metadata = Arc::new(basic_region_metadata());
938        let env = TestEnv::new().await;
939        let mut manager = env
940            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
941            .await
942            .unwrap()
943            .unwrap();
944
945        let mut new_metadata_builder = RegionMetadataBuilder::from_existing((*metadata).clone());
946        new_metadata_builder.push_column_metadata(ColumnMetadata {
947            column_schema: ColumnSchema::new("val2", ConcreteDataType::float64_datatype(), false),
948            semantic_type: SemanticType::Field,
949            column_id: 252,
950        });
951        let new_metadata = Arc::new(new_metadata_builder.build().unwrap());
952
953        let action_list =
954            RegionMetaActionList::with_action(RegionMetaAction::Change(RegionChange {
955                metadata: new_metadata.clone(),
956                sst_format: FormatType::PrimaryKey,
957                append_mode: None,
958            }));
959
960        let current_version = manager.update(action_list, false).await.unwrap();
961        assert_eq!(current_version, 1);
962        manager.validate_manifest(&new_metadata, 1);
963
964        // Reopen the manager.
965        manager.stop().await;
966        let manager = env
967            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
968            .await
969            .unwrap()
970            .unwrap();
971        manager.validate_manifest(&new_metadata, 1);
972    }
973
974    /// Just for test, refer to wal_dir_usage in src/store-api/src/logstore.rs.
975    async fn manifest_dir_usage(path: &str) -> u64 {
976        let mut size = 0;
977        let mut read_dir = tokio::fs::read_dir(path).await.unwrap();
978        while let Ok(dir_entry) = read_dir.next_entry().await {
979            let Some(entry) = dir_entry else {
980                break;
981            };
982            if entry.file_type().await.unwrap().is_file() {
983                let file_name = entry.file_name().into_string().unwrap();
984                if file_name.contains(".checkpoint") || file_name.contains(".json") {
985                    let file_size = entry.metadata().await.unwrap().len() as usize;
986                    debug!("File: {file_name:?}, size: {file_size}");
987                    size += file_size;
988                }
989            }
990        }
991        size as u64
992    }
993
994    #[tokio::test]
995    async fn test_manifest_size() {
996        let metadata = Arc::new(basic_region_metadata());
997        let data_home = create_temp_dir("");
998        let data_home_path = data_home.path().to_str().unwrap().to_string();
999        let env = TestEnv::with_data_home(data_home).await;
1000
1001        let manifest_dir = format!("{}/manifest", data_home_path);
1002
1003        let mut manager = env
1004            .create_manifest_manager(CompressionType::Uncompressed, 10, Some(metadata.clone()))
1005            .await
1006            .unwrap()
1007            .unwrap();
1008
1009        let mut new_metadata_builder = RegionMetadataBuilder::from_existing((*metadata).clone());
1010        new_metadata_builder.push_column_metadata(ColumnMetadata {
1011            column_schema: ColumnSchema::new("val2", ConcreteDataType::float64_datatype(), false),
1012            semantic_type: SemanticType::Field,
1013            column_id: 252,
1014        });
1015        let new_metadata = Arc::new(new_metadata_builder.build().unwrap());
1016
1017        let action_list =
1018            RegionMetaActionList::with_action(RegionMetaAction::Change(RegionChange {
1019                metadata: new_metadata.clone(),
1020                sst_format: FormatType::PrimaryKey,
1021                append_mode: None,
1022            }));
1023
1024        let current_version = manager.update(action_list, false).await.unwrap();
1025        assert_eq!(current_version, 1);
1026        manager.validate_manifest(&new_metadata, 1);
1027
1028        // get manifest size
1029        let manifest_size = manager.manifest_usage();
1030        assert_eq!(manifest_size, manifest_dir_usage(&manifest_dir).await);
1031
1032        // update 10 times nop_action to trigger checkpoint
1033        for _ in 0..10 {
1034            manager
1035                .update(
1036                    RegionMetaActionList::new(vec![RegionMetaAction::Edit(RegionEdit {
1037                        files_to_add: vec![],
1038                        files_to_remove: vec![],
1039                        timestamp_ms: None,
1040                        compaction_time_window: None,
1041                        flushed_entry_id: None,
1042                        flushed_sequence: None,
1043                        committed_sequence: None,
1044                    })]),
1045                    false,
1046                )
1047                .await
1048                .unwrap();
1049        }
1050
1051        while manager.checkpointer.is_doing_checkpoint() {
1052            tokio::time::sleep(Duration::from_millis(10)).await;
1053        }
1054
1055        // check manifest size again
1056        let manifest_size = manager.manifest_usage();
1057        assert_eq!(manifest_size, manifest_dir_usage(&manifest_dir).await);
1058
1059        // Reopen the manager,
1060        // we just calculate the size from the latest checkpoint file
1061        manager.stop().await;
1062        let manager = env
1063            .create_manifest_manager(CompressionType::Uncompressed, 10, None)
1064            .await
1065            .unwrap()
1066            .unwrap();
1067        manager.validate_manifest(&new_metadata, 11);
1068
1069        // get manifest size again
1070        let manifest_size = manager.manifest_usage();
1071        assert_eq!(manifest_size, 1397);
1072    }
1073}