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