1use std::any::TypeId;
18use std::collections::HashMap;
19use std::sync::atomic::{AtomicI64, AtomicU64};
20use std::sync::{Arc, LazyLock};
21use std::time::Instant;
22
23use arrow_schema::extension::ExtensionType;
24use common_telemetry::{debug, error, info, warn};
25use common_wal::options::WalOptions;
26use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
27use futures::StreamExt;
28use futures::future::BoxFuture;
29use log_store::kafka::log_store::KafkaLogStore;
30use log_store::noop::log_store::NoopLogStore;
31use log_store::raft_engine::log_store::RaftEngineLogStore;
32use object_store::ObjectStore;
33use object_store::manager::ObjectStoreManagerRef;
34use object_store::util::{is_object_storage, normalize_dir};
35use parquet::file::metadata::PageIndexPolicy;
36use snafu::{OptionExt, ResultExt, ensure};
37use store_api::codec::PrimaryKeyEncoding;
38use store_api::logstore::LogStore;
39use store_api::logstore::provider::Provider;
40use store_api::metadata::{
41 ColumnMetadata, RegionMetadata, RegionMetadataBuilder, RegionMetadataRef,
42};
43use store_api::region_engine::RegionRole;
44use store_api::region_request::{PathType, RegionRequirements};
45use store_api::storage::{ColumnId, RegionId};
46use tokio::sync::Semaphore;
47
48use crate::access_layer::AccessLayer;
49use crate::cache::file_cache::{FileCache, FileType, IndexKey};
50use crate::cache::{CacheManagerRef, SstMetaPreparation, prepare_sst_meta};
51use crate::config::MitoConfig;
52use crate::engine::region_hook::RegionHookRef;
53use crate::error;
54use crate::error::{
55 DataTypeMismatchSnafu, EmptyRegionDirSnafu, InvalidMetadataSnafu, InvalidRegionOptionsSnafu,
56 ObjectStoreNotFoundSnafu, RegionCorruptedSnafu, Result, StaleLogEntrySnafu,
57};
58use crate::lease::NodeLease;
59use crate::manifest::action::RegionManifest;
60use crate::manifest::manager::{RegionManifestManager, RegionManifestOptions};
61use crate::memtable::bulk::part::BulkPart;
62use crate::memtable::time_partition::TimePartitions;
63use crate::memtable::{MemtableBuilderProvider, ensure_json2_uses_flat_format};
64use crate::metrics::{CACHE_FILL_DOWNLOADED_FILES, CACHE_FILL_PENDING_FILES};
65use crate::read::series_candidate::is_sparse_metric_metadata;
66use crate::region::options::RegionOptions;
67use crate::region::version::{Version, VersionBuilder, VersionControl, VersionControlRef};
68use crate::region::{
69 ManifestContext, ManifestStats, MitoRegion, MitoRegionRef, RegionLeaderState, RegionRoleState,
70 RegionStats,
71};
72use crate::region_write_ctx::{RegionWriteCtx, WriteSource};
73use crate::request::OptionOutputTx;
74use crate::schedule::scheduler::SchedulerRef;
75use crate::series_index::{IndexFilePurger, load_version_control};
76use crate::sst::FormatType;
77use crate::sst::file::{FileHandle, RegionFileId, RegionIndexId};
78use crate::sst::file_purger::{FilePurgerRef, create_file_purger};
79use crate::sst::file_ref::FileReferenceManagerRef;
80use crate::sst::index::intermediate::IntermediateManager;
81use crate::sst::index::puffin_manager::PuffinManagerFactory;
82use crate::sst::location::{self, region_dir_from_table_dir};
83use crate::sst::parquet::metadata::{MetadataLoader, extract_primary_key_range};
84use crate::sst::parquet::reader::MetadataCacheMetrics;
85use crate::sst::range_index::RangeIndexDeleter;
86use crate::time_provider::TimeProviderRef;
87use crate::wal::entry_reader::WalEntryReader;
88use crate::wal::{EntryId, Wal};
89
90const PARQUET_META_PRELOAD_CONCURRENCY: usize = 8;
91
92static PARQUET_META_PRELOAD_SEMAPHORE: LazyLock<Semaphore> =
93 LazyLock::new(|| Semaphore::new(PARQUET_META_PRELOAD_CONCURRENCY));
94
95fn initial_pruned_entry_id(wal_options: &WalOptions) -> EntryId {
96 match wal_options {
97 WalOptions::Kafka(options) => options.initial_pruned_entry_id.unwrap_or(0),
98 WalOptions::RaftEngine | WalOptions::Noop | WalOptions::ObjectStore(_) => 0,
99 }
100}
101
102fn maybe_upgrade_json2_layout(metadata: RegionMetadataRef) -> Result<RegionMetadataRef> {
103 let mut upgrades = Vec::new();
104 for (index, column) in metadata.column_metadatas.iter().enumerate() {
105 if !column.column_schema.data_type.is_json2() {
106 continue;
107 }
108 let Some(extension) = column
109 .column_schema
110 .extension_type::<Json2ExtensionType>()
111 .context(DataTypeMismatchSnafu)?
112 else {
113 continue;
114 };
115 if extension.metadata().is_version_2() {
116 continue;
117 }
118 upgrades.push((index, extension.metadata().json_settings().clone()));
119 }
120
121 if upgrades.is_empty() {
122 return Ok(metadata);
123 }
124
125 let mut upgraded = metadata.as_ref().clone();
126 for (index, settings) in upgrades {
127 let extension = Json2ExtensionType::new(Arc::new(JsonMetadata::new(settings)));
128 upgraded.column_metadatas[index]
129 .column_schema
130 .with_extension_type(&extension);
131 }
132
133 let builder = RegionMetadataBuilder::from_existing(upgraded);
134 Ok(Arc::new(builder.build().context(InvalidMetadataSnafu)?))
135}
136
137#[async_trait::async_trait]
143pub trait PartitionExprFetcher {
144 async fn fetch_expr(&self, region_id: RegionId) -> Option<String>;
145}
146
147pub type PartitionExprFetcherRef = Arc<dyn PartitionExprFetcher + Send + Sync>;
148
149pub(crate) struct RegionOpener {
151 region_id: RegionId,
152 metadata_builder: Option<RegionMetadataBuilder>,
153 memtable_builder_provider: MemtableBuilderProvider,
154 object_store_manager: ObjectStoreManagerRef,
155 table_dir: String,
156 path_type: PathType,
157 purge_scheduler: SchedulerRef,
158 options: Option<RegionOptions>,
159 cache_manager: Option<CacheManagerRef>,
160 skip_wal_replay: bool,
161 node_lease: Arc<NodeLease>,
162 puffin_manager_factory: PuffinManagerFactory,
163 intermediate_manager: IntermediateManager,
164 time_provider: TimeProviderRef,
165 stats: ManifestStats,
166 wal_entry_reader: Option<Box<dyn WalEntryReader>>,
167 replay_checkpoint: Option<u64>,
168 file_ref_manager: FileReferenceManagerRef,
169 partition_expr_fetcher: PartitionExprFetcherRef,
170 hook: Option<RegionHookRef>,
171 series_index_store: Option<ObjectStore>,
172 series_index_purger: Option<IndexFilePurger>,
173}
174
175impl RegionOpener {
176 #[allow(clippy::too_many_arguments)]
179 pub(crate) fn new(
180 region_id: RegionId,
181 table_dir: &str,
182 path_type: PathType,
183 node_lease: Arc<NodeLease>,
184 memtable_builder_provider: MemtableBuilderProvider,
185 object_store_manager: ObjectStoreManagerRef,
186 purge_scheduler: SchedulerRef,
187 puffin_manager_factory: PuffinManagerFactory,
188 intermediate_manager: IntermediateManager,
189 time_provider: TimeProviderRef,
190 file_ref_manager: FileReferenceManagerRef,
191 partition_expr_fetcher: PartitionExprFetcherRef,
192 ) -> RegionOpener {
193 RegionOpener {
194 region_id,
195 metadata_builder: None,
196 memtable_builder_provider,
197 object_store_manager,
198 table_dir: normalize_dir(table_dir),
199 path_type,
200 purge_scheduler,
201 options: None,
202 cache_manager: None,
203 skip_wal_replay: false,
204 node_lease,
205 puffin_manager_factory,
206 intermediate_manager,
207 time_provider,
208 stats: ManifestStats::with_region_metrics(region_id),
209 wal_entry_reader: None,
210 replay_checkpoint: None,
211 file_ref_manager,
212 partition_expr_fetcher,
213 hook: None,
214 series_index_store: None,
215 series_index_purger: None,
216 }
217 }
218
219 pub(crate) fn series_index_store(mut self, store: Option<ObjectStore>) -> Self {
221 self.series_index_store = store;
222 self
223 }
224
225 pub(crate) fn series_index_purger(mut self, purger: Option<IndexFilePurger>) -> Self {
227 self.series_index_purger = purger;
228 self
229 }
230
231 pub(crate) fn hook(mut self, hook: Option<RegionHookRef>) -> Self {
233 self.hook = hook;
234 self
235 }
236
237 pub(crate) fn metadata_builder(mut self, builder: RegionMetadataBuilder) -> Self {
239 self.metadata_builder = Some(builder);
240 self
241 }
242
243 fn region_dir(&self) -> String {
245 region_dir_from_table_dir(&self.table_dir, self.region_id, self.path_type)
246 }
247
248 fn build_metadata(&mut self) -> Result<RegionMetadata> {
254 let options = self.options.as_ref().unwrap();
255 let mut metadata_builder = self.metadata_builder.take().unwrap();
256 metadata_builder.primary_key_encoding(options.primary_key_encoding());
257 metadata_builder.build().context(InvalidMetadataSnafu)
258 }
259
260 pub(crate) fn parse_options(self, options: HashMap<String, String>) -> Result<Self> {
262 let region_id = self.region_id;
263 let options = self
264 .memtable_builder_provider
265 .parse_options(region_id, &options)?;
266 self.options(options)
267 }
268
269 pub(crate) fn replay_checkpoint(mut self, replay_checkpoint: Option<u64>) -> Self {
271 self.replay_checkpoint = replay_checkpoint;
272 self
273 }
274
275 pub(crate) fn wal_entry_reader(
278 mut self,
279 wal_entry_reader: Option<Box<dyn WalEntryReader>>,
280 ) -> Self {
281 self.wal_entry_reader = wal_entry_reader;
282 self
283 }
284
285 pub(crate) fn options(mut self, options: RegionOptions) -> Result<Self> {
287 options.validate()?;
288 self.options = Some(options);
289 Ok(self)
290 }
291
292 pub(crate) fn ensure_region_requirements(
294 &self,
295 requirements: RegionRequirements,
296 ) -> Result<()> {
297 if !requirements.object_storage {
298 return Ok(());
299 }
300
301 let options = self.options.as_ref().context(InvalidRegionOptionsSnafu {
302 reason: "missing region options before requirement check".to_string(),
303 })?;
304 let object_store = get_object_store(&options.storage, &self.object_store_manager)?;
305
306 ensure!(
307 supports_open_region_object_storage_requirement(&object_store),
308 error::RegionRequirementSnafu {
309 region_id: self.region_id,
310 requirement: "object storage",
311 reason: "region data must be accessible from another datanode",
312 }
313 );
314
315 Ok(())
316 }
317
318 pub(crate) fn cache(mut self, cache_manager: Option<CacheManagerRef>) -> Self {
320 self.cache_manager = cache_manager;
321 self
322 }
323
324 pub(crate) fn skip_wal_replay(mut self, skip: bool) -> Self {
326 self.skip_wal_replay = skip;
327 self
328 }
329
330 pub(crate) async fn create_or_open<S: LogStore>(
337 mut self,
338 config: &MitoConfig,
339 wal: &Wal<S>,
340 ) -> Result<MitoRegionRef> {
341 let region_id = self.region_id;
342 let region_dir = self.region_dir();
343 let metadata = self.build_metadata()?;
344 match self.maybe_open(config, wal).await? {
346 Some(region) => {
347 let recovered = region.metadata();
348 let expect = &metadata;
350 check_recovered_region(
351 &recovered,
352 expect.region_id,
353 &expect.column_metadatas,
354 &expect.primary_key,
355 )?;
356 region.set_role(RegionRole::Leader);
358
359 return Ok(region);
360 }
361 None => {
362 debug!(
363 "No data under directory {}, region_id: {}",
364 region_dir, self.region_id
365 );
366 }
367 }
368 let mut options = self.options.take().unwrap();
370 let object_store = get_object_store(&options.storage, &self.object_store_manager)?;
371 let provider = self.provider::<S>(&options.wal_options)?;
372 let metadata = Arc::new(metadata);
373 let sst_format = if let Some(format) = options.sst_format {
375 format
376 } else if config.default_flat_format {
377 options.sst_format = Some(FormatType::Flat);
378 FormatType::Flat
379 } else {
380 options.sst_format = Some(FormatType::PrimaryKey);
382 FormatType::PrimaryKey
383 };
384 ensure_json2_uses_flat_format(&metadata, &options)?;
385 options.validate_column_compression(&metadata)?;
386 wal.start_region_history(region_id, &provider)?;
391 let mut region_manifest_options =
393 RegionManifestOptions::new(config, ®ion_dir, &object_store)
394 .with_conditional_publication(
395 options.requires_conditional_manifest_publication(self.node_lease.is_enabled()),
396 );
397 region_manifest_options.manifest_cache = self
399 .cache_manager
400 .as_ref()
401 .and_then(|cm| cm.write_cache())
402 .and_then(|wc| wc.manifest_cache());
403 let flushed_entry_id = provider
406 .initial_flushed_entry_id::<S>(wal.store())
407 .max(initial_pruned_entry_id(&options.wal_options));
408 let manifest_manager = RegionManifestManager::new(
409 metadata.clone(),
410 flushed_entry_id,
411 region_manifest_options,
412 sst_format,
413 options.append_mode,
414 &self.stats,
415 )
416 .await?;
417
418 let memtable_builder = self.memtable_builder_provider.builder_for_options(&options);
419 let part_duration = options.compaction.time_window();
420 let mutable =
422 TimePartitions::new(metadata.clone(), memtable_builder.clone(), 0, part_duration);
423
424 debug!(
425 "Create region {} with options: {:?}, default_flat_format: {}",
426 region_id, options, config.default_flat_format
427 );
428
429 let version = VersionBuilder::new(metadata, mutable)
430 .options(options)
431 .flushed_entry_id(flushed_entry_id)
432 .build();
433 let version_control = Arc::new(VersionControl::new(version));
434 let access_layer = Arc::new(AccessLayer::new(
435 self.table_dir.clone(),
436 self.path_type,
437 object_store,
438 self.puffin_manager_factory,
439 self.intermediate_manager,
440 ));
441 let now = self.time_provider.current_time_millis();
442
443 Ok(Arc::new(MitoRegion {
444 region_id,
445 compaction_state: Default::default(),
446 version_control,
447 series_index_version_control: Default::default(),
448 series_index_store: self.series_index_store.clone(),
449 access_layer: access_layer.clone(),
450 manifest_ctx: Arc::new(ManifestContext::new(
452 manifest_manager,
453 RegionRoleState::Leader(RegionLeaderState::Writable),
454 self.hook.clone(),
455 self.node_lease.clone(),
456 )),
457 file_purger: create_file_purger(
458 config.gc.enable,
459 self.path_type,
460 self.purge_scheduler,
461 access_layer,
462 self.cache_manager,
463 self.file_ref_manager.clone(),
464 self.series_index_store
465 .map(|store| RangeIndexDeleter::new(store, region_id)),
466 ),
467 provider,
468 last_flush_millis: AtomicI64::new(now),
469 last_schedule_compaction_millis: AtomicI64::new(now),
470 time_provider: self.time_provider.clone(),
471 topic_latest_entry_id: AtomicU64::new(flushed_entry_id),
472 region_stats: RegionStats::new(),
473 stats: self.stats,
474 }))
475 }
476
477 pub(crate) async fn open<S: LogStore>(
481 mut self,
482 config: &MitoConfig,
483 wal: &Wal<S>,
484 ) -> Result<MitoRegionRef> {
485 let region_id = self.region_id;
486 let region_dir = self.region_dir();
487 let region = self
488 .maybe_open(config, wal)
489 .await?
490 .with_context(|| EmptyRegionDirSnafu {
491 region_id,
492 region_dir: ®ion_dir,
493 })?;
494
495 ensure!(
496 region.region_id == self.region_id,
497 RegionCorruptedSnafu {
498 region_id: self.region_id,
499 reason: format!(
500 "recovered region has different region id {}",
501 region.region_id
502 ),
503 }
504 );
505
506 Ok(region)
507 }
508
509 fn provider<S: LogStore>(&self, wal_options: &WalOptions) -> Result<Provider> {
510 provider_from_wal_options::<S>(self.region_id, wal_options)
511 }
512
513 async fn maybe_open<S: LogStore>(
517 &mut self,
518 config: &MitoConfig,
519 wal: &Wal<S>,
520 ) -> Result<Option<MitoRegionRef>> {
521 let now = Instant::now();
522 let mut region_options = self.options.as_ref().unwrap().clone();
523 let object_storage = get_object_store(®ion_options.storage, &self.object_store_manager)?;
524 let mut region_manifest_options =
525 RegionManifestOptions::new(config, &self.region_dir(), &object_storage)
526 .with_conditional_publication(
527 region_options
528 .requires_conditional_manifest_publication(self.node_lease.is_enabled()),
529 );
530 region_manifest_options.manifest_cache = self
532 .cache_manager
533 .as_ref()
534 .and_then(|cm| cm.write_cache())
535 .and_then(|wc| wc.manifest_cache());
536 let Some(manifest_manager) =
537 RegionManifestManager::open(region_manifest_options, &self.stats).await?
538 else {
539 return Ok(None);
540 };
541
542 let manifest = manifest_manager.manifest();
544 let metadata = if manifest.metadata.partition_expr.is_none()
545 && let Some(expr_json) = self.partition_expr_fetcher.fetch_expr(self.region_id).await
546 {
547 let metadata = manifest.metadata.as_ref().clone();
548 let mut builder = RegionMetadataBuilder::from_existing(metadata);
549 builder.partition_expr_json(Some(expr_json));
550 Arc::new(builder.build().context(InvalidMetadataSnafu)?)
551 } else {
552 manifest.metadata.clone()
553 };
554 let metadata = maybe_upgrade_json2_layout(metadata)?;
555 sanitize_region_options(&manifest, &mut region_options);
557 ensure_json2_uses_flat_format(&metadata, ®ion_options)?;
558 region_options.validate_column_compression(&metadata)?;
559
560 let region_id = self.region_id;
561 let provider = self.provider::<S>(®ion_options.wal_options)?;
562 let wal_entry_reader = self
563 .wal_entry_reader
564 .take()
565 .unwrap_or_else(|| wal.wal_entry_reader(&provider, region_id));
566 let on_region_opened = wal.on_region_opened();
567 let object_store = get_object_store(®ion_options.storage, &self.object_store_manager)?;
568
569 debug!(
570 "Open region {} at {} with options: {:?}",
571 region_id, self.table_dir, self.options
572 );
573
574 let access_layer = Arc::new(AccessLayer::new(
575 self.table_dir.clone(),
576 self.path_type,
577 object_store,
578 self.puffin_manager_factory.clone(),
579 self.intermediate_manager.clone(),
580 ));
581 let file_purger = create_file_purger(
582 config.gc.enable,
583 self.path_type,
584 self.purge_scheduler.clone(),
585 access_layer.clone(),
586 self.cache_manager.clone(),
587 self.file_ref_manager.clone(),
588 self.series_index_store
589 .clone()
590 .map(|store| RangeIndexDeleter::new(store, region_id)),
591 );
592 let memtable_builder = self
594 .memtable_builder_provider
595 .builder_for_options(®ion_options);
596 let part_duration = region_options
599 .compaction
600 .time_window()
601 .or(manifest.compaction_time_window);
602 let mutable =
604 TimePartitions::new(metadata.clone(), memtable_builder.clone(), 0, part_duration);
605
606 let version_builder = version_builder_from_manifest(
608 &manifest,
609 metadata,
610 file_purger.clone(),
611 mutable,
612 region_options,
613 );
614 let version = version_builder.build();
615 let flushed_entry_id = version.flushed_entry_id;
616 let version_control = Arc::new(VersionControl::new(version));
617
618 let replay_from_entry_id = self
619 .replay_checkpoint
620 .unwrap_or_default()
621 .max(flushed_entry_id);
622 let topic_latest_entry_id = if !self.skip_wal_replay {
623 check_wal_history(wal, &provider, &version_control.current().version).await?;
624 info!(
625 "Start replaying memtable at replay_from_entry_id: {} for region {}, manifest version: {}, flushed entry id: {}, elapsed: {:?}",
626 replay_from_entry_id,
627 region_id,
628 manifest.manifest_version,
629 flushed_entry_id,
630 now.elapsed()
631 );
632 replay_memtable(
633 &provider,
634 wal_entry_reader,
635 region_id,
636 replay_from_entry_id,
637 &version_control,
638 config.allow_stale_entries,
639 on_region_opened,
640 )
641 .await?;
642 if provider.is_remote_wal() && version_control.current().version.memtables.is_empty() {
646 wal.store()
647 .latest_entry_id(&provider)
648 .unwrap_or(replay_from_entry_id)
649 } else if provider.is_remote_wal() {
650 replay_from_entry_id
651 } else {
652 0
653 }
654 } else {
655 info!(
656 "Skip the WAL replay for region: {}, manifest version: {}, flushed_entry_id: {}, elapsed: {:?}",
657 region_id,
658 manifest.manifest_version,
659 flushed_entry_id,
660 now.elapsed()
661 );
662
663 if provider.is_remote_wal() {
664 replay_from_entry_id
665 } else {
666 0
667 }
668 };
669
670 if let Some(committed_in_manifest) = manifest.committed_sequence {
671 let committed_after_replay = version_control.committed_sequence();
672 if committed_in_manifest > committed_after_replay {
673 info!(
674 "Overriding committed sequence, region: {}, flushed_sequence: {}, committed_sequence: {} -> {}",
675 self.region_id,
676 version_control.current().version.flushed_sequence,
677 version_control.committed_sequence(),
678 committed_in_manifest
679 );
680 version_control.set_committed_sequence(committed_in_manifest);
681 }
682 }
683
684 let now = self.time_provider.current_time_millis();
685
686 let series_index_version_control =
687 match (&self.series_index_store, &self.series_index_purger) {
688 (Some(store), Some(purger))
689 if is_sparse_metric_metadata(&version_control.current().version.metadata) =>
690 {
691 load_version_control(store, self.region_id, purger).await
692 }
693 _ => Default::default(),
694 };
695
696 let region = MitoRegion {
697 region_id: self.region_id,
698 compaction_state: Default::default(),
699 version_control: version_control.clone(),
700 series_index_version_control,
701 series_index_store: self.series_index_store.clone(),
702 access_layer: access_layer.clone(),
703 manifest_ctx: Arc::new(ManifestContext::new(
705 manifest_manager,
706 RegionRoleState::Follower,
707 self.hook.clone(),
708 self.node_lease.clone(),
709 )),
710 file_purger,
711 provider: provider.clone(),
712 last_flush_millis: AtomicI64::new(now),
713 last_schedule_compaction_millis: AtomicI64::new(now),
714 time_provider: self.time_provider.clone(),
715 topic_latest_entry_id: AtomicU64::new(topic_latest_entry_id),
716 region_stats: RegionStats::new(),
717 stats: self.stats.clone(),
718 };
719
720 let region = Arc::new(region);
721
722 maybe_load_cache(®ion, config, &self.cache_manager);
723 maybe_preload_parquet_meta_cache(®ion, config, &self.cache_manager);
724
725 Ok(Some(region))
726 }
727}
728
729pub(crate) fn provider_from_wal_options<S: LogStore>(
730 region_id: RegionId,
731 wal_options: &WalOptions,
732) -> Result<Provider> {
733 match wal_options {
734 WalOptions::RaftEngine => {
735 ensure!(
736 TypeId::of::<RaftEngineLogStore>() == TypeId::of::<S>()
737 || TypeId::of::<NoopLogStore>() == TypeId::of::<S>(),
738 error::IncompatibleWalProviderChangeSnafu {
739 global: "`kafka`",
740 region: "`raft_engine`",
741 }
742 );
743 Ok(Provider::raft_engine_provider(region_id.as_u64()))
744 }
745 WalOptions::Kafka(options) => {
746 ensure!(
747 TypeId::of::<KafkaLogStore>() == TypeId::of::<S>()
748 || TypeId::of::<NoopLogStore>() == TypeId::of::<S>(),
749 error::IncompatibleWalProviderChangeSnafu {
750 global: "`raft_engine`",
751 region: "`kafka`",
752 }
753 );
754 Ok(Provider::kafka_provider(options.topic.clone()))
755 }
756 WalOptions::ObjectStore(options) => {
757 ensure!(
758 TypeId::of::<RaftEngineLogStore>() != TypeId::of::<S>(),
759 error::IncompatibleWalProviderChangeSnafu {
760 global: "`raft_engine`",
761 region: "`object_store`",
762 }
763 );
764 ensure!(
765 TypeId::of::<KafkaLogStore>() != TypeId::of::<S>(),
766 error::IncompatibleWalProviderChangeSnafu {
767 global: "`kafka`",
768 region: "`object_store`",
769 }
770 );
771 Ok(Provider::object_store_provider(
772 region_id,
773 options.prefix.clone(),
774 ))
775 }
776 WalOptions::Noop => Ok(Provider::noop_provider()),
777 }
778}
779
780#[cfg(not(feature = "test-shared-fs-region-migration"))]
781fn supports_open_region_object_storage_requirement(object_store: &ObjectStore) -> bool {
782 is_object_storage(object_store)
783}
784
785#[cfg(feature = "test-shared-fs-region-migration")]
786fn supports_open_region_object_storage_requirement(object_store: &ObjectStore) -> bool {
787 is_object_storage(object_store)
792 || object_store.info().scheme() == object_store::services::FS_SCHEME
793}
794
795pub(crate) fn version_builder_from_manifest(
797 manifest: &RegionManifest,
798 metadata: RegionMetadataRef,
799 file_purger: FilePurgerRef,
800 mutable: TimePartitions,
801 region_options: RegionOptions,
802) -> VersionBuilder {
803 VersionBuilder::new(metadata, mutable)
804 .add_files(file_purger, manifest.files.values().cloned())
805 .flushed_entry_id(manifest.flushed_entry_id)
806 .flushed_sequence(manifest.flushed_sequence)
807 .truncated_entry_id(manifest.truncated_entry_id)
808 .compaction_time_window(manifest.compaction_time_window)
809 .options(region_options)
810}
811
812pub(crate) fn sanitize_region_options(manifest: &RegionManifest, options: &mut RegionOptions) {
814 if let Some(encoding) = options.primary_key_encoding
815 && encoding != manifest.metadata.primary_key_encoding
816 {
817 common_telemetry::warn!(
818 "Overriding primary key encoding from {:?} to {:?} for region {}",
819 encoding,
820 manifest.metadata.primary_key_encoding,
821 manifest.metadata.region_id,
822 );
823 }
824 options.primary_key_encoding = Some(manifest.metadata.primary_key_encoding);
825 if manifest.metadata.primary_key_encoding == PrimaryKeyEncoding::Sparse {
826 options.sst_format = Some(FormatType::Flat);
827 }
828 let format = options.sst_format.unwrap_or(manifest.sst_format);
830 if format != FormatType::Flat
832 && manifest
833 .metadata
834 .column_metadatas
835 .iter()
836 .any(|column| column.column_schema.data_type.is_json2())
837 {
838 warn!(
839 "Overriding SST format from {:?} to flat for existing JSON2 region {}",
840 format, manifest.metadata.region_id,
841 );
842 options.sst_format = Some(FormatType::Flat);
843 } else {
844 if format != manifest.sst_format {
845 warn!(
846 "Overriding SST format from {:?} (manifest) to {:?} (options) for region {}",
847 manifest.sst_format, format, manifest.metadata.region_id,
848 );
849 }
850 options.sst_format = Some(format);
851 }
852 if let Some(manifest_append_mode) = manifest.append_mode
853 && options.append_mode != manifest_append_mode
854 {
855 common_telemetry::warn!(
856 "Overriding append_mode from {} to {} for region {}",
857 options.append_mode,
858 manifest_append_mode,
859 manifest.metadata.region_id,
860 );
861 options.append_mode = manifest_append_mode;
862 }
863 if options.append_mode && options.merge_mode.take().is_some() {
864 common_telemetry::warn!(
865 "Ignoring merge_mode because append_mode is enabled for region {}",
866 manifest.metadata.region_id,
867 );
868 }
869}
870
871pub(crate) fn sanitize_open_request_options(options: &mut HashMap<String, String>) {
873 let append_mode_enabled = options
874 .get("append_mode")
875 .is_some_and(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "true" | "1"));
876
877 if append_mode_enabled && options.remove("merge_mode").is_some() {
878 common_telemetry::warn!(
879 "Ignoring merge_mode in open request options because append_mode is enabled"
880 );
881 }
882}
883
884pub fn get_object_store(
886 name: &Option<String>,
887 object_store_manager: &ObjectStoreManagerRef,
888) -> Result<object_store::ObjectStore> {
889 if let Some(name) = name {
890 Ok(object_store_manager
891 .find(name)
892 .with_context(|| ObjectStoreNotFoundSnafu {
893 object_store: name.clone(),
894 })?
895 .clone())
896 } else {
897 Ok(object_store_manager.default_object_store().clone())
898 }
899}
900
901pub(crate) fn check_recovered_region(
903 recovered: &RegionMetadata,
904 region_id: RegionId,
905 column_metadatas: &[ColumnMetadata],
906 primary_key: &[ColumnId],
907) -> Result<()> {
908 if recovered.region_id != region_id {
909 error!(
910 "Recovered region {}, expect region {}",
911 recovered.region_id, region_id
912 );
913 return RegionCorruptedSnafu {
914 region_id,
915 reason: format!(
916 "recovered metadata has different region id {}",
917 recovered.region_id
918 ),
919 }
920 .fail();
921 }
922 if recovered.column_metadatas != column_metadatas {
923 error!(
924 "Unexpected schema in recovered region {}, recovered: {:?}, expect: {:?}",
925 recovered.region_id, recovered.column_metadatas, column_metadatas
926 );
927
928 return RegionCorruptedSnafu {
929 region_id,
930 reason: "recovered metadata has different schema",
931 }
932 .fail();
933 }
934 if recovered.primary_key != primary_key {
935 error!(
936 "Unexpected primary key in recovered region {}, recovered: {:?}, expect: {:?}",
937 recovered.region_id, recovered.primary_key, primary_key
938 );
939
940 return RegionCorruptedSnafu {
941 region_id,
942 reason: "recovered metadata has different primary key",
943 }
944 .fail();
945 }
946
947 Ok(())
948}
949
950pub(crate) async fn check_wal_history<S: LogStore>(
955 wal: &Wal<S>,
956 provider: &Provider,
957 version: &Version,
958) -> Result<()> {
959 if version.options.skip_wal {
960 return Ok(());
961 }
962 let watermark = version
963 .flushed_entry_id
964 .max(version.truncated_entry_id.unwrap_or_default());
965 wal.check_region_history(version.metadata.region_id, provider, watermark)
966 .await
967}
968
969pub(crate) async fn replay_memtable<F>(
971 provider: &Provider,
972 mut wal_entry_reader: Box<dyn WalEntryReader>,
973 region_id: RegionId,
974 flushed_entry_id: EntryId,
975 version_control: &VersionControlRef,
976 allow_stale_entries: bool,
977 on_region_opened: F,
978) -> Result<EntryId>
979where
980 F: FnOnce(RegionId, EntryId, &Provider) -> BoxFuture<Result<()>> + Send,
981{
982 let now = Instant::now();
983 let mut rows_replayed = 0;
984 let mut last_entry_id = flushed_entry_id;
987 let replay_from_entry_id = flushed_entry_id + 1;
988 let region_metadata = version_control.current().version.metadata.clone();
989
990 let mut wal_stream = wal_entry_reader.read(provider, replay_from_entry_id)?;
991 while let Some(res) = wal_stream.next().await {
992 let (entry_id, entry) = res?;
993 if entry_id <= flushed_entry_id {
994 warn!(
995 "Stale WAL entries read during replay, region id: {}, flushed entry id: {}, entry id read: {}",
996 region_id, flushed_entry_id, entry_id
997 );
998 ensure!(
999 allow_stale_entries,
1000 StaleLogEntrySnafu {
1001 region_id,
1002 flushed_entry_id,
1003 unexpected_entry_id: entry_id,
1004 }
1005 );
1006 }
1007 last_entry_id = last_entry_id.max(entry_id);
1008
1009 let mut region_write_ctx = RegionWriteCtx::new(
1010 region_id,
1011 version_control,
1012 provider.clone(),
1013 None,
1015 WriteSource::WalReplay,
1016 );
1017 for mutation in entry.mutations {
1018 rows_replayed += mutation
1019 .rows
1020 .as_ref()
1021 .map(|rows| rows.rows.len())
1022 .unwrap_or(0);
1023 region_write_ctx.push_mutation(
1024 mutation.op_type,
1025 mutation.rows,
1026 mutation.write_hint,
1027 OptionOutputTx::none(),
1028 Some(mutation.sequence),
1030 false,
1031 );
1032 }
1033
1034 for bulk_entry in entry.bulk_entries {
1035 let mut part = BulkPart::try_from(bulk_entry)?;
1036 part.fill_missing_columns(®ion_metadata)?;
1043 rows_replayed += part.num_rows();
1044 let bulk_sequence_from_wal = part.sequence;
1046 ensure!(
1047 region_write_ctx.push_bulk(
1048 OptionOutputTx::none(),
1049 part,
1050 Some(bulk_sequence_from_wal),
1051 false
1052 ),
1053 RegionCorruptedSnafu {
1054 region_id,
1055 reason: "unable to replay memtable with bulk entries",
1056 }
1057 );
1058 }
1059
1060 region_write_ctx.set_next_entry_id(last_entry_id + 1);
1062 region_write_ctx.write_memtables().await;
1065 }
1066
1067 (on_region_opened)(region_id, flushed_entry_id, provider).await?;
1070
1071 let series_count = version_control.current().series_count();
1072 info!(
1073 "Replay WAL for region: {}, provider: {:?}, rows recovered: {}, replay from entry id: {}, last entry id: {}, total timeseries replayed: {}, elapsed: {:?}",
1074 region_id,
1075 provider,
1076 rows_replayed,
1077 replay_from_entry_id,
1078 last_entry_id,
1079 series_count,
1080 now.elapsed()
1081 );
1082 Ok(last_entry_id)
1083}
1084
1085pub(crate) struct RegionLoadCacheTask {
1087 region: MitoRegionRef,
1088}
1089
1090impl RegionLoadCacheTask {
1091 pub(crate) fn new(region: MitoRegionRef) -> Self {
1092 Self { region }
1093 }
1094
1095 pub(crate) async fn fill_cache(&self, file_cache: &FileCache) {
1097 let region_id = self.region.region_id;
1098 let table_dir = self.region.access_layer.table_dir();
1099 let path_type = self.region.access_layer.path_type();
1100 let object_store = self.region.access_layer.object_store();
1101 let version_control = &self.region.version_control;
1102
1103 let mut files_to_download = Vec::new();
1105 let mut files_already_cached = 0;
1106
1107 {
1108 let version = version_control.current().version;
1109 for level in version.ssts.levels() {
1110 for file_handle in level.files.values() {
1111 let file_meta = file_handle.meta_ref();
1112 if file_meta.exists_index() {
1113 let puffin_key = IndexKey::new(
1114 file_meta.region_id,
1115 file_meta.file_id,
1116 FileType::Puffin(file_meta.index_version),
1117 );
1118
1119 if !file_cache.contains_key(&puffin_key) {
1120 files_to_download.push((
1121 puffin_key,
1122 file_meta.index_file_size,
1123 file_meta.time_range.1, ));
1125 } else {
1126 files_already_cached += 1;
1127 }
1128 }
1129 }
1130 }
1131 }
1134
1135 files_to_download.sort_by_key(|b| std::cmp::Reverse(b.2));
1137
1138 let total_files = files_to_download.len() as i64;
1139
1140 info!(
1141 "Starting background index cache preload for region {}, total_files_to_download: {}, files_already_cached: {}",
1142 region_id, total_files, files_already_cached
1143 );
1144
1145 CACHE_FILL_PENDING_FILES.add(total_files);
1146
1147 let mut files_downloaded = 0;
1148 let mut files_skipped = 0;
1149
1150 for (puffin_key, file_size, max_timestamp) in files_to_download {
1151 let current_size = file_cache.puffin_cache_size();
1152 let capacity = file_cache.puffin_cache_capacity();
1153 let region_state = self.region.state();
1154 if !can_load_cache(region_state) {
1155 info!(
1156 "Stopping index cache by state: {:?}, region: {}, current_size: {}, capacity: {}",
1157 region_state, region_id, current_size, capacity
1158 );
1159 break;
1160 }
1161
1162 if current_size + file_size > capacity {
1164 info!(
1165 "Stopping index cache preload due to capacity limit, region: {}, file_id: {}, current_size: {}, file_size: {}, capacity: {}, file_timestamp: {:?}",
1166 region_id, puffin_key.file_id, current_size, file_size, capacity, max_timestamp
1167 );
1168 files_skipped = (total_files - files_downloaded) as usize;
1169 CACHE_FILL_PENDING_FILES.sub(total_files - files_downloaded);
1170 break;
1171 }
1172
1173 let index_version = if let FileType::Puffin(version) = puffin_key.file_type {
1174 version
1175 } else {
1176 unreachable!("`files_to_download` should only contains Puffin files");
1177 };
1178 let index_id = RegionIndexId::new(
1179 RegionFileId::new(puffin_key.region_id, puffin_key.file_id),
1180 index_version,
1181 );
1182
1183 let index_remote_path = location::index_file_path(table_dir, index_id, path_type);
1184
1185 match file_cache
1186 .download(puffin_key, &index_remote_path, object_store, file_size)
1187 .await
1188 {
1189 Ok(_) => {
1190 debug!(
1191 "Downloaded index file to write cache, region: {}, file_id: {}",
1192 region_id, puffin_key.file_id
1193 );
1194 files_downloaded += 1;
1195 CACHE_FILL_DOWNLOADED_FILES.inc_by(1);
1196 CACHE_FILL_PENDING_FILES.dec();
1197 }
1198 Err(e) => {
1199 warn!(
1200 e; "Failed to download index file to write cache, region: {}, file_id: {}",
1201 region_id, puffin_key.file_id
1202 );
1203 CACHE_FILL_PENDING_FILES.dec();
1204 }
1205 }
1206 }
1207
1208 info!(
1209 "Completed background cache fill task for region {}, total_files: {}, files_downloaded: {}, files_already_cached: {}, files_skipped: {}",
1210 region_id, total_files, files_downloaded, files_already_cached, files_skipped
1211 );
1212 }
1213}
1214
1215fn maybe_load_cache(
1217 region: &MitoRegionRef,
1218 config: &MitoConfig,
1219 cache_manager: &Option<CacheManagerRef>,
1220) {
1221 let Some(cache_manager) = cache_manager else {
1222 return;
1223 };
1224 let Some(write_cache) = cache_manager.write_cache() else {
1225 return;
1226 };
1227
1228 let preload_enabled = config.preload_index_cache;
1229 if !preload_enabled {
1230 return;
1231 }
1232
1233 let task = RegionLoadCacheTask::new(region.clone());
1234 write_cache.load_region_cache(task);
1235}
1236
1237#[allow(clippy::too_many_arguments)]
1248async fn preload_parquet_meta_cache_for_files(
1249 region_id: RegionId,
1250 cache_manager: CacheManagerRef,
1251 sst_meta_cache_capacity: u64,
1252 table_dir: String,
1253 path_type: PathType,
1254 object_store: object_store::ObjectStore,
1255 region_metadata: RegionMetadataRef,
1256 mut files: Vec<FileHandle>,
1257) -> usize {
1258 if !cache_manager.sst_meta_cache_enabled()
1259 || sst_meta_cache_capacity == 0
1260 || cache_manager.sst_meta_cache_is_full()
1261 {
1262 return 0;
1263 }
1264
1265 let allow_direct_load = object_store.info().scheme() == object_store::services::FS_SCHEME;
1266
1267 files.sort_by_key(|b| std::cmp::Reverse(b.meta_ref().time_range.1));
1269
1270 let mut loaded = 0usize;
1271 for file_handle in files {
1272 if cache_manager.sst_meta_cache_is_full() {
1274 break;
1275 }
1276
1277 let file_id = file_handle.file_id();
1278 let mut cache_metrics = MetadataCacheMetrics::default();
1279 if let Some(metadata) = cache_manager
1280 .get_compact_sst_meta_data(file_id, PageIndexPolicy::Optional)
1281 .await
1282 {
1283 if file_handle.raw_primary_key_range().is_none()
1284 && let Some(primary_key_range) = extract_primary_key_range(
1285 metadata.parquet_metadata().as_ref(),
1286 ®ion_metadata,
1287 )
1288 {
1289 file_handle.set_primary_key_range(primary_key_range);
1290 }
1291 continue;
1292 }
1293
1294 if let Some(write_cache) = cache_manager.write_cache() {
1295 let key = IndexKey::new(file_id.region_id(), file_id.file_id(), FileType::Parquet);
1296 if let Some(metadata) = write_cache
1297 .file_cache()
1298 .get_sst_meta_data(
1299 key,
1300 &mut cache_metrics,
1301 PageIndexPolicy::Optional,
1302 &common_runtime::global_runtime(),
1303 )
1304 .await
1305 {
1306 let decoded = metadata.decoded();
1307 if file_handle.raw_primary_key_range().is_none()
1308 && let Some(primary_key_range) = extract_primary_key_range(
1309 decoded.parquet_metadata().as_ref(),
1310 ®ion_metadata,
1311 )
1312 {
1313 file_handle.set_primary_key_range(primary_key_range);
1314 }
1315 match metadata {
1316 SstMetaPreparation::Prepared(metadata) => {
1317 cache_manager.put_compact_sst_meta(file_id, metadata.compact);
1318 loaded += 1;
1319 }
1320 SstMetaPreparation::DecodedOnly { encoding_error, .. } => warn!(
1321 encoding_error;
1322 "Failed to encode file-cached SST metadata during preload, region: {}, file: {}",
1323 region_id,
1324 file_id.file_id()
1325 ),
1326 }
1327 continue;
1328 }
1329 }
1330
1331 if !allow_direct_load {
1332 continue;
1333 }
1334
1335 let file_size = file_handle.meta_ref().file_size;
1336 let file_path = file_handle.file_path(&table_dir, path_type);
1337 let mut loader = MetadataLoader::new(object_store.clone(), &file_path, file_size);
1338 loader.with_page_index_policy(PageIndexPolicy::Optional);
1339 match loader.load(&mut cache_metrics).await {
1340 Ok(metadata) => {
1341 if let Some(primary_key_range) =
1342 extract_primary_key_range(&metadata, ®ion_metadata)
1343 {
1344 file_handle.set_primary_key_range(primary_key_range);
1345 }
1346 match prepare_sst_meta(
1347 &file_path,
1348 metadata,
1349 None,
1352 PageIndexPolicy::Optional,
1353 &common_runtime::global_runtime(),
1354 )
1355 .await
1356 {
1357 Ok(SstMetaPreparation::Prepared(metadata)) => {
1358 cache_manager.put_compact_sst_meta(file_id, metadata.compact);
1361 loaded += 1;
1362 }
1363 Ok(SstMetaPreparation::DecodedOnly { encoding_error, .. }) => warn!(
1364 encoding_error;
1365 "Failed to encode preloaded parquet metadata, region: {}, file: {}",
1366 region_id,
1367 file_path
1368 ),
1369 Err(err) => {
1370 warn!(
1371 err; "Failed to prepare preloaded parquet metadata, region: {}, file: {}",
1372 region_id, file_path
1373 );
1374 }
1375 }
1376 }
1377 Err(err) => {
1378 warn!(
1380 err; "Failed to preload parquet metadata from local store, region: {}, file: {}",
1381 region_id, file_path
1382 );
1383 }
1384 }
1385 }
1386
1387 loaded
1388}
1389
1390fn maybe_preload_parquet_meta_cache(
1391 region: &MitoRegionRef,
1392 config: &MitoConfig,
1393 cache_manager: &Option<CacheManagerRef>,
1394) {
1395 let Some(cache_manager) = cache_manager else {
1396 return;
1397 };
1398 if !cache_manager.sst_meta_cache_enabled() {
1399 return;
1400 }
1401
1402 if config.sst_meta_cache_size.as_bytes() == 0 {
1404 return;
1405 }
1406 if !config.preload_index_cache {
1407 return;
1408 }
1409
1410 let region = region.clone();
1411 let cache_manager = cache_manager.clone();
1412 let sst_meta_cache_capacity = config.sst_meta_cache_size.as_bytes();
1413
1414 tokio::spawn(async move {
1415 let _permit = PARQUET_META_PRELOAD_SEMAPHORE.acquire().await.unwrap();
1417
1418 let region_id = region.region_id;
1419 let table_dir = region.access_layer.table_dir().to_string();
1420 let path_type = region.access_layer.path_type();
1421 let object_store = region.access_layer.object_store().clone();
1422 let region_metadata = region.version_control.current().version.metadata.clone();
1423
1424 let mut files = Vec::new();
1426 {
1427 let version = region.version_control.current().version;
1428 for level in version.ssts.levels() {
1429 for file_handle in level.files.values() {
1430 files.push(file_handle.clone());
1431 }
1432 }
1433 }
1434 let preloading_start = Instant::now();
1435 let loaded = preload_parquet_meta_cache_for_files(
1436 region_id,
1437 cache_manager,
1438 sst_meta_cache_capacity,
1439 table_dir,
1440 path_type,
1441 object_store,
1442 region_metadata,
1443 files,
1444 )
1445 .await;
1446 let preloading_cost = preloading_start.elapsed();
1447
1448 if loaded > 0 {
1449 info!(
1450 "Preloaded parquet metadata for region {}, loaded_files: {}, elapsed_ms: {}",
1451 region_id,
1452 loaded,
1453 preloading_cost.as_millis()
1454 );
1455 }
1456 });
1457}
1458
1459fn can_load_cache(state: RegionRoleState) -> bool {
1460 match state {
1461 RegionRoleState::Leader(RegionLeaderState::Writable)
1462 | RegionRoleState::Leader(RegionLeaderState::Staging)
1463 | RegionRoleState::Leader(RegionLeaderState::Altering)
1464 | RegionRoleState::Leader(RegionLeaderState::EnteringStaging)
1465 | RegionRoleState::Leader(RegionLeaderState::Editing)
1466 | RegionRoleState::Follower => true,
1467 RegionRoleState::Leader(RegionLeaderState::Downgrading)
1469 | RegionRoleState::Leader(RegionLeaderState::Dropping)
1470 | RegionRoleState::Leader(RegionLeaderState::Truncating) => false,
1471 }
1472}
1473
1474#[cfg(test)]
1475mod tests {
1476 use std::collections::HashMap;
1477 use std::sync::Arc;
1478 use store_api::codec::PrimaryKeyEncoding;
1479
1480 use arrow_schema::extension::ExtensionType;
1481 use common_base::readable_size::ReadableSize;
1482 use common_error::ext::WhateverResult;
1483 use common_test_util::temp_dir::create_temp_dir;
1484 use common_time::Timestamp;
1485 use common_wal::options::{KafkaWalOptions, ObjectStoreWalOptions, WalOptions};
1486 use datatypes::arrow::array::{ArrayRef, BinaryArray, Int64Array};
1487 use datatypes::arrow::record_batch::RecordBatch;
1488 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
1489 use datatypes::json::JsonSettings;
1490 use datatypes::prelude::ConcreteDataType;
1491 use datatypes::schema::ColumnSchema;
1492 use datatypes::types::json_type::{JsonNativeType, JsonObjectType};
1493 use log_store::kafka::log_store::KafkaLogStore;
1494 use log_store::noop::log_store::NoopLogStore;
1495 use log_store::raft_engine::log_store::RaftEngineLogStore;
1496 use object_store::ObjectStore;
1497 use object_store::services::{Fs, Memory, S3};
1498 use parquet::arrow::ArrowWriter;
1499 use parquet::file::metadata::{KeyValue, PageIndexPolicy};
1500 use parquet::file::properties::WriterProperties;
1501 use store_api::logstore::provider::Provider;
1502 use store_api::metadata::RegionMetadataBuilder;
1503 use store_api::region_request::PathType;
1504 use store_api::storage::{FileId, RegionId};
1505
1506 use super::{
1507 initial_pruned_entry_id, maybe_upgrade_json2_layout, preload_parquet_meta_cache_for_files,
1508 provider_from_wal_options, sanitize_region_options,
1509 supports_open_region_object_storage_requirement,
1510 };
1511 use crate::cache::CacheManager;
1512 use crate::cache::file_cache::{FileType, IndexKey};
1513 use crate::error;
1514 use crate::manifest::action::{RegionManifest, RemovedFilesRecord};
1515 use crate::region::options::RegionOptions;
1516 use crate::sst::FormatType;
1517 use crate::sst::file::{FileHandle, FileMeta};
1518 use crate::sst::file_purger::NoopFilePurger;
1519 use crate::sst::parquet::PARQUET_METADATA_KEY;
1520 use crate::test_util::TestEnv;
1521 use crate::test_util::sst_util::sst_region_metadata;
1522
1523 fn build_test_manifest(sst_format: FormatType) -> RegionManifest {
1524 RegionManifest {
1525 metadata: Arc::new(sst_region_metadata()),
1526 files: HashMap::new(),
1527 removed_files: RemovedFilesRecord::default(),
1528 flushed_entry_id: 0,
1529 flushed_sequence: 0,
1530 committed_sequence: None,
1531 manifest_version: 0,
1532 truncated_entry_id: None,
1533 compaction_time_window: None,
1534 sst_format,
1535 append_mode: None,
1536 }
1537 }
1538
1539 fn build_fs_object_store() -> ObjectStore {
1540 ObjectStore::new(Fs::default().root("/tmp")).unwrap()
1541 }
1542
1543 #[test]
1544 fn test_provider_from_object_store_wal_options() {
1545 let region_id = RegionId::new(1, 2);
1546 let wal_options = WalOptions::ObjectStore(ObjectStoreWalOptions::new("wal".to_string()));
1547
1548 let provider = provider_from_wal_options::<NoopLogStore>(region_id, &wal_options).unwrap();
1549 assert_eq!(
1550 Provider::object_store_provider(region_id, "wal".to_string()),
1551 provider
1552 );
1553
1554 let err =
1555 provider_from_wal_options::<RaftEngineLogStore>(region_id, &wal_options).unwrap_err();
1556 assert!(matches!(
1557 err,
1558 error::Error::IncompatibleWalProviderChange { .. }
1559 ));
1560 let err = provider_from_wal_options::<KafkaLogStore>(region_id, &wal_options).unwrap_err();
1561 assert!(matches!(
1562 err,
1563 error::Error::IncompatibleWalProviderChange { .. }
1564 ));
1565 }
1566
1567 #[test]
1568 fn test_initial_pruned_entry_id() {
1569 assert_eq!(0, initial_pruned_entry_id(&WalOptions::RaftEngine));
1570 assert_eq!(0, initial_pruned_entry_id(&WalOptions::Noop));
1571 assert_eq!(
1572 0,
1573 initial_pruned_entry_id(&WalOptions::ObjectStore(ObjectStoreWalOptions::new(
1574 "wal".to_string()
1575 )))
1576 );
1577 assert_eq!(
1578 0,
1579 initial_pruned_entry_id(&WalOptions::Kafka(KafkaWalOptions::new(
1580 "test_topic".to_string()
1581 )))
1582 );
1583 assert_eq!(
1584 42,
1585 initial_pruned_entry_id(&WalOptions::Kafka(KafkaWalOptions {
1586 topic: "test_topic".to_string(),
1587 initial_pruned_entry_id: Some(42),
1588 }))
1589 );
1590 }
1591
1592 #[test]
1593 fn test_upgrade_json2_layout() -> WhateverResult<()> {
1594 let settings = JsonSettings::try_new(vec![], Some(3))?;
1595 let extension = Json2ExtensionType::new(Arc::new(JsonMetadata::new_v1(settings.clone())));
1596 let mut column = ColumnSchema::new(
1597 "field_0",
1598 ConcreteDataType::json2(JsonNativeType::Object(JsonObjectType::new())),
1599 true,
1600 );
1601 column.with_extension_type(&extension);
1602
1603 let mut metadata = sst_region_metadata();
1604 metadata.column_metadatas[2].column_schema = column;
1605 let builder = RegionMetadataBuilder::from_existing(metadata);
1606 let metadata = Arc::new(builder.build()?);
1607
1608 let upgraded = maybe_upgrade_json2_layout(metadata)?;
1609 let column = &upgraded.column_metadatas[2].column_schema;
1610 let extension = column.extension_type::<Json2ExtensionType>()?.unwrap();
1611 assert!(extension.metadata().is_version_2());
1612 assert_eq!(&settings, extension.metadata().json_settings());
1613
1614 let arrow_schema = upgraded.schema.arrow_schema();
1615 let field = arrow_schema.field_with_name("field_0").unwrap();
1616 let extension = field.try_extension_type::<Json2ExtensionType>().unwrap();
1617 assert!(extension.metadata().is_version_2());
1618
1619 let unchanged = maybe_upgrade_json2_layout(upgraded.clone())?;
1620 assert!(Arc::ptr_eq(&upgraded, &unchanged));
1621 Ok(())
1622 }
1623
1624 #[test]
1625 #[cfg(not(feature = "test-shared-fs-region-migration"))]
1626 fn test_open_requirement_rejects_fs_object_store() {
1627 let object_store = build_fs_object_store();
1628
1629 assert!(!supports_open_region_object_storage_requirement(
1630 &object_store
1631 ));
1632 }
1633
1634 #[test]
1635 #[cfg(feature = "test-shared-fs-region-migration")]
1636 fn test_open_requirement_accepts_shared_fs_object_store_for_tests() {
1637 let object_store = build_fs_object_store();
1638
1639 assert!(supports_open_region_object_storage_requirement(
1640 &object_store
1641 ));
1642 }
1643
1644 #[test]
1645 fn test_open_requirement_accepts_s3_object_store() {
1646 let object_store = ObjectStore::new(
1647 S3::default()
1648 .bucket("test-bucket")
1649 .region("us-east-1")
1650 .disable_ec2_metadata(),
1651 )
1652 .unwrap();
1653
1654 assert!(supports_open_region_object_storage_requirement(
1655 &object_store
1656 ));
1657 }
1658
1659 #[test]
1660 fn test_sparse_reopen_uses_persisted_encoding_and_flat_sst() {
1661 for supplied in [None, Some(PrimaryKeyEncoding::Dense)] {
1662 let mut manifest = build_test_manifest(FormatType::PrimaryKey);
1663 Arc::make_mut(&mut manifest.metadata).primary_key_encoding = PrimaryKeyEncoding::Sparse;
1664 let mut options = RegionOptions {
1665 primary_key_encoding: supplied,
1666 sst_format: Some(FormatType::PrimaryKey),
1667 ..Default::default()
1668 };
1669 sanitize_region_options(&manifest, &mut options);
1670 assert_eq!(options.primary_key_encoding(), PrimaryKeyEncoding::Sparse);
1671 assert_eq!(options.sst_format, Some(FormatType::Flat));
1672 }
1673 }
1674
1675 #[test]
1676 fn test_sanitize_region_options_options_format_wins() {
1677 let manifest = build_test_manifest(FormatType::PrimaryKey);
1680 let mut options = RegionOptions {
1681 sst_format: Some(FormatType::Flat),
1682 ..Default::default()
1683 };
1684 sanitize_region_options(&manifest, &mut options);
1685 assert_eq!(options.sst_format, Some(FormatType::Flat));
1686 }
1687
1688 #[test]
1689 fn test_sanitize_region_options_fills_from_manifest_when_unset() {
1690 let manifest = build_test_manifest(FormatType::Flat);
1693 let mut options = RegionOptions {
1694 sst_format: None,
1695 ..Default::default()
1696 };
1697 sanitize_region_options(&manifest, &mut options);
1698 assert_eq!(options.sst_format, Some(FormatType::Flat));
1699 }
1700
1701 #[rstest::rstest]
1702 fn test_sanitize_region_options_recovers_json2(
1703 #[values(FormatType::PrimaryKey, FormatType::Flat)] persisted: FormatType,
1704 #[values(None, Some(FormatType::PrimaryKey), Some(FormatType::Flat))] requested: Option<
1705 FormatType,
1706 >,
1707 ) {
1708 let mut manifest = build_test_manifest(persisted);
1709 let mut metadata = manifest.metadata.as_ref().clone();
1710 metadata.column_metadatas[2].column_schema.data_type =
1711 ConcreteDataType::json2(JsonNativeType::Object(JsonObjectType::new()));
1712 manifest.metadata = Arc::new(
1713 RegionMetadataBuilder::from_existing(metadata)
1714 .build()
1715 .unwrap(),
1716 );
1717 let mut options = RegionOptions {
1718 sst_format: requested,
1719 ..Default::default()
1720 };
1721 sanitize_region_options(&manifest, &mut options);
1722 assert_eq!(Some(FormatType::Flat), options.sst_format);
1723 assert_eq!(persisted, manifest.sst_format);
1724 }
1725
1726 fn sst_parquet_bytes(batch: &RecordBatch) -> Vec<u8> {
1727 let key_value_meta = KeyValue::new(
1728 PARQUET_METADATA_KEY.to_string(),
1729 sst_region_metadata().to_json().unwrap(),
1730 );
1731 let props = WriterProperties::builder()
1732 .set_key_value_metadata(Some(vec![key_value_meta]))
1733 .build();
1734
1735 let mut parquet_bytes = Vec::new();
1736 let mut writer =
1737 ArrowWriter::try_new(&mut parquet_bytes, batch.schema(), Some(props)).unwrap();
1738 writer.write(batch).unwrap();
1739 writer.close().unwrap();
1740
1741 parquet_bytes
1742 }
1743
1744 #[tokio::test]
1745 async fn test_preload_parquet_meta_cache_uses_file_cache() {
1746 let env = TestEnv::new().await;
1747
1748 let local_store = ObjectStore::new(Memory::default()).unwrap();
1749 let write_cache = env
1750 .create_write_cache(local_store, ReadableSize::mb(1024))
1751 .await;
1752 let cache_manager = Arc::new(
1753 CacheManager::builder()
1754 .sst_meta_cache_size(1024 * 1024)
1755 .write_cache(Some(write_cache.clone()))
1756 .build(),
1757 );
1758
1759 let region_id = RegionId::new(1, 1);
1760 let file_id = FileId::random();
1761
1762 let col = Arc::new(Int64Array::from_iter_values([1, 2, 3])) as ArrayRef;
1763 let keys =
1764 ["a", "b", "c"].map(|tag| crate::test_util::sst_util::new_primary_key(&[tag, ""]));
1765 let primary_key = Arc::new(BinaryArray::from_iter_values(&keys)) as ArrayRef;
1766 let batch = RecordBatch::try_from_iter([
1767 ("col", col),
1768 (
1769 store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME,
1770 primary_key,
1771 ),
1772 ])
1773 .unwrap();
1774 let parquet_bytes = sst_parquet_bytes(&batch);
1775 let file_size = parquet_bytes.len() as u64;
1776
1777 let file_meta = FileMeta {
1778 region_id,
1779 file_id,
1780 time_range: (Timestamp::new_millisecond(0), Timestamp::new_millisecond(1)),
1781 level: 0,
1782 file_size,
1783 max_row_group_uncompressed_size: 0,
1784 available_indexes: Default::default(),
1785 indexes: vec![],
1786 index_file_size: 0,
1787 index_version: 0,
1788 num_rows: 3,
1789 num_row_groups: 1,
1790 sequence: None,
1791 partition_expr: None,
1792 num_series: 0,
1793 ..Default::default()
1794 };
1795 let file_handle = FileHandle::new(file_meta, Arc::new(NoopFilePurger));
1796
1797 let table_dir = "test_table";
1798 let path_type = PathType::Bare;
1799 let remote_path = file_handle.file_path(table_dir, path_type);
1800
1801 let source_store = ObjectStore::new(Memory::default()).unwrap();
1802 source_store
1803 .write(&remote_path, parquet_bytes)
1804 .await
1805 .unwrap();
1806
1807 let index_key = IndexKey::new(region_id, file_id, FileType::Parquet);
1809 write_cache
1810 .file_cache()
1811 .download(index_key, &remote_path, &source_store, file_size)
1812 .await
1813 .unwrap();
1814
1815 let region_file_id = file_handle.file_id();
1816 assert!(
1817 cache_manager
1818 .get_parquet_meta_data_from_mem_cache(region_file_id)
1819 .await
1820 .is_none()
1821 );
1822
1823 let loaded = preload_parquet_meta_cache_for_files(
1824 region_id,
1825 cache_manager.clone(),
1826 1024 * 1024,
1827 table_dir.to_string(),
1828 path_type,
1829 source_store.clone(),
1830 Arc::new(sst_region_metadata()),
1831 vec![file_handle.clone()],
1832 )
1833 .await;
1834
1835 assert_eq!(loaded, 1);
1837 assert!(
1838 cache_manager
1839 .get_compact_sst_meta_data(
1840 region_file_id,
1841 parquet::file::metadata::PageIndexPolicy::Optional,
1842 )
1843 .await
1844 .is_some()
1845 );
1846 assert!(file_handle.raw_primary_key_range().is_some());
1847 }
1848
1849 #[tokio::test]
1850 async fn test_preload_parquet_meta_cache_skips_files_not_in_file_cache() {
1851 let cache_manager = Arc::new(
1852 CacheManager::builder()
1853 .sst_meta_cache_size(1024 * 1024)
1854 .build(),
1855 );
1856
1857 let region_id = RegionId::new(1, 1);
1858 let file_id = FileId::random();
1859
1860 let file_meta = FileMeta {
1862 region_id,
1863 file_id,
1864 time_range: (Timestamp::new_millisecond(0), Timestamp::new_millisecond(1)),
1865 level: 0,
1866 file_size: 0,
1867 max_row_group_uncompressed_size: 0,
1868 available_indexes: Default::default(),
1869 indexes: vec![],
1870 index_file_size: 0,
1871 index_version: 0,
1872 num_rows: 3,
1873 num_row_groups: 1,
1874 sequence: None,
1875 partition_expr: None,
1876 num_series: 0,
1877 ..Default::default()
1878 };
1879 let file_handle = FileHandle::new(file_meta, Arc::new(NoopFilePurger));
1880
1881 let table_dir = "test_table";
1882 let path_type = PathType::Bare;
1883 let remote_path = file_handle.file_path(table_dir, path_type);
1884
1885 let object_store = ObjectStore::new(Memory::default()).unwrap();
1887 object_store
1888 .write(&remote_path, b"noop".as_slice())
1889 .await
1890 .unwrap();
1891
1892 let region_file_id = file_handle.file_id();
1893 assert!(
1894 cache_manager
1895 .get_parquet_meta_data_from_mem_cache(region_file_id)
1896 .await
1897 .is_none()
1898 );
1899
1900 let loaded = preload_parquet_meta_cache_for_files(
1901 region_id,
1902 cache_manager.clone(),
1903 1024 * 1024,
1904 table_dir.to_string(),
1905 path_type,
1906 object_store,
1907 Arc::new(sst_region_metadata()),
1908 vec![file_handle],
1909 )
1910 .await;
1911
1912 assert_eq!(loaded, 0);
1913 assert!(
1914 cache_manager
1915 .get_parquet_meta_data_from_mem_cache(region_file_id)
1916 .await
1917 .is_none()
1918 );
1919 }
1920
1921 #[tokio::test]
1922 async fn test_preload_parquet_meta_cache_loads_from_local_fs() {
1923 let cache_manager = Arc::new(
1924 CacheManager::builder()
1925 .sst_meta_cache_size(1024 * 1024)
1926 .build(),
1927 );
1928
1929 let region_id = RegionId::new(1, 1);
1930 let file_id = FileId::random();
1931
1932 let col = Arc::new(Int64Array::from_iter_values([1, 2, 3])) as ArrayRef;
1933 let batch = RecordBatch::try_from_iter([("col", col)]).unwrap();
1934 let parquet_bytes = sst_parquet_bytes(&batch);
1935
1936 let file_meta = FileMeta {
1939 region_id,
1940 file_id,
1941 time_range: (Timestamp::new_millisecond(0), Timestamp::new_millisecond(1)),
1942 level: 0,
1943 file_size: 0,
1944 max_row_group_uncompressed_size: 0,
1945 available_indexes: Default::default(),
1946 indexes: vec![],
1947 index_file_size: 0,
1948 index_version: 0,
1949 num_rows: 3,
1950 num_row_groups: 1,
1951 sequence: None,
1952 partition_expr: None,
1953 num_series: 0,
1954 ..Default::default()
1955 };
1956 let file_handle = FileHandle::new(file_meta, Arc::new(NoopFilePurger));
1957
1958 let table_dir = "test_table";
1959 let path_type = PathType::Bare;
1960 let file_path = file_handle.file_path(table_dir, path_type);
1961
1962 let root = create_temp_dir("parquet-meta-preload");
1963 let object_store =
1964 ObjectStore::new(Fs::default().root(root.path().to_str().unwrap())).unwrap();
1965 object_store.write(&file_path, parquet_bytes).await.unwrap();
1966
1967 let region_file_id = file_handle.file_id();
1968 assert!(
1969 cache_manager
1970 .get_parquet_meta_data_from_mem_cache(region_file_id)
1971 .await
1972 .is_none()
1973 );
1974
1975 let loaded = preload_parquet_meta_cache_for_files(
1976 region_id,
1977 cache_manager.clone(),
1978 1024 * 1024,
1979 table_dir.to_string(),
1980 path_type,
1981 object_store,
1982 Arc::new(sst_region_metadata()),
1983 vec![file_handle],
1984 )
1985 .await;
1986
1987 assert_eq!(loaded, 1);
1988 assert!(
1989 cache_manager
1990 .get_compact_sst_meta_data(region_file_id, PageIndexPolicy::Optional)
1991 .await
1992 .is_some()
1993 );
1994 }
1995}