Skip to main content

mito2/
engine.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 engine.
16
17#[cfg(test)]
18mod alter_test;
19#[cfg(test)]
20mod append_mode_test;
21#[cfg(test)]
22mod basic_test;
23#[cfg(test)]
24mod batch_catchup_test;
25#[cfg(test)]
26mod batch_open_test;
27#[cfg(test)]
28mod bump_committed_sequence_test;
29#[cfg(test)]
30mod catchup_test;
31#[cfg(test)]
32mod close_test;
33#[cfg(test)]
34pub(crate) mod compaction_test;
35#[cfg(test)]
36mod create_test;
37#[cfg(test)]
38mod drop_test;
39#[cfg(test)]
40mod edit_region_test;
41#[cfg(test)]
42mod file_ref_test;
43#[cfg(test)]
44mod filter_deleted_test;
45#[cfg(test)]
46mod flush_test;
47#[cfg(test)]
48mod index_build_test;
49#[cfg(any(test, feature = "test"))]
50pub mod listener;
51#[cfg(test)]
52mod merge_mode_test;
53#[cfg(test)]
54mod object_store_wal_recovery_test;
55#[cfg(test)]
56mod object_store_wal_test;
57#[cfg(test)]
58mod open_test;
59#[cfg(test)]
60mod parallel_test;
61#[cfg(test)]
62mod projection_test;
63#[cfg(test)]
64mod prune_test;
65pub mod region_hook;
66#[cfg(test)]
67mod row_selector_test;
68#[cfg(test)]
69mod scan_corrupt;
70#[cfg(test)]
71mod scan_test;
72#[cfg(test)]
73mod set_role_state_test;
74#[cfg(test)]
75mod skip_wal_test;
76#[cfg(test)]
77mod staging_test;
78#[cfg(test)]
79mod sync_test;
80#[cfg(test)]
81mod truncate_test;
82
83#[cfg(test)]
84mod copy_region_from_test;
85#[cfg(test)]
86mod remap_manifests_test;
87
88#[cfg(test)]
89mod apply_staging_manifest_test;
90#[cfg(test)]
91mod partition_filter_test;
92mod puffin_index;
93
94use std::any::Any;
95use std::collections::{HashMap, HashSet};
96use std::sync::Arc;
97use std::time::Instant;
98
99use api::region::RegionResponse;
100use async_trait::async_trait;
101use common_base::Plugins;
102use common_error::ext::BoxedError;
103use common_meta::error::UnexpectedSnafu;
104use common_meta::key::SchemaMetadataManagerRef;
105use common_recordbatch::{QueryMemoryTracker, SendableRecordBatchStream};
106use common_stat::get_total_memory_bytes;
107use common_telemetry::{debug, info, tracing, warn};
108use common_wal::options::WalOptions;
109use datafusion::execution::memory_pool::{GreedyMemoryPool, MemoryPool, UnboundedMemoryPool};
110use futures::future::{join_all, try_join_all};
111use futures::stream::{self, Stream, StreamExt};
112use object_store::manager::ObjectStoreManagerRef;
113use region_hook::RegionHookRef;
114use snafu::{OptionExt, ResultExt, ensure};
115use store_api::ManifestVersion;
116use store_api::codec::PrimaryKeyEncoding;
117use store_api::logstore::LogStore;
118use store_api::logstore::provider::{KafkaProvider, Provider};
119use store_api::metadata::{ColumnMetadata, RegionMetadataRef};
120use store_api::metric_engine_consts::{
121    MANIFEST_INFO_EXTENSION_KEY, TABLE_COLUMN_METADATA_EXTENSION_KEY,
122};
123use store_api::region_engine::{
124    BatchResponses, MitoCopyRegionFromRequest, MitoCopyRegionFromResponse, RegionEngine,
125    RegionManifestInfo, RegionRole, RegionScannerRef, RegionStatistic, RemapManifestsRequest,
126    RemapManifestsResponse, SetRegionRoleStateResponse, SettableRegionRoleState,
127    SyncRegionFromRequest, SyncRegionFromResponse,
128};
129use store_api::region_info::RegionInfoEntry;
130use store_api::region_request::{
131    AffectedRows, RegionCatchupRequest, RegionOpenRequest, RegionRequest,
132};
133use store_api::sst_entry::{ManifestSstEntry, PuffinIndexMetaEntry, StorageSstEntry};
134use store_api::storage::{FileId, FileRefsManifest, RegionId, ScanRequest, SequenceNumber};
135use tokio::sync::{Semaphore, oneshot};
136
137use crate::access_layer::RegionFilePathFactory;
138use crate::cache::{CacheManagerRef, CacheStrategy};
139use crate::config::MitoConfig;
140use crate::engine::puffin_index::{IndexEntryContext, collect_index_entries_from_puffin};
141use crate::error::{
142    IncrementalQueryStaleSnafu, InvalidRequestSnafu, JoinSnafu, MitoManifestInfoSnafu, RecvSnafu,
143    RegionNotFoundSnafu, Result, SequenceRangeUnsupportedSnafu, SerdeJsonSnafu,
144    SerializeColumnMetadataSnafu, SnapshotFenceStaleSnafu,
145};
146#[cfg(feature = "enterprise")]
147use crate::extension::BoxedExtensionRangeProviderFactory;
148use crate::gc::GcLimiterRef;
149use crate::manifest::action::RegionEdit;
150use crate::memtable::MemtableStats;
151use crate::metrics::{
152    HANDLE_REQUEST_ELAPSED, SCAN_MEMORY_EXHAUSTED_TOTAL, SCAN_MEMORY_USAGE_BYTES,
153    SCAN_REQUESTS_REJECTED_TOTAL,
154};
155use crate::read::scan_region::{ScanRegion, Scanner, exact_sequence_range};
156use crate::read::stream::ScanBatchStream;
157use crate::region::MitoRegionRef;
158use crate::region::opener::PartitionExprFetcherRef;
159use crate::region::options::parse_wal_options;
160use crate::request::{RegionEditRequest, WorkerRequest};
161use crate::sst::file::{FileMeta, RegionFileId, RegionIndexId};
162use crate::sst::file_ref::FileReferenceManagerRef;
163use crate::sst::index::intermediate::IntermediateManager;
164use crate::sst::index::puffin_manager::PuffinManagerFactory;
165use crate::wal::entry_distributor::{
166    DEFAULT_ENTRY_RECEIVER_BUFFER_SIZE, build_wal_entry_distributor_and_receivers,
167};
168use crate::wal::raw_entry_reader::{LogStoreRawEntryReader, RawEntryReader};
169use crate::worker::WorkerGroup;
170
171pub const MITO_ENGINE_NAME: &str = "mito";
172
173pub struct MitoEngineBuilder<'a, S: LogStore> {
174    data_home: &'a str,
175    config: MitoConfig,
176    log_store: Arc<S>,
177    object_store_manager: ObjectStoreManagerRef,
178    schema_metadata_manager: SchemaMetadataManagerRef,
179    file_ref_manager: FileReferenceManagerRef,
180    partition_expr_fetcher: PartitionExprFetcherRef,
181    plugins: Plugins,
182    #[cfg(feature = "enterprise")]
183    extension_range_provider_factory: Option<BoxedExtensionRangeProviderFactory>,
184}
185
186impl<'a, S: LogStore> MitoEngineBuilder<'a, S> {
187    #[allow(clippy::too_many_arguments)]
188    pub fn new(
189        data_home: &'a str,
190        config: MitoConfig,
191        log_store: Arc<S>,
192        object_store_manager: ObjectStoreManagerRef,
193        schema_metadata_manager: SchemaMetadataManagerRef,
194        file_ref_manager: FileReferenceManagerRef,
195        partition_expr_fetcher: PartitionExprFetcherRef,
196        plugins: Plugins,
197    ) -> Self {
198        Self {
199            data_home,
200            config,
201            log_store,
202            object_store_manager,
203            schema_metadata_manager,
204            file_ref_manager,
205            plugins,
206            partition_expr_fetcher,
207            #[cfg(feature = "enterprise")]
208            extension_range_provider_factory: None,
209        }
210    }
211
212    #[cfg(feature = "enterprise")]
213    #[must_use]
214    pub fn with_extension_range_provider_factory(
215        self,
216        extension_range_provider_factory: Option<BoxedExtensionRangeProviderFactory>,
217    ) -> Self {
218        Self {
219            extension_range_provider_factory,
220            ..self
221        }
222    }
223
224    pub async fn try_build(mut self) -> Result<MitoEngine> {
225        self.config.sanitize(self.data_home)?;
226
227        let config = Arc::new(self.config);
228        // Extract the region hook before `plugins` is moved into the WorkerGroup,
229        // so the engine (and thus the GC worker) can fire `on_region_gc`.
230        let region_hook = self.plugins.get::<RegionHookRef>();
231        let workers = WorkerGroup::start(
232            self.data_home,
233            config.clone(),
234            self.log_store.clone(),
235            self.object_store_manager,
236            self.schema_metadata_manager,
237            self.file_ref_manager,
238            self.partition_expr_fetcher.clone(),
239            self.plugins,
240        )
241        .await?;
242        let wal_raw_entry_reader = Arc::new(LogStoreRawEntryReader::new(self.log_store));
243        let total_memory = get_total_memory_bytes().max(0) as u64;
244        let scan_memory_limit = config.scan_memory_limit.resolve(total_memory) as usize;
245        let scan_memory_pool = new_scan_memory_pool(scan_memory_limit);
246        let scan_memory_tracker =
247            QueryMemoryTracker::builder(scan_memory_limit, config.scan_memory_on_exhausted)
248                .on_update(|usage| {
249                    SCAN_MEMORY_USAGE_BYTES.set(usage as i64);
250                })
251                .on_exhausted(|| {
252                    SCAN_MEMORY_EXHAUSTED_TOTAL.inc();
253                })
254                .on_reject(|| {
255                    SCAN_REQUESTS_REJECTED_TOTAL.inc();
256                })
257                .build();
258
259        let inner = EngineInner {
260            workers,
261            config,
262            wal_raw_entry_reader,
263            scan_memory_tracker,
264            scan_memory_pool,
265            region_hook,
266            #[cfg(feature = "enterprise")]
267            extension_range_provider_factory: None,
268        };
269
270        #[cfg(feature = "enterprise")]
271        let inner =
272            inner.with_extension_range_provider_factory(self.extension_range_provider_factory);
273
274        Ok(MitoEngine {
275            inner: Arc::new(inner),
276        })
277    }
278}
279
280/// Region engine implementation for timeseries data.
281#[derive(Clone)]
282pub struct MitoEngine {
283    inner: Arc<EngineInner>,
284}
285
286impl MitoEngine {
287    /// Returns a new [MitoEngine] with specific `config`, `log_store` and `object_store`.
288    #[allow(clippy::too_many_arguments)]
289    pub async fn new<S: LogStore>(
290        data_home: &str,
291        config: MitoConfig,
292        log_store: Arc<S>,
293        object_store_manager: ObjectStoreManagerRef,
294        schema_metadata_manager: SchemaMetadataManagerRef,
295        file_ref_manager: FileReferenceManagerRef,
296        partition_expr_fetcher: PartitionExprFetcherRef,
297        plugins: Plugins,
298    ) -> Result<MitoEngine> {
299        let builder = MitoEngineBuilder::new(
300            data_home,
301            config,
302            log_store,
303            object_store_manager,
304            schema_metadata_manager,
305            file_ref_manager,
306            partition_expr_fetcher,
307            plugins,
308        );
309        builder.try_build().await
310    }
311
312    pub fn mito_config(&self) -> &MitoConfig {
313        &self.inner.config
314    }
315
316    pub fn cache_manager(&self) -> CacheManagerRef {
317        self.inner.workers.cache_manager()
318    }
319
320    pub fn file_ref_manager(&self) -> FileReferenceManagerRef {
321        self.inner.workers.file_ref_manager()
322    }
323
324    pub fn gc_limiter(&self) -> GcLimiterRef {
325        self.inner.workers.gc_limiter()
326    }
327
328    pub fn object_store_manager(&self) -> &ObjectStoreManagerRef {
329        self.inner.workers.object_store_manager()
330    }
331
332    pub fn puffin_manager_factory(&self) -> &PuffinManagerFactory {
333        self.inner.workers.puffin_manager_factory()
334    }
335
336    pub fn intermediate_manager(&self) -> &IntermediateManager {
337        self.inner.workers.intermediate_manager()
338    }
339
340    pub fn schema_metadata_manager(&self) -> &SchemaMetadataManagerRef {
341        self.inner.workers.schema_metadata_manager()
342    }
343
344    /// Returns the registered region hook (if any), for the GC worker to fire
345    /// [`RegionHook::on_region_gc`].
346    pub fn region_hook(&self) -> Option<RegionHookRef> {
347        self.inner.region_hook.clone()
348    }
349
350    /// Get all tmp ref files for given region ids, excluding files that's already in manifest.
351    pub async fn get_snapshot_of_file_refs(
352        &self,
353        file_handle_regions: impl IntoIterator<Item = RegionId>,
354        related_regions: HashMap<RegionId, HashSet<RegionId>>,
355    ) -> Result<FileRefsManifest> {
356        let file_ref_mgr = self.file_ref_manager();
357
358        let file_handle_regions = file_handle_regions.into_iter().collect::<Vec<_>>();
359        // Convert region IDs to MitoRegionRef objects, ignore regions that do not exist on current datanode
360        // as regions on other datanodes are not managed by this engine.
361        let query_regions: Vec<MitoRegionRef> = file_handle_regions
362            .into_iter()
363            .filter_map(|region_id| self.find_region(region_id))
364            .collect();
365
366        let dst_region_to_src_regions: Vec<(MitoRegionRef, HashSet<RegionId>)> = {
367            let dst2src = related_regions
368                .into_iter()
369                .flat_map(|(src, dsts)| dsts.into_iter().map(move |dst| (dst, src)))
370                .fold(
371                    HashMap::<RegionId, HashSet<RegionId>>::new(),
372                    |mut acc, (k, v)| {
373                        let entry = acc.entry(k).or_default();
374                        entry.insert(v);
375                        acc
376                    },
377                );
378            let mut dst_region_to_src_regions = Vec::with_capacity(dst2src.len());
379            for (dst_region, srcs) in dst2src {
380                let Some(region) = self.find_region(dst_region) else {
381                    return RegionNotFoundSnafu {
382                        region_id: dst_region,
383                    }
384                    .fail();
385                };
386                dst_region_to_src_regions.push((region, srcs));
387            }
388            dst_region_to_src_regions
389        };
390
391        file_ref_mgr
392            .get_snapshot_of_file_refs(query_regions, dst_region_to_src_regions)
393            .await
394    }
395
396    /// Returns true if the specific region exists.
397    pub fn is_region_exists(&self, region_id: RegionId) -> bool {
398        self.inner.workers.is_region_exists(region_id)
399    }
400
401    /// Returns true if the specific region exists.
402    pub fn is_region_opening(&self, region_id: RegionId) -> bool {
403        self.inner.workers.is_region_opening(region_id)
404    }
405
406    /// Returns true if the specific region is catching up.
407    pub fn is_region_catching_up(&self, region_id: RegionId) -> bool {
408        self.inner.workers.is_region_catching_up(region_id)
409    }
410
411    /// Returns the region disk/memory statistic.
412    pub fn get_region_statistic(&self, region_id: RegionId) -> Option<RegionStatistic> {
413        self.find_region(region_id)
414            .map(|region| region.region_statistic())
415    }
416
417    /// Returns primary key encoding of the region.
418    pub fn get_primary_key_encoding(&self, region_id: RegionId) -> Option<PrimaryKeyEncoding> {
419        self.find_region(region_id)
420            .map(|r| r.primary_key_encoding())
421    }
422
423    /// Handle substrait query and return a stream of record batches
424    ///
425    /// Notice that the output stream's ordering is not guranateed. If order
426    /// matter, please use [`scanner`] to build a [`Scanner`] to consume.
427    #[tracing::instrument(skip_all)]
428    pub async fn scan_to_stream(
429        &self,
430        region_id: RegionId,
431        request: ScanRequest,
432    ) -> Result<SendableRecordBatchStream, BoxedError> {
433        self.scanner(region_id, request)
434            .await
435            .map_err(BoxedError::new)?
436            .scan()
437            .await
438    }
439
440    /// Scan [`Batch`]es by [`ScanRequest`].
441    pub async fn scan_batch(
442        &self,
443        region_id: RegionId,
444        request: ScanRequest,
445        filter_deleted: bool,
446    ) -> Result<ScanBatchStream> {
447        let mut scan_region = self.scan_region(region_id, request)?;
448        scan_region.set_filter_deleted(filter_deleted);
449        scan_region.scanner().await?.scan_batch()
450    }
451
452    /// Returns a scanner to scan for `request`.
453    pub(crate) async fn scanner(
454        &self,
455        region_id: RegionId,
456        request: ScanRequest,
457    ) -> Result<Scanner> {
458        self.scan_region(region_id, request)?.scanner().await
459    }
460
461    /// Scans a region.
462    #[tracing::instrument(skip_all, fields(region_id = %region_id))]
463    fn scan_region(&self, region_id: RegionId, request: ScanRequest) -> Result<ScanRegion> {
464        self.inner.scan_region(region_id, request)
465    }
466
467    /// Edit region's metadata by [RegionEdit] directly. Use with care.
468    /// Now we only allow adding files or removing files from region (the [RegionEdit] struct can only contain a non-empty "files_to_add" or "files_to_remove" field).
469    /// Other region editing intention will result in an "invalid request" error.
470    /// Also note that if a region is to be edited directly, we MUST not write data to it thereafter.
471    pub async fn edit_region(&self, region_id: RegionId, edit: RegionEdit) -> Result<()> {
472        let _timer = HANDLE_REQUEST_ELAPSED
473            .with_label_values(&["edit_region"])
474            .start_timer();
475
476        ensure!(
477            is_valid_region_edit(&edit),
478            InvalidRequestSnafu {
479                region_id,
480                reason: "invalid region edit"
481            }
482        );
483
484        let (tx, rx) = oneshot::channel();
485        let request = WorkerRequest::EditRegion(RegionEditRequest::new(region_id, edit, true, tx));
486        self.inner
487            .workers
488            .submit_to_worker(region_id, request)
489            .await?;
490        rx.await.context(RecvSnafu)?
491    }
492
493    /// Handles copy region from request.
494    ///
495    /// This method is only supported for internal use and is not exposed in the trait implementation.
496    pub async fn copy_region_from(
497        &self,
498        region_id: RegionId,
499        request: MitoCopyRegionFromRequest,
500    ) -> Result<MitoCopyRegionFromResponse> {
501        self.inner.copy_region_from(region_id, request).await
502    }
503
504    #[cfg(test)]
505    pub(crate) fn get_region(&self, id: RegionId) -> Option<crate::region::MitoRegionRef> {
506        self.find_region(id)
507    }
508
509    pub fn find_region(&self, region_id: RegionId) -> Option<MitoRegionRef> {
510        self.inner.workers.get_region(region_id)
511    }
512
513    /// Returns all regions.
514    pub fn regions(&self) -> Vec<MitoRegionRef> {
515        self.inner.workers.all_regions().collect()
516    }
517
518    fn encode_manifest_info_to_extensions(
519        region_id: &RegionId,
520        manifest_info: RegionManifestInfo,
521        extensions: &mut HashMap<String, Vec<u8>>,
522    ) -> Result<()> {
523        let region_manifest_info = vec![(*region_id, manifest_info)];
524
525        extensions.insert(
526            MANIFEST_INFO_EXTENSION_KEY.to_string(),
527            RegionManifestInfo::encode_list(&region_manifest_info).context(SerdeJsonSnafu)?,
528        );
529        info!(
530            "Added manifest info: {:?} to extensions, region_id: {:?}",
531            region_manifest_info, region_id
532        );
533        Ok(())
534    }
535
536    fn encode_column_metadatas_to_extensions(
537        region_id: &RegionId,
538        column_metadatas: Vec<ColumnMetadata>,
539        extensions: &mut HashMap<String, Vec<u8>>,
540    ) -> Result<()> {
541        extensions.insert(
542            TABLE_COLUMN_METADATA_EXTENSION_KEY.to_string(),
543            ColumnMetadata::encode_list(&column_metadatas).context(SerializeColumnMetadataSnafu)?,
544        );
545        info!(
546            "Added column metadatas: {:?} to extensions, region_id: {:?}",
547            column_metadatas, region_id
548        );
549        Ok(())
550    }
551
552    /// Find the current version's memtables and SSTs stats by region_id.
553    /// The stats must be collected in one place one time to ensure data consistency.
554    pub fn find_memtable_and_sst_stats(
555        &self,
556        region_id: RegionId,
557    ) -> Result<(Vec<MemtableStats>, Vec<FileMeta>)> {
558        let region = self
559            .find_region(region_id)
560            .context(RegionNotFoundSnafu { region_id })?;
561
562        let version = region.version();
563        let memtable_stats = version
564            .memtables
565            .list_memtables()
566            .iter()
567            .map(|x| x.stats())
568            .collect::<Vec<_>>();
569
570        let sst_stats = version
571            .ssts
572            .levels()
573            .iter()
574            .flat_map(|level| level.files().map(|x| x.meta_ref()))
575            .cloned()
576            .collect::<Vec<_>>();
577        Ok((memtable_stats, sst_stats))
578    }
579
580    /// Lists all SSTs from the manifest of all regions in the engine.
581    pub async fn all_ssts_from_manifest(&self) -> Vec<ManifestSstEntry> {
582        let node_id = self.inner.workers.file_ref_manager().node_id();
583        let regions = self.inner.workers.all_regions();
584
585        let mut results = Vec::new();
586        for region in regions {
587            let mut entries = region.manifest_sst_entries().await;
588            for e in &mut entries {
589                e.node_id = node_id;
590            }
591            results.extend(entries);
592        }
593
594        results
595    }
596
597    /// Lists metadata about all puffin index targets stored in the engine.
598    pub async fn all_index_metas(&self) -> Vec<PuffinIndexMetaEntry> {
599        let node_id = self.inner.workers.file_ref_manager().node_id();
600        let cache_manager = self.inner.workers.cache_manager();
601        let puffin_metadata_cache = cache_manager.puffin_metadata_cache().cloned();
602        let bloom_filter_cache = cache_manager.bloom_filter_index_cache().cloned();
603        let inverted_index_cache = cache_manager.inverted_index_cache().cloned();
604
605        let mut results = Vec::new();
606
607        for region in self.inner.workers.all_regions() {
608            let manifest_entries = region.manifest_sst_entries().await;
609            let access_layer = region.access_layer.clone();
610            let table_dir = access_layer.table_dir().to_string();
611            let path_type = access_layer.path_type();
612            let object_store = access_layer.object_store().clone();
613            let puffin_factory = access_layer.puffin_manager_factory().clone();
614            let path_factory = RegionFilePathFactory::new(table_dir, path_type);
615
616            let entry_futures = manifest_entries.into_iter().map(|entry| {
617                let object_store = object_store.clone();
618                let path_factory = path_factory.clone();
619                let puffin_factory = puffin_factory.clone();
620                let puffin_metadata_cache = puffin_metadata_cache.clone();
621                let bloom_filter_cache = bloom_filter_cache.clone();
622                let inverted_index_cache = inverted_index_cache.clone();
623
624                async move {
625                    let Some(index_file_path) = entry.index_file_path.as_ref() else {
626                        return Vec::new();
627                    };
628
629                    let index_version = entry.index_version;
630                    let file_id = match FileId::parse_str(&entry.file_id) {
631                        Ok(file_id) => file_id,
632                        Err(err) => {
633                            warn!(
634                                err;
635                                "Failed to parse puffin index file id, table_dir: {}, file_id: {}",
636                                entry.table_dir,
637                                entry.file_id
638                            );
639                            return Vec::new();
640                        }
641                    };
642                    // The index file path is derived from the physical file owner. After
643                    // repartition, `entry.region_id` is only the referring region.
644                    let region_index_id = RegionIndexId::new(
645                        RegionFileId::new(entry.origin_region_id, file_id),
646                        index_version,
647                    );
648                    let context = IndexEntryContext {
649                        table_dir: &entry.table_dir,
650                        index_file_path: index_file_path.as_str(),
651                        region_id: entry.region_id,
652                        table_id: entry.table_id,
653                        region_number: entry.region_number,
654                        region_group: entry.region_group,
655                        region_sequence: entry.region_sequence,
656                        file_id: &entry.file_id,
657                        index_file_size: entry.index_file_size,
658                        node_id,
659                    };
660
661                    let manager = puffin_factory
662                        .build(object_store, path_factory)
663                        .with_puffin_metadata_cache(puffin_metadata_cache);
664
665                    collect_index_entries_from_puffin(
666                        manager,
667                        region_index_id,
668                        context,
669                        bloom_filter_cache,
670                        inverted_index_cache,
671                    )
672                    .await
673                }
674            });
675
676            let mut meta_stream = stream::iter(entry_futures).buffer_unordered(8); // Parallelism is 8.
677            while let Some(mut metas) = meta_stream.next().await {
678                results.append(&mut metas);
679            }
680        }
681
682        results
683    }
684
685    /// Lists region info entries of all regions in the engine.
686    pub async fn all_region_infos(&self) -> Vec<RegionInfoEntry> {
687        let node_id = self.inner.workers.file_ref_manager().node_id();
688        self.inner
689            .workers
690            .all_regions()
691            .map(|region| region.region_info_entry(node_id))
692            .collect()
693    }
694
695    /// Lists all SSTs from the storage layer of all regions in the engine.
696    pub fn all_ssts_from_storage(&self) -> impl Stream<Item = Result<StorageSstEntry>> {
697        let node_id = self.inner.workers.file_ref_manager().node_id();
698        let regions = self.inner.workers.all_regions();
699
700        let mut layers_distinct_table_dirs = HashMap::new();
701        for region in regions {
702            let table_dir = region.access_layer.table_dir();
703            if !layers_distinct_table_dirs.contains_key(table_dir) {
704                layers_distinct_table_dirs
705                    .insert(table_dir.to_string(), region.access_layer.clone());
706            }
707        }
708
709        stream::iter(layers_distinct_table_dirs)
710            .map(|(_, access_layer)| access_layer.storage_sst_entries())
711            .flatten()
712            .map(move |entry| {
713                entry.map(move |mut entry| {
714                    entry.node_id = node_id;
715                    entry
716                })
717            })
718    }
719}
720
721/// Check whether the region edit is valid.
722///
723/// Only adding or removing files to region is considered valid now.
724fn is_valid_region_edit(edit: &RegionEdit) -> bool {
725    (!edit.files_to_add.is_empty() || !edit.files_to_remove.is_empty())
726        && matches!(
727            edit,
728            RegionEdit {
729                files_to_add: _,
730                files_to_remove: _,
731                timestamp_ms: _,
732                compaction_time_window: None,
733                flushed_entry_id: None,
734                flushed_sequence: None,
735                ..
736            }
737        )
738}
739
740/// Inner struct of [MitoEngine].
741struct EngineInner {
742    /// Region workers group.
743    workers: WorkerGroup,
744    /// Config of the engine.
745    config: Arc<MitoConfig>,
746    /// The Wal raw entry reader.
747    wal_raw_entry_reader: Arc<dyn RawEntryReader>,
748    /// Memory tracker for table scans.
749    scan_memory_tracker: QueryMemoryTracker,
750    /// Memory pool shared by internal scan operators across all queries.
751    scan_memory_pool: Arc<dyn MemoryPool>,
752    /// The region hook (if any) registered via plugins; exposed for the GC worker
753    /// to fire [`RegionHook::on_region_gc`].
754    region_hook: Option<RegionHookRef>,
755    #[cfg(feature = "enterprise")]
756    extension_range_provider_factory: Option<BoxedExtensionRangeProviderFactory>,
757}
758
759type TopicGroupedRegionOpenRequests = HashMap<String, Vec<(RegionId, RegionOpenRequest)>>;
760
761/// Returns requests([TopicGroupedRegionOpenRequests]) grouped by topic and remaining requests.
762fn prepare_batch_open_requests(
763    requests: Vec<(RegionId, RegionOpenRequest)>,
764) -> Result<(
765    TopicGroupedRegionOpenRequests,
766    Vec<(RegionId, RegionOpenRequest)>,
767)> {
768    let mut topic_to_regions: HashMap<String, Vec<(RegionId, RegionOpenRequest)>> = HashMap::new();
769    let mut remaining_regions: Vec<(RegionId, RegionOpenRequest)> = Vec::new();
770    for (region_id, request) in requests {
771        match parse_wal_options(&request.options).context(SerdeJsonSnafu)? {
772            WalOptions::Kafka(options) => {
773                topic_to_regions
774                    .entry(options.topic)
775                    .or_default()
776                    .push((region_id, request));
777            }
778            WalOptions::RaftEngine | WalOptions::Noop | WalOptions::ObjectStore(_) => {
779                remaining_regions.push((region_id, request));
780            }
781        }
782    }
783
784    Ok((topic_to_regions, remaining_regions))
785}
786
787impl EngineInner {
788    #[cfg(feature = "enterprise")]
789    #[must_use]
790    fn with_extension_range_provider_factory(
791        self,
792        extension_range_provider_factory: Option<BoxedExtensionRangeProviderFactory>,
793    ) -> Self {
794        Self {
795            extension_range_provider_factory,
796            ..self
797        }
798    }
799
800    /// Stop the inner engine.
801    async fn stop(&self) -> Result<()> {
802        self.workers.stop().await
803    }
804
805    fn find_region(&self, region_id: RegionId) -> Result<MitoRegionRef> {
806        self.workers
807            .get_region(region_id)
808            .context(RegionNotFoundSnafu { region_id })
809    }
810
811    /// Get metadata of a region.
812    ///
813    /// Returns error if the region doesn't exist.
814    fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef> {
815        // Reading a region doesn't need to go through the region worker thread.
816        let region = self.find_region(region_id)?;
817        Ok(region.metadata())
818    }
819
820    async fn open_topic_regions(
821        &self,
822        topic: String,
823        region_requests: Vec<(RegionId, RegionOpenRequest)>,
824    ) -> Result<Vec<(RegionId, Result<AffectedRows>)>> {
825        let now = Instant::now();
826        let region_ids = region_requests
827            .iter()
828            .map(|(region_id, _)| *region_id)
829            .collect::<Vec<_>>();
830        let provider = Provider::kafka_provider(topic);
831        let (distributor, entry_receivers) = build_wal_entry_distributor_and_receivers(
832            provider.clone(),
833            self.wal_raw_entry_reader.clone(),
834            &region_ids,
835            DEFAULT_ENTRY_RECEIVER_BUFFER_SIZE,
836        );
837
838        let mut responses = Vec::with_capacity(region_requests.len());
839        for ((region_id, request), entry_receiver) in
840            region_requests.into_iter().zip(entry_receivers)
841        {
842            let (request, receiver) =
843                WorkerRequest::new_open_region_request(region_id, request, Some(entry_receiver));
844            self.workers.submit_to_worker(region_id, request).await?;
845            responses.push(async move { receiver.await.context(RecvSnafu)? });
846        }
847
848        // Waits for entries distribution.
849        let distribution =
850            common_runtime::spawn_global(async move { distributor.distribute().await });
851        // Waits for worker returns.
852        let responses = join_all(responses).await;
853        distribution.await.context(JoinSnafu)??;
854
855        let num_failure = responses.iter().filter(|r| r.is_err()).count();
856        info!(
857            "Opened {} regions for topic '{}', failures: {}, elapsed: {:?}",
858            region_ids.len() - num_failure,
859            // Safety: provider is kafka provider.
860            provider.as_kafka_provider().unwrap(),
861            num_failure,
862            now.elapsed(),
863        );
864        Ok(region_ids.into_iter().zip(responses).collect())
865    }
866
867    async fn handle_batch_open_requests(
868        &self,
869        parallelism: usize,
870        requests: Vec<(RegionId, RegionOpenRequest)>,
871    ) -> Result<Vec<(RegionId, Result<AffectedRows>)>> {
872        let semaphore = Arc::new(Semaphore::new(parallelism));
873        let (topic_to_region_requests, remaining_region_requests) =
874            prepare_batch_open_requests(requests)?;
875        let mut responses =
876            Vec::with_capacity(topic_to_region_requests.len() + remaining_region_requests.len());
877
878        if !topic_to_region_requests.is_empty() {
879            let mut tasks = Vec::with_capacity(topic_to_region_requests.len());
880            for (topic, region_requests) in topic_to_region_requests {
881                let semaphore_moved = semaphore.clone();
882                tasks.push(async move {
883                    // Safety: semaphore must exist
884                    let _permit = semaphore_moved.acquire().await.unwrap();
885                    self.open_topic_regions(topic, region_requests).await
886                })
887            }
888            let r = try_join_all(tasks).await?;
889            responses.extend(r.into_iter().flatten());
890        }
891
892        if !remaining_region_requests.is_empty() {
893            let mut tasks = Vec::with_capacity(remaining_region_requests.len());
894            let mut region_ids = Vec::with_capacity(remaining_region_requests.len());
895            for (region_id, request) in remaining_region_requests {
896                let semaphore_moved = semaphore.clone();
897                region_ids.push(region_id);
898                tasks.push(async move {
899                    // Safety: semaphore must exist
900                    let _permit = semaphore_moved.acquire().await.unwrap();
901                    let (request, receiver) =
902                        WorkerRequest::new_open_region_request(region_id, request, None);
903
904                    self.workers.submit_to_worker(region_id, request).await?;
905
906                    receiver.await.context(RecvSnafu)?
907                })
908            }
909
910            let results = join_all(tasks).await;
911            responses.extend(region_ids.into_iter().zip(results));
912        }
913
914        Ok(responses)
915    }
916
917    async fn catchup_topic_regions(
918        &self,
919        provider: Provider,
920        region_requests: Vec<(RegionId, RegionCatchupRequest)>,
921    ) -> Result<Vec<(RegionId, Result<AffectedRows>)>> {
922        let now = Instant::now();
923        let region_ids = region_requests
924            .iter()
925            .map(|(region_id, _)| *region_id)
926            .collect::<Vec<_>>();
927        let (distributor, entry_receivers) = build_wal_entry_distributor_and_receivers(
928            provider.clone(),
929            self.wal_raw_entry_reader.clone(),
930            &region_ids,
931            DEFAULT_ENTRY_RECEIVER_BUFFER_SIZE,
932        );
933
934        let mut responses = Vec::with_capacity(region_requests.len());
935        for ((region_id, request), entry_receiver) in
936            region_requests.into_iter().zip(entry_receivers)
937        {
938            let (request, receiver) =
939                WorkerRequest::new_catchup_region_request(region_id, request, Some(entry_receiver));
940            self.workers.submit_to_worker(region_id, request).await?;
941            responses.push(async move { receiver.await.context(RecvSnafu)? });
942        }
943
944        // Wait for entries distribution.
945        let distribution =
946            common_runtime::spawn_global(async move { distributor.distribute().await });
947        // Wait for worker returns.
948        let responses = join_all(responses).await;
949        distribution.await.context(JoinSnafu)??;
950
951        let num_failure = responses.iter().filter(|r| r.is_err()).count();
952        info!(
953            "Caught up {} regions for topic '{}', failures: {}, elapsed: {:?}",
954            region_ids.len() - num_failure,
955            // Safety: provider is kafka provider.
956            provider.as_kafka_provider().unwrap(),
957            num_failure,
958            now.elapsed(),
959        );
960
961        Ok(region_ids.into_iter().zip(responses).collect())
962    }
963
964    async fn handle_batch_catchup_requests(
965        &self,
966        parallelism: usize,
967        requests: Vec<(RegionId, RegionCatchupRequest)>,
968    ) -> Result<Vec<(RegionId, Result<AffectedRows>)>> {
969        let mut responses = Vec::with_capacity(requests.len());
970        let mut topic_regions: HashMap<Arc<KafkaProvider>, Vec<_>> = HashMap::new();
971        let mut remaining_region_requests = vec![];
972
973        for (region_id, request) in requests {
974            match self.workers.get_region(region_id) {
975                Some(region) => match region.provider.as_kafka_provider() {
976                    Some(provider) => {
977                        topic_regions
978                            .entry(provider.clone())
979                            .or_default()
980                            .push((region_id, request));
981                    }
982                    None => {
983                        remaining_region_requests.push((region_id, request));
984                    }
985                },
986                None => responses.push((region_id, RegionNotFoundSnafu { region_id }.fail())),
987            }
988        }
989
990        let semaphore = Arc::new(Semaphore::new(parallelism));
991
992        if !topic_regions.is_empty() {
993            let mut tasks = Vec::with_capacity(topic_regions.len());
994            for (provider, region_requests) in topic_regions {
995                let semaphore_moved = semaphore.clone();
996                tasks.push(async move {
997                    // Safety: semaphore must exist
998                    let _permit = semaphore_moved.acquire().await.unwrap();
999                    self.catchup_topic_regions(Provider::Kafka(provider), region_requests)
1000                        .await
1001                })
1002            }
1003
1004            let r = try_join_all(tasks).await?;
1005            responses.extend(r.into_iter().flatten());
1006        }
1007
1008        if !remaining_region_requests.is_empty() {
1009            let mut tasks = Vec::with_capacity(remaining_region_requests.len());
1010            let mut region_ids = Vec::with_capacity(remaining_region_requests.len());
1011            for (region_id, request) in remaining_region_requests {
1012                let semaphore_moved = semaphore.clone();
1013                region_ids.push(region_id);
1014                tasks.push(async move {
1015                    // Safety: semaphore must exist
1016                    let _permit = semaphore_moved.acquire().await.unwrap();
1017                    let (request, receiver) =
1018                        WorkerRequest::new_catchup_region_request(region_id, request, None);
1019
1020                    self.workers.submit_to_worker(region_id, request).await?;
1021
1022                    receiver.await.context(RecvSnafu)?
1023                })
1024            }
1025
1026            let results = join_all(tasks).await;
1027            responses.extend(region_ids.into_iter().zip(results));
1028        }
1029
1030        Ok(responses)
1031    }
1032
1033    /// Handles [RegionRequest] and return its executed result.
1034    async fn handle_request(
1035        &self,
1036        region_id: RegionId,
1037        request: RegionRequest,
1038    ) -> Result<AffectedRows> {
1039        let region_metadata = self.get_metadata(region_id).ok();
1040        let (request, receiver) =
1041            WorkerRequest::try_from_region_request(region_id, request, region_metadata)?;
1042        self.workers.submit_to_worker(region_id, request).await?;
1043
1044        receiver.await.context(RecvSnafu)?
1045    }
1046
1047    /// Returns the sequence of latest committed data.
1048    fn get_committed_sequence(&self, region_id: RegionId) -> Result<SequenceNumber> {
1049        // Reading a region doesn't need to go through the region worker thread.
1050        self.find_region(region_id)
1051            .map(|r| r.find_committed_sequence())
1052    }
1053
1054    /// Handles the scan `request` and returns a [ScanRegion].
1055    #[tracing::instrument(skip_all, fields(region_id = %region_id))]
1056    fn scan_region(&self, region_id: RegionId, mut request: ScanRequest) -> Result<ScanRegion> {
1057        let query_start = Instant::now();
1058        // Reading a region doesn't need to go through the region worker thread.
1059        let region = self.find_region(region_id)?;
1060        // Pin the index before the data snapshot: compaction and index publication
1061        // could otherwise give us a newer index that omits series still visible
1062        // in the query's older SST snapshot.
1063        let series_index = region.series_index_store.as_ref().map(|store| {
1064            crate::series_index::SeriesIndexReadContext {
1065                store: store.clone(),
1066                version: region.series_index_version(),
1067            }
1068        });
1069        let version_data = region.version_control.current();
1070        let version = version_data.version;
1071
1072        if request.snapshot_on_scan && request.memtable_max_sequence.is_none() {
1073            request.memtable_max_sequence = Some(version_data.committed_sequence);
1074        }
1075
1076        // Select the files and decide the exact capability from the same version
1077        // snapshot. Keep them together so the reader cannot recompute either
1078        // side from a different file set.
1079        let exact_selection = if request.exact_sequence_range {
1080            match exact_sequence_range(&request, &version) {
1081                Ok((files, sequence_range)) => Some((files, sequence_range)),
1082                Err(err) => {
1083                    debug!(
1084                        "Scan region {} exact sequence range denied: min={:?}, max={:?}, denial_reason=foreign_file_missing_barrier",
1085                        region_id, request.memtable_min_sequence, request.memtable_max_sequence,
1086                    );
1087                    return Err(err);
1088                }
1089            }
1090        } else {
1091            None
1092        };
1093        let exact_sequence_range = exact_selection
1094            .as_ref()
1095            .and_then(|(_, sequence_range)| *sequence_range);
1096
1097        // An extension range provider may contribute ranges on a follower
1098        // region, and extension streams are returned without a row-level
1099        // sequence filter (`scan_flat_extension_range`), so exactness cannot
1100        // be proven when one is attached. Treat the capability as missing so
1101        // the request fails closed below instead of emitting out-of-range
1102        // rows. See the injection point in `ScanRegion::scan_input`.
1103        #[cfg(feature = "enterprise")]
1104        let extension_provider_blocks_exact = region.is_follower()
1105            && self.extension_range_provider_factory.is_some()
1106            && exact_sequence_range.is_some();
1107        #[cfg(not(feature = "enterprise"))]
1108        let extension_provider_blocks_exact = false;
1109        let exact_sequence_range = if extension_provider_blocks_exact {
1110            None
1111        } else {
1112            exact_sequence_range
1113        };
1114        let exact_selection = exact_selection.map(|(files, _)| (files, exact_sequence_range));
1115        let exact_denial_reason = if !request.exact_sequence_range {
1116            "not_requested"
1117        } else if request.skip_sst_files {
1118            "sst_files_skipped"
1119        } else if request.memtable_min_sequence.is_none() {
1120            "missing_lower_bound"
1121        } else if request.memtable_max_sequence.is_none() {
1122            "missing_upper_bound"
1123        } else if !version.options.preserve_row_sequence {
1124            "preserve_row_sequence_disabled"
1125        } else if extension_provider_blocks_exact {
1126            "extension_provider"
1127        } else if exact_sequence_range.is_none() {
1128            "selected_file_barrier_not_admitted"
1129        } else {
1130            "none"
1131        };
1132
1133        debug!(
1134            "Scan region {} exact sequence range: requested={}, min={:?}, max={:?}, committed={}, flushed={}, available={}, selected_files={}, denial_reason={}",
1135            region_id,
1136            request.exact_sequence_range,
1137            request.memtable_min_sequence,
1138            request.memtable_max_sequence,
1139            version_data.committed_sequence,
1140            version.flushed_sequence,
1141            exact_sequence_range.is_some(),
1142            exact_selection.as_ref().map_or(0, |(files, _)| files.len()),
1143            exact_denial_reason,
1144        );
1145        validate_sequence_fences(
1146            &request,
1147            &version,
1148            region_id,
1149            exact_sequence_range.is_some(),
1150            extension_provider_blocks_exact,
1151        )?;
1152
1153        // Get cache.
1154        let cache_manager = self.workers.cache_manager();
1155
1156        let scan_region = ScanRegion::new(
1157            version,
1158            region.access_layer.clone(),
1159            request,
1160            CacheStrategy::EnableAll(cache_manager),
1161        )
1162        .with_series_index(series_index)
1163        .with_ignore_range_index(!self.config.experimental_enable_range_index)
1164        .with_query_stat_counters(region.region_stats.query_stat_counters())
1165        .with_max_concurrent_scan_files(self.config.max_concurrent_scan_files)
1166        .with_scan_memory_pool(self.scan_memory_pool.clone())
1167        .with_experimental_series_scan_v2(self.config.experimental_series_scan_v2)
1168        .with_ignore_inverted_index(self.config.inverted_index.apply_on_query.disabled())
1169        .with_ignore_fulltext_index(self.config.fulltext_index.apply_on_query.disabled())
1170        .with_ignore_bloom_filter(self.config.bloom_filter_index.apply_on_query.disabled())
1171        .with_start_time(query_start);
1172        let scan_region = if let Some(selection) = exact_selection {
1173            scan_region.with_exact_selection(selection)
1174        } else {
1175            scan_region
1176        };
1177
1178        #[cfg(feature = "enterprise")]
1179        let scan_region = self.maybe_fill_extension_range_provider(scan_region, region);
1180
1181        Ok(scan_region)
1182    }
1183
1184    #[cfg(feature = "enterprise")]
1185    fn maybe_fill_extension_range_provider(
1186        &self,
1187        mut scan_region: ScanRegion,
1188        region: MitoRegionRef,
1189    ) -> ScanRegion {
1190        if region.is_follower()
1191            && let Some(factory) = self.extension_range_provider_factory.as_ref()
1192        {
1193            scan_region
1194                .set_extension_range_provider(factory.create_extension_range_provider(region));
1195        }
1196        scan_region
1197    }
1198
1199    /// Converts the [`RegionRole`].
1200    fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<()> {
1201        let region = self.find_region(region_id)?;
1202        region.set_role(role);
1203        Ok(())
1204    }
1205
1206    /// Sets read-only for a region and ensures no more writes in the region after it returns.
1207    async fn set_region_role_state_gracefully(
1208        &self,
1209        region_id: RegionId,
1210        region_role_state: SettableRegionRoleState,
1211    ) -> Result<SetRegionRoleStateResponse> {
1212        // Notes: It acquires the mutable ownership to ensure no other threads,
1213        // Therefore, we submit it to the worker.
1214        let (request, receiver) =
1215            WorkerRequest::new_set_readonly_gracefully(region_id, region_role_state);
1216        self.workers.submit_to_worker(region_id, request).await?;
1217
1218        receiver.await.context(RecvSnafu)
1219    }
1220
1221    async fn sync_region(
1222        &self,
1223        region_id: RegionId,
1224        manifest_info: RegionManifestInfo,
1225    ) -> Result<(ManifestVersion, bool)> {
1226        ensure!(manifest_info.is_mito(), MitoManifestInfoSnafu);
1227        let manifest_version = manifest_info.data_manifest_version();
1228        let (request, receiver) =
1229            WorkerRequest::new_sync_region_request(region_id, manifest_version);
1230        self.workers.submit_to_worker(region_id, request).await?;
1231
1232        receiver.await.context(RecvSnafu)?
1233    }
1234
1235    async fn remap_manifests(
1236        &self,
1237        request: RemapManifestsRequest,
1238    ) -> Result<RemapManifestsResponse> {
1239        let region_id = request.region_id;
1240        let (request, receiver) = WorkerRequest::try_from_remap_manifests_request(request)?;
1241        self.workers.submit_to_worker(region_id, request).await?;
1242        let manifest_paths = receiver.await.context(RecvSnafu)??;
1243        Ok(RemapManifestsResponse { manifest_paths })
1244    }
1245
1246    async fn copy_region_from(
1247        &self,
1248        region_id: RegionId,
1249        request: MitoCopyRegionFromRequest,
1250    ) -> Result<MitoCopyRegionFromResponse> {
1251        let (request, receiver) =
1252            WorkerRequest::try_from_copy_region_from_request(region_id, request)?;
1253        self.workers.submit_to_worker(region_id, request).await?;
1254        let response = receiver.await.context(RecvSnafu)??;
1255        Ok(response)
1256    }
1257
1258    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
1259        self.workers
1260            .get_region(region_id)
1261            .map(|region| region.region_role())
1262    }
1263}
1264
1265fn validate_sequence_fences(
1266    request: &ScanRequest,
1267    version: &crate::region::version::Version,
1268    region_id: RegionId,
1269    exact_sequence_range_available: bool,
1270    extension_provider_blocks_exact: bool,
1271) -> Result<()> {
1272    if request.exact_sequence_range {
1273        // Exact `sequence_range` mode: when the capability is available the
1274        // requested (C, H] delta is enforced row-level on memtables and every
1275        // SST, so the lower/upper fences can be relaxed. When it is not, we
1276        // must fail with a structured stale/unsupported error so Flow falls
1277        // back instead of silently reading an approximate superset.
1278        if !exact_sequence_range_available {
1279            if let Some(given_seq) = request.memtable_min_sequence {
1280                let min_readable_seq = version.flushed_sequence;
1281                ensure!(
1282                    given_seq >= min_readable_seq,
1283                    IncrementalQueryStaleSnafu {
1284                        region_id,
1285                        given_seq,
1286                        min_readable_seq,
1287                    }
1288                );
1289            }
1290
1291            if let Some(given_seq) = request.memtable_max_sequence {
1292                let min_enforceable_seq = version.flushed_sequence;
1293                ensure!(
1294                    given_seq >= min_enforceable_seq,
1295                    SnapshotFenceStaleSnafu {
1296                        region_id,
1297                        given_seq,
1298                        min_enforceable_seq,
1299                    }
1300                );
1301            }
1302
1303            // Both bounds are enforceable against the flushed frontier, yet the
1304            // region cannot serve an exact row-level delta (preserve option off,
1305            // a file without the preserved-sequence marker, or a follower with
1306            // an extension range provider). Main semantics would silently
1307            // return rows outside (C, H], so fail instead.
1308            return SequenceRangeUnsupportedSnafu {
1309                region_id,
1310                min_seq: request.memtable_min_sequence.unwrap_or_default(),
1311                max_seq: request.memtable_max_sequence.unwrap_or_default(),
1312                reason: sequence_range_unsupported_reason(version, extension_provider_blocks_exact),
1313            }
1314            .fail();
1315        }
1316    } else {
1317        // Non-exact mode (including historical `memtable_only` scans): keep
1318        // main's fences exactly as-is. The preserve option never relaxes them
1319        // because `exact_sequence_range` is the only explicit exact intent.
1320        if let Some(given_seq) = request.memtable_min_sequence {
1321            let min_readable_seq = version.flushed_sequence;
1322            ensure!(
1323                given_seq >= min_readable_seq,
1324                IncrementalQueryStaleSnafu {
1325                    region_id,
1326                    given_seq,
1327                    min_readable_seq,
1328                }
1329            );
1330        }
1331
1332        if let Some(given_seq) = request.memtable_max_sequence
1333            && !request.skip_sst_files
1334        {
1335            // Explicit snapshot fences that include SST reads are enforceable
1336            // only while the requested upper bound is not older than the
1337            // region's flushed frontier. If H has already been flushed into SST,
1338            // mito cannot apply a memtable-only sequence upper bound to that
1339            // SST scan, so fail and let Flow rebind the fenced repair instead
1340            // of reading rows beyond H.
1341            let min_enforceable_seq = version.flushed_sequence;
1342            ensure!(
1343                given_seq >= min_enforceable_seq,
1344                SnapshotFenceStaleSnafu {
1345                    region_id,
1346                    given_seq,
1347                    min_enforceable_seq,
1348                }
1349            );
1350        }
1351    }
1352
1353    Ok(())
1354}
1355
1356fn sequence_range_unsupported_reason(
1357    version: &crate::region::version::Version,
1358    extension_provider_blocks_exact: bool,
1359) -> String {
1360    if extension_provider_blocks_exact {
1361        "region is a follower with an extension range provider, whose streams cannot be filtered by sequence".to_string()
1362    } else if version.options.preserve_row_sequence {
1363        "region has files without preserved per-row sequences".to_string()
1364    } else {
1365        "region does not preserve per-row sequences (preserve_row_sequence is off)".to_string()
1366    }
1367}
1368
1369fn map_batch_responses(responses: Vec<(RegionId, Result<AffectedRows>)>) -> BatchResponses {
1370    responses
1371        .into_iter()
1372        .map(|(region_id, response)| {
1373            (
1374                region_id,
1375                response.map(RegionResponse::new).map_err(BoxedError::new),
1376            )
1377        })
1378        .collect()
1379}
1380
1381#[async_trait]
1382impl RegionEngine for MitoEngine {
1383    fn name(&self) -> &str {
1384        MITO_ENGINE_NAME
1385    }
1386
1387    #[tracing::instrument(skip_all)]
1388    async fn handle_batch_open_requests(
1389        &self,
1390        parallelism: usize,
1391        requests: Vec<(RegionId, RegionOpenRequest)>,
1392    ) -> Result<BatchResponses, BoxedError> {
1393        // TODO(weny): add metrics.
1394        self.inner
1395            .handle_batch_open_requests(parallelism, requests)
1396            .await
1397            .map(map_batch_responses)
1398            .map_err(BoxedError::new)
1399    }
1400
1401    #[tracing::instrument(skip_all)]
1402    async fn handle_batch_catchup_requests(
1403        &self,
1404        parallelism: usize,
1405        requests: Vec<(RegionId, RegionCatchupRequest)>,
1406    ) -> Result<BatchResponses, BoxedError> {
1407        self.inner
1408            .handle_batch_catchup_requests(parallelism, requests)
1409            .await
1410            .map(map_batch_responses)
1411            .map_err(BoxedError::new)
1412    }
1413
1414    #[tracing::instrument(skip_all)]
1415    async fn handle_request(
1416        &self,
1417        region_id: RegionId,
1418        request: RegionRequest,
1419    ) -> Result<RegionResponse, BoxedError> {
1420        let _timer = HANDLE_REQUEST_ELAPSED
1421            .with_label_values(&[request.request_type()])
1422            .start_timer();
1423
1424        let is_alter = matches!(request, RegionRequest::Alter(_));
1425        let is_create = matches!(request, RegionRequest::Create(_));
1426        let mut response = self
1427            .inner
1428            .handle_request(region_id, request)
1429            .await
1430            .map(RegionResponse::new)
1431            .map_err(BoxedError::new)?;
1432
1433        if is_alter {
1434            self.handle_alter_response(region_id, &mut response)
1435                .map_err(BoxedError::new)?;
1436        } else if is_create {
1437            self.handle_create_response(region_id, &mut response)
1438                .map_err(BoxedError::new)?;
1439        }
1440
1441        Ok(response)
1442    }
1443
1444    #[tracing::instrument(skip_all)]
1445    async fn handle_query(
1446        &self,
1447        region_id: RegionId,
1448        request: ScanRequest,
1449    ) -> Result<RegionScannerRef, BoxedError> {
1450        self.scan_region(region_id, request)
1451            .map_err(BoxedError::new)?
1452            .region_scanner()
1453            .await
1454            .map_err(BoxedError::new)
1455    }
1456
1457    fn query_memory_tracker(&self) -> Option<QueryMemoryTracker> {
1458        Some(self.inner.scan_memory_tracker.clone())
1459    }
1460
1461    async fn get_committed_sequence(
1462        &self,
1463        region_id: RegionId,
1464    ) -> Result<SequenceNumber, BoxedError> {
1465        self.inner
1466            .get_committed_sequence(region_id)
1467            .map_err(BoxedError::new)
1468    }
1469
1470    /// Retrieve region's metadata.
1471    async fn get_metadata(
1472        &self,
1473        region_id: RegionId,
1474    ) -> std::result::Result<RegionMetadataRef, BoxedError> {
1475        self.inner.get_metadata(region_id).map_err(BoxedError::new)
1476    }
1477
1478    /// Stop the engine.
1479    ///
1480    /// Stopping the engine doesn't stop the underlying log store as other components might
1481    /// still use it. (When no other components are referencing the log store, it will
1482    /// automatically shutdown.)
1483    async fn stop(&self) -> std::result::Result<(), BoxedError> {
1484        self.inner.stop().await.map_err(BoxedError::new)
1485    }
1486
1487    fn region_statistic(&self, region_id: RegionId) -> Option<RegionStatistic> {
1488        self.get_region_statistic(region_id)
1489    }
1490
1491    fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
1492        self.inner
1493            .set_region_role(region_id, role)
1494            .map_err(BoxedError::new)
1495    }
1496
1497    async fn set_region_role_state_gracefully(
1498        &self,
1499        region_id: RegionId,
1500        region_role_state: SettableRegionRoleState,
1501    ) -> Result<SetRegionRoleStateResponse, BoxedError> {
1502        let _timer = HANDLE_REQUEST_ELAPSED
1503            .with_label_values(&["set_region_role_state_gracefully"])
1504            .start_timer();
1505
1506        self.inner
1507            .set_region_role_state_gracefully(region_id, region_role_state)
1508            .await
1509            .map_err(BoxedError::new)
1510    }
1511
1512    async fn sync_region(
1513        &self,
1514        region_id: RegionId,
1515        request: SyncRegionFromRequest,
1516    ) -> Result<SyncRegionFromResponse, BoxedError> {
1517        let manifest_info = request
1518            .into_region_manifest_info()
1519            .context(UnexpectedSnafu {
1520                err_msg: "Expected a manifest info request",
1521            })
1522            .map_err(BoxedError::new)?;
1523        let (_, synced) = self
1524            .inner
1525            .sync_region(region_id, manifest_info)
1526            .await
1527            .map_err(BoxedError::new)?;
1528
1529        Ok(SyncRegionFromResponse::Mito { synced })
1530    }
1531
1532    async fn remap_manifests(
1533        &self,
1534        request: RemapManifestsRequest,
1535    ) -> Result<RemapManifestsResponse, BoxedError> {
1536        self.inner
1537            .remap_manifests(request)
1538            .await
1539            .map_err(BoxedError::new)
1540    }
1541
1542    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
1543        self.inner.role(region_id)
1544    }
1545
1546    fn as_any(&self) -> &dyn Any {
1547        self
1548    }
1549}
1550
1551impl MitoEngine {
1552    fn handle_alter_response(
1553        &self,
1554        region_id: RegionId,
1555        response: &mut RegionResponse,
1556    ) -> Result<()> {
1557        if let Some(statistic) = self.region_statistic(region_id) {
1558            Self::encode_manifest_info_to_extensions(
1559                &region_id,
1560                statistic.manifest,
1561                &mut response.extensions,
1562            )?;
1563        }
1564        let column_metadatas = self
1565            .inner
1566            .find_region(region_id)
1567            .ok()
1568            .map(|r| r.metadata().column_metadatas.clone());
1569        if let Some(column_metadatas) = column_metadatas {
1570            Self::encode_column_metadatas_to_extensions(
1571                &region_id,
1572                column_metadatas,
1573                &mut response.extensions,
1574            )?;
1575        }
1576        Ok(())
1577    }
1578
1579    fn handle_create_response(
1580        &self,
1581        region_id: RegionId,
1582        response: &mut RegionResponse,
1583    ) -> Result<()> {
1584        let column_metadatas = self
1585            .inner
1586            .find_region(region_id)
1587            .ok()
1588            .map(|r| r.metadata().column_metadatas.clone());
1589        if let Some(column_metadatas) = column_metadatas {
1590            Self::encode_column_metadatas_to_extensions(
1591                &region_id,
1592                column_metadatas,
1593                &mut response.extensions,
1594            )?;
1595        }
1596        Ok(())
1597    }
1598}
1599
1600// Tests methods.
1601#[cfg(any(test, feature = "test"))]
1602#[allow(clippy::too_many_arguments)]
1603impl MitoEngine {
1604    /// Returns a new [MitoEngine] for tests.
1605    pub async fn new_for_test<S: LogStore>(
1606        data_home: &str,
1607        mut config: MitoConfig,
1608        log_store: Arc<S>,
1609        object_store_manager: ObjectStoreManagerRef,
1610        write_buffer_manager: Option<crate::flush::WriteBufferManagerRef>,
1611        listener: Option<crate::engine::listener::EventListenerRef>,
1612        time_provider: crate::time_provider::TimeProviderRef,
1613        schema_metadata_manager: SchemaMetadataManagerRef,
1614        file_ref_manager: FileReferenceManagerRef,
1615        partition_expr_fetcher: PartitionExprFetcherRef,
1616    ) -> Result<MitoEngine> {
1617        config.sanitize(data_home)?;
1618
1619        let config = Arc::new(config);
1620        let wal_raw_entry_reader = Arc::new(LogStoreRawEntryReader::new(log_store.clone()));
1621        let total_memory = get_total_memory_bytes().max(0) as u64;
1622        let scan_memory_limit = config.scan_memory_limit.resolve(total_memory) as usize;
1623        let scan_memory_pool = new_scan_memory_pool(scan_memory_limit);
1624        let scan_memory_tracker =
1625            QueryMemoryTracker::builder(scan_memory_limit, config.scan_memory_on_exhausted)
1626                .on_update(|usage| {
1627                    SCAN_MEMORY_USAGE_BYTES.set(usage as i64);
1628                })
1629                .on_exhausted(|| {
1630                    SCAN_MEMORY_EXHAUSTED_TOTAL.inc();
1631                })
1632                .on_reject(|| {
1633                    SCAN_REQUESTS_REJECTED_TOTAL.inc();
1634                })
1635                .build();
1636        Ok(MitoEngine {
1637            inner: Arc::new(EngineInner {
1638                workers: WorkerGroup::start_for_test(
1639                    data_home,
1640                    config.clone(),
1641                    log_store,
1642                    object_store_manager,
1643                    write_buffer_manager,
1644                    listener,
1645                    schema_metadata_manager,
1646                    file_ref_manager,
1647                    time_provider,
1648                    partition_expr_fetcher,
1649                )
1650                .await?,
1651                config,
1652                wal_raw_entry_reader,
1653                scan_memory_tracker,
1654                scan_memory_pool,
1655                region_hook: None,
1656                #[cfg(feature = "enterprise")]
1657                extension_range_provider_factory: None,
1658            }),
1659        })
1660    }
1661
1662    /// Returns the purge scheduler.
1663    pub fn purge_scheduler(&self) -> &crate::schedule::scheduler::SchedulerRef {
1664        self.inner.workers.purge_scheduler()
1665    }
1666}
1667
1668fn new_scan_memory_pool(scan_memory_limit: usize) -> Arc<dyn MemoryPool> {
1669    if scan_memory_limit == 0 {
1670        Arc::new(UnboundedMemoryPool::default())
1671    } else {
1672        Arc::new(GreedyMemoryPool::new(scan_memory_limit))
1673    }
1674}
1675
1676#[cfg(test)]
1677mod tests {
1678    use std::time::Duration;
1679
1680    use datafusion::execution::memory_pool::MemoryConsumer;
1681
1682    use super::*;
1683    use crate::sst::file::FileMeta;
1684
1685    #[test]
1686    fn test_is_valid_region_edit() {
1687        // Valid: has only "files_to_add"
1688        let edit = RegionEdit {
1689            files_to_add: vec![FileMeta::default()],
1690            files_to_remove: vec![],
1691            timestamp_ms: None,
1692            compaction_time_window: None,
1693            flushed_entry_id: None,
1694            flushed_sequence: None,
1695            committed_sequence: None,
1696        };
1697        assert!(is_valid_region_edit(&edit));
1698
1699        // Invalid: "files_to_add" and "files_to_remove" are both empty
1700        let edit = RegionEdit {
1701            files_to_add: vec![],
1702            files_to_remove: vec![],
1703            timestamp_ms: None,
1704            compaction_time_window: None,
1705            flushed_entry_id: None,
1706            flushed_sequence: None,
1707            committed_sequence: None,
1708        };
1709        assert!(!is_valid_region_edit(&edit));
1710
1711        // Valid: has only "files_to_remove"
1712        let edit = RegionEdit {
1713            files_to_add: vec![],
1714            files_to_remove: vec![FileMeta::default()],
1715            timestamp_ms: None,
1716            compaction_time_window: None,
1717            flushed_entry_id: None,
1718            flushed_sequence: None,
1719            committed_sequence: None,
1720        };
1721        assert!(is_valid_region_edit(&edit));
1722
1723        // Valid: both "files_to_add" and "files_to_remove" are not empty
1724        let edit = RegionEdit {
1725            files_to_add: vec![FileMeta::default()],
1726            files_to_remove: vec![FileMeta::default()],
1727            timestamp_ms: None,
1728            compaction_time_window: None,
1729            flushed_entry_id: None,
1730            flushed_sequence: None,
1731            committed_sequence: None,
1732        };
1733        assert!(is_valid_region_edit(&edit));
1734
1735        // Invalid: other fields are not all "None"s
1736        let edit = RegionEdit {
1737            files_to_add: vec![FileMeta::default()],
1738            files_to_remove: vec![],
1739            timestamp_ms: None,
1740            compaction_time_window: Some(Duration::from_secs(1)),
1741            flushed_entry_id: None,
1742            flushed_sequence: None,
1743            committed_sequence: None,
1744        };
1745        assert!(!is_valid_region_edit(&edit));
1746        let edit = RegionEdit {
1747            files_to_add: vec![FileMeta::default()],
1748            files_to_remove: vec![],
1749            timestamp_ms: None,
1750            compaction_time_window: None,
1751            flushed_entry_id: Some(1),
1752            flushed_sequence: None,
1753            committed_sequence: None,
1754        };
1755        assert!(!is_valid_region_edit(&edit));
1756        let edit = RegionEdit {
1757            files_to_add: vec![FileMeta::default()],
1758            files_to_remove: vec![],
1759            timestamp_ms: None,
1760            compaction_time_window: None,
1761            flushed_entry_id: None,
1762            flushed_sequence: Some(1),
1763            committed_sequence: None,
1764        };
1765        assert!(!is_valid_region_edit(&edit));
1766    }
1767
1768    #[test]
1769    fn test_scan_memory_pool_is_shared_across_consumers() {
1770        let pool = new_scan_memory_pool(100);
1771        let cloned_pool = pool.clone();
1772        let first = MemoryConsumer::new("first-scan").register(&pool);
1773        let second = MemoryConsumer::new("second-scan").register(&cloned_pool);
1774
1775        first.try_grow(60).unwrap();
1776        assert!(second.try_grow(50).is_err());
1777        second.try_grow(40).unwrap();
1778        assert_eq!(100, pool.reserved());
1779    }
1780}