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