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