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