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