Skip to main content

mito2/
region.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
15//! Mito region.
16
17pub mod catchup;
18pub mod opener;
19pub mod options;
20pub mod utils;
21pub(crate) mod version;
22
23use std::collections::hash_map::Entry;
24use std::collections::{HashMap, HashSet};
25use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
26use std::sync::{Arc, Mutex, RwLock};
27
28use common_base::hash::partition_expr_version;
29use common_recordbatch::adapter::RegionQueryStatCounters;
30use common_telemetry::{error, info, warn};
31use crossbeam_utils::atomic::AtomicCell;
32use object_store::ObjectStore;
33use partition::expr::PartitionExpr;
34use snafu::{OptionExt, ResultExt, ensure};
35use store_api::ManifestVersion;
36use store_api::codec::PrimaryKeyEncoding;
37use store_api::logstore::provider::Provider;
38use store_api::metadata::RegionMetadataRef;
39use store_api::metrics::{REGION_QUERY_CPU_TIME, REGION_QUERY_SCANNED_BYTES};
40use store_api::region_engine::{
41    RegionManifestInfo, RegionRole, RegionStatistic, SettableRegionRoleState,
42};
43use store_api::region_info::RegionInfoEntry;
44use store_api::region_request::{PathType, StagingPartitionDirective};
45use store_api::sst_entry::ManifestSstEntry;
46use store_api::storage::{FileId, RegionId, SequenceNumber};
47use tokio::sync::RwLockWriteGuard;
48pub use utils::*;
49
50use crate::access_layer::AccessLayerRef;
51use crate::engine::region_hook::{PendingManifestHook, RegionHookRef};
52use crate::error::{
53    FlushableRegionStateSnafu, InvalidPartitionExprSnafu, RegionNotFoundSnafu, RegionStateSnafu,
54    RegionTruncatedSnafu, Result, UnexpectedSnafu, UpdateManifestSnafu,
55};
56use crate::manifest::action::{
57    RegionChange, RegionManifest, RegionMetaAction, RegionMetaActionList,
58};
59use crate::manifest::manager::RegionManifestManager;
60use crate::region::version::{VersionControlRef, VersionRef};
61use crate::request::{OnFailure, OptionOutputTx};
62use crate::series_index::{SeriesIndexVersion, SeriesIndexVersionControl};
63use crate::sst::file::FileMeta;
64use crate::sst::file_purger::FilePurgerRef;
65use crate::sst::location::{index_file_path, sst_file_path};
66use crate::time_provider::TimeProviderRef;
67
68/// This is the approximate factor to estimate the size of wal.
69const ESTIMATED_WAL_FACTOR: f32 = 0.42825;
70
71/// Region status include region id, memtable usage, sst usage, wal usage and manifest usage.
72#[derive(Debug)]
73pub struct RegionUsage {
74    pub region_id: RegionId,
75    pub wal_usage: u64,
76    pub sst_usage: u64,
77    pub manifest_usage: u64,
78}
79
80impl RegionUsage {
81    pub fn disk_usage(&self) -> u64 {
82        self.wal_usage + self.sst_usage + self.manifest_usage
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum RegionLeaderState {
88    /// The region is opened and is writable.
89    Writable,
90    /// The region is in staging mode - writable but no checkpoint/compaction.
91    Staging,
92    /// The region is entering staging mode. - write requests will be stalled.
93    EnteringStaging,
94    /// The region is altering.
95    Altering,
96    /// The region is dropping.
97    Dropping,
98    /// The region is truncating.
99    Truncating,
100    /// The region is handling a region edit.
101    Editing,
102    /// The region is stepping down.
103    Downgrading,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum RegionRoleState {
108    Leader(RegionLeaderState),
109    Follower,
110}
111
112impl RegionRoleState {
113    /// Converts the region role state to leader state if it is a leader state.
114    pub fn into_leader_state(self) -> Option<RegionLeaderState> {
115        match self {
116            RegionRoleState::Leader(leader_state) => Some(leader_state),
117            RegionRoleState::Follower => None,
118        }
119    }
120
121    pub(crate) fn as_str(&self) -> &'static str {
122        match self {
123            RegionRoleState::Follower => "Follower",
124            RegionRoleState::Leader(RegionLeaderState::Writable) => "Leader(Writable)",
125            RegionRoleState::Leader(RegionLeaderState::Staging) => "Leader(Staging)",
126            RegionRoleState::Leader(RegionLeaderState::EnteringStaging) => {
127                "Leader(EnteringStaging)"
128            }
129            RegionRoleState::Leader(RegionLeaderState::Altering) => "Leader(Altering)",
130            RegionRoleState::Leader(RegionLeaderState::Dropping) => "Leader(Dropping)",
131            RegionRoleState::Leader(RegionLeaderState::Truncating) => "Leader(Truncating)",
132            RegionRoleState::Leader(RegionLeaderState::Editing) => "Leader(Editing)",
133            RegionRoleState::Leader(RegionLeaderState::Downgrading) => "Leader(Downgrading)",
134        }
135    }
136}
137
138/// Metadata and runtime status of a region.
139///
140/// Writing and reading a region follow a single-writer-multi-reader rule:
141/// - Only the region worker thread this region belongs to can modify the metadata.
142/// - Multiple reader threads are allowed to read a specific `version` of a region.
143#[derive(Debug)]
144pub struct MitoRegion {
145    /// Id of this region.
146    ///
147    /// Accessing region id from the version control is inconvenient so
148    /// we also store it here.
149    pub(crate) region_id: RegionId,
150
151    /// Version controller for this region.
152    ///
153    /// We MUST update the version control inside the write lock of the region manifest manager.
154    pub(crate) version_control: VersionControlRef,
155    /// Snapshot controller for range and series indexes.
156    pub(crate) series_index_version_control: SeriesIndexVersionControl,
157    /// Store containing the region's series indexes.
158    pub(crate) series_index_store: Option<ObjectStore>,
159    /// SSTs accessor for this region.
160    pub(crate) access_layer: AccessLayerRef,
161    /// Context to maintain manifest for this region.
162    pub(crate) manifest_ctx: ManifestContextRef,
163    /// SST file purger.
164    pub(crate) file_purger: FilePurgerRef,
165    /// The provider of log store.
166    pub(crate) provider: Provider,
167    /// Last flush time in millis.
168    last_flush_millis: AtomicI64,
169    /// Last schedule compaction time in millis.
170    last_schedule_compaction_millis: AtomicI64,
171    /// Provider to get current time.
172    time_provider: TimeProviderRef,
173    /// The topic's latest entry id since the region's last flushing.
174    /// **Only used for remote WAL pruning.**
175    ///
176    /// The value will be updated to the latest offset of the topic
177    /// if region receives a flush request or schedules a periodic flush task
178    /// and the region's memtable is empty.
179    ///
180    /// There are no WAL entries in range [flushed_entry_id, topic_latest_entry_id] for current region,
181    /// which means these WAL entries maybe able to be pruned up to `topic_latest_entry_id`.
182    pub(crate) topic_latest_entry_id: AtomicU64,
183    /// Region stats.
184    pub(crate) region_stats: RegionStats,
185    /// manifest stats
186    stats: ManifestStats,
187}
188
189/// Runtime statistics of a region.
190#[derive(Debug)]
191pub(crate) struct RegionStats {
192    /// The total bytes written to the region.
193    pub(crate) written_bytes: Arc<AtomicU64>,
194    /// The total query CPU time of the region in nanoseconds.
195    pub(crate) query_cpu_time: Arc<AtomicU64>,
196    /// The total scanned bytes of the region.
197    pub(crate) query_scanned_bytes: Arc<AtomicU64>,
198}
199
200impl RegionStats {
201    pub(crate) fn new() -> Self {
202        Self {
203            written_bytes: Arc::new(AtomicU64::new(0)),
204            query_cpu_time: Arc::new(AtomicU64::new(0)),
205            query_scanned_bytes: Arc::new(AtomicU64::new(0)),
206        }
207    }
208
209    pub(crate) fn query_stat_counters(&self) -> RegionQueryStatCounters {
210        RegionQueryStatCounters {
211            query_cpu_time: self.query_cpu_time.clone(),
212            query_scanned_bytes: self.query_scanned_bytes.clone(),
213        }
214    }
215}
216
217pub type MitoRegionRef = Arc<MitoRegion>;
218
219#[derive(Debug, Clone)]
220pub(crate) struct StagingPartitionInfo {
221    pub(crate) partition_directive: StagingPartitionDirective,
222    pub(crate) partition_rule_version: u64,
223}
224
225impl StagingPartitionInfo {
226    /// Returns the partition expression carried by the staging directive, if any.
227    pub(crate) fn partition_expr(&self) -> Option<&str> {
228        self.partition_directive.partition_expr()
229    }
230
231    /// Builds staging partition info from a directive and derives its version marker.
232    pub(crate) fn from_partition_directive(partition_directive: StagingPartitionDirective) -> Self {
233        let partition_rule_version = match &partition_directive {
234            StagingPartitionDirective::UpdatePartitionExpr(expr) => {
235                partition_expr_version(Some(expr))
236            }
237            StagingPartitionDirective::RejectAllWrites => 0,
238        };
239        Self {
240            partition_directive,
241            partition_rule_version,
242        }
243    }
244}
245
246impl MitoRegion {
247    /// Returns the current immutable series-index snapshot.
248    pub(crate) fn series_index_version(&self) -> Arc<SeriesIndexVersion> {
249        self.series_index_version_control.current()
250    }
251
252    fn remove_region_metrics(&self) {
253        let region_id = self.region_id.as_u64().to_string();
254        let labels = &[region_id.as_str()];
255        let _ = REGION_QUERY_CPU_TIME.remove_label_values(labels);
256        let _ = REGION_QUERY_SCANNED_BYTES.remove_label_values(labels);
257    }
258
259    /// Stop background managers for this region.
260    pub(crate) async fn stop(&self) {
261        self.manifest_ctx
262            .manifest_manager
263            .write()
264            .await
265            .stop()
266            .await;
267
268        info!(
269            "Stopped region manifest manager, region_id: {}",
270            self.region_id
271        );
272    }
273
274    /// Returns current metadata of the region.
275    pub fn metadata(&self) -> RegionMetadataRef {
276        let version_data = self.version_control.current();
277        version_data.version.metadata.clone()
278    }
279
280    /// Returns primary key encoding of the region.
281    pub(crate) fn primary_key_encoding(&self) -> PrimaryKeyEncoding {
282        let version_data = self.version_control.current();
283        version_data.version.metadata.primary_key_encoding
284    }
285
286    /// Returns current version of the region.
287    pub(crate) fn version(&self) -> VersionRef {
288        let version_data = self.version_control.current();
289        version_data.version
290    }
291
292    /// Returns whether writes to this region should skip WAL.
293    pub(crate) fn skip_wal(&self) -> bool {
294        self.provider == Provider::Noop || self.version().options.skip_wal
295    }
296
297    /// Returns last flush timestamp in millis.
298    pub(crate) fn last_flush_millis(&self) -> i64 {
299        self.last_flush_millis.load(Ordering::Relaxed)
300    }
301
302    /// Update flush time to current time.
303    pub(crate) fn update_flush_millis(&self) {
304        let now = self.time_provider.current_time_millis();
305        self.last_flush_millis.store(now, Ordering::Relaxed);
306    }
307
308    /// Returns last schedule compaction timestamp in millis.
309    pub(crate) fn last_schedule_compaction_millis(&self) -> i64 {
310        self.last_schedule_compaction_millis.load(Ordering::Relaxed)
311    }
312
313    /// Update schedule compaction time to current time.
314    pub(crate) fn update_schedule_compaction_millis(&self) {
315        let now = self.time_provider.current_time_millis();
316        self.last_schedule_compaction_millis
317            .store(now, Ordering::Relaxed);
318    }
319
320    /// Returns the table dir.
321    pub(crate) fn table_dir(&self) -> &str {
322        self.access_layer.table_dir()
323    }
324
325    /// Returns the path type of the region.
326    pub(crate) fn path_type(&self) -> PathType {
327        self.access_layer.path_type()
328    }
329
330    /// Returns whether the region is writable.
331    pub(crate) fn is_writable(&self) -> bool {
332        matches!(
333            self.manifest_ctx.state.load(),
334            RegionRoleState::Leader(RegionLeaderState::Writable)
335                | RegionRoleState::Leader(RegionLeaderState::Staging)
336        )
337    }
338
339    /// Returns whether the region is flushable.
340    pub(crate) fn is_flushable(&self) -> bool {
341        matches!(
342            self.manifest_ctx.state.load(),
343            RegionRoleState::Leader(RegionLeaderState::Writable)
344                | RegionRoleState::Leader(RegionLeaderState::Staging)
345                | RegionRoleState::Leader(RegionLeaderState::Downgrading)
346        )
347    }
348
349    /// Returns whether the region should abort index building.
350    pub(crate) fn should_abort_index(&self) -> bool {
351        matches!(
352            self.manifest_ctx.state.load(),
353            RegionRoleState::Follower
354                | RegionRoleState::Leader(RegionLeaderState::Dropping)
355                | RegionRoleState::Leader(RegionLeaderState::Truncating)
356                | RegionRoleState::Leader(RegionLeaderState::Downgrading)
357                | RegionRoleState::Leader(RegionLeaderState::Staging)
358        )
359    }
360
361    /// Returns whether the region is downgrading.
362    pub(crate) fn is_downgrading(&self) -> bool {
363        matches!(
364            self.manifest_ctx.state.load(),
365            RegionRoleState::Leader(RegionLeaderState::Downgrading)
366        )
367    }
368
369    /// Returns whether the region is in staging mode.
370    pub(crate) fn is_staging(&self) -> bool {
371        self.manifest_ctx.state.load() == RegionRoleState::Leader(RegionLeaderState::Staging)
372    }
373
374    /// Returns whether the region is entering staging mode.
375    pub(crate) fn is_enter_staging(&self) -> bool {
376        self.manifest_ctx.state.load()
377            == RegionRoleState::Leader(RegionLeaderState::EnteringStaging)
378    }
379
380    /// Returns whether the region is truncating.
381    pub(crate) fn is_truncating(&self) -> bool {
382        self.manifest_ctx.state.load() == RegionRoleState::Leader(RegionLeaderState::Truncating)
383    }
384
385    pub fn region_id(&self) -> RegionId {
386        self.region_id
387    }
388
389    pub fn find_committed_sequence(&self) -> SequenceNumber {
390        self.version_control.committed_sequence()
391    }
392
393    /// Returns the latest sequence that has already been persisted into SSTs.
394    ///
395    /// Incremental memtable-only reads must use a cursor greater than or equal to
396    /// this boundary; older cursors are stale because the corresponding updates may
397    /// already have been flushed out of memtables.
398    pub fn flushed_sequence(&self) -> SequenceNumber {
399        self.version_control.current().version.flushed_sequence
400    }
401
402    /// Returns whether the region is readonly.
403    pub fn is_follower(&self) -> bool {
404        self.manifest_ctx.state.load() == RegionRoleState::Follower
405    }
406
407    /// Returns the state of the region.
408    pub(crate) fn state(&self) -> RegionRoleState {
409        self.manifest_ctx.state.load()
410    }
411
412    /// Sets the region role state.
413    pub(crate) fn set_role(&self, next_role: RegionRole) {
414        self.manifest_ctx.set_role(next_role, self.region_id);
415    }
416
417    pub(crate) fn region_role(&self) -> RegionRole {
418        match self.state() {
419            RegionRoleState::Follower => RegionRole::Follower,
420            RegionRoleState::Leader(RegionLeaderState::Staging) => RegionRole::StagingLeader,
421            RegionRoleState::Leader(RegionLeaderState::Downgrading) => {
422                RegionRole::DowngradingLeader
423            }
424            RegionRoleState::Leader(_) => RegionRole::Leader,
425        }
426    }
427
428    /// Sets the altering state.
429    /// You should call this method in the worker loop.
430    pub(crate) fn set_altering(&self) -> Result<()> {
431        self.compare_exchange_state(
432            RegionLeaderState::Writable,
433            RegionRoleState::Leader(RegionLeaderState::Altering),
434        )
435    }
436
437    /// Sets the dropping state.
438    /// You should call this method in the worker loop.
439    pub(crate) fn set_dropping(&self, expect: RegionLeaderState) -> Result<()> {
440        self.compare_exchange_state(expect, RegionRoleState::Leader(RegionLeaderState::Dropping))
441    }
442
443    /// Sets the truncating state.
444    /// You should call this method in the worker loop.
445    pub(crate) fn set_truncating(&self) -> Result<()> {
446        self.compare_exchange_state(
447            RegionLeaderState::Writable,
448            RegionRoleState::Leader(RegionLeaderState::Truncating),
449        )
450    }
451
452    /// Sets the editing state.
453    /// You should call this method in the worker loop.
454    pub(crate) fn set_editing(&self, expect: RegionLeaderState) -> Result<()> {
455        self.compare_exchange_state(expect, RegionRoleState::Leader(RegionLeaderState::Editing))
456    }
457
458    /// Sets the staging state.
459    ///
460    /// You should call this method in the worker loop.
461    /// Transitions from Writable to Staging state.
462    /// Cleans any existing staging manifests before entering staging mode.
463    pub(crate) async fn set_staging(
464        &self,
465        manager: &mut RwLockWriteGuard<'_, RegionManifestManager>,
466    ) -> Result<()> {
467        manager.store().clear_staging_manifests().await?;
468
469        self.compare_exchange_state(
470            RegionLeaderState::Writable,
471            RegionRoleState::Leader(RegionLeaderState::Staging),
472        )
473    }
474
475    /// Sets the entering staging state.
476    pub(crate) fn set_entering_staging(&self) -> Result<()> {
477        self.compare_exchange_state(
478            RegionLeaderState::Writable,
479            RegionRoleState::Leader(RegionLeaderState::EnteringStaging),
480        )
481    }
482
483    /// Exits the staging state back to writable.
484    ///
485    /// You should call this method in the worker loop.
486    /// Transitions from Staging to Writable state.
487    pub fn exit_staging(&self) -> Result<()> {
488        self.manifest_ctx.exit_staging(
489            self.region_id,
490            RegionRoleState::Leader(RegionLeaderState::Writable),
491        )
492    }
493
494    /// Sets the region role state gracefully. This acquires the manifest write lock.
495    pub(crate) async fn set_role_state_gracefully(
496        &self,
497        state: SettableRegionRoleState,
498    ) -> Result<()> {
499        let mut manager: RwLockWriteGuard<'_, RegionManifestManager> =
500            self.manifest_ctx.manifest_manager.write().await;
501        let current_state = self.state();
502        let mut wait_for_checkpoint = false;
503
504        let hook_payload: Option<PendingManifestHook> = match state {
505            SettableRegionRoleState::Leader => {
506                // Exit staging mode and return to normal writable leader
507                // Only allowed from staging state
508                match current_state {
509                    RegionRoleState::Leader(RegionLeaderState::Staging) => {
510                        info!("Exiting staging mode for region {}", self.region_id);
511                        // Use the success exit path that merges all staged manifests
512                        self.exit_staging_on_success(&mut manager).await?
513                    }
514                    RegionRoleState::Leader(RegionLeaderState::Writable) => {
515                        // Already in desired state - no-op
516                        info!("Region {} already in normal leader mode", self.region_id);
517                        None
518                    }
519                    _ => {
520                        // Only staging -> leader transition is allowed
521                        return Err(RegionStateSnafu {
522                            region_id: self.region_id,
523                            state: current_state,
524                            expect: RegionRoleState::Leader(RegionLeaderState::Staging),
525                        }
526                        .build());
527                    }
528                }
529            }
530
531            SettableRegionRoleState::StagingLeader => {
532                // Enter staging mode from normal writable leader
533                // Only allowed from writable leader state
534                match current_state {
535                    RegionRoleState::Leader(RegionLeaderState::Writable) => {
536                        info!("Entering staging mode for region {}", self.region_id);
537                        self.set_staging(&mut manager).await?;
538                    }
539                    RegionRoleState::Leader(RegionLeaderState::Staging) => {
540                        // Already in desired state - no-op
541                        info!("Region {} already in staging mode", self.region_id);
542                    }
543                    _ => {
544                        return Err(RegionStateSnafu {
545                            region_id: self.region_id,
546                            state: current_state,
547                            expect: RegionRoleState::Leader(RegionLeaderState::Writable),
548                        }
549                        .build());
550                    }
551                }
552                None
553            }
554
555            SettableRegionRoleState::Follower => {
556                // Make this region a follower
557                match current_state {
558                    RegionRoleState::Leader(RegionLeaderState::Staging) => {
559                        info!(
560                            "Exiting staging and demoting region {} to follower",
561                            self.region_id
562                        );
563                        self.exit_staging()?;
564                        self.set_role(RegionRole::Follower);
565                        wait_for_checkpoint = true;
566                    }
567                    RegionRoleState::Leader(_) => {
568                        info!("Demoting region {} from leader to follower", self.region_id);
569                        self.set_role(RegionRole::Follower);
570                        wait_for_checkpoint = true;
571                    }
572                    RegionRoleState::Follower => {
573                        // Already in desired state - no-op
574                        info!("Region {} already in follower mode", self.region_id);
575                    }
576                }
577                None
578            }
579
580            SettableRegionRoleState::DowngradingLeader => {
581                // downgrade this region to downgrading leader
582                match current_state {
583                    RegionRoleState::Leader(RegionLeaderState::Staging) => {
584                        info!(
585                            "Exiting staging and entering downgrade for region {}",
586                            self.region_id
587                        );
588                        self.exit_staging()?;
589                        self.set_role(RegionRole::DowngradingLeader);
590                        wait_for_checkpoint = true;
591                    }
592                    RegionRoleState::Leader(RegionLeaderState::Writable) => {
593                        info!("Starting downgrade for region {}", self.region_id);
594                        self.set_role(RegionRole::DowngradingLeader);
595                        wait_for_checkpoint = true;
596                    }
597                    RegionRoleState::Leader(RegionLeaderState::Downgrading) => {
598                        // Already in desired state - no-op
599                        info!("Region {} already in downgrading mode", self.region_id);
600                        wait_for_checkpoint = true;
601                    }
602                    RegionRoleState::Follower => {
603                        warn!(
604                            "Cannot start downgrade for region {} from state {:?}",
605                            self.region_id, current_state
606                        );
607                    }
608                    RegionRoleState::Leader(_) => {
609                        // The region may become writable again when the
610                        // current operation, such as an ALTER, finishes. Fail
611                        // with a retryable error instead of reporting a
612                        // downgrade that does not fence writes.
613                        return Err(RegionStateSnafu {
614                            region_id: self.region_id,
615                            state: current_state,
616                            expect: RegionRoleState::Leader(RegionLeaderState::Writable),
617                        }
618                        .build());
619                    }
620                }
621                None
622            }
623        };
624
625        // The state is changed before waiting, so no new writable-leader work
626        // can race with the barrier. Keep the manager lock while joining to
627        // serialize the barrier with checkpoint scheduling.
628        if wait_for_checkpoint {
629            manager.wait_for_pending_checkpoint().await;
630        }
631
632        // Hack(zhongzc): If we have just become leader (writable), persist any backfilled metadata.
633        let mut backfill_hook_payload: Option<PendingManifestHook> = None;
634        if self.state() == RegionRoleState::Leader(RegionLeaderState::Writable) {
635            // Persist backfilled metadata if manifest is missing fields (e.g., partition_expr)
636            let manifest_meta = &manager.manifest().metadata;
637            let current_version = self.version();
638            let current_meta = &current_version.metadata;
639            if manifest_meta.partition_expr.is_none() && current_meta.partition_expr.is_some() {
640                let action = RegionMetaAction::Change(RegionChange {
641                    metadata: current_meta.clone(),
642                    sst_format: current_version.options.sst_format.unwrap_or_default(),
643                    append_mode: None,
644                });
645                let action_list = RegionMetaActionList::with_action(action);
646                match self
647                    .manifest_ctx
648                    .update_locked(&mut manager, action_list, false)
649                    .await
650                {
651                    Ok(pending) => {
652                        info!(
653                            "Successfully persisted backfilled metadata for region {}, version: {}",
654                            self.region_id,
655                            pending.version()
656                        );
657                        backfill_hook_payload = Some(pending);
658                    }
659                    Err(e) => {
660                        warn!(e; "Failed to persist backfilled metadata for region {}", self.region_id);
661                    }
662                }
663            }
664        }
665
666        drop(manager);
667
668        // Merge both payloads so consumers see the complete set of actions in
669        // one notification. The lock is released, so it's safe to fire.
670        let merged = match (hook_payload, backfill_hook_payload) {
671            (Some(staging), Some(backfill)) => Some(staging.merge(backfill)),
672            (Some(payload), None) => Some(payload),
673            (None, Some(payload)) => Some(payload),
674            (None, None) => None,
675        };
676
677        if let Some(pending) = merged {
678            pending.fire().await;
679        }
680
681        Ok(())
682    }
683
684    /// Switches the region state to `RegionRoleState::Leader(RegionLeaderState::Writable)` if the current state is `expect`.
685    /// Otherwise, logs an error.
686    pub(crate) fn switch_state_to_writable(&self, expect: RegionLeaderState) {
687        if let Err(e) = self
688            .compare_exchange_state(expect, RegionRoleState::Leader(RegionLeaderState::Writable))
689        {
690            error!(e; "failed to switch region state to writable, expect state is {:?}", expect);
691        }
692    }
693
694    /// Switches the region state to `RegionRoleState::Leader(RegionLeaderState::Staging)` if the current state is `expect`.
695    /// Otherwise, logs an error.
696    pub(crate) fn switch_state_to_staging(&self, expect: RegionLeaderState) {
697        if let Err(e) =
698            self.compare_exchange_state(expect, RegionRoleState::Leader(RegionLeaderState::Staging))
699        {
700            error!(e; "failed to switch region state to staging, expect state is {:?}", expect);
701        }
702    }
703
704    /// Returns the region statistic.
705    pub(crate) fn region_statistic(&self) -> RegionStatistic {
706        let version = self.version();
707        let memtables = &version.memtables;
708        let memtable_usage = (memtables.mutable_usage() + memtables.immutables_usage()) as u64;
709
710        let sst_usage = version.ssts.owned_sst_usage(self.region_id);
711        let index_usage = version.ssts.owned_index_usage(self.region_id);
712        let flushed_entry_id = version.flushed_entry_id;
713
714        let wal_usage = self.estimated_wal_usage(memtable_usage);
715        let manifest_usage = self.stats.total_manifest_size();
716        let num_rows = version.ssts.owned_num_rows(self.region_id) + version.memtables.num_rows();
717        let num_files = version.ssts.owned_num_files(self.region_id);
718        let manifest_version = self.stats.manifest_version();
719        let file_removed_cnt = self.stats.file_removed_cnt();
720
721        let time_range = match (version.ssts.time_range(), version.memtables.time_range()) {
722            (Some((sst_min, sst_max)), Some((mem_min, mem_max))) => {
723                Some((sst_min.min(mem_min), sst_max.max(mem_max)))
724            }
725            (range, None) | (None, range) => range,
726        };
727
728        let topic_latest_entry_id = self.topic_latest_entry_id.load(Ordering::Relaxed);
729        let written_bytes = self.region_stats.written_bytes.load(Ordering::Relaxed);
730        let query_cpu_time = self.region_stats.query_cpu_time.load(Ordering::Relaxed);
731        let query_scanned_bytes = self
732            .region_stats
733            .query_scanned_bytes
734            .load(Ordering::Relaxed);
735
736        RegionStatistic {
737            num_rows,
738            memtable_size: memtable_usage,
739            wal_size: wal_usage,
740            manifest_size: manifest_usage,
741            sst_size: sst_usage,
742            sst_num: num_files,
743            index_size: index_usage,
744            manifest: RegionManifestInfo::Mito {
745                manifest_version,
746                flushed_entry_id,
747                file_removed_cnt,
748            },
749            data_topic_latest_entry_id: topic_latest_entry_id,
750            metadata_topic_latest_entry_id: topic_latest_entry_id,
751            written_bytes,
752            query_cpu_time,
753            query_scanned_bytes,
754            min_timestamp: time_range.map(|(min, _)| min),
755            max_timestamp: time_range.map(|(_, max)| max),
756        }
757    }
758
759    /// Estimated WAL size in bytes.
760    /// Use the memtables size to estimate the size of wal.
761    fn estimated_wal_usage(&self, memtable_usage: u64) -> u64 {
762        ((memtable_usage as f32) * ESTIMATED_WAL_FACTOR) as u64
763    }
764
765    /// Sets the state of the region to given state if the current state equals to
766    /// the expected.
767    fn compare_exchange_state(
768        &self,
769        expect: RegionLeaderState,
770        state: RegionRoleState,
771    ) -> Result<()> {
772        self.manifest_ctx
773            .state
774            .compare_exchange(RegionRoleState::Leader(expect), state)
775            .map_err(|actual| {
776                RegionStateSnafu {
777                    region_id: self.region_id,
778                    state: actual,
779                    expect: RegionRoleState::Leader(expect),
780                }
781                .build()
782            })?;
783        Ok(())
784    }
785
786    pub fn access_layer(&self) -> AccessLayerRef {
787        self.access_layer.clone()
788    }
789
790    /// Returns the region info entry of the region.
791    pub(crate) fn region_info_entry(&self, node_id: Option<u64>) -> RegionInfoEntry {
792        let region_id = self.region_id;
793        let version = self.version();
794        let state = self.state();
795        let role = self.region_role();
796        let region_options = serde_json::to_string(&version.options)
797            .unwrap_or_else(|err| serde_json::json!({ "error": err.to_string() }).to_string());
798        let sst_format = match version.options.sst_format.unwrap_or_default() {
799            crate::sst::FormatType::PrimaryKey => "primary_key",
800            crate::sst::FormatType::Flat => "flat",
801        }
802        .to_string();
803
804        RegionInfoEntry {
805            region_id,
806            table_id: region_id.table_id(),
807            region_number: region_id.region_number(),
808            region_group: region_id.region_group(),
809            region_sequence: region_id.region_sequence(),
810            state: state.as_str().to_string(),
811            role: role.to_string(),
812            writable: self.is_writable(),
813            committed_sequence: self.find_committed_sequence(),
814            flushed_sequence: Some(self.flushed_sequence()).filter(|sequence| *sequence > 0),
815            manifest_version: self.stats.manifest_version(),
816            compaction_time_window: version
817                .compaction_time_window
818                .map(|duration| humantime::format_duration(duration).to_string()),
819            region_options,
820            sst_format,
821            node_id,
822        }
823    }
824
825    /// Returns the SST entries of the region.
826    pub async fn manifest_sst_entries(&self) -> Vec<ManifestSstEntry> {
827        let table_dir = self.table_dir();
828        let path_type = self.access_layer.path_type();
829
830        let visible_ssts = self
831            .version()
832            .ssts
833            .levels()
834            .iter()
835            .flat_map(|level| level.files().map(|file| file.file_id().file_id()))
836            .collect::<HashSet<_>>();
837
838        let manifest_files = self.manifest_ctx.manifest().await.files.clone();
839        let staging_files = self
840            .manifest_ctx
841            .staging_manifest()
842            .await
843            .map(|m| m.files.clone())
844            .unwrap_or_default();
845        let files = manifest_files
846            .into_iter()
847            .chain(staging_files)
848            .collect::<HashMap<_, _>>();
849
850        files
851            .values()
852            .map(|meta| {
853                let region_id = self.region_id;
854                let origin_region_id = meta.region_id;
855                let (index_version, index_file_path, index_file_size) = if meta.index_file_size > 0
856                {
857                    let index_file_path = index_file_path(table_dir, meta.index_id(), path_type);
858                    (
859                        meta.index_version,
860                        Some(index_file_path),
861                        Some(meta.index_file_size),
862                    )
863                } else {
864                    (0, None, None)
865                };
866                let visible = visible_ssts.contains(&meta.file_id);
867                ManifestSstEntry {
868                    table_dir: table_dir.to_string(),
869                    region_id,
870                    table_id: region_id.table_id(),
871                    region_number: region_id.region_number(),
872                    region_group: region_id.region_group(),
873                    region_sequence: region_id.region_sequence(),
874                    file_id: meta.file_id.to_string(),
875                    index_version,
876                    level: meta.level,
877                    file_path: sst_file_path(table_dir, meta.file_id(), path_type),
878                    file_size: meta.file_size,
879                    max_row_group_uncompressed_size: meta.max_row_group_uncompressed_size,
880                    index_file_path,
881                    index_file_size,
882                    num_rows: meta.num_rows,
883                    num_row_groups: meta.num_row_groups,
884                    num_series: Some(meta.num_series),
885                    min_ts: meta.time_range.0,
886                    max_ts: meta.time_range.1,
887                    sequence: meta.sequence.map(|s| s.get()),
888                    partition_expr: meta.partition_expr.as_ref().map(ToString::to_string),
889                    origin_region_id,
890                    node_id: None,
891                    visible,
892                    primary_key_min: meta.primary_key_min.clone(),
893                    primary_key_max: meta.primary_key_max.clone(),
894                }
895            })
896            .collect()
897    }
898
899    /// Returns the file metas of the region by file ids.
900    pub async fn file_metas(&self, file_ids: &[FileId]) -> Vec<Option<FileMeta>> {
901        let manifest_files = self.manifest_ctx.manifest().await.files.clone();
902
903        file_ids
904            .iter()
905            .map(|file_id| manifest_files.get(file_id).cloned())
906            .collect::<Vec<_>>()
907    }
908
909    /// Returns all live SST file metas and the current manifest version from
910    /// the region manifest, merging both the normal and staging manifests.
911    ///
912    /// While the region is in staging mode (e.g. during region copy/migration),
913    /// the authoritative live file set lives in the staging manifest, so this
914    /// method merges both — matching the semantics of [`manifest_sst_entries`].
915    ///
916    /// The returned manifest version is the staging version when a staging
917    /// manifest is present, otherwise the normal manifest version.
918    pub async fn all_manifest_files(&self) -> (Vec<FileMeta>, ManifestVersion) {
919        let manifest = self.manifest_ctx.manifest().await;
920        let staging = self
921            .manifest_ctx
922            .staging_manifest()
923            .await
924            .map(|m| (m.files.clone(), m.manifest_version));
925
926        let version = staging
927            .as_ref()
928            .map(|(_, v)| *v)
929            .unwrap_or(manifest.manifest_version);
930
931        let files = match staging {
932            Some((staging_files, _)) => {
933                let merged = manifest
934                    .files
935                    .clone()
936                    .into_iter()
937                    .chain(staging_files)
938                    .collect::<std::collections::HashMap<_, _>>();
939                merged.into_values().collect()
940            }
941            None => manifest.files.values().cloned().collect(),
942        };
943
944        (files, version)
945    }
946
947    /// Exit staging mode successfully by merging all staged manifests and making them visible.
948    /// Merges staged manifest actions into the live manifest and exits staging mode.
949    ///
950    /// The caller must hold the manifest write lock and pass it via `manager`.
951    /// Returns `Ok(Some(pending))` when staging manifests were merged, or
952    /// `Ok(None)` when there were no staged manifests to merge.
953    ///
954    /// **Important:** [`fire`](PendingManifestHook::fire) the receipt only after
955    /// dropping the lock — the hook may read the manifest and deadlock otherwise.
956    pub(crate) async fn exit_staging_on_success(
957        &self,
958        manager: &mut RwLockWriteGuard<'_, RegionManifestManager>,
959    ) -> Result<Option<PendingManifestHook>> {
960        let current_state = self.manifest_ctx.current_state();
961        ensure!(
962            current_state == RegionRoleState::Leader(RegionLeaderState::Staging),
963            RegionStateSnafu {
964                region_id: self.region_id,
965                state: current_state,
966                expect: RegionRoleState::Leader(RegionLeaderState::Staging),
967            }
968        );
969
970        // Merge all staged manifest actions
971        let merged_actions = match manager.merge_staged_actions(current_state).await? {
972            Some(actions) => actions,
973            None => {
974                info!(
975                    "No staged manifests to merge for region {}, exiting staging mode without changes",
976                    self.region_id
977                );
978                // Even if no manifests to merge, we still need to exit staging mode
979                self.exit_staging()?;
980                return Ok(None);
981            }
982        };
983        let expect_change = merged_actions.actions.iter().any(|a| a.is_change());
984        let expect_partition_expr_change = merged_actions
985            .actions
986            .iter()
987            .any(|a| a.is_partition_expr_change());
988        let expect_edit = merged_actions.actions.iter().any(|a| a.is_edit());
989        ensure!(
990            !(expect_change && expect_partition_expr_change),
991            UnexpectedSnafu {
992                reason: "unexpected both change and partition expr change actions in merged actions"
993            }
994        );
995        ensure!(
996            expect_change || expect_partition_expr_change,
997            UnexpectedSnafu {
998                reason: "expect a change or partition expr change action in merged actions"
999            }
1000        );
1001        ensure!(
1002            expect_edit,
1003            UnexpectedSnafu {
1004                reason: "expect an edit action in merged actions"
1005            }
1006        );
1007
1008        let (merged_partition_expr_change, merged_change, merged_edit) =
1009            merged_actions.clone().split_region_change_and_edit();
1010        if let Some(change) = &merged_change {
1011            // In staging exit we only allow metadata-only updates. A `Change`
1012            // action is accepted only when column definitions are unchanged;
1013            // otherwise it is treated as a schema change and rejected.
1014            let current_column_metadatas = &self.version().metadata.column_metadatas;
1015            ensure!(
1016                change.metadata.column_metadatas == *current_column_metadatas,
1017                UnexpectedSnafu {
1018                    reason: "change action alters column metadata in staging exit"
1019                }
1020            );
1021        }
1022
1023        // Submit merged actions using the manifest manager's update method.
1024        // Pass `false` so it saves to normal directory, not staging.
1025        let pending = self
1026            .manifest_ctx
1027            .update_locked(manager, merged_actions, false)
1028            .await?;
1029        let new_version = pending.version();
1030        info!(
1031            "Successfully submitted merged staged manifests for region {}, new version: {}",
1032            self.region_id, new_version
1033        );
1034
1035        // Apply the merged changes to in-memory version control
1036        if let Some(change) = merged_partition_expr_change {
1037            let mut new_metadata = self.version().metadata.as_ref().clone();
1038            new_metadata.set_partition_expr(change.partition_expr);
1039            self.version_control.alter_metadata(new_metadata.into());
1040        }
1041        if let Some(change) = merged_change {
1042            self.version_control.alter_metadata(change.metadata);
1043        }
1044        self.version_control
1045            .apply_edit(Some(merged_edit), &[], self.file_purger.clone());
1046
1047        // Clear all staging manifests and transit state
1048        if let Err(e) = manager.clear_staging_manifest_and_dir().await {
1049            error!(e; "Failed to clear staging manifest dir for region {}", self.region_id);
1050        }
1051        self.exit_staging()?;
1052
1053        // Return the hook payload; the caller invokes the hook after dropping the lock.
1054        Ok(Some(pending))
1055    }
1056
1057    /// Returns the partition expression string for this region.
1058    ///
1059    /// If the region is currently in staging state, this returns the partition expression held in
1060    /// the staging partition field. Otherwise, it returns the partition expression from the primary
1061    /// region metadata (current committed version).
1062    pub fn maybe_staging_partition_expr_str(&self) -> Option<String> {
1063        let is_staging = self.is_staging();
1064        if is_staging {
1065            let staging_partition_info = self.manifest_ctx.staging_partition_info();
1066            if staging_partition_info.is_none() {
1067                warn!(
1068                    "Staging partition expr is none for region {} in staging state",
1069                    self.region_id
1070                );
1071            }
1072            staging_partition_info
1073                .as_ref()
1074                .and_then(|info| info.partition_expr().map(ToString::to_string))
1075        } else {
1076            let version = self.version();
1077            version.metadata.partition_expr.clone()
1078        }
1079    }
1080
1081    pub fn expected_partition_expr_version(&self) -> u64 {
1082        if self.is_staging() {
1083            self.manifest_ctx
1084                .staging_partition_info()
1085                .as_ref()
1086                .map(|info| info.partition_rule_version)
1087                .unwrap_or_default()
1088        } else {
1089            self.version().metadata.partition_expr_version
1090        }
1091    }
1092
1093    /// Returns whether writes should be rejected for this region in staging mode.
1094    pub(crate) fn reject_all_writes_in_staging(&self) -> bool {
1095        if !self.is_staging() {
1096            return false;
1097        }
1098        self.manifest_ctx
1099            .staging_partition_info()
1100            .as_ref()
1101            .map(|info| {
1102                matches!(
1103                    info.partition_directive,
1104                    StagingPartitionDirective::RejectAllWrites
1105                )
1106            })
1107            .unwrap_or(false)
1108    }
1109}
1110
1111impl Drop for MitoRegion {
1112    fn drop(&mut self) {
1113        self.remove_region_metrics();
1114    }
1115}
1116
1117/// Result of publishing rebuilt index metadata to the manifest.
1118#[derive(Debug)]
1119pub(crate) enum IndexPublication {
1120    /// The index metadata was committed.
1121    Committed {
1122        manifest_version: ManifestVersion,
1123        file_meta: FileMeta,
1124    },
1125    /// The build no longer matches the current manifest.
1126    Stale(IndexPublicationStale),
1127}
1128
1129/// Why an index publication became stale.
1130#[derive(Debug, PartialEq, Eq)]
1131pub(crate) enum IndexPublicationStale {
1132    /// The source SST or region incarnation is no longer publishable.
1133    SourceChanged,
1134    /// The SST still matches, but the schema generation changed.
1135    SchemaChanged,
1136}
1137
1138/// Manifest state an index build is based on.
1139///
1140/// Both fields must still match when the rebuilt index is published. The file
1141/// metadata identifies the exact SST generation, while the schema version
1142/// identifies the exact index definition used by the builder.
1143#[derive(Clone, Debug, PartialEq, Eq)]
1144pub(crate) struct IndexBuildSource {
1145    pub(crate) file_meta: FileMeta,
1146    pub(crate) schema_version: u64,
1147}
1148
1149impl IndexBuildSource {
1150    pub(crate) fn new(file_meta: FileMeta, schema_version: u64) -> Self {
1151        Self {
1152            file_meta,
1153            schema_version,
1154        }
1155    }
1156}
1157
1158/// Context to update the region manifest.
1159#[derive(Debug)]
1160pub(crate) struct ManifestContext {
1161    /// Manager to maintain manifest for this region. Logical writes go through
1162    /// [`update_locked`](Self::update_locked) (or an [`update_manifest`](Self::update_manifest)
1163    /// variant) so they produce a [`PendingManifestHook`].
1164    pub(crate) manifest_manager: tokio::sync::RwLock<RegionManifestManager>,
1165    /// The state of the region. The region checks the state before updating
1166    /// manifest.
1167    state: AtomicCell<RegionRoleState>,
1168    /// Partition info of the region in staging mode.
1169    ///
1170    /// During the staging mode, the region metadata in [`VersionControlRef`] is not updated,
1171    /// so we need to store the partition info separately.
1172    staging_partition_info: Mutex<Option<StagingPartitionInfo>>,
1173    /// Optional region hook for observing manifest mutations.
1174    hook: Option<RegionHookRef>,
1175}
1176
1177impl ManifestContext {
1178    pub(crate) fn new(
1179        manager: RegionManifestManager,
1180        state: RegionRoleState,
1181        hook: Option<RegionHookRef>,
1182    ) -> Self {
1183        ManifestContext {
1184            manifest_manager: tokio::sync::RwLock::new(manager),
1185            state: AtomicCell::new(state),
1186            staging_partition_info: Mutex::new(None),
1187            hook,
1188        }
1189    }
1190
1191    /// Returns the region hook if one is registered.
1192    pub(crate) fn hook(&self) -> Option<RegionHookRef> {
1193        self.hook.clone()
1194    }
1195
1196    pub(crate) fn staging_partition_info(&self) -> Option<StagingPartitionInfo> {
1197        self.staging_partition_info.lock().unwrap().clone()
1198    }
1199
1200    pub(crate) fn set_staging_partition_info(&self, staging_partition_info: StagingPartitionInfo) {
1201        let mut current = self.staging_partition_info.lock().unwrap();
1202        debug_assert!(current.is_none());
1203        *current = Some(staging_partition_info);
1204    }
1205
1206    fn clear_staging_partition_info(&self) {
1207        *self.staging_partition_info.lock().unwrap() = None;
1208    }
1209
1210    pub(crate) fn exit_staging(
1211        &self,
1212        region_id: RegionId,
1213        next_state: RegionRoleState,
1214    ) -> Result<()> {
1215        self.state
1216            .compare_exchange(
1217                RegionRoleState::Leader(RegionLeaderState::Staging),
1218                next_state,
1219            )
1220            .map_err(|actual| {
1221                RegionStateSnafu {
1222                    region_id,
1223                    state: actual,
1224                    expect: RegionRoleState::Leader(RegionLeaderState::Staging),
1225                }
1226                .build()
1227            })?;
1228        self.clear_staging_partition_info();
1229        Ok(())
1230    }
1231
1232    pub(crate) async fn manifest_version(&self) -> ManifestVersion {
1233        self.manifest_manager
1234            .read()
1235            .await
1236            .manifest()
1237            .manifest_version
1238    }
1239
1240    pub(crate) async fn has_update(&self) -> Result<bool> {
1241        self.manifest_manager.read().await.has_update().await
1242    }
1243
1244    /// Returns the current region role state.
1245    pub(crate) fn current_state(&self) -> RegionRoleState {
1246        self.state.load()
1247    }
1248
1249    /// Installs the manifest changes from the current version to the target version (inclusive).
1250    ///
1251    /// Returns installed [RegionManifest].
1252    /// **Note**: This function is not guaranteed to install the target version strictly.
1253    /// The installed version may be greater than the target version.
1254    pub(crate) async fn install_manifest_to(
1255        &self,
1256        version: ManifestVersion,
1257    ) -> Result<Arc<RegionManifest>> {
1258        let mut manager = self.manifest_manager.write().await;
1259        manager.install_manifest_to(version).await?;
1260
1261        Ok(manager.manifest())
1262    }
1263
1264    /// Updates the manifest if current state is `expect_state`.
1265    pub(crate) async fn update_manifest(
1266        &self,
1267        expect_state: RegionLeaderState,
1268        action_list: RegionMetaActionList,
1269        is_staging: bool,
1270    ) -> Result<ManifestVersion> {
1271        self.update_manifest_with_state_check(action_list, is_staging, |current_state, region_id| {
1272            // If expect_state is not downgrading, the current state must be either `expect_state` or downgrading.
1273            //
1274            // A downgrading leader rejects user writes but still allows
1275            // flushing the memtable and updating the manifest.
1276            if expect_state != RegionLeaderState::Downgrading {
1277                if current_state == RegionRoleState::Leader(RegionLeaderState::Downgrading) {
1278                    info!(
1279                        "Region {} is in downgrading leader state, updating manifest. Expect state is {:?}",
1280                        region_id, expect_state
1281                    );
1282                }
1283                ensure!(
1284                    current_state == RegionRoleState::Leader(expect_state)
1285                        || current_state == RegionRoleState::Leader(RegionLeaderState::Downgrading),
1286                    UpdateManifestSnafu {
1287                        region_id,
1288                        state: current_state,
1289                    }
1290                );
1291            } else {
1292                ensure!(
1293                    current_state == RegionRoleState::Leader(expect_state),
1294                    RegionStateSnafu {
1295                        region_id,
1296                        state: current_state,
1297                        expect: RegionRoleState::Leader(expect_state),
1298                    }
1299                );
1300            }
1301
1302            Ok(())
1303        })
1304        .await
1305    }
1306
1307    /// Updates the manifest for compaction.
1308    ///
1309    /// Compaction may finish while a direct external region edit is in the transient
1310    /// `Editing` state. Direct external edits can remove files both when followers
1311    /// apply sync-region metadata and when a writable leader performs a direct edit
1312    /// such as `edit_region()`. Allowing compaction to publish in `Editing` is still
1313    /// safe because publication happens under the manifest write lock and compaction
1314    /// rechecks that its input files are still valid before committing.
1315    ///
1316    /// This intentionally writes to the normal manifest path (`is_staging = false`).
1317    /// Entering staging cancels or waits for active compactions before switching the
1318    /// region to `Staging`, so a compaction that started before staging still finishes
1319    /// against the normal manifest. Even if a manual compaction is requested while the
1320    /// region is already staging, compaction only sees SSTs in the normal visible
1321    /// region version; SSTs from staging manifests are not applied to region version
1322    /// control until staging exits successfully.
1323    pub(crate) async fn update_manifest_for_compaction(
1324        &self,
1325        action_list: RegionMetaActionList,
1326    ) -> Result<ManifestVersion> {
1327        self.update_manifest_with_state_check(action_list, false, |current_state, region_id| {
1328            ensure!(
1329                matches!(
1330                    current_state,
1331                    RegionRoleState::Leader(RegionLeaderState::Writable)
1332                        | RegionRoleState::Leader(RegionLeaderState::Editing)
1333                        | RegionRoleState::Leader(RegionLeaderState::Downgrading)
1334                ),
1335                UpdateManifestSnafu {
1336                    region_id,
1337                    state: current_state,
1338                }
1339            );
1340
1341            Ok(())
1342        })
1343        .await
1344    }
1345
1346    /// Conditionally publishes rebuilt index metadata for `source`.
1347    ///
1348    /// The source SST and schema-generation checks share the manifest write lock
1349    /// with the update, so a concurrent compaction, schema change, or another
1350    /// index build cannot commit between them.
1351    pub(crate) async fn update_manifest_for_index(
1352        &self,
1353        source: &IndexBuildSource,
1354        updated: FileMeta,
1355    ) -> Result<IndexPublication> {
1356        let manager = self.manifest_manager.write().await;
1357        let manifest = manager.manifest();
1358        let current_state = self.state.load();
1359        if !matches!(
1360            current_state,
1361            RegionRoleState::Leader(RegionLeaderState::Writable)
1362                | RegionRoleState::Leader(RegionLeaderState::Downgrading)
1363        ) || manager.is_stopped()
1364        {
1365            return Ok(IndexPublication::Stale(
1366                IndexPublicationStale::SourceChanged,
1367            ));
1368        }
1369
1370        if manifest.files.get(&source.file_meta.file_id) != Some(&source.file_meta) {
1371            return Ok(IndexPublication::Stale(
1372                IndexPublicationStale::SourceChanged,
1373            ));
1374        }
1375
1376        if manifest.metadata.schema_version != source.schema_version {
1377            return Ok(IndexPublication::Stale(
1378                IndexPublicationStale::SchemaChanged,
1379            ));
1380        }
1381
1382        // Only index metadata is allowed to change in this publication.
1383        let mut committed = source.file_meta.clone();
1384        committed.available_indexes = updated.available_indexes;
1385        committed.indexes = updated.indexes;
1386        committed.index_file_size = updated.index_file_size;
1387        committed.index_version = updated.index_version;
1388
1389        let edit = crate::manifest::action::RegionEdit {
1390            files_to_add: vec![committed.clone()],
1391            files_to_remove: Vec::new(),
1392            timestamp_ms: Some(chrono::Utc::now().timestamp_millis()),
1393            flushed_sequence: None,
1394            flushed_entry_id: None,
1395            committed_sequence: None,
1396            compaction_time_window: None,
1397        };
1398        let action_list = RegionMetaActionList::with_action(RegionMetaAction::Edit(edit));
1399        let manifest_version = self.update_and_fire(manager, action_list, false).await?;
1400
1401        Ok(IndexPublication::Committed {
1402            manifest_version,
1403            file_meta: committed,
1404        })
1405    }
1406
1407    /// Performs a manifest write under a caller-held write lock and returns a
1408    /// [`PendingManifestHook`] to [`fire`](PendingManifestHook::fire) after
1409    /// dropping the lock. This is the sole caller of
1410    /// [`RegionManifestManager::update`], so it is the funnel through which all
1411    /// logical manifest writes notify the hook.
1412    ///
1413    /// Does not validate state or edit applicability — the caller must do so
1414    /// while still holding the lock (see `update_manifest_with_state_check`
1415    /// and `MitoRegion::exit_staging_on_success`).
1416    pub(crate) async fn update_locked(
1417        &self,
1418        manager: &mut RegionManifestManager,
1419        action_list: RegionMetaActionList,
1420        is_staging: bool,
1421    ) -> Result<PendingManifestHook> {
1422        let region_id = manager.manifest().metadata.region_id;
1423        // Clone before `action_list` is moved into `update` so the hook still
1424        // sees what was written.
1425        let action_list_for_hook = self.hook.as_ref().map(|_| action_list.clone());
1426        let version = if !is_staging
1427            && self.state.load() == RegionRoleState::Leader(RegionLeaderState::Downgrading)
1428        {
1429            manager.update_normal_without_checkpoint(action_list).await
1430        } else {
1431            manager.update(action_list, is_staging).await
1432        }
1433        .inspect_err(|e| error!(e; "Failed to update manifest, region_id: {}", region_id))?;
1434
1435        Ok(PendingManifestHook::new(
1436            region_id,
1437            action_list_for_hook,
1438            version,
1439            self.hook.clone(),
1440            is_staging,
1441        ))
1442    }
1443
1444    /// Updates the manifest using a caller-held write lock, releases the lock,
1445    /// and then fires the manifest hook.
1446    ///
1447    /// Callers must perform state and applicability checks before calling this
1448    /// method so the checks and update remain in the same lock critical section.
1449    async fn update_and_fire(
1450        &self,
1451        mut manager: RwLockWriteGuard<'_, RegionManifestManager>,
1452        action_list: RegionMetaActionList,
1453        is_staging: bool,
1454    ) -> Result<ManifestVersion> {
1455        let region_id = manager.manifest().metadata.region_id;
1456        let pending = self
1457            .update_locked(&mut manager, action_list, is_staging)
1458            .await?;
1459        let version = pending.version();
1460
1461        // Hook implementations may read the manifest or send region requests
1462        // that acquire this lock.
1463        drop(manager);
1464
1465        if self.state.load() == RegionRoleState::Follower {
1466            warn!(
1467                "Region {} becomes follower while updating manifest which may cause inconsistency, manifest version: {version}",
1468                region_id
1469            );
1470        }
1471
1472        pending.fire().await;
1473        Ok(version)
1474    }
1475
1476    async fn update_manifest_with_state_check(
1477        &self,
1478        action_list: RegionMetaActionList,
1479        is_staging: bool,
1480        check_state: impl FnOnce(RegionRoleState, RegionId) -> Result<()>,
1481    ) -> Result<ManifestVersion> {
1482        // Acquires the write lock of the manifest manager.
1483        let manager = self.manifest_manager.write().await;
1484        // Gets current manifest.
1485        let manifest = manager.manifest();
1486        // Checks state inside the lock. This is to ensure that we won't update the manifest
1487        // after `set_readonly_gracefully()` is called.
1488        let current_state = self.state.load();
1489        check_state(current_state, manifest.metadata.region_id)?;
1490
1491        for action in &action_list.actions {
1492            // Checks whether the edit is still applicable.
1493            let RegionMetaAction::Edit(edit) = &action else {
1494                continue;
1495            };
1496
1497            // Checks whether the region is truncated.
1498            let Some(truncated_entry_id) = manifest.truncated_entry_id else {
1499                continue;
1500            };
1501
1502            // This is an edit from flush.
1503            if let Some(flushed_entry_id) = edit.flushed_entry_id {
1504                // A flush edit is valid after truncate in two cases:
1505                // 1. `flushed_entry_id` moves past `truncated_entry_id`, meaning it definitely
1506                //    flushed data newer than the truncate point.
1507                // 2. `flushed_entry_id` equals `truncated_entry_id`, but `flushed_sequence`
1508                //    increases. This happens in skip-WAL tables where entry id can stay at 0,
1509                //    while sequence still advances for post-truncate writes.
1510                //
1511                // We still reject stale flushes from before truncate:
1512                // if entry id is equal and sequence does not advance, the flush is outdated.
1513                let is_newer_entry = truncated_entry_id < flushed_entry_id;
1514                let is_same_entry_with_newer_sequence = truncated_entry_id == flushed_entry_id
1515                    && edit.flushed_sequence.is_some_and(|flushed_sequence| {
1516                        manifest.flushed_sequence < flushed_sequence
1517                    });
1518
1519                ensure!(
1520                    is_newer_entry || is_same_entry_with_newer_sequence,
1521                    RegionTruncatedSnafu {
1522                        region_id: manifest.metadata.region_id,
1523                    }
1524                );
1525            }
1526
1527            // This is an edit from compaction.
1528            if !edit.files_to_remove.is_empty() {
1529                // Input files of the compaction task has been truncated.
1530                for file in &edit.files_to_remove {
1531                    ensure!(
1532                        manifest.files.contains_key(&file.file_id),
1533                        RegionTruncatedSnafu {
1534                            region_id: manifest.metadata.region_id,
1535                        }
1536                    );
1537                }
1538            }
1539        }
1540
1541        self.update_and_fire(manager, action_list, is_staging).await
1542    }
1543
1544    /// Sets the [`RegionRole`].
1545    ///
1546    /// ```text
1547    ///                  +---------------------+
1548    ///                  |   Staging Leader    |
1549    ///                  +----------+----------+
1550    ///                             |
1551    ///                             v
1552    ///     +----------+     +------+-------+     +-------------+
1553    ///     | Follower | <-> |    Leader    | <-> | Downgrading |
1554    ///     +-----+----+     +------+-------+     +------+------+
1555    ///           ^                 ^                    |
1556    ///           +-----------------+--------------------+
1557    ///
1558    /// ```
1559    ///
1560    /// # State Transitions
1561    ///
1562    /// From `Follower`:
1563    /// - `Follower -> Leader`
1564    ///
1565    /// From `Leader`:
1566    /// - `Leader -> Follower`
1567    /// - `Leader -> Downgrading Leader`
1568    ///
1569    /// From `Staging Leader`:
1570    /// - `Staging Leader -> Leader`
1571    /// - `Staging Leader -> Follower`
1572    /// - `Staging Leader -> Downgrading Leader`
1573    ///
1574    /// From `Downgrading Leader`:
1575    /// - `Downgrading Leader -> Leader`
1576    /// - `Downgrading Leader -> Follower`
1577    pub(crate) fn set_role(&self, next_role: RegionRole, region_id: RegionId) {
1578        match next_role {
1579            RegionRole::Follower => {
1580                if self
1581                    .exit_staging(region_id, RegionRoleState::Follower)
1582                    .is_ok()
1583                {
1584                    info!(
1585                        "Convert region {} to follower, previous role state: {:?}",
1586                        region_id,
1587                        RegionRoleState::Leader(RegionLeaderState::Staging)
1588                    );
1589                    return;
1590                }
1591                match self.state.fetch_update(|state| {
1592                    if !matches!(state, RegionRoleState::Follower) {
1593                        Some(RegionRoleState::Follower)
1594                    } else {
1595                        None
1596                    }
1597                }) {
1598                    Ok(state) => info!(
1599                        "Convert region {} to follower, previous role state: {:?}",
1600                        region_id, state
1601                    ),
1602                    Err(state) => {
1603                        if state != RegionRoleState::Follower {
1604                            warn!(
1605                                "Failed to convert region {} to follower, current role state: {:?}",
1606                                region_id, state
1607                            )
1608                        }
1609                    }
1610                }
1611            }
1612            RegionRole::Leader => {
1613                if self
1614                    .exit_staging(
1615                        region_id,
1616                        RegionRoleState::Leader(RegionLeaderState::Writable),
1617                    )
1618                    .is_ok()
1619                {
1620                    info!(
1621                        "Convert region {} to leader, previous role state: {:?}",
1622                        region_id,
1623                        RegionRoleState::Leader(RegionLeaderState::Staging)
1624                    );
1625                    return;
1626                }
1627                match self.state.fetch_update(|state| {
1628                    if matches!(
1629                        state,
1630                        RegionRoleState::Follower
1631                            | RegionRoleState::Leader(RegionLeaderState::Downgrading)
1632                    ) {
1633                        Some(RegionRoleState::Leader(RegionLeaderState::Writable))
1634                    } else {
1635                        None
1636                    }
1637                }) {
1638                    Ok(state) => info!(
1639                        "Convert region {} to leader, previous role state: {:?}",
1640                        region_id, state
1641                    ),
1642                    Err(state) => {
1643                        if state != RegionRoleState::Leader(RegionLeaderState::Writable) {
1644                            warn!(
1645                                "Failed to convert region {} to leader, current role state: {:?}",
1646                                region_id, state
1647                            )
1648                        }
1649                    }
1650                }
1651            }
1652            RegionRole::StagingLeader => {
1653                info!(
1654                    "Ignore direct conversion of region {} to staging leader; staging requires the dedicated workflow",
1655                    region_id
1656                );
1657            }
1658            RegionRole::DowngradingLeader => {
1659                if self
1660                    .exit_staging(
1661                        region_id,
1662                        RegionRoleState::Leader(RegionLeaderState::Downgrading),
1663                    )
1664                    .is_ok()
1665                {
1666                    info!(
1667                        "Convert region {} to downgrading region, previous role state: {:?}",
1668                        region_id,
1669                        RegionRoleState::Leader(RegionLeaderState::Staging)
1670                    );
1671                    return;
1672                }
1673                match self.state.compare_exchange(
1674                    RegionRoleState::Leader(RegionLeaderState::Writable),
1675                    RegionRoleState::Leader(RegionLeaderState::Downgrading),
1676                ) {
1677                    Ok(state) => info!(
1678                        "Convert region {} to downgrading region, previous role state: {:?}",
1679                        region_id, state
1680                    ),
1681                    Err(state) => {
1682                        if state != RegionRoleState::Leader(RegionLeaderState::Downgrading) {
1683                            warn!(
1684                                "Failed to convert region {} to downgrading leader, current role state: {:?}",
1685                                region_id, state
1686                            )
1687                        }
1688                    }
1689                }
1690            }
1691        }
1692    }
1693
1694    /// Returns the normal manifest of the region.
1695    pub(crate) async fn manifest(&self) -> Arc<crate::manifest::action::RegionManifest> {
1696        self.manifest_manager.read().await.manifest()
1697    }
1698
1699    /// Returns the staging manifest of the region.
1700    pub(crate) async fn staging_manifest(
1701        &self,
1702    ) -> Option<Arc<crate::manifest::action::RegionManifest>> {
1703        self.manifest_manager.read().await.staging_manifest()
1704    }
1705}
1706
1707pub(crate) type ManifestContextRef = Arc<ManifestContext>;
1708
1709/// Regions indexed by ids.
1710#[derive(Debug, Default)]
1711pub(crate) struct RegionMap {
1712    regions: RwLock<HashMap<RegionId, MitoRegionRef>>,
1713}
1714
1715impl RegionMap {
1716    /// Returns true if the region exists.
1717    pub(crate) fn is_region_exists(&self, region_id: RegionId) -> bool {
1718        let regions = self.regions.read().unwrap();
1719        regions.contains_key(&region_id)
1720    }
1721
1722    /// Inserts a new region into the map.
1723    pub(crate) fn insert_region(&self, region: MitoRegionRef) {
1724        let mut regions = self.regions.write().unwrap();
1725        regions.insert(region.region_id, region);
1726    }
1727
1728    /// Gets region by region id.
1729    pub(crate) fn get_region(&self, region_id: RegionId) -> Option<MitoRegionRef> {
1730        let regions = self.regions.read().unwrap();
1731        regions.get(&region_id).cloned()
1732    }
1733
1734    /// Gets writable region by region id.
1735    ///
1736    /// Returns error if the region does not exist or is readonly.
1737    pub(crate) fn writable_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
1738        let region = self
1739            .get_region(region_id)
1740            .context(RegionNotFoundSnafu { region_id })?;
1741        ensure!(
1742            region.is_writable(),
1743            RegionStateSnafu {
1744                region_id,
1745                state: region.state(),
1746                expect: RegionRoleState::Leader(RegionLeaderState::Writable),
1747            }
1748        );
1749        Ok(region)
1750    }
1751
1752    /// Gets readonly region by region id.
1753    ///
1754    /// Returns error if the region does not exist or is writable.
1755    pub(crate) fn follower_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
1756        let region = self
1757            .get_region(region_id)
1758            .context(RegionNotFoundSnafu { region_id })?;
1759        ensure!(
1760            region.is_follower(),
1761            RegionStateSnafu {
1762                region_id,
1763                state: region.state(),
1764                expect: RegionRoleState::Follower,
1765            }
1766        );
1767
1768        Ok(region)
1769    }
1770
1771    /// Gets region by region id.
1772    ///
1773    /// Calls the callback if the region does not exist.
1774    pub(crate) fn get_region_or<F: OnFailure>(
1775        &self,
1776        region_id: RegionId,
1777        cb: &mut F,
1778    ) -> Option<MitoRegionRef> {
1779        match self
1780            .get_region(region_id)
1781            .context(RegionNotFoundSnafu { region_id })
1782        {
1783            Ok(region) => Some(region),
1784            Err(e) => {
1785                cb.on_failure(e);
1786                None
1787            }
1788        }
1789    }
1790
1791    /// Gets writable region by region id.
1792    ///
1793    /// Calls the callback if the region does not exist or is readonly.
1794    pub(crate) fn writable_region_or<F: OnFailure>(
1795        &self,
1796        region_id: RegionId,
1797        cb: &mut F,
1798    ) -> Option<MitoRegionRef> {
1799        match self.writable_region(region_id) {
1800            Ok(region) => Some(region),
1801            Err(e) => {
1802                cb.on_failure(e);
1803                None
1804            }
1805        }
1806    }
1807
1808    /// Gets writable non-staging region by region id.
1809    ///
1810    /// Returns error if the region does not exist, is readonly, or is in staging mode.
1811    pub(crate) fn writable_non_staging_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
1812        let region = self.writable_region(region_id)?;
1813        if region.is_staging() {
1814            return Err(crate::error::RegionStateSnafu {
1815                region_id,
1816                state: region.state(),
1817                expect: RegionRoleState::Leader(RegionLeaderState::Writable),
1818            }
1819            .build());
1820        }
1821        Ok(region)
1822    }
1823
1824    /// Gets staging region by region id.
1825    ///
1826    /// Returns error if the region does not exist or is not in staging state.
1827    pub(crate) fn staging_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
1828        let region = self
1829            .get_region(region_id)
1830            .context(RegionNotFoundSnafu { region_id })?;
1831        ensure!(
1832            region.is_staging(),
1833            RegionStateSnafu {
1834                region_id,
1835                state: region.state(),
1836                expect: RegionRoleState::Leader(RegionLeaderState::Staging),
1837            }
1838        );
1839        Ok(region)
1840    }
1841
1842    /// Gets flushable region by region id.
1843    ///
1844    /// Returns error if the region does not exist or not flushable.
1845    pub(crate) fn flushable_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
1846        let region = self
1847            .get_region(region_id)
1848            .context(RegionNotFoundSnafu { region_id })?;
1849        ensure!(
1850            region.is_flushable(),
1851            FlushableRegionStateSnafu {
1852                region_id,
1853                state: region.state(),
1854            }
1855        );
1856        Ok(region)
1857    }
1858
1859    /// Remove region by id.
1860    pub(crate) fn remove_region(&self, region_id: RegionId) -> Option<MitoRegionRef> {
1861        let mut regions = self.regions.write().unwrap();
1862        regions.remove(&region_id)
1863    }
1864
1865    /// List all regions.
1866    pub(crate) fn list_regions(&self) -> Vec<MitoRegionRef> {
1867        let regions = self.regions.read().unwrap();
1868        regions.values().cloned().collect()
1869    }
1870
1871    /// Clear the map.
1872    pub(crate) fn clear(&self) {
1873        self.regions.write().unwrap().clear();
1874    }
1875}
1876
1877pub(crate) type RegionMapRef = Arc<RegionMap>;
1878
1879/// Opening regions
1880#[derive(Debug, Default)]
1881pub(crate) struct OpeningRegions {
1882    regions: RwLock<HashMap<RegionId, Vec<OptionOutputTx>>>,
1883}
1884
1885impl OpeningRegions {
1886    /// Registers `sender` for an opening region; Otherwise, it returns `None`.
1887    pub(crate) fn wait_for_opening_region(
1888        &self,
1889        region_id: RegionId,
1890        sender: OptionOutputTx,
1891    ) -> Option<OptionOutputTx> {
1892        let mut regions = self.regions.write().unwrap();
1893        match regions.entry(region_id) {
1894            Entry::Occupied(mut senders) => {
1895                senders.get_mut().push(sender);
1896                None
1897            }
1898            Entry::Vacant(_) => Some(sender),
1899        }
1900    }
1901
1902    /// Returns true if the region exists.
1903    pub(crate) fn is_region_exists(&self, region_id: RegionId) -> bool {
1904        let regions = self.regions.read().unwrap();
1905        regions.contains_key(&region_id)
1906    }
1907
1908    /// Inserts a new region into the map.
1909    pub(crate) fn insert_sender(&self, region: RegionId, sender: OptionOutputTx) {
1910        let mut regions = self.regions.write().unwrap();
1911        regions.insert(region, vec![sender]);
1912    }
1913
1914    /// Remove region by id.
1915    pub(crate) fn remove_sender(&self, region_id: RegionId) -> Vec<OptionOutputTx> {
1916        let mut regions = self.regions.write().unwrap();
1917        regions.remove(&region_id).unwrap_or_default()
1918    }
1919
1920    #[cfg(test)]
1921    pub(crate) fn sender_len(&self, region_id: RegionId) -> usize {
1922        let regions = self.regions.read().unwrap();
1923        if let Some(senders) = regions.get(&region_id) {
1924            senders.len()
1925        } else {
1926            0
1927        }
1928    }
1929}
1930
1931pub(crate) type OpeningRegionsRef = Arc<OpeningRegions>;
1932
1933/// The regions that are catching up.
1934#[derive(Debug, Default)]
1935pub(crate) struct CatchupRegions {
1936    regions: RwLock<HashSet<RegionId>>,
1937}
1938
1939impl CatchupRegions {
1940    /// Returns true if the region exists.
1941    pub(crate) fn is_region_exists(&self, region_id: RegionId) -> bool {
1942        let regions = self.regions.read().unwrap();
1943        regions.contains(&region_id)
1944    }
1945
1946    /// Inserts a new region into the set.
1947    pub(crate) fn insert_region(&self, region_id: RegionId) {
1948        let mut regions = self.regions.write().unwrap();
1949        regions.insert(region_id);
1950    }
1951
1952    /// Remove region by id.
1953    pub(crate) fn remove_region(&self, region_id: RegionId) {
1954        let mut regions = self.regions.write().unwrap();
1955        regions.remove(&region_id);
1956    }
1957}
1958
1959pub(crate) type CatchupRegionsRef = Arc<CatchupRegions>;
1960
1961/// Manifest stats.
1962#[derive(Default, Debug, Clone)]
1963pub struct ManifestStats {
1964    pub(crate) total_manifest_size: Arc<AtomicU64>,
1965    pub(crate) manifest_version: Arc<AtomicU64>,
1966    pub(crate) file_removed_cnt: Arc<AtomicU64>,
1967}
1968
1969impl ManifestStats {
1970    fn total_manifest_size(&self) -> u64 {
1971        self.total_manifest_size.load(Ordering::Relaxed)
1972    }
1973
1974    fn manifest_version(&self) -> u64 {
1975        self.manifest_version.load(Ordering::Relaxed)
1976    }
1977
1978    fn file_removed_cnt(&self) -> u64 {
1979        self.file_removed_cnt.load(Ordering::Relaxed)
1980    }
1981}
1982
1983/// Parses the partition expression from a JSON string.
1984pub fn parse_partition_expr(partition_expr_str: Option<&str>) -> Result<Option<PartitionExpr>> {
1985    match partition_expr_str {
1986        None => Ok(None),
1987        Some("") => Ok(None),
1988        Some(json_str) => {
1989            let expr = partition::expr::PartitionExpr::from_json_str(json_str)
1990                .with_context(|_| InvalidPartitionExprSnafu { expr: json_str })?;
1991            Ok(expr)
1992        }
1993    }
1994}
1995
1996#[cfg(test)]
1997mod tests {
1998    use std::sync::Arc;
1999
2000    use common_datasource::compression::CompressionType;
2001    use common_test_util::temp_dir::create_temp_dir;
2002    use crossbeam_utils::atomic::AtomicCell;
2003    use object_store::ObjectStore;
2004    use object_store::services::Fs;
2005    use store_api::logstore::provider::Provider;
2006    use store_api::region_engine::RegionRole;
2007    use store_api::region_request::PathType;
2008    use store_api::storage::{FileId, RegionId};
2009
2010    use crate::access_layer::AccessLayer;
2011    use crate::error::Error;
2012    use crate::manifest::action::{
2013        RegionChange, RegionEdit, RegionMetaAction, RegionMetaActionList, RegionPartitionExprChange,
2014    };
2015    use crate::manifest::manager::{RegionManifestManager, RegionManifestOptions};
2016    use crate::region::{
2017        IndexBuildSource, IndexPublication, IndexPublicationStale, ManifestContext, ManifestStats,
2018        MitoRegion, RegionLeaderState, RegionRoleState, RegionStats,
2019    };
2020    use crate::sst::FormatType;
2021    use crate::sst::index::intermediate::IntermediateManager;
2022    use crate::sst::index::puffin_manager::PuffinManagerFactory;
2023    use crate::test_util::scheduler_util::SchedulerEnv;
2024    use crate::test_util::version_util::VersionControlBuilder;
2025    use crate::time_provider::StdTimeProvider;
2026
2027    #[test]
2028    fn test_region_state_lock_free() {
2029        assert!(AtomicCell::<RegionRoleState>::is_lock_free());
2030    }
2031
2032    #[test]
2033    fn test_region_role_state_as_str() {
2034        assert_eq!("Follower", RegionRoleState::Follower.as_str());
2035        assert_eq!(
2036            "Leader(Writable)",
2037            RegionRoleState::Leader(RegionLeaderState::Writable).as_str()
2038        );
2039        assert_eq!(
2040            "Leader(Staging)",
2041            RegionRoleState::Leader(RegionLeaderState::Staging).as_str()
2042        );
2043        assert_eq!(
2044            "Leader(Downgrading)",
2045            RegionRoleState::Leader(RegionLeaderState::Downgrading).as_str()
2046        );
2047    }
2048
2049    async fn build_test_region(env: &SchedulerEnv) -> MitoRegion {
2050        let builder = VersionControlBuilder::new();
2051        let version_control = Arc::new(builder.build());
2052        let metadata = version_control.current().version.metadata.clone();
2053
2054        let manager = RegionManifestManager::new(
2055            metadata.clone(),
2056            0,
2057            RegionManifestOptions {
2058                manifest_dir: "".to_string(),
2059                object_store: env.access_layer.object_store().clone(),
2060                compress_type: CompressionType::Uncompressed,
2061                checkpoint_distance: 10,
2062                remove_file_options: Default::default(),
2063                manifest_cache: None,
2064            },
2065            FormatType::PrimaryKey,
2066            &Default::default(),
2067        )
2068        .await
2069        .unwrap();
2070
2071        let manifest_ctx = Arc::new(ManifestContext::new(
2072            manager,
2073            RegionRoleState::Leader(RegionLeaderState::Writable),
2074            None,
2075        ));
2076
2077        MitoRegion {
2078            region_id: metadata.region_id,
2079            version_control,
2080            series_index_version_control: Default::default(),
2081            series_index_store: None,
2082            access_layer: env.access_layer.clone(),
2083            manifest_ctx,
2084            file_purger: crate::test_util::new_noop_file_purger(),
2085            provider: Provider::noop_provider(),
2086            last_flush_millis: Default::default(),
2087            last_schedule_compaction_millis: Default::default(),
2088            time_provider: Arc::new(StdTimeProvider),
2089            topic_latest_entry_id: Default::default(),
2090            region_stats: RegionStats::new(),
2091            stats: ManifestStats::default(),
2092        }
2093    }
2094
2095    fn empty_edit() -> RegionEdit {
2096        RegionEdit {
2097            files_to_add: Vec::new(),
2098            files_to_remove: Vec::new(),
2099            timestamp_ms: None,
2100            compaction_time_window: None,
2101            flushed_entry_id: None,
2102            flushed_sequence: None,
2103            committed_sequence: None,
2104        }
2105    }
2106
2107    #[tokio::test]
2108    async fn test_compaction_update_manifest_allows_editing_state() {
2109        let env = SchedulerEnv::new().await;
2110        let region = build_test_region(&env).await;
2111        region.set_editing(RegionLeaderState::Writable).unwrap();
2112
2113        let file_id = FileId::random();
2114        let action_list = RegionMetaActionList::with_action(RegionMetaAction::Edit(RegionEdit {
2115            files_to_add: vec![crate::sst::file::FileMeta {
2116                region_id: region.region_id,
2117                file_id,
2118                level: 1,
2119                ..Default::default()
2120            }],
2121            files_to_remove: Vec::new(),
2122            timestamp_ms: None,
2123            compaction_time_window: None,
2124            flushed_entry_id: None,
2125            flushed_sequence: None,
2126            committed_sequence: None,
2127        }));
2128
2129        region
2130            .manifest_ctx
2131            .update_manifest_for_compaction(action_list)
2132            .await
2133            .unwrap();
2134
2135        assert!(
2136            region
2137                .manifest_ctx
2138                .manifest()
2139                .await
2140                .files
2141                .contains_key(&file_id)
2142        );
2143    }
2144
2145    #[tokio::test]
2146    async fn test_index_publication_rejects_older_source_generation() {
2147        let env = SchedulerEnv::new().await;
2148        let region = build_test_region(&env).await;
2149        let source = crate::sst::file::FileMeta {
2150            region_id: region.region_id,
2151            file_id: FileId::random(),
2152            level: 1,
2153            file_size: 1024,
2154            ..Default::default()
2155        };
2156        region
2157            .manifest_ctx
2158            .update_manifest(
2159                RegionLeaderState::Writable,
2160                RegionMetaActionList::with_action(RegionMetaAction::Edit(RegionEdit {
2161                    files_to_add: vec![source.clone()],
2162                    ..empty_edit()
2163                })),
2164                false,
2165            )
2166            .await
2167            .unwrap();
2168
2169        let mut first_update = source.clone();
2170        first_update.index_version = 1;
2171        first_update.index_file_size = 128;
2172        let schema_version = region.version().metadata.schema_version;
2173        let initial_source = IndexBuildSource::new(source.clone(), schema_version);
2174        let first_committed = match region
2175            .manifest_ctx
2176            .update_manifest_for_index(&initial_source, first_update)
2177            .await
2178            .unwrap()
2179        {
2180            IndexPublication::Committed { file_meta, .. } => file_meta,
2181            IndexPublication::Stale(_) => panic!("first index publication should commit"),
2182        };
2183
2184        let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();
2185        let (release_tx, release_rx) = tokio::sync::oneshot::channel();
2186        let delayed_manifest_ctx = region.manifest_ctx.clone();
2187        let delayed_source = IndexBuildSource::new(first_committed.clone(), schema_version);
2188        let delayed_publication = tokio::spawn(async move {
2189            let mut delayed_update = delayed_source.file_meta.clone();
2190            delayed_update.index_version = 2;
2191            delayed_update.index_file_size = 128;
2192            ready_tx.send(()).unwrap();
2193            release_rx.await.unwrap();
2194            delayed_manifest_ctx
2195                .update_manifest_for_index(&delayed_source, delayed_update)
2196                .await
2197        });
2198        ready_rx.await.unwrap();
2199
2200        let mut newer_update = first_committed.clone();
2201        newer_update.index_version = 3;
2202        newer_update.index_file_size = 256;
2203        let newer_source = IndexBuildSource::new(first_committed, schema_version);
2204        let newer_committed = match region
2205            .manifest_ctx
2206            .update_manifest_for_index(&newer_source, newer_update)
2207            .await
2208            .unwrap()
2209        {
2210            IndexPublication::Committed { file_meta, .. } => file_meta,
2211            IndexPublication::Stale(_) => panic!("newer index publication should commit"),
2212        };
2213
2214        release_tx.send(()).unwrap();
2215        assert!(matches!(
2216            delayed_publication.await.unwrap().unwrap(),
2217            IndexPublication::Stale(IndexPublicationStale::SourceChanged)
2218        ));
2219        assert_eq!(
2220            region
2221                .manifest_ctx
2222                .manifest()
2223                .await
2224                .files
2225                .get(&source.file_id),
2226            Some(&newer_committed)
2227        );
2228    }
2229
2230    #[tokio::test]
2231    async fn test_index_publication_rejects_older_schema_generation() {
2232        let env = SchedulerEnv::new().await;
2233        let region = build_test_region(&env).await;
2234        let source_meta = crate::sst::file::FileMeta {
2235            region_id: region.region_id,
2236            file_id: FileId::random(),
2237            level: 1,
2238            file_size: 1024,
2239            ..Default::default()
2240        };
2241        region
2242            .manifest_ctx
2243            .update_manifest(
2244                RegionLeaderState::Writable,
2245                RegionMetaActionList::with_action(RegionMetaAction::Edit(RegionEdit {
2246                    files_to_add: vec![source_meta.clone()],
2247                    ..empty_edit()
2248                })),
2249                false,
2250            )
2251            .await
2252            .unwrap();
2253
2254        let old_schema_version = region.version().metadata.schema_version;
2255        let source = IndexBuildSource::new(source_meta.clone(), old_schema_version);
2256        let mut new_metadata = region.version().metadata.as_ref().clone();
2257        new_metadata.schema_version += 1;
2258        region
2259            .manifest_ctx
2260            .update_manifest(
2261                RegionLeaderState::Writable,
2262                RegionMetaActionList::with_action(RegionMetaAction::Change(RegionChange {
2263                    metadata: Arc::new(new_metadata),
2264                    sst_format: FormatType::PrimaryKey,
2265                    append_mode: None,
2266                })),
2267                false,
2268            )
2269            .await
2270            .unwrap();
2271
2272        let mut updated = source_meta.clone();
2273        updated.index_version = 1;
2274        updated.index_file_size = 128;
2275        assert!(matches!(
2276            region
2277                .manifest_ctx
2278                .update_manifest_for_index(&source, updated)
2279                .await
2280                .unwrap(),
2281            IndexPublication::Stale(IndexPublicationStale::SchemaChanged)
2282        ));
2283
2284        let manifest = region.manifest_ctx.manifest().await;
2285        assert_eq!(manifest.metadata.schema_version, old_schema_version + 1);
2286        assert_eq!(manifest.files.get(&source_meta.file_id), Some(&source_meta));
2287    }
2288
2289    #[tokio::test]
2290    async fn test_exit_staging_partition_expr_change_and_edit_success() {
2291        let env = SchedulerEnv::new().await;
2292        let region = build_test_region(&env).await;
2293
2294        let mut manager = region.manifest_ctx.manifest_manager.write().await;
2295        region.set_staging(&mut manager).await.unwrap();
2296        manager
2297            .update(
2298                RegionMetaActionList::new(vec![
2299                    RegionMetaAction::PartitionExprChange(RegionPartitionExprChange {
2300                        partition_expr: Some("expr_a".to_string()),
2301                    }),
2302                    RegionMetaAction::Edit(empty_edit()),
2303                ]),
2304                true,
2305            )
2306            .await
2307            .unwrap();
2308
2309        let _hook_payload = region.exit_staging_on_success(&mut manager).await.unwrap();
2310        drop(manager);
2311
2312        assert_eq!(
2313            region.version().metadata.partition_expr.as_deref(),
2314            Some("expr_a")
2315        );
2316        assert_eq!(
2317            region.state(),
2318            RegionRoleState::Leader(RegionLeaderState::Writable)
2319        );
2320    }
2321
2322    #[tokio::test]
2323    async fn test_exit_staging_change_with_same_columns_success() {
2324        let env = SchedulerEnv::new().await;
2325        let region = build_test_region(&env).await;
2326
2327        let mut manager = region.manifest_ctx.manifest_manager.write().await;
2328        region.set_staging(&mut manager).await.unwrap();
2329
2330        let mut changed_metadata = region.version().metadata.as_ref().clone();
2331        changed_metadata.set_partition_expr(Some("expr_b".to_string()));
2332
2333        manager
2334            .update(
2335                RegionMetaActionList::new(vec![
2336                    RegionMetaAction::Change(RegionChange {
2337                        metadata: Arc::new(changed_metadata),
2338                        sst_format: FormatType::PrimaryKey,
2339                        append_mode: None,
2340                    }),
2341                    RegionMetaAction::Edit(empty_edit()),
2342                ]),
2343                true,
2344            )
2345            .await
2346            .unwrap();
2347
2348        let _hook_payload = region.exit_staging_on_success(&mut manager).await.unwrap();
2349        drop(manager);
2350
2351        assert_eq!(
2352            region.version().metadata.partition_expr.as_deref(),
2353            Some("expr_b")
2354        );
2355        assert_eq!(
2356            region.state(),
2357            RegionRoleState::Leader(RegionLeaderState::Writable)
2358        );
2359    }
2360
2361    #[tokio::test]
2362    async fn test_exit_staging_change_with_different_columns_fails() {
2363        let env = SchedulerEnv::new().await;
2364        let region = build_test_region(&env).await;
2365
2366        let mut manager = region.manifest_ctx.manifest_manager.write().await;
2367        region.set_staging(&mut manager).await.unwrap();
2368
2369        let mut changed_metadata = region.version().metadata.as_ref().clone();
2370        changed_metadata.column_metadatas.rotate_left(1);
2371
2372        manager
2373            .update(
2374                RegionMetaActionList::new(vec![
2375                    RegionMetaAction::Change(RegionChange {
2376                        metadata: Arc::new(changed_metadata),
2377                        sst_format: FormatType::PrimaryKey,
2378                        append_mode: None,
2379                    }),
2380                    RegionMetaAction::Edit(empty_edit()),
2381                ]),
2382                true,
2383            )
2384            .await
2385            .unwrap();
2386
2387        let result = region.exit_staging_on_success(&mut manager).await;
2388        assert!(matches!(result, Err(Error::Unexpected { .. })));
2389    }
2390
2391    #[tokio::test]
2392    async fn test_exit_staging_partition_expr_change_and_change_conflict_fails() {
2393        let env = SchedulerEnv::new().await;
2394        let region = build_test_region(&env).await;
2395
2396        let mut manager = region.manifest_ctx.manifest_manager.write().await;
2397        region.set_staging(&mut manager).await.unwrap();
2398
2399        let mut changed_metadata = region.version().metadata.as_ref().clone();
2400        changed_metadata.set_partition_expr(Some("expr_c".to_string()));
2401
2402        manager
2403            .update(
2404                RegionMetaActionList::new(vec![
2405                    RegionMetaAction::PartitionExprChange(RegionPartitionExprChange {
2406                        partition_expr: Some("expr_c".to_string()),
2407                    }),
2408                    RegionMetaAction::Change(RegionChange {
2409                        metadata: Arc::new(changed_metadata),
2410                        sst_format: FormatType::PrimaryKey,
2411                        append_mode: None,
2412                    }),
2413                    RegionMetaAction::Edit(empty_edit()),
2414                ]),
2415                true,
2416            )
2417            .await
2418            .unwrap();
2419
2420        let result = region.exit_staging_on_success(&mut manager).await;
2421        assert!(matches!(result, Err(Error::Unexpected { .. })));
2422    }
2423
2424    #[tokio::test]
2425    async fn test_set_region_state() {
2426        let env = SchedulerEnv::new().await;
2427        let builder = VersionControlBuilder::new();
2428        let version_control = Arc::new(builder.build());
2429        let manifest_ctx = env
2430            .mock_manifest_context(version_control.current().version.metadata.clone())
2431            .await;
2432
2433        let region_id = RegionId::new(1024, 0);
2434        // Leader -> Follower
2435        manifest_ctx.set_role(RegionRole::Follower, region_id);
2436        assert_eq!(manifest_ctx.state.load(), RegionRoleState::Follower);
2437
2438        // Follower -> Leader
2439        manifest_ctx.set_role(RegionRole::Leader, region_id);
2440        assert_eq!(
2441            manifest_ctx.state.load(),
2442            RegionRoleState::Leader(RegionLeaderState::Writable)
2443        );
2444
2445        // Direct Leader -> StagingLeader should be ignored.
2446        manifest_ctx.set_role(RegionRole::StagingLeader, region_id);
2447        assert_eq!(
2448            manifest_ctx.state.load(),
2449            RegionRoleState::Leader(RegionLeaderState::Writable)
2450        );
2451
2452        // Leader -> Downgrading Leader
2453        manifest_ctx.set_role(RegionRole::DowngradingLeader, region_id);
2454        assert_eq!(
2455            manifest_ctx.state.load(),
2456            RegionRoleState::Leader(RegionLeaderState::Downgrading)
2457        );
2458
2459        // Downgrading Leader -> Follower
2460        manifest_ctx.set_role(RegionRole::Follower, region_id);
2461        assert_eq!(manifest_ctx.state.load(), RegionRoleState::Follower);
2462
2463        // Can't downgrade from follower (Follower -> Downgrading Leader)
2464        manifest_ctx.set_role(RegionRole::DowngradingLeader, region_id);
2465        assert_eq!(manifest_ctx.state.load(), RegionRoleState::Follower);
2466
2467        // Set region role too Downgrading Leader
2468        manifest_ctx.set_role(RegionRole::Leader, region_id);
2469        manifest_ctx.set_role(RegionRole::DowngradingLeader, region_id);
2470        assert_eq!(
2471            manifest_ctx.state.load(),
2472            RegionRoleState::Leader(RegionLeaderState::Downgrading)
2473        );
2474
2475        // Downgrading Leader -> Leader
2476        manifest_ctx.set_role(RegionRole::Leader, region_id);
2477        assert_eq!(
2478            manifest_ctx.state.load(),
2479            RegionRoleState::Leader(RegionLeaderState::Writable)
2480        );
2481    }
2482
2483    #[tokio::test]
2484    async fn test_staging_state_validation() {
2485        let env = SchedulerEnv::new().await;
2486        let builder = VersionControlBuilder::new();
2487        let version_control = Arc::new(builder.build());
2488
2489        // Create context with staging state using the correct pattern from SchedulerEnv
2490        let staging_ctx = {
2491            let manager = RegionManifestManager::new(
2492                version_control.current().version.metadata.clone(),
2493                0,
2494                RegionManifestOptions {
2495                    manifest_dir: "".to_string(),
2496                    object_store: env.access_layer.object_store().clone(),
2497                    compress_type: CompressionType::Uncompressed,
2498                    checkpoint_distance: 10,
2499                    remove_file_options: Default::default(),
2500                    manifest_cache: None,
2501                },
2502                FormatType::PrimaryKey,
2503                &Default::default(),
2504            )
2505            .await
2506            .unwrap();
2507            Arc::new(ManifestContext::new(
2508                manager,
2509                RegionRoleState::Leader(RegionLeaderState::Staging),
2510                None,
2511            ))
2512        };
2513
2514        // Test staging state behavior
2515        assert_eq!(
2516            staging_ctx.current_state(),
2517            RegionRoleState::Leader(RegionLeaderState::Staging)
2518        );
2519
2520        // Test writable context for comparison
2521        let writable_ctx = env
2522            .mock_manifest_context(version_control.current().version.metadata.clone())
2523            .await;
2524
2525        assert_eq!(
2526            writable_ctx.current_state(),
2527            RegionRoleState::Leader(RegionLeaderState::Writable)
2528        );
2529    }
2530
2531    #[tokio::test]
2532    async fn test_staging_state_transitions() {
2533        let builder = VersionControlBuilder::new();
2534        let version_control = Arc::new(builder.build());
2535        let metadata = version_control.current().version.metadata.clone();
2536
2537        // Create MitoRegion for testing state transitions
2538        let temp_dir = create_temp_dir("");
2539        let path_str = temp_dir.path().display().to_string();
2540        let fs_builder = Fs::default().root(&path_str);
2541        let object_store = ObjectStore::new(fs_builder).unwrap();
2542
2543        let index_aux_path = temp_dir.path().join("index_aux");
2544        let puffin_mgr = PuffinManagerFactory::new(&index_aux_path, 4096, None, None)
2545            .await
2546            .unwrap();
2547        let intm_mgr = IntermediateManager::init_fs(index_aux_path.to_str().unwrap())
2548            .await
2549            .unwrap();
2550
2551        let access_layer = Arc::new(AccessLayer::new(
2552            "",
2553            PathType::Bare,
2554            object_store,
2555            puffin_mgr,
2556            intm_mgr,
2557        ));
2558
2559        let manager = RegionManifestManager::new(
2560            metadata.clone(),
2561            0,
2562            RegionManifestOptions {
2563                manifest_dir: "".to_string(),
2564                object_store: access_layer.object_store().clone(),
2565                compress_type: CompressionType::Uncompressed,
2566                checkpoint_distance: 10,
2567                remove_file_options: Default::default(),
2568                manifest_cache: None,
2569            },
2570            FormatType::PrimaryKey,
2571            &Default::default(),
2572        )
2573        .await
2574        .unwrap();
2575
2576        let manifest_ctx = Arc::new(ManifestContext::new(
2577            manager,
2578            RegionRoleState::Leader(RegionLeaderState::Writable),
2579            None,
2580        ));
2581
2582        let region = MitoRegion {
2583            region_id: metadata.region_id,
2584            version_control,
2585            series_index_version_control: Default::default(),
2586            series_index_store: None,
2587            access_layer,
2588            manifest_ctx: manifest_ctx.clone(),
2589            file_purger: crate::test_util::new_noop_file_purger(),
2590            provider: Provider::noop_provider(),
2591            last_flush_millis: Default::default(),
2592            last_schedule_compaction_millis: Default::default(),
2593            time_provider: Arc::new(StdTimeProvider),
2594            topic_latest_entry_id: Default::default(),
2595            region_stats: RegionStats::new(),
2596            stats: ManifestStats::default(),
2597        };
2598
2599        // Test initial state
2600        assert_eq!(
2601            region.state(),
2602            RegionRoleState::Leader(RegionLeaderState::Writable)
2603        );
2604        assert!(!region.is_staging());
2605
2606        // Test transition to staging
2607        let mut manager = manifest_ctx.manifest_manager.write().await;
2608        region.set_staging(&mut manager).await.unwrap();
2609        drop(manager);
2610        assert_eq!(
2611            region.state(),
2612            RegionRoleState::Leader(RegionLeaderState::Staging)
2613        );
2614        assert!(region.is_staging());
2615
2616        // Test transition back to writable
2617        region.exit_staging().unwrap();
2618        assert_eq!(
2619            region.state(),
2620            RegionRoleState::Leader(RegionLeaderState::Writable)
2621        );
2622        assert!(!region.is_staging());
2623
2624        // Test staging directory cleanup: Create dirty staging files before entering staging mode
2625        {
2626            // Create some dummy staging manifest files to simulate interrupted session
2627            let manager = manifest_ctx.manifest_manager.write().await;
2628            let dummy_actions = RegionMetaActionList::new(vec![]);
2629            let dummy_bytes = dummy_actions.encode().unwrap();
2630
2631            // Create dirty staging files with versions 100 and 101
2632            manager.store().save(100, &dummy_bytes, true).await.unwrap();
2633            manager.store().save(101, &dummy_bytes, true).await.unwrap();
2634            drop(manager);
2635
2636            // Verify dirty files exist before entering staging
2637            let manager = manifest_ctx.manifest_manager.read().await;
2638            let dirty_manifests = manager.store().fetch_staging_manifests().await.unwrap();
2639            assert_eq!(
2640                dirty_manifests.len(),
2641                2,
2642                "Should have 2 dirty staging files"
2643            );
2644            drop(manager);
2645
2646            // Enter staging mode - this should clean up the dirty files
2647            let mut manager = manifest_ctx.manifest_manager.write().await;
2648            region.set_staging(&mut manager).await.unwrap();
2649            drop(manager);
2650
2651            // Verify dirty files are cleaned up after entering staging
2652            let manager = manifest_ctx.manifest_manager.read().await;
2653            let cleaned_manifests = manager.store().fetch_staging_manifests().await.unwrap();
2654            assert_eq!(
2655                cleaned_manifests.len(),
2656                0,
2657                "Dirty staging files should be cleaned up"
2658            );
2659            drop(manager);
2660
2661            // Exit staging to restore normal state for remaining tests
2662            region.exit_staging().unwrap();
2663        }
2664
2665        // Test invalid transitions
2666        let mut manager = manifest_ctx.manifest_manager.write().await;
2667        assert!(region.set_staging(&mut manager).await.is_ok()); // Writable -> Staging should work
2668        drop(manager);
2669        let mut manager = manifest_ctx.manifest_manager.write().await;
2670        assert!(region.set_staging(&mut manager).await.is_err()); // Staging -> Staging should fail
2671        drop(manager);
2672        assert!(region.exit_staging().is_ok()); // Staging -> Writable should work
2673        assert!(region.exit_staging().is_err()); // Writable -> Writable should fail
2674    }
2675}