1mod alter;
16mod bulk_insert;
17mod catchup;
18mod close;
19mod create;
20mod drop;
21mod flush;
22mod open;
23mod options;
24mod put;
25mod read;
26mod region_metadata;
27mod staging;
28mod state;
29mod sync;
30
31use std::any::Any;
32use std::collections::HashMap;
33use std::sync::{Arc, RwLock};
34use std::time::Instant;
35
36use api::region::RegionResponse;
37use async_trait::async_trait;
38use common_error::ext::{BoxedError, ErrorExt};
39use common_error::status_code::StatusCode;
40use common_runtime::RepeatedTask;
41use mito2::engine::MitoEngine;
42pub(crate) use options::IndexOptions;
43use snafu::{OptionExt, ResultExt};
44pub(crate) use state::MetricEngineState;
45use store_api::metadata::RegionMetadataRef;
46use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
47use store_api::region_engine::{
48 BatchResponses, RegionEngine, RegionRole, RegionScannerRef, RegionStatistic,
49 RemapManifestsRequest, RemapManifestsResponse, SetRegionRoleStateResponse,
50 SetRegionRoleStateSuccess, SettableRegionRoleState, SyncRegionFromRequest,
51 SyncRegionFromResponse,
52};
53use store_api::region_request::{
54 AffectedRows, BatchRegionDdlRequest, RegionCatchupRequest, RegionOpenRequest, RegionPutRequest,
55 RegionRequest, RegionTruncateRequest,
56};
57use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
58
59use crate::config::EngineConfig;
60use crate::data_region::DataRegion;
61use crate::error::{
62 self, Error, Result, StartRepeatedTaskSnafu, UnsupportedRegionRequestSnafu,
63 UnsupportedRemapManifestsRequestSnafu,
64};
65use crate::metadata_region::MetadataRegion;
66use crate::repeated_task::FlushMetadataRegionTask;
67use crate::row_modifier::RowModifier;
68use crate::utils::{self, get_region_statistic};
69
70#[cfg_attr(doc, aquamarine::aquamarine)]
71#[derive(Clone)]
133pub struct MetricEngine {
134 inner: Arc<MetricEngineInner>,
135}
136
137#[async_trait]
138impl RegionEngine for MetricEngine {
139 fn name(&self) -> &str {
141 METRIC_ENGINE_NAME
142 }
143
144 async fn handle_batch_open_requests(
145 &self,
146 parallelism: usize,
147 requests: Vec<(RegionId, RegionOpenRequest)>,
148 ) -> Result<BatchResponses, BoxedError> {
149 self.inner
150 .handle_batch_open_requests(parallelism, requests)
151 .await
152 .map_err(BoxedError::new)
153 }
154
155 async fn handle_batch_catchup_requests(
156 &self,
157 parallelism: usize,
158 requests: Vec<(RegionId, RegionCatchupRequest)>,
159 ) -> Result<BatchResponses, BoxedError> {
160 self.inner
161 .handle_batch_catchup_requests(parallelism, requests)
162 .await
163 .map_err(BoxedError::new)
164 }
165
166 async fn handle_batch_ddl_requests(
167 &self,
168 batch_request: BatchRegionDdlRequest,
169 ) -> Result<RegionResponse, BoxedError> {
170 match batch_request {
171 BatchRegionDdlRequest::Create(requests) => {
172 let mut extension_return_value = HashMap::new();
173 let rows = self
174 .inner
175 .create_regions(requests, &mut extension_return_value)
176 .await
177 .map_err(BoxedError::new)?;
178
179 Ok(RegionResponse {
180 affected_rows: rows,
181 extensions: extension_return_value,
182 metadata: Vec::new(),
183 })
184 }
185 BatchRegionDdlRequest::Alter(requests) => {
186 let mut extension_return_value = HashMap::new();
187 let rows = self
188 .inner
189 .alter_regions(requests, &mut extension_return_value)
190 .await
191 .map_err(BoxedError::new)?;
192
193 Ok(RegionResponse {
194 affected_rows: rows,
195 extensions: extension_return_value,
196 metadata: Vec::new(),
197 })
198 }
199 BatchRegionDdlRequest::Drop(requests) => {
200 self.handle_requests(
201 requests
202 .into_iter()
203 .map(|(region_id, req)| (region_id, RegionRequest::Drop(req))),
204 )
205 .await
206 }
207 }
208 }
209
210 async fn handle_request(
212 &self,
213 region_id: RegionId,
214 request: RegionRequest,
215 ) -> Result<RegionResponse, BoxedError> {
216 let mut extension_return_value = HashMap::new();
217
218 let result = match request {
219 RegionRequest::EnterStaging(_) => {
220 if self.inner.is_physical_region(region_id) {
221 self.handle_enter_staging_request(region_id, request).await
222 } else {
223 UnsupportedRegionRequestSnafu { request }.fail()
224 }
225 }
226 RegionRequest::ApplyStagingManifest(_) => {
227 if self.inner.is_physical_region(region_id) {
228 return self.inner.mito.handle_request(region_id, request).await;
229 } else {
230 UnsupportedRegionRequestSnafu { request }.fail()
231 }
232 }
233 RegionRequest::Put(put) => self.inner.put_region(region_id, put).await,
234 RegionRequest::Create(create) => {
235 self.inner
236 .create_regions(vec![(region_id, create)], &mut extension_return_value)
237 .await
238 }
239 RegionRequest::Drop(drop) => self.inner.drop_region(region_id, drop).await,
240 RegionRequest::Open(open) => self.inner.open_region(region_id, open).await,
241 RegionRequest::CleanUp(clean_up) => {
242 self.inner.clean_up_region(region_id, clean_up).await
243 }
244 RegionRequest::Close(close) => self.inner.close_region(region_id, close).await,
245 RegionRequest::Alter(alter) => {
246 self.inner
247 .alter_regions(vec![(region_id, alter)], &mut extension_return_value)
248 .await
249 }
250 RegionRequest::Compact(_) | RegionRequest::CancelCompaction(_) => {
251 if self.inner.is_physical_region(region_id) {
252 self.inner
253 .mito
254 .handle_request(region_id, request)
255 .await
256 .context(error::MitoFlushOperationSnafu)
257 .map(|response| response.affected_rows)
258 } else {
259 UnsupportedRegionRequestSnafu { request }.fail()
260 }
261 }
262 RegionRequest::Flush(req) => self.inner.flush_region(region_id, req).await,
263 RegionRequest::BuildIndex(_) => {
264 if self.inner.is_physical_region(region_id) {
265 self.inner
266 .mito
267 .handle_request(region_id, request)
268 .await
269 .context(error::MitoFlushOperationSnafu)
270 .map(|response| response.affected_rows)
271 } else {
272 UnsupportedRegionRequestSnafu { request }.fail()
273 }
274 }
275 RegionRequest::Truncate(RegionTruncateRequest::Unflushed) => {
276 if self.inner.is_physical_region(region_id) {
277 self.inner
278 .mito
279 .handle_request(
280 utils::to_data_region_id(region_id),
281 RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
282 )
283 .await
284 .context(error::MitoTruncateOperationSnafu)
285 .map(|response| response.affected_rows)
286 } else {
287 UnsupportedRegionRequestSnafu {
288 request: RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
289 }
290 .fail()
291 }
292 }
293 RegionRequest::Truncate(request) => UnsupportedRegionRequestSnafu {
294 request: RegionRequest::Truncate(request),
295 }
296 .fail(),
297 RegionRequest::Delete(delete) => self.inner.delete_region(region_id, delete).await,
298 RegionRequest::Catchup(_) => {
299 let mut response = self
300 .inner
301 .handle_batch_catchup_requests(
302 1,
303 vec![(region_id, RegionCatchupRequest::default())],
304 )
305 .await
306 .map_err(BoxedError::new)?;
307 debug_assert_eq!(response.len(), 1);
308 let (resp_region_id, response) = response
309 .pop()
310 .context(error::UnexpectedRequestSnafu {
311 reason: "expected 1 response, but got zero responses",
312 })
313 .map_err(BoxedError::new)?;
314 debug_assert_eq!(region_id, resp_region_id);
315 return response;
316 }
317 RegionRequest::BulkInserts(bulk) => {
318 self.inner.bulk_insert_region(region_id, bulk).await
319 }
320 };
321
322 result.map_err(BoxedError::new).map(|rows| RegionResponse {
323 affected_rows: rows,
324 extensions: extension_return_value,
325 metadata: Vec::new(),
326 })
327 }
328
329 async fn handle_query(
330 &self,
331 region_id: RegionId,
332 request: ScanRequest,
333 ) -> Result<RegionScannerRef, BoxedError> {
334 self.handle_query(region_id, request).await
335 }
336
337 async fn get_committed_sequence(
338 &self,
339 region_id: RegionId,
340 ) -> Result<SequenceNumber, BoxedError> {
341 self.inner
342 .get_last_seq_num(region_id)
343 .await
344 .map_err(BoxedError::new)
345 }
346
347 async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
349 self.inner
350 .load_region_metadata(region_id)
351 .await
352 .map_err(BoxedError::new)
353 }
354
355 fn region_statistic(&self, region_id: RegionId) -> Option<RegionStatistic> {
359 if self.inner.is_physical_region(region_id) {
360 get_region_statistic(&self.inner.mito, region_id)
361 } else {
362 None
363 }
364 }
365
366 async fn stop(&self) -> Result<(), BoxedError> {
368 Ok(())
370 }
371
372 fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
373 for x in [
375 utils::to_metadata_region_id(region_id),
376 utils::to_data_region_id(region_id),
377 ] {
378 if let Err(e) = self.inner.mito.set_region_role(x, role)
379 && e.status_code() != StatusCode::RegionNotFound
380 {
381 return Err(e);
382 }
383 }
384 Ok(())
385 }
386
387 fn set_region_role_with_lease(
388 &self,
389 region_id: RegionId,
390 role: RegionRole,
391 sent_at: Instant,
392 deadline: Instant,
393 ) -> Result<bool, BoxedError> {
394 for x in [
397 utils::to_metadata_region_id(region_id),
398 utils::to_data_region_id(region_id),
399 ] {
400 match self
401 .inner
402 .mito
403 .set_region_role_with_lease(x, role, sent_at, deadline)
404 {
405 Ok(true) => {}
406 Ok(false) => return Ok(false),
407 Err(e) if e.status_code() != StatusCode::RegionNotFound => return Err(e),
408 Err(_) => {}
409 }
410 }
411 Ok(true)
412 }
413
414 async fn sync_region(
415 &self,
416 region_id: RegionId,
417 request: SyncRegionFromRequest,
418 ) -> Result<SyncRegionFromResponse, BoxedError> {
419 match request {
420 SyncRegionFromRequest::FromManifest(manifest_info) => self
421 .inner
422 .sync_region_from_manifest(region_id, manifest_info)
423 .await
424 .map_err(BoxedError::new),
425 SyncRegionFromRequest::FromRegion {
426 source_region_id,
427 parallelism,
428 } => {
429 if self.inner.is_physical_region(region_id) {
430 self.inner
431 .sync_region_from_region(region_id, source_region_id, parallelism)
432 .await
433 .map_err(BoxedError::new)
434 } else {
435 Err(BoxedError::new(
436 error::UnsupportedSyncRegionFromRequestSnafu { region_id }.build(),
437 ))
438 }
439 }
440 }
441 }
442
443 async fn remap_manifests(
444 &self,
445 request: RemapManifestsRequest,
446 ) -> Result<RemapManifestsResponse, BoxedError> {
447 let region_id = request.region_id;
448 if self.inner.is_physical_region(region_id) {
449 self.inner.mito.remap_manifests(request).await
450 } else {
451 Err(BoxedError::new(
452 UnsupportedRemapManifestsRequestSnafu { region_id }.build(),
453 ))
454 }
455 }
456
457 async fn set_region_role_state_gracefully(
458 &self,
459 region_id: RegionId,
460 region_role_state: SettableRegionRoleState,
461 ) -> std::result::Result<SetRegionRoleStateResponse, BoxedError> {
462 let metadata_result = match self
463 .inner
464 .mito
465 .set_region_role_state_gracefully(
466 utils::to_metadata_region_id(region_id),
467 region_role_state,
468 )
469 .await?
470 {
471 SetRegionRoleStateResponse::Success(success) => success,
472 SetRegionRoleStateResponse::NotFound => {
473 return Ok(SetRegionRoleStateResponse::NotFound);
474 }
475 SetRegionRoleStateResponse::InvalidTransition(error) => {
476 return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
477 }
478 };
479
480 let data_result = match self
481 .inner
482 .mito
483 .set_region_role_state_gracefully(region_id, region_role_state)
484 .await?
485 {
486 SetRegionRoleStateResponse::Success(success) => success,
487 SetRegionRoleStateResponse::NotFound => {
488 return Ok(SetRegionRoleStateResponse::NotFound);
489 }
490 SetRegionRoleStateResponse::InvalidTransition(error) => {
491 return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
492 }
493 };
494
495 Ok(SetRegionRoleStateResponse::success(
496 SetRegionRoleStateSuccess::metric(
497 data_result.last_entry_id().unwrap_or_default(),
498 metadata_result.last_entry_id().unwrap_or_default(),
499 ),
500 ))
501 }
502
503 fn role(&self, region_id: RegionId) -> Option<RegionRole> {
507 if self.inner.is_physical_region(region_id) {
508 self.inner.mito.role(region_id)
509 } else {
510 None
511 }
512 }
513
514 fn as_any(&self) -> &dyn Any {
515 self
516 }
517}
518
519#[derive(Debug)]
523pub struct PutGroupResult {
524 pub request_indices: Vec<usize>,
526 pub result: Result<AffectedRows>,
528}
529
530impl MetricEngine {
531 pub fn try_new(mito: MitoEngine, mut config: EngineConfig) -> Result<Self> {
532 let metadata_region = MetadataRegion::new(mito.clone());
533 let data_region = DataRegion::new(mito.clone());
534 let state = Arc::new(RwLock::default());
535 config.sanitize();
536 let flush_interval = config.flush_metadata_region_interval;
537 let inner = Arc::new(MetricEngineInner {
538 mito: mito.clone(),
539 metadata_region,
540 data_region,
541 state: state.clone(),
542 row_modifier: RowModifier::default(),
543 flush_task: RepeatedTask::new(
544 flush_interval,
545 Box::new(FlushMetadataRegionTask {
546 state: state.clone(),
547 mito: mito.clone(),
548 }),
549 ),
550 });
551 inner
552 .flush_task
553 .start(common_runtime::global_runtime())
554 .context(StartRepeatedTaskSnafu { name: "flush_task" })?;
555 Ok(Self { inner })
556 }
557
558 pub fn mito(&self) -> MitoEngine {
559 self.inner.mito.clone()
560 }
561
562 pub async fn put_regions_batch(
564 &self,
565 requests: impl ExactSizeIterator<Item = (RegionId, RegionPutRequest)>,
566 ) -> Vec<PutGroupResult> {
567 self.inner.put_regions_batch(requests).await
568 }
569
570 pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
572 self.inner
573 .metadata_region
574 .logical_regions(physical_region_id)
575 .await
576 }
577
578 async fn handle_query(
580 &self,
581 region_id: RegionId,
582 request: ScanRequest,
583 ) -> Result<RegionScannerRef, BoxedError> {
584 self.inner
585 .read_region(region_id, request)
586 .await
587 .map_err(BoxedError::new)
588 }
589
590 async fn handle_requests(
591 &self,
592 requests: impl IntoIterator<Item = (RegionId, RegionRequest)>,
593 ) -> Result<RegionResponse, BoxedError> {
594 let mut affected_rows = 0;
595 let mut extensions = HashMap::new();
596 for (region_id, request) in requests {
597 let response = self.handle_request(region_id, request).await?;
598 affected_rows += response.affected_rows;
599 extensions.extend(response.extensions);
600 }
601
602 Ok(RegionResponse {
603 affected_rows,
604 extensions,
605 metadata: Vec::new(),
606 })
607 }
608}
609
610fn validate_shared_column(
612 region_id: store_api::storage::RegionId,
613 existing: &store_api::metadata::ColumnMetadata,
614 proposed: &store_api::metadata::ColumnMetadata,
615) -> crate::error::Result<()> {
616 snafu::ensure!(
617 existing.column_schema.data_type == proposed.column_schema.data_type
618 && existing.semantic_type == proposed.semantic_type,
619 crate::error::InvalidRequestSnafu {
620 region_id,
621 reason: format!(
622 "incompatible shared physical column `{}`",
623 proposed.column_schema.name
624 ),
625 }
626 );
627 Ok(())
628}
629
630#[cfg(test)]
631impl MetricEngine {
632 pub async fn scan_to_stream(
633 &self,
634 region_id: RegionId,
635 request: ScanRequest,
636 ) -> Result<common_recordbatch::SendableRecordBatchStream, BoxedError> {
637 self.inner.scan_to_stream(region_id, request).await
638 }
639}
640
641struct MetricEngineInner {
642 mito: MitoEngine,
643 metadata_region: MetadataRegion,
644 data_region: DataRegion,
645 state: Arc<RwLock<MetricEngineState>>,
646 row_modifier: RowModifier,
647 flush_task: RepeatedTask<Error>,
648}
649
650#[cfg(test)]
651mod test {
652 use std::assert_matches;
653 use std::collections::HashMap;
654
655 use api::v1::Rows;
656 use common_recordbatch::RecordBatches;
657 use common_telemetry::info;
658 use common_wal::options::{KafkaWalOptions, WalOptions};
659 use mito2::sst::location::region_dir_from_table_dir;
660 use mito2::test_util::{kafka_log_store_factory, prepare_test_for_kafka_log_store};
661 use store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
662 use store_api::mito_engine_options::WAL_OPTIONS_KEY;
663 use store_api::region_request::{
664 PathType, RegionCancelCompactionRequest, RegionCleanUpRequest, RegionCloseRequest,
665 RegionDropRequest, RegionFlushRequest, RegionOpenRequest, RegionPutRequest, RegionRequest,
666 RegionTruncateRequest,
667 };
668
669 use super::*;
670 use crate::maybe_skip_kafka_log_store_integration_test;
671 use crate::test_util::{
672 TestEnv, build_rows, create_logical_region_request, row_schema_with_tags,
673 };
674
675 #[tokio::test]
676 async fn test_build_series_index_forwarding() {
677 use api::v1::region::build_index_request;
678 use store_api::region_request::RegionBuildIndexRequest;
679
680 let env = TestEnv::new().await;
681 env.init_metric_region().await;
682 let request = || {
683 RegionRequest::BuildIndex(RegionBuildIndexRequest {
684 options: Some(build_index_request::Options::SeriesIndex(Default::default())),
685 })
686 };
687 let error = env
689 .metric()
690 .handle_request(env.default_physical_region_id(), request())
691 .await
692 .unwrap_err();
693 let error = format!("{error:?}");
694 assert!(error.contains("series index is disabled"), "{error}");
695 let error = env
696 .metric()
697 .handle_request(env.default_logical_region_id(), request())
698 .await
699 .unwrap_err();
700 assert_eq!(
701 common_error::status_code::StatusCode::Unsupported,
702 error.status_code()
703 );
704 }
705
706 #[tokio::test]
707 async fn test_cancel_compaction_physical_and_logical_regions() {
708 let env = TestEnv::new().await;
709 env.init_metric_region().await;
710 let request = || {
711 RegionRequest::CancelCompaction(RegionCancelCompactionRequest {
712 task_id: "stale-task-id".to_string(),
713 })
714 };
715 let response = env
717 .metric()
718 .handle_request(env.default_physical_region_id(), request())
719 .await
720 .unwrap();
721 assert_eq!(response.affected_rows, 0);
722
723 let error = env
724 .metric()
725 .handle_request(env.default_logical_region_id(), request())
726 .await
727 .unwrap_err();
728 assert_eq!(
729 common_error::status_code::StatusCode::Unsupported,
730 error.status_code()
731 );
732 }
733
734 #[tokio::test]
735 async fn test_discard_unflushed_data_only() {
736 let env = TestEnv::new().await;
737 env.init_metric_region().await;
738
739 let engine = env.metric();
740 let mito = env.mito();
741 let physical_region_id = env.default_physical_region_id();
742 let logical_region_id = env.default_logical_region_id();
743 let data_region_id = utils::to_data_region_id(physical_region_id);
744 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
745
746 let metadata_memtable_size = mito
747 .region_statistic(metadata_region_id)
748 .unwrap()
749 .memtable_size;
750 assert!(metadata_memtable_size > 0);
751
752 engine
753 .handle_request(
754 logical_region_id,
755 RegionRequest::Put(RegionPutRequest {
756 skip_wal: false,
757 rows: Rows {
758 schema: row_schema_with_tags(&["job"]),
759 rows: build_rows(1, 5),
760 },
761 hint: None,
762 partition_expr_version: None,
763 }),
764 )
765 .await
766 .unwrap();
767 assert!(mito.region_statistic(data_region_id).unwrap().memtable_size > 0);
768
769 engine
770 .handle_request(
771 physical_region_id,
772 RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
773 )
774 .await
775 .unwrap();
776
777 assert_eq!(
778 0,
779 mito.region_statistic(data_region_id).unwrap().memtable_size
780 );
781 assert_eq!(
782 metadata_memtable_size,
783 mito.region_statistic(metadata_region_id)
784 .unwrap()
785 .memtable_size
786 );
787
788 let stream = engine
789 .scan_to_stream(logical_region_id, ScanRequest::default())
790 .await
791 .unwrap();
792 let batches = RecordBatches::try_collect(stream).await.unwrap();
793 assert_eq!(
794 0,
795 batches.iter().map(|batch| batch.num_rows()).sum::<usize>()
796 );
797
798 engine
800 .handle_request(
801 logical_region_id,
802 RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
803 )
804 .await
805 .unwrap_err();
806 }
807
808 #[tokio::test]
809 async fn close_open_regions() {
810 let env = TestEnv::new().await;
811 env.init_metric_region().await;
812 let engine = env.metric();
813
814 let physical_region_id = env.default_physical_region_id();
816 engine
817 .handle_request(
818 physical_region_id,
819 RegionRequest::Close(RegionCloseRequest::default()),
820 )
821 .await
822 .unwrap();
823
824 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
826 .into_iter()
827 .collect();
828 let open_request = RegionOpenRequest {
829 engine: METRIC_ENGINE_NAME.to_string(),
830 table_dir: TestEnv::default_table_dir(),
831 path_type: PathType::Bare, options: physical_region_option,
833 skip_wal_replay: false,
834 checkpoint: None,
835 requirements: Default::default(),
836 };
837 engine
838 .handle_request(physical_region_id, RegionRequest::Open(open_request))
839 .await
840 .unwrap();
841
842 let nonexistent_region_id = RegionId::new(12313, 12);
844 engine
845 .handle_request(
846 nonexistent_region_id,
847 RegionRequest::Close(RegionCloseRequest::default()),
848 )
849 .await
850 .unwrap();
851
852 let invalid_open_request = RegionOpenRequest {
854 engine: METRIC_ENGINE_NAME.to_string(),
855 table_dir: TestEnv::default_table_dir(),
856 path_type: PathType::Bare, options: HashMap::new(),
858 skip_wal_replay: false,
859 checkpoint: None,
860 requirements: Default::default(),
861 };
862 engine
863 .handle_request(
864 nonexistent_region_id,
865 RegionRequest::Open(invalid_open_request),
866 )
867 .await
868 .unwrap();
869 }
870
871 #[tokio::test]
872 async fn test_offline_cleanup_physical_region() {
873 let env = TestEnv::new().await;
874 env.init_metric_region().await;
875 let engine = env.metric();
876 let mito = env.mito();
877 let physical_region_id = env.default_physical_region_id();
878 let metadata_region_id = crate::utils::to_metadata_region_id(physical_region_id);
879 let data_region_id = crate::utils::to_data_region_id(physical_region_id);
880
881 engine
882 .handle_request(
883 physical_region_id,
884 RegionRequest::Close(RegionCloseRequest::default()),
885 )
886 .await
887 .unwrap();
888
889 let object_store = env.get_object_store().unwrap();
890 let metadata_region_dir = region_dir_from_table_dir(
891 &TestEnv::default_table_dir(),
892 metadata_region_id,
893 PathType::Metadata,
894 );
895 let data_region_dir = region_dir_from_table_dir(
896 &TestEnv::default_table_dir(),
897 data_region_id,
898 PathType::Data,
899 );
900 assert!(object_store.exists(&metadata_region_dir).await.unwrap());
901 assert!(object_store.exists(&data_region_dir).await.unwrap());
902
903 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
904 .into_iter()
905 .collect();
906 let clean_up_request = RegionCleanUpRequest {
907 engine: METRIC_ENGINE_NAME.to_string(),
908 table_dir: TestEnv::default_table_dir(),
909 path_type: PathType::Bare,
910 options: physical_region_option,
911 };
912 engine
913 .handle_request(physical_region_id, RegionRequest::CleanUp(clean_up_request))
914 .await
915 .unwrap();
916
917 assert!(!mito.is_region_exists(metadata_region_id));
918 assert!(!mito.is_region_exists(data_region_id));
919 assert!(!object_store.exists(&metadata_region_dir).await.unwrap());
920 assert!(!object_store.exists(&data_region_dir).await.unwrap());
921 }
922
923 #[tokio::test]
924 async fn test_leader_grant_ignored_by_metadata_region_leaves_data_region() {
925 let env = TestEnv::new().await;
926 env.init_metric_region().await;
927 let (engine, mito) = (env.metric(), env.mito());
928 let physical_region_id = env.default_physical_region_id();
929 let lease = std::time::Duration::from_secs(10);
930 let downgrade = |region_id| {
932 mito.set_region_role_state_gracefully(region_id, SettableRegionRoleState::Follower)
933 };
934 downgrade(utils::to_data_region_id(physical_region_id))
935 .await
936 .unwrap();
937 let sent_at = Instant::now();
938 downgrade(utils::to_metadata_region_id(physical_region_id))
939 .await
940 .unwrap();
941
942 let grant = |sent_at: Instant| {
943 engine
944 .set_region_role_with_lease(
945 physical_region_id,
946 RegionRole::Leader,
947 sent_at,
948 sent_at + lease,
949 )
950 .unwrap()
951 };
952 assert!(!grant(sent_at));
953 assert_eq!(engine.role(physical_region_id), Some(RegionRole::Follower));
954
955 assert!(grant(Instant::now()));
956 assert_eq!(engine.role(physical_region_id), Some(RegionRole::Leader));
957 }
958
959 #[tokio::test]
960 async fn test_role() {
961 let env = TestEnv::new().await;
962 env.init_metric_region().await;
963
964 let logical_region_id = env.default_logical_region_id();
965 let physical_region_id = env.default_physical_region_id();
966
967 assert!(env.metric().role(logical_region_id).is_none());
968 assert!(env.metric().role(physical_region_id).is_some());
969 }
970
971 #[tokio::test]
972 async fn test_region_disk_usage() {
973 let env = TestEnv::new().await;
974 env.init_metric_region().await;
975
976 let logical_region_id = env.default_logical_region_id();
977 let physical_region_id = env.default_physical_region_id();
978
979 assert!(env.metric().region_statistic(logical_region_id).is_none());
980 assert!(env.metric().region_statistic(physical_region_id).is_some());
981 }
982
983 #[tokio::test]
984 async fn test_open_region_failure() {
985 let env = TestEnv::new().await;
986 env.init_metric_region().await;
987 let physical_region_id = env.default_physical_region_id();
988
989 let metric_engine = env.metric();
990 metric_engine
991 .handle_request(
992 physical_region_id,
993 RegionRequest::Flush(RegionFlushRequest::default()),
994 )
995 .await
996 .unwrap();
997
998 let path = region_dir_from_table_dir(
999 &TestEnv::default_table_dir(),
1000 physical_region_id,
1001 PathType::Metadata,
1002 );
1003 let object_store = env.get_object_store().unwrap();
1004 let list = object_store.list(&path).await.unwrap();
1005 for entry in list {
1007 if entry.metadata().is_dir() {
1008 continue;
1009 }
1010 if entry.name().ends_with("parquet") {
1011 info!("deleting {}", entry.path());
1012 object_store.delete(entry.path()).await.unwrap();
1013 }
1014 }
1015
1016 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
1017 .into_iter()
1018 .collect();
1019 let open_request = RegionOpenRequest {
1020 engine: METRIC_ENGINE_NAME.to_string(),
1021 table_dir: TestEnv::default_table_dir(),
1022 path_type: PathType::Bare,
1023 options: physical_region_option,
1024 skip_wal_replay: false,
1025 checkpoint: None,
1026 requirements: Default::default(),
1027 };
1028 metric_engine
1031 .handle_request(physical_region_id, RegionRequest::Open(open_request))
1032 .await
1033 .unwrap();
1034
1035 metric_engine
1037 .handle_request(
1038 physical_region_id,
1039 RegionRequest::Close(RegionCloseRequest::default()),
1040 )
1041 .await
1042 .unwrap();
1043
1044 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
1046 .into_iter()
1047 .collect();
1048 let open_request = RegionOpenRequest {
1049 engine: METRIC_ENGINE_NAME.to_string(),
1050 table_dir: TestEnv::default_table_dir(),
1051 path_type: PathType::Bare,
1052 options: physical_region_option,
1053 skip_wal_replay: false,
1054 checkpoint: None,
1055 requirements: Default::default(),
1056 };
1057 let err = metric_engine
1058 .handle_request(physical_region_id, RegionRequest::Open(open_request))
1059 .await
1060 .unwrap_err();
1061 assert_eq!(err.status_code(), StatusCode::StorageUnavailable);
1063
1064 let mito_engine = metric_engine.mito();
1065 let data_region_id = utils::to_data_region_id(physical_region_id);
1066 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
1067 let err = mito_engine.get_metadata(data_region_id).await.unwrap_err();
1069 assert_eq!(err.status_code(), StatusCode::RegionNotFound);
1070 let err = mito_engine
1071 .get_metadata(metadata_region_id)
1072 .await
1073 .unwrap_err();
1074 assert_eq!(err.status_code(), StatusCode::RegionNotFound);
1075 }
1076
1077 #[tokio::test]
1078 async fn test_catchup_regions() {
1079 common_telemetry::init_default_ut_logging();
1080 maybe_skip_kafka_log_store_integration_test!();
1081 let kafka_log_store_factory = kafka_log_store_factory().unwrap();
1082 let mito_env = mito2::test_util::TestEnv::new()
1083 .await
1084 .with_log_store_factory(kafka_log_store_factory.clone());
1085 let env = TestEnv::with_mito_env(mito_env).await;
1086 let table_dir = |region_id| format!("table/{region_id}");
1087 let mut physical_region_ids = vec![];
1088 let mut logical_region_ids = vec![];
1089
1090 let num_topics = 3;
1091 let num_physical_regions = 8;
1092 let num_logical_regions = 16;
1093 let parallelism = 2;
1094 let mut topics = Vec::with_capacity(num_topics);
1095 for _ in 0..num_topics {
1096 let topic = prepare_test_for_kafka_log_store(&kafka_log_store_factory)
1097 .await
1098 .unwrap();
1099 topics.push(topic);
1100 }
1101
1102 let topic_idx = |id| (id as usize) % num_topics;
1103 for i in 0..num_physical_regions {
1105 let physical_region_id = RegionId::new(1, i);
1106 physical_region_ids.push(physical_region_id);
1107
1108 let wal_options = WalOptions::Kafka(KafkaWalOptions::new(topics[topic_idx(i)].clone()));
1109 env.create_physical_region(
1110 physical_region_id,
1111 &table_dir(physical_region_id),
1112 vec![(
1113 WAL_OPTIONS_KEY.to_string(),
1114 serde_json::to_string(&wal_options).unwrap(),
1115 )],
1116 )
1117 .await;
1118 for j in 0..num_logical_regions {
1120 let logical_region_id = RegionId::new(1024 + i, j);
1121 logical_region_ids.push(logical_region_id);
1122 env.create_logical_region(physical_region_id, logical_region_id)
1123 .await;
1124 }
1125 }
1126
1127 let metric_engine = env.metric();
1128 for region_id in logical_region_ids.iter().chain(physical_region_ids.iter()) {
1130 metric_engine
1131 .handle_request(
1132 *region_id,
1133 RegionRequest::Close(RegionCloseRequest::default()),
1134 )
1135 .await
1136 .unwrap();
1137 }
1138
1139 let requests = physical_region_ids
1141 .iter()
1142 .enumerate()
1143 .map(|(idx, region_id)| {
1144 let mut options = HashMap::new();
1145 let wal_options =
1146 WalOptions::Kafka(KafkaWalOptions::new(topics[topic_idx(idx as u32)].clone()));
1147 options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new());
1148 options.insert(
1149 WAL_OPTIONS_KEY.to_string(),
1150 serde_json::to_string(&wal_options).unwrap(),
1151 );
1152 (
1153 *region_id,
1154 RegionOpenRequest {
1155 engine: METRIC_ENGINE_NAME.to_string(),
1156 table_dir: table_dir(*region_id),
1157 path_type: PathType::Bare,
1158 options: options.clone(),
1159 skip_wal_replay: true,
1160 checkpoint: None,
1161 requirements: Default::default(),
1162 },
1163 )
1164 })
1165 .collect::<Vec<_>>();
1166 info!("Open batch regions with parallelism: {parallelism}");
1167 metric_engine
1168 .handle_batch_open_requests(parallelism, requests)
1169 .await
1170 .unwrap();
1171 {
1172 let state = metric_engine.inner.state.read().unwrap();
1173 for logical_region in &logical_region_ids {
1174 assert!(!state.logical_regions().contains_key(logical_region));
1175 }
1176 }
1177
1178 let catch_requests = physical_region_ids
1179 .iter()
1180 .map(|region_id| {
1181 (
1182 *region_id,
1183 RegionCatchupRequest {
1184 set_writable: true,
1185 ..Default::default()
1186 },
1187 )
1188 })
1189 .collect::<Vec<_>>();
1190 metric_engine
1191 .handle_batch_catchup_requests(parallelism, catch_requests)
1192 .await
1193 .unwrap();
1194 {
1195 let state = metric_engine.inner.state.read().unwrap();
1196 for logical_region in &logical_region_ids {
1197 assert!(state.logical_regions().contains_key(logical_region));
1198 }
1199 }
1200 }
1201
1202 #[tokio::test]
1203 async fn test_drop_region() {
1204 let env = TestEnv::new().await;
1205 let engine = env.metric();
1206 let physical_region_id1 = RegionId::new(1024, 0);
1207 let logical_region_id1 = RegionId::new(1025, 0);
1208 env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
1209 .await;
1210 let region_create_request1 =
1211 create_logical_region_request(&["job"], physical_region_id1, "logical1");
1212 engine
1213 .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![(
1214 logical_region_id1,
1215 region_create_request1,
1216 )]))
1217 .await
1218 .unwrap();
1219 let err = engine
1220 .handle_request(
1221 physical_region_id1,
1222 RegionRequest::Drop(RegionDropRequest {
1223 fast_path: false,
1224 force: false,
1225 partial_drop: false,
1226 }),
1227 )
1228 .await
1229 .unwrap_err();
1230 assert_matches!(
1231 err.as_any().downcast_ref::<Error>().unwrap(),
1232 &Error::PhysicalRegionBusy { .. }
1233 );
1234
1235 engine
1236 .handle_request(
1237 physical_region_id1,
1238 RegionRequest::Drop(RegionDropRequest {
1239 fast_path: false,
1240 force: true,
1241 partial_drop: false,
1242 }),
1243 )
1244 .await
1245 .unwrap();
1246 assert!(
1247 engine
1248 .inner
1249 .state
1250 .read()
1251 .unwrap()
1252 .physical_region_states()
1253 .get(&physical_region_id1)
1254 .is_none()
1255 );
1256 assert!(
1257 engine
1258 .inner
1259 .state
1260 .read()
1261 .unwrap()
1262 .logical_regions()
1263 .get(&logical_region_id1)
1264 .is_none()
1265 );
1266 }
1267}