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};
34
35use api::region::RegionResponse;
36use async_trait::async_trait;
37use common_error::ext::{BoxedError, ErrorExt};
38use common_error::status_code::StatusCode;
39use common_runtime::RepeatedTask;
40use mito2::engine::MitoEngine;
41pub(crate) use options::IndexOptions;
42use snafu::{OptionExt, ResultExt};
43pub(crate) use state::MetricEngineState;
44use store_api::metadata::RegionMetadataRef;
45use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
46use store_api::region_engine::{
47 BatchResponses, RegionEngine, RegionRole, RegionScannerRef, RegionStatistic,
48 RemapManifestsRequest, RemapManifestsResponse, SetRegionRoleStateResponse,
49 SetRegionRoleStateSuccess, SettableRegionRoleState, SyncRegionFromRequest,
50 SyncRegionFromResponse,
51};
52use store_api::region_request::{
53 AffectedRows, BatchRegionDdlRequest, RegionCatchupRequest, RegionOpenRequest, RegionPutRequest,
54 RegionRequest,
55};
56use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
57
58use crate::config::EngineConfig;
59use crate::data_region::DataRegion;
60use crate::error::{
61 self, Error, Result, StartRepeatedTaskSnafu, UnsupportedRegionRequestSnafu,
62 UnsupportedRemapManifestsRequestSnafu,
63};
64use crate::metadata_region::MetadataRegion;
65use crate::repeated_task::FlushMetadataRegionTask;
66use crate::row_modifier::RowModifier;
67use crate::utils::{self, get_region_statistic};
68
69#[cfg_attr(doc, aquamarine::aquamarine)]
70#[derive(Clone)]
132pub struct MetricEngine {
133 inner: Arc<MetricEngineInner>,
134}
135
136#[async_trait]
137impl RegionEngine for MetricEngine {
138 fn name(&self) -> &str {
140 METRIC_ENGINE_NAME
141 }
142
143 async fn handle_batch_open_requests(
144 &self,
145 parallelism: usize,
146 requests: Vec<(RegionId, RegionOpenRequest)>,
147 ) -> Result<BatchResponses, BoxedError> {
148 self.inner
149 .handle_batch_open_requests(parallelism, requests)
150 .await
151 .map_err(BoxedError::new)
152 }
153
154 async fn handle_batch_catchup_requests(
155 &self,
156 parallelism: usize,
157 requests: Vec<(RegionId, RegionCatchupRequest)>,
158 ) -> Result<BatchResponses, BoxedError> {
159 self.inner
160 .handle_batch_catchup_requests(parallelism, requests)
161 .await
162 .map_err(BoxedError::new)
163 }
164
165 async fn handle_batch_ddl_requests(
166 &self,
167 batch_request: BatchRegionDdlRequest,
168 ) -> Result<RegionResponse, BoxedError> {
169 match batch_request {
170 BatchRegionDdlRequest::Create(requests) => {
171 let mut extension_return_value = HashMap::new();
172 let rows = self
173 .inner
174 .create_regions(requests, &mut extension_return_value)
175 .await
176 .map_err(BoxedError::new)?;
177
178 Ok(RegionResponse {
179 affected_rows: rows,
180 extensions: extension_return_value,
181 metadata: Vec::new(),
182 })
183 }
184 BatchRegionDdlRequest::Alter(requests) => {
185 let mut extension_return_value = HashMap::new();
186 let rows = self
187 .inner
188 .alter_regions(requests, &mut extension_return_value)
189 .await
190 .map_err(BoxedError::new)?;
191
192 Ok(RegionResponse {
193 affected_rows: rows,
194 extensions: extension_return_value,
195 metadata: Vec::new(),
196 })
197 }
198 BatchRegionDdlRequest::Drop(requests) => {
199 self.handle_requests(
200 requests
201 .into_iter()
202 .map(|(region_id, req)| (region_id, RegionRequest::Drop(req))),
203 )
204 .await
205 }
206 }
207 }
208
209 async fn handle_request(
211 &self,
212 region_id: RegionId,
213 request: RegionRequest,
214 ) -> Result<RegionResponse, BoxedError> {
215 let mut extension_return_value = HashMap::new();
216
217 let result = match request {
218 RegionRequest::EnterStaging(_) => {
219 if self.inner.is_physical_region(region_id) {
220 self.handle_enter_staging_request(region_id, request).await
221 } else {
222 UnsupportedRegionRequestSnafu { request }.fail()
223 }
224 }
225 RegionRequest::ApplyStagingManifest(_) => {
226 if self.inner.is_physical_region(region_id) {
227 return self.inner.mito.handle_request(region_id, request).await;
228 } else {
229 UnsupportedRegionRequestSnafu { request }.fail()
230 }
231 }
232 RegionRequest::Put(put) => self.inner.put_region(region_id, put).await,
233 RegionRequest::Create(create) => {
234 self.inner
235 .create_regions(vec![(region_id, create)], &mut extension_return_value)
236 .await
237 }
238 RegionRequest::Drop(drop) => self.inner.drop_region(region_id, drop).await,
239 RegionRequest::Open(open) => self.inner.open_region(region_id, open).await,
240 RegionRequest::Close(close) => self.inner.close_region(region_id, close).await,
241 RegionRequest::Alter(alter) => {
242 self.inner
243 .alter_regions(vec![(region_id, alter)], &mut extension_return_value)
244 .await
245 }
246 RegionRequest::Compact(_) => {
247 if self.inner.is_physical_region(region_id) {
248 self.inner
249 .mito
250 .handle_request(region_id, request)
251 .await
252 .context(error::MitoFlushOperationSnafu)
253 .map(|response| response.affected_rows)
254 } else {
255 UnsupportedRegionRequestSnafu { request }.fail()
256 }
257 }
258 RegionRequest::Flush(req) => self.inner.flush_region(region_id, req).await,
259 RegionRequest::BuildIndex(_) => {
260 if self.inner.is_physical_region(region_id) {
261 self.inner
262 .mito
263 .handle_request(region_id, request)
264 .await
265 .context(error::MitoFlushOperationSnafu)
266 .map(|response| response.affected_rows)
267 } else {
268 UnsupportedRegionRequestSnafu { request }.fail()
269 }
270 }
271 RegionRequest::Truncate(_) => UnsupportedRegionRequestSnafu { request }.fail(),
272 RegionRequest::Delete(delete) => self.inner.delete_region(region_id, delete).await,
273 RegionRequest::Catchup(_) => {
274 let mut response = self
275 .inner
276 .handle_batch_catchup_requests(
277 1,
278 vec![(region_id, RegionCatchupRequest::default())],
279 )
280 .await
281 .map_err(BoxedError::new)?;
282 debug_assert_eq!(response.len(), 1);
283 let (resp_region_id, response) = response
284 .pop()
285 .context(error::UnexpectedRequestSnafu {
286 reason: "expected 1 response, but got zero responses",
287 })
288 .map_err(BoxedError::new)?;
289 debug_assert_eq!(region_id, resp_region_id);
290 return response;
291 }
292 RegionRequest::BulkInserts(bulk) => {
293 self.inner.bulk_insert_region(region_id, bulk).await
294 }
295 };
296
297 result.map_err(BoxedError::new).map(|rows| RegionResponse {
298 affected_rows: rows,
299 extensions: extension_return_value,
300 metadata: Vec::new(),
301 })
302 }
303
304 async fn handle_query(
305 &self,
306 region_id: RegionId,
307 request: ScanRequest,
308 ) -> Result<RegionScannerRef, BoxedError> {
309 self.handle_query(region_id, request).await
310 }
311
312 async fn get_committed_sequence(
313 &self,
314 region_id: RegionId,
315 ) -> Result<SequenceNumber, BoxedError> {
316 self.inner
317 .get_last_seq_num(region_id)
318 .await
319 .map_err(BoxedError::new)
320 }
321
322 async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
324 self.inner
325 .load_region_metadata(region_id)
326 .await
327 .map_err(BoxedError::new)
328 }
329
330 fn region_statistic(&self, region_id: RegionId) -> Option<RegionStatistic> {
334 if self.inner.is_physical_region(region_id) {
335 get_region_statistic(&self.inner.mito, region_id)
336 } else {
337 None
338 }
339 }
340
341 async fn stop(&self) -> Result<(), BoxedError> {
343 Ok(())
345 }
346
347 fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
348 for x in [
350 utils::to_metadata_region_id(region_id),
351 utils::to_data_region_id(region_id),
352 ] {
353 if let Err(e) = self.inner.mito.set_region_role(x, role)
354 && e.status_code() != StatusCode::RegionNotFound
355 {
356 return Err(e);
357 }
358 }
359 Ok(())
360 }
361
362 async fn sync_region(
363 &self,
364 region_id: RegionId,
365 request: SyncRegionFromRequest,
366 ) -> Result<SyncRegionFromResponse, BoxedError> {
367 match request {
368 SyncRegionFromRequest::FromManifest(manifest_info) => self
369 .inner
370 .sync_region_from_manifest(region_id, manifest_info)
371 .await
372 .map_err(BoxedError::new),
373 SyncRegionFromRequest::FromRegion {
374 source_region_id,
375 parallelism,
376 } => {
377 if self.inner.is_physical_region(region_id) {
378 self.inner
379 .sync_region_from_region(region_id, source_region_id, parallelism)
380 .await
381 .map_err(BoxedError::new)
382 } else {
383 Err(BoxedError::new(
384 error::UnsupportedSyncRegionFromRequestSnafu { region_id }.build(),
385 ))
386 }
387 }
388 }
389 }
390
391 async fn remap_manifests(
392 &self,
393 request: RemapManifestsRequest,
394 ) -> Result<RemapManifestsResponse, BoxedError> {
395 let region_id = request.region_id;
396 if self.inner.is_physical_region(region_id) {
397 self.inner.mito.remap_manifests(request).await
398 } else {
399 Err(BoxedError::new(
400 UnsupportedRemapManifestsRequestSnafu { region_id }.build(),
401 ))
402 }
403 }
404
405 async fn set_region_role_state_gracefully(
406 &self,
407 region_id: RegionId,
408 region_role_state: SettableRegionRoleState,
409 ) -> std::result::Result<SetRegionRoleStateResponse, BoxedError> {
410 let metadata_result = match self
411 .inner
412 .mito
413 .set_region_role_state_gracefully(
414 utils::to_metadata_region_id(region_id),
415 region_role_state,
416 )
417 .await?
418 {
419 SetRegionRoleStateResponse::Success(success) => success,
420 SetRegionRoleStateResponse::NotFound => {
421 return Ok(SetRegionRoleStateResponse::NotFound);
422 }
423 SetRegionRoleStateResponse::InvalidTransition(error) => {
424 return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
425 }
426 };
427
428 let data_result = match self
429 .inner
430 .mito
431 .set_region_role_state_gracefully(region_id, region_role_state)
432 .await?
433 {
434 SetRegionRoleStateResponse::Success(success) => success,
435 SetRegionRoleStateResponse::NotFound => {
436 return Ok(SetRegionRoleStateResponse::NotFound);
437 }
438 SetRegionRoleStateResponse::InvalidTransition(error) => {
439 return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
440 }
441 };
442
443 Ok(SetRegionRoleStateResponse::success(
444 SetRegionRoleStateSuccess::metric(
445 data_result.last_entry_id().unwrap_or_default(),
446 metadata_result.last_entry_id().unwrap_or_default(),
447 ),
448 ))
449 }
450
451 fn role(&self, region_id: RegionId) -> Option<RegionRole> {
455 if self.inner.is_physical_region(region_id) {
456 self.inner.mito.role(region_id)
457 } else {
458 None
459 }
460 }
461
462 fn as_any(&self) -> &dyn Any {
463 self
464 }
465}
466
467impl MetricEngine {
468 pub fn try_new(mito: MitoEngine, mut config: EngineConfig) -> Result<Self> {
469 let metadata_region = MetadataRegion::new(mito.clone());
470 let data_region = DataRegion::new(mito.clone());
471 let state = Arc::new(RwLock::default());
472 config.sanitize();
473 let flush_interval = config.flush_metadata_region_interval;
474 let inner = Arc::new(MetricEngineInner {
475 mito: mito.clone(),
476 metadata_region,
477 data_region,
478 state: state.clone(),
479 config,
480 row_modifier: RowModifier::default(),
481 flush_task: RepeatedTask::new(
482 flush_interval,
483 Box::new(FlushMetadataRegionTask {
484 state: state.clone(),
485 mito: mito.clone(),
486 }),
487 ),
488 });
489 inner
490 .flush_task
491 .start(common_runtime::global_runtime())
492 .context(StartRepeatedTaskSnafu { name: "flush_task" })?;
493 Ok(Self { inner })
494 }
495
496 pub fn mito(&self) -> MitoEngine {
497 self.inner.mito.clone()
498 }
499
500 pub async fn put_regions_batch(
504 &self,
505 requests: impl ExactSizeIterator<Item = (RegionId, RegionPutRequest)>,
506 ) -> Result<AffectedRows> {
507 self.inner.put_regions_batch(requests).await
508 }
509
510 pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
512 self.inner
513 .metadata_region
514 .logical_regions(physical_region_id)
515 .await
516 }
517
518 async fn handle_query(
520 &self,
521 region_id: RegionId,
522 request: ScanRequest,
523 ) -> Result<RegionScannerRef, BoxedError> {
524 self.inner
525 .read_region(region_id, request)
526 .await
527 .map_err(BoxedError::new)
528 }
529
530 async fn handle_requests(
531 &self,
532 requests: impl IntoIterator<Item = (RegionId, RegionRequest)>,
533 ) -> Result<RegionResponse, BoxedError> {
534 let mut affected_rows = 0;
535 let mut extensions = HashMap::new();
536 for (region_id, request) in requests {
537 let response = self.handle_request(region_id, request).await?;
538 affected_rows += response.affected_rows;
539 extensions.extend(response.extensions);
540 }
541
542 Ok(RegionResponse {
543 affected_rows,
544 extensions,
545 metadata: Vec::new(),
546 })
547 }
548}
549
550#[cfg(test)]
551impl MetricEngine {
552 pub async fn scan_to_stream(
553 &self,
554 region_id: RegionId,
555 request: ScanRequest,
556 ) -> Result<common_recordbatch::SendableRecordBatchStream, BoxedError> {
557 self.inner.scan_to_stream(region_id, request).await
558 }
559
560 pub fn config(&self) -> &EngineConfig {
562 &self.inner.config
563 }
564}
565
566struct MetricEngineInner {
567 mito: MitoEngine,
568 metadata_region: MetadataRegion,
569 data_region: DataRegion,
570 state: Arc<RwLock<MetricEngineState>>,
571 config: EngineConfig,
572 row_modifier: RowModifier,
573 flush_task: RepeatedTask<Error>,
574}
575
576#[cfg(test)]
577mod test {
578 use std::assert_matches;
579 use std::collections::HashMap;
580
581 use common_telemetry::info;
582 use common_wal::options::{KafkaWalOptions, WalOptions};
583 use mito2::sst::location::region_dir_from_table_dir;
584 use mito2::test_util::{kafka_log_store_factory, prepare_test_for_kafka_log_store};
585 use store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
586 use store_api::mito_engine_options::WAL_OPTIONS_KEY;
587 use store_api::region_request::{
588 PathType, RegionCloseRequest, RegionDropRequest, RegionFlushRequest, RegionOpenRequest,
589 RegionRequest,
590 };
591
592 use super::*;
593 use crate::maybe_skip_kafka_log_store_integration_test;
594 use crate::test_util::{TestEnv, create_logical_region_request};
595
596 #[tokio::test]
597 async fn close_open_regions() {
598 let env = TestEnv::new().await;
599 env.init_metric_region().await;
600 let engine = env.metric();
601
602 let physical_region_id = env.default_physical_region_id();
604 engine
605 .handle_request(
606 physical_region_id,
607 RegionRequest::Close(RegionCloseRequest {}),
608 )
609 .await
610 .unwrap();
611
612 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
614 .into_iter()
615 .collect();
616 let open_request = RegionOpenRequest {
617 engine: METRIC_ENGINE_NAME.to_string(),
618 table_dir: TestEnv::default_table_dir(),
619 path_type: PathType::Bare, options: physical_region_option,
621 skip_wal_replay: false,
622 checkpoint: None,
623 requirements: Default::default(),
624 };
625 engine
626 .handle_request(physical_region_id, RegionRequest::Open(open_request))
627 .await
628 .unwrap();
629
630 let nonexistent_region_id = RegionId::new(12313, 12);
632 engine
633 .handle_request(
634 nonexistent_region_id,
635 RegionRequest::Close(RegionCloseRequest {}),
636 )
637 .await
638 .unwrap();
639
640 let invalid_open_request = RegionOpenRequest {
642 engine: METRIC_ENGINE_NAME.to_string(),
643 table_dir: TestEnv::default_table_dir(),
644 path_type: PathType::Bare, options: HashMap::new(),
646 skip_wal_replay: false,
647 checkpoint: None,
648 requirements: Default::default(),
649 };
650 engine
651 .handle_request(
652 nonexistent_region_id,
653 RegionRequest::Open(invalid_open_request),
654 )
655 .await
656 .unwrap();
657 }
658
659 #[tokio::test]
660 async fn test_role() {
661 let env = TestEnv::new().await;
662 env.init_metric_region().await;
663
664 let logical_region_id = env.default_logical_region_id();
665 let physical_region_id = env.default_physical_region_id();
666
667 assert!(env.metric().role(logical_region_id).is_none());
668 assert!(env.metric().role(physical_region_id).is_some());
669 }
670
671 #[tokio::test]
672 async fn test_region_disk_usage() {
673 let env = TestEnv::new().await;
674 env.init_metric_region().await;
675
676 let logical_region_id = env.default_logical_region_id();
677 let physical_region_id = env.default_physical_region_id();
678
679 assert!(env.metric().region_statistic(logical_region_id).is_none());
680 assert!(env.metric().region_statistic(physical_region_id).is_some());
681 }
682
683 #[tokio::test]
684 async fn test_open_region_failure() {
685 let env = TestEnv::new().await;
686 env.init_metric_region().await;
687 let physical_region_id = env.default_physical_region_id();
688
689 let metric_engine = env.metric();
690 metric_engine
691 .handle_request(
692 physical_region_id,
693 RegionRequest::Flush(RegionFlushRequest::default()),
694 )
695 .await
696 .unwrap();
697
698 let path = region_dir_from_table_dir(
699 &TestEnv::default_table_dir(),
700 physical_region_id,
701 PathType::Metadata,
702 );
703 let object_store = env.get_object_store().unwrap();
704 let list = object_store.list(&path).await.unwrap();
705 for entry in list {
707 if entry.metadata().is_dir() {
708 continue;
709 }
710 if entry.name().ends_with("parquet") {
711 info!("deleting {}", entry.path());
712 object_store.delete(entry.path()).await.unwrap();
713 }
714 }
715
716 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
717 .into_iter()
718 .collect();
719 let open_request = RegionOpenRequest {
720 engine: METRIC_ENGINE_NAME.to_string(),
721 table_dir: TestEnv::default_table_dir(),
722 path_type: PathType::Bare,
723 options: physical_region_option,
724 skip_wal_replay: false,
725 checkpoint: None,
726 requirements: Default::default(),
727 };
728 metric_engine
731 .handle_request(physical_region_id, RegionRequest::Open(open_request))
732 .await
733 .unwrap();
734
735 metric_engine
737 .handle_request(
738 physical_region_id,
739 RegionRequest::Close(RegionCloseRequest {}),
740 )
741 .await
742 .unwrap();
743
744 let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
746 .into_iter()
747 .collect();
748 let open_request = RegionOpenRequest {
749 engine: METRIC_ENGINE_NAME.to_string(),
750 table_dir: TestEnv::default_table_dir(),
751 path_type: PathType::Bare,
752 options: physical_region_option,
753 skip_wal_replay: false,
754 checkpoint: None,
755 requirements: Default::default(),
756 };
757 let err = metric_engine
758 .handle_request(physical_region_id, RegionRequest::Open(open_request))
759 .await
760 .unwrap_err();
761 assert_eq!(err.status_code(), StatusCode::StorageUnavailable);
763
764 let mito_engine = metric_engine.mito();
765 let data_region_id = utils::to_data_region_id(physical_region_id);
766 let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
767 let err = mito_engine.get_metadata(data_region_id).await.unwrap_err();
769 assert_eq!(err.status_code(), StatusCode::RegionNotFound);
770 let err = mito_engine
771 .get_metadata(metadata_region_id)
772 .await
773 .unwrap_err();
774 assert_eq!(err.status_code(), StatusCode::RegionNotFound);
775 }
776
777 #[tokio::test]
778 async fn test_catchup_regions() {
779 common_telemetry::init_default_ut_logging();
780 maybe_skip_kafka_log_store_integration_test!();
781 let kafka_log_store_factory = kafka_log_store_factory().unwrap();
782 let mito_env = mito2::test_util::TestEnv::new()
783 .await
784 .with_log_store_factory(kafka_log_store_factory.clone());
785 let env = TestEnv::with_mito_env(mito_env).await;
786 let table_dir = |region_id| format!("table/{region_id}");
787 let mut physical_region_ids = vec![];
788 let mut logical_region_ids = vec![];
789
790 let num_topics = 3;
791 let num_physical_regions = 8;
792 let num_logical_regions = 16;
793 let parallelism = 2;
794 let mut topics = Vec::with_capacity(num_topics);
795 for _ in 0..num_topics {
796 let topic = prepare_test_for_kafka_log_store(&kafka_log_store_factory)
797 .await
798 .unwrap();
799 topics.push(topic);
800 }
801
802 let topic_idx = |id| (id as usize) % num_topics;
803 for i in 0..num_physical_regions {
805 let physical_region_id = RegionId::new(1, i);
806 physical_region_ids.push(physical_region_id);
807
808 let wal_options = WalOptions::Kafka(KafkaWalOptions {
809 topic: topics[topic_idx(i)].clone(),
810 });
811 env.create_physical_region(
812 physical_region_id,
813 &table_dir(physical_region_id),
814 vec![(
815 WAL_OPTIONS_KEY.to_string(),
816 serde_json::to_string(&wal_options).unwrap(),
817 )],
818 )
819 .await;
820 for j in 0..num_logical_regions {
822 let logical_region_id = RegionId::new(1024 + i, j);
823 logical_region_ids.push(logical_region_id);
824 env.create_logical_region(physical_region_id, logical_region_id)
825 .await;
826 }
827 }
828
829 let metric_engine = env.metric();
830 for region_id in logical_region_ids.iter().chain(physical_region_ids.iter()) {
832 metric_engine
833 .handle_request(*region_id, RegionRequest::Close(RegionCloseRequest {}))
834 .await
835 .unwrap();
836 }
837
838 let requests = physical_region_ids
840 .iter()
841 .enumerate()
842 .map(|(idx, region_id)| {
843 let mut options = HashMap::new();
844 let wal_options = WalOptions::Kafka(KafkaWalOptions {
845 topic: topics[topic_idx(idx as u32)].clone(),
846 });
847 options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new());
848 options.insert(
849 WAL_OPTIONS_KEY.to_string(),
850 serde_json::to_string(&wal_options).unwrap(),
851 );
852 (
853 *region_id,
854 RegionOpenRequest {
855 engine: METRIC_ENGINE_NAME.to_string(),
856 table_dir: table_dir(*region_id),
857 path_type: PathType::Bare,
858 options: options.clone(),
859 skip_wal_replay: true,
860 checkpoint: None,
861 requirements: Default::default(),
862 },
863 )
864 })
865 .collect::<Vec<_>>();
866 info!("Open batch regions with parallelism: {parallelism}");
867 metric_engine
868 .handle_batch_open_requests(parallelism, requests)
869 .await
870 .unwrap();
871 {
872 let state = metric_engine.inner.state.read().unwrap();
873 for logical_region in &logical_region_ids {
874 assert!(!state.logical_regions().contains_key(logical_region));
875 }
876 }
877
878 let catch_requests = physical_region_ids
879 .iter()
880 .map(|region_id| {
881 (
882 *region_id,
883 RegionCatchupRequest {
884 set_writable: true,
885 ..Default::default()
886 },
887 )
888 })
889 .collect::<Vec<_>>();
890 metric_engine
891 .handle_batch_catchup_requests(parallelism, catch_requests)
892 .await
893 .unwrap();
894 {
895 let state = metric_engine.inner.state.read().unwrap();
896 for logical_region in &logical_region_ids {
897 assert!(state.logical_regions().contains_key(logical_region));
898 }
899 }
900 }
901
902 #[tokio::test]
903 async fn test_drop_region() {
904 let env = TestEnv::new().await;
905 let engine = env.metric();
906 let physical_region_id1 = RegionId::new(1024, 0);
907 let logical_region_id1 = RegionId::new(1025, 0);
908 env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
909 .await;
910 let region_create_request1 =
911 create_logical_region_request(&["job"], physical_region_id1, "logical1");
912 engine
913 .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![(
914 logical_region_id1,
915 region_create_request1,
916 )]))
917 .await
918 .unwrap();
919 let err = engine
920 .handle_request(
921 physical_region_id1,
922 RegionRequest::Drop(RegionDropRequest {
923 fast_path: false,
924 force: false,
925 partial_drop: false,
926 }),
927 )
928 .await
929 .unwrap_err();
930 assert_matches!(
931 err.as_any().downcast_ref::<Error>().unwrap(),
932 &Error::PhysicalRegionBusy { .. }
933 );
934
935 engine
936 .handle_request(
937 physical_region_id1,
938 RegionRequest::Drop(RegionDropRequest {
939 fast_path: false,
940 force: true,
941 partial_drop: false,
942 }),
943 )
944 .await
945 .unwrap();
946 assert!(
947 engine
948 .inner
949 .state
950 .read()
951 .unwrap()
952 .physical_region_states()
953 .get(&physical_region_id1)
954 .is_none()
955 );
956 assert!(
957 engine
958 .inner
959 .state
960 .read()
961 .unwrap()
962 .logical_regions()
963 .get(&logical_region_id1)
964 .is_none()
965 );
966 }
967}