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