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