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