1pub mod allocate_region;
16pub mod collect;
17pub mod deallocate_region;
18pub mod dispatch;
19pub mod gc_requirement;
20pub mod group;
21pub mod plan;
22pub mod repartition_end;
23pub mod repartition_start;
24pub mod update_partition_metadata;
25pub mod utils;
26
27use std::any::Any;
28use std::collections::{HashMap, HashSet};
29use std::fmt::{Debug, Display};
30use std::time::{Duration, Instant};
31
32use common_error::ext::BoxedError;
33use common_meta::cache_invalidator::CacheInvalidatorRef;
34use common_meta::ddl::DdlContext;
35use common_meta::ddl::allocator::region_routes::RegionRoutesAllocatorRef;
36use common_meta::ddl::allocator::wal_options::WalOptionsAllocatorRef;
37use common_meta::ddl_manager::{RepartitionProcedureFactory, RepartitionSource};
38use common_meta::instruction::CacheIdent;
39use common_meta::key::datanode_table::RegionInfo;
40use common_meta::key::table_info::TableInfoValue;
41use common_meta::key::table_route::TableRouteValue;
42use common_meta::key::{DeserializedValueWithBytes, TableMetadataManagerRef};
43use common_meta::lock_key::{CatalogLock, SchemaLock, TableLock, TableNameLock};
44use common_meta::node_manager::NodeManagerRef;
45use common_meta::region_keeper::{MemoryRegionKeeperRef, OperatingRegionGuard};
46use common_meta::region_registry::LeaderRegionRegistryRef;
47use common_meta::rpc::router::{RegionRoute, operating_leader_region_roles};
48use common_meta::wal_provider::RegionWalOptions;
49use common_procedure::error::{FromJsonSnafu, ToJsonSnafu};
50use common_procedure::{
51 BoxedProcedure, Context as ProcedureContext, Error as ProcedureError, EventContext,
52 EventTrigger, LockKey, Procedure, ProcedureManagerRef, Result as ProcedureResult, Status,
53 StringKey,
54};
55use common_telemetry::{error, info, warn};
56use partition::expr::PartitionExpr;
57use serde::{Deserialize, Serialize};
58use snafu::{OptionExt, ResultExt};
59use store_api::storage::TableId;
60use table::table_name::TableName;
61
62use crate::error::{self, Result};
63use crate::event::repartition::{REPARTITION_EVENT_TYPE, RepartitionEvent};
64use crate::procedure::repartition::collect::ProcedureMeta;
65use crate::procedure::repartition::deallocate_region::DeallocateRegion;
66use crate::procedure::repartition::gc_requirement::RepartitionGcRequirementManagerRef;
67use crate::procedure::repartition::group::{
68 Context as RepartitionGroupContext, RepartitionGroupProcedure, region_routes,
69};
70use crate::procedure::repartition::plan::RepartitionPlanEntry;
71use crate::procedure::repartition::repartition_start::{RepartitionFrom, RepartitionStart};
72use crate::procedure::repartition::update_partition_metadata::PartitionMetadataUpdate;
73use crate::procedure::repartition::utils::{
74 get_datanode_table_value, rollback_group_metadata_routes,
75};
76use crate::service::mailbox::MailboxRef;
77
78#[cfg(test)]
79pub mod test_util;
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct PersistentContext {
83 pub catalog_name: String,
84 pub schema_name: String,
85 pub table_name: String,
86 pub table_id: TableId,
87 pub plans: Vec<RepartitionPlanEntry>,
88 #[serde(default)]
89 pub failed_procedures: Vec<ProcedureMeta>,
94 #[serde(default)]
95 pub unknown_procedures: Vec<ProcedureMeta>,
100 #[serde(with = "humantime_serde", default = "default_timeout")]
102 pub timeout: Duration,
103 #[serde(default)]
104 pub partition_metadata_update: Option<PartitionMetadataUpdate>,
106}
107
108fn default_timeout() -> Duration {
109 Duration::from_mins(2)
110}
111
112impl PersistentContext {
113 pub fn new(
117 TableName {
118 catalog_name,
119 schema_name,
120 table_name,
121 }: TableName,
122 table_id: TableId,
123 timeout: Option<Duration>,
124 ) -> Self {
125 Self {
126 catalog_name,
127 schema_name,
128 table_name,
129 table_id,
130 plans: vec![],
131 failed_procedures: vec![],
132 unknown_procedures: vec![],
133 timeout: timeout.unwrap_or_else(default_timeout),
134 partition_metadata_update: None,
135 }
136 }
137
138 pub fn lock_key(&self) -> Vec<StringKey> {
139 vec![
140 CatalogLock::Read(&self.catalog_name).into(),
141 SchemaLock::read(&self.catalog_name, &self.schema_name).into(),
142 TableLock::Write(self.table_id).into(),
143 TableNameLock::new(&self.catalog_name, &self.schema_name, &self.table_name).into(),
144 ]
145 }
146}
147
148#[derive(Clone)]
149pub struct Context {
150 pub persistent_ctx: PersistentContext,
151 pub volatile_ctx: VolatileContext,
152 pub table_metadata_manager: TableMetadataManagerRef,
153 pub memory_region_keeper: MemoryRegionKeeperRef,
154 pub node_manager: NodeManagerRef,
155 pub leader_region_registry: LeaderRegionRegistryRef,
156 pub mailbox: MailboxRef,
157 pub server_addr: String,
158 pub cache_invalidator: CacheInvalidatorRef,
159 pub region_routes_allocator: RegionRoutesAllocatorRef,
160 pub wal_options_allocator: WalOptionsAllocatorRef,
161 pub start_time: Instant,
162}
163
164#[derive(Debug, Clone, Default)]
165pub struct VolatileContext {
166 pub metrics: Metrics,
167 pub dispatch_start_time: Option<Instant>,
168}
169
170#[derive(Debug, Clone, Default)]
172pub struct Metrics {
173 build_plan_elapsed: Duration,
175 allocate_region_elapsed: Duration,
177 finish_groups_elapsed: Duration,
179 deallocate_region_elapsed: Duration,
181}
182
183impl Display for Metrics {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 let total = self.build_plan_elapsed
186 + self.allocate_region_elapsed
187 + self.finish_groups_elapsed
188 + self.deallocate_region_elapsed;
189 write!(f, "total: {:?}", total)?;
190 let mut parts = Vec::with_capacity(4);
191 if self.build_plan_elapsed > Duration::ZERO {
192 parts.push(format!("build_plan_elapsed: {:?}", self.build_plan_elapsed));
193 }
194 if self.allocate_region_elapsed > Duration::ZERO {
195 parts.push(format!(
196 "allocate_region_elapsed: {:?}",
197 self.allocate_region_elapsed
198 ));
199 }
200 if self.finish_groups_elapsed > Duration::ZERO {
201 parts.push(format!(
202 "finish_groups_elapsed: {:?}",
203 self.finish_groups_elapsed
204 ));
205 }
206 if self.deallocate_region_elapsed > Duration::ZERO {
207 parts.push(format!(
208 "deallocate_region_elapsed: {:?}",
209 self.deallocate_region_elapsed
210 ));
211 }
212
213 if !parts.is_empty() {
214 write!(f, ", {}", parts.join(", "))?;
215 }
216 Ok(())
217 }
218}
219
220impl Metrics {
221 pub fn update_build_plan_elapsed(&mut self, elapsed: Duration) {
223 self.build_plan_elapsed += elapsed;
224 }
225
226 pub fn update_allocate_region_elapsed(&mut self, elapsed: Duration) {
228 self.allocate_region_elapsed += elapsed;
229 }
230
231 pub fn update_finish_groups_elapsed(&mut self, elapsed: Duration) {
233 self.finish_groups_elapsed += elapsed;
234 }
235
236 pub fn update_deallocate_region_elapsed(&mut self, elapsed: Duration) {
238 self.deallocate_region_elapsed += elapsed;
239 }
240}
241
242impl Context {
243 pub fn new(
244 ddl_ctx: &DdlContext,
245 mailbox: MailboxRef,
246 server_addr: String,
247 persistent_ctx: PersistentContext,
248 ) -> Self {
249 Self {
250 persistent_ctx,
251 table_metadata_manager: ddl_ctx.table_metadata_manager.clone(),
252 memory_region_keeper: ddl_ctx.memory_region_keeper.clone(),
253 node_manager: ddl_ctx.node_manager.clone(),
254 leader_region_registry: ddl_ctx.leader_region_registry.clone(),
255 mailbox,
256 server_addr,
257 cache_invalidator: ddl_ctx.cache_invalidator.clone(),
258 region_routes_allocator: ddl_ctx.table_metadata_allocator.region_routes_allocator(),
259 wal_options_allocator: ddl_ctx.table_metadata_allocator.wal_options_allocator(),
260 start_time: Instant::now(),
261 volatile_ctx: VolatileContext::default(),
262 }
263 }
264
265 pub fn next_operation_timeout(&self) -> Option<Duration> {
267 self.persistent_ctx
268 .timeout
269 .checked_sub(self.start_time.elapsed())
270 }
271
272 pub fn update_build_plan_elapsed(&mut self, elapsed: Duration) {
274 self.volatile_ctx.metrics.update_build_plan_elapsed(elapsed);
275 }
276
277 pub fn update_allocate_region_elapsed(&mut self, elapsed: Duration) {
279 self.volatile_ctx
280 .metrics
281 .update_allocate_region_elapsed(elapsed);
282 }
283
284 pub fn update_finish_groups_elapsed(&mut self, elapsed: Duration) {
286 self.volatile_ctx
287 .metrics
288 .update_finish_groups_elapsed(elapsed);
289 }
290
291 pub fn update_deallocate_region_elapsed(&mut self, elapsed: Duration) {
293 self.volatile_ctx
294 .metrics
295 .update_deallocate_region_elapsed(elapsed);
296 }
297
298 pub async fn get_table_route_value(
306 &self,
307 ) -> Result<DeserializedValueWithBytes<TableRouteValue>> {
308 let table_id = self.persistent_ctx.table_id;
309 let table_route_value = self
310 .table_metadata_manager
311 .table_route_manager()
312 .table_route_storage()
313 .get_with_raw_bytes(table_id)
314 .await
315 .map_err(BoxedError::new)
316 .with_context(|_| error::RetryLaterWithSourceSnafu {
317 reason: format!("Failed to get table route for table: {}", table_id),
318 })?
319 .context(error::TableRouteNotFoundSnafu { table_id })?;
320
321 Ok(table_route_value)
322 }
323
324 pub async fn get_raw_table_info_value(
332 &self,
333 ) -> Result<DeserializedValueWithBytes<TableInfoValue>> {
334 let table_id = self.persistent_ctx.table_id;
335 let table_info_value = self
336 .table_metadata_manager
337 .table_info_manager()
338 .get(table_id)
339 .await
340 .map_err(BoxedError::new)
341 .with_context(|_| error::RetryLaterWithSourceSnafu {
342 reason: format!("Failed to get table info for table: {}", table_id),
343 })?
344 .context(error::TableInfoNotFoundSnafu { table_id })?;
345
346 Ok(table_info_value)
347 }
348
349 pub async fn get_table_info_value(&self) -> Result<TableInfoValue> {
350 let table_info_value = self.get_raw_table_info_value().await?.into_inner();
351 Ok(table_info_value)
352 }
353
354 pub async fn update_table_info(
356 &self,
357 current_table_info_value: &DeserializedValueWithBytes<TableInfoValue>,
358 new_table_info_value: TableInfoValue,
359 ) -> Result<()> {
360 let table_id = self.persistent_ctx.table_id;
361 self.table_metadata_manager
362 .update_table_info(
363 current_table_info_value,
364 None,
365 new_table_info_value.table_info,
366 )
367 .await
368 .map_err(BoxedError::new)
369 .with_context(|_| error::RetryLaterWithSourceSnafu {
370 reason: format!("Failed to update table info for table: {}", table_id),
371 })
372 }
373
374 pub async fn update_table_route(
383 &self,
384 current_table_route_value: &DeserializedValueWithBytes<TableRouteValue>,
385 new_region_routes: Vec<RegionRoute>,
386 new_region_wal_options: RegionWalOptions,
387 ) -> Result<()> {
388 let table_id = self.persistent_ctx.table_id;
389 if new_region_routes.is_empty() {
390 return error::UnexpectedSnafu {
391 violated: format!("new_region_routes is empty for table: {}", table_id),
392 }
393 .fail();
394 }
395 let datanode_id = new_region_routes
396 .first()
397 .unwrap()
398 .leader_peer
399 .as_ref()
400 .context(error::NoLeaderSnafu)?
401 .id;
402 let datanode_table_value =
403 get_datanode_table_value(&self.table_metadata_manager, table_id, datanode_id).await?;
404
405 let RegionInfo {
406 region_options,
407 region_wal_options,
408 ..
409 } = &datanode_table_value.region_info;
410
411 let validated_region_wal_options =
413 crate::procedure::repartition::utils::merge_and_validate_region_wal_options(
414 region_wal_options,
415 new_region_wal_options,
416 &new_region_routes,
417 table_id,
418 )?;
419 info!(
420 "Updating table route for table: {}, new region routes: {:?}",
421 table_id, new_region_routes
422 );
423 self.table_metadata_manager
424 .update_table_route(
425 table_id,
426 datanode_table_value.region_info.clone(),
427 current_table_route_value,
428 new_region_routes,
429 region_options,
430 &validated_region_wal_options,
431 )
432 .await
433 .context(error::TableMetadataManagerSnafu)
434 }
435
436 pub async fn invalidate_table_cache(&self) -> Result<()> {
438 let table_id = self.persistent_ctx.table_id;
439 let subject = format!(
440 "Invalidate table cache for repartition table, table: {}",
441 table_id,
442 );
443 let ctx = common_meta::cache_invalidator::Context {
444 subject: Some(subject),
445 };
446 let _ = self
447 .cache_invalidator
448 .invalidate(
449 &ctx,
450 &[
451 CacheIdent::TableId(table_id),
452 CacheIdent::TableName(TableName {
453 catalog_name: self.persistent_ctx.catalog_name.clone(),
454 schema_name: self.persistent_ctx.schema_name.clone(),
455 table_name: self.persistent_ctx.table_name.clone(),
456 }),
457 ],
458 )
459 .await;
460 Ok(())
461 }
462
463 pub fn register_operating_regions(
464 memory_region_keeper: &MemoryRegionKeeperRef,
465 region_routes: &[RegionRoute],
466 ) -> Result<Vec<OperatingRegionGuard>> {
467 let mut operating_guards = Vec::with_capacity(region_routes.len());
468 for (region_id, datanode_id, role) in operating_leader_region_roles(region_routes) {
469 let guard = memory_region_keeper
470 .register_with_role(datanode_id, region_id, role)
471 .context(error::RegionOperatingRaceSnafu {
472 peer_id: datanode_id,
473 region_id,
474 })?;
475 operating_guards.push(guard);
476 }
477 Ok(operating_guards)
478 }
479}
480
481#[async_trait::async_trait]
482#[typetag::serde(tag = "repartition_state")]
483pub(crate) trait State: Sync + Send + Debug {
484 fn name(&self) -> &'static str {
485 let type_name = std::any::type_name::<Self>();
486 type_name.split("::").last().unwrap_or(type_name)
488 }
489
490 async fn next(
492 &mut self,
493 ctx: &mut Context,
494 procedure_ctx: &ProcedureContext,
495 ) -> Result<(Box<dyn State>, Status)>;
496
497 fn as_any(&self) -> &dyn Any;
498}
499
500pub struct RepartitionProcedure {
501 state: Box<dyn State>,
502 context: Context,
503}
504
505#[derive(Debug, Serialize)]
506struct RepartitionData<'a> {
507 state: &'a dyn State,
508 persistent_ctx: &'a PersistentContext,
509}
510
511#[derive(Debug, Deserialize)]
512struct RepartitionDataOwned {
513 state: Box<dyn State>,
514 persistent_ctx: PersistentContext,
515}
516
517impl RepartitionProcedure {
518 const TYPE_NAME: &'static str = "metasrv-procedure::Repartition";
519
520 pub fn new(from: RepartitionFrom, to_exprs: Vec<PartitionExpr>, context: Context) -> Self {
521 let state = Box::new(RepartitionStart::new(from, to_exprs));
522
523 Self { state, context }
524 }
525
526 pub fn from_json<F>(json: &str, ctx_factory: F) -> ProcedureResult<Self>
527 where
528 F: FnOnce(PersistentContext) -> Context,
529 {
530 let RepartitionDataOwned {
531 state,
532 persistent_ctx,
533 } = serde_json::from_str(json).context(FromJsonSnafu)?;
534 let context = ctx_factory(persistent_ctx);
535
536 Ok(Self { state, context })
537 }
538
539 fn should_rollback(&self) -> bool {
553 self.state
554 .as_any()
555 .is::<update_partition_metadata::UpdatePartitionMetadata>()
556 || self.state.as_any().is::<allocate_region::AllocateRegion>()
557 || self.state.as_any().is::<dispatch::Dispatch>()
558 || self.state.as_any().is::<collect::Collect>()
559 }
560
561 fn rollback_plan_indices(&self) -> HashSet<usize> {
562 self.context
563 .persistent_ctx
564 .failed_procedures
565 .iter()
566 .chain(self.context.persistent_ctx.unknown_procedures.iter())
567 .map(|procedure_meta| procedure_meta.plan_index)
568 .collect()
569 }
570
571 fn rollback_allocated_region_ids(&self) -> HashSet<store_api::storage::RegionId> {
579 if self.state.as_any().is::<allocate_region::AllocateRegion>()
580 || self.state.as_any().is::<dispatch::Dispatch>()
581 {
582 return self
583 .context
584 .persistent_ctx
585 .plans
586 .iter()
587 .flat_map(|plan| plan.allocated_region_ids.iter().copied())
588 .collect();
589 }
590
591 self.rollback_plan_indices()
592 .into_iter()
593 .flat_map(|plan_index| {
594 self.context.persistent_ctx.plans[plan_index]
595 .allocated_region_ids
596 .iter()
597 .copied()
598 })
599 .collect()
600 }
601
602 async fn rollback_group_metadata_for_selected_plans(
606 &mut self,
607 region_routes: &mut [RegionRoute],
608 ) -> Result<()> {
609 let rollback_plan_indices = self.rollback_plan_indices();
610 if rollback_plan_indices.is_empty() {
611 return Ok(());
612 }
613
614 let mut region_routes_map = region_routes
615 .iter_mut()
616 .map(|route| (route.region.id, route))
617 .collect::<HashMap<_, _>>();
618 for plan_index in rollback_plan_indices {
619 let plan = &self.context.persistent_ctx.plans[plan_index];
620 rollback_group_metadata_routes(
621 plan.group_id,
622 &plan.source_regions,
623 &plan.original_target_routes,
624 &plan.allocated_region_ids,
625 &plan.pending_deallocate_region_ids,
626 &mut region_routes_map,
627 )?;
628 }
629
630 Ok(())
631 }
632
633 async fn rollback_partition_metadata(&mut self) -> Result<()> {
634 let Some(update) = self
635 .context
636 .persistent_ctx
637 .partition_metadata_update
638 .as_ref()
639 else {
640 return Ok(());
641 };
642 let table_info_value = self.context.get_raw_table_info_value().await?;
643 let current_partition_key_indices = &table_info_value.table_info.meta.partition_key_indices;
644 let Some(new_partition_key_indices) = update.rollback_partition_key_indices(
645 self.context.persistent_ctx.table_id,
646 current_partition_key_indices,
647 )?
648 else {
649 return Ok(());
650 };
651
652 let mut new_table_info = table_info_value.table_info.clone();
653 new_table_info.meta.partition_key_indices = new_partition_key_indices;
654 self.context
655 .update_table_info(&table_info_value, table_info_value.update(new_table_info))
656 .await?;
657
658 Ok(())
665 }
666
667 async fn rollback_inner(&mut self, procedure_ctx: &ProcedureContext) -> Result<()> {
668 if !self.should_rollback() {
669 return Ok(());
670 }
671
672 let table_id = self.context.persistent_ctx.table_id;
673 let allocated_region_ids = self.rollback_allocated_region_ids();
674
675 let table_lock = TableLock::Write(table_id).into();
676 let _guard = procedure_ctx.provider.acquire_lock(&table_lock).await;
677
678 self.rollback_partition_metadata().await?;
679 let table_route_value = self.context.get_table_route_value().await?;
680 let original_region_routes = region_routes(table_id, table_route_value.get_inner_ref())?;
681 let mut current_region_routes = original_region_routes.clone();
682 self.rollback_group_metadata_for_selected_plans(&mut current_region_routes)
683 .await?;
684 let allocated_region_routes = DeallocateRegion::filter_deallocatable_region_routes(
685 table_id,
686 ¤t_region_routes,
687 &allocated_region_ids,
688 );
689 if !allocated_region_routes.is_empty() {
690 let table = TableName {
691 catalog_name: self.context.persistent_ctx.catalog_name.clone(),
692 schema_name: self.context.persistent_ctx.schema_name.clone(),
693 table_name: self.context.persistent_ctx.table_name.clone(),
694 };
695 if let Err(err) = DeallocateRegion::deallocate_regions(
698 &self.context.node_manager,
699 &self.context.leader_region_registry,
700 table,
701 table_id,
702 &allocated_region_routes,
703 )
704 .await
705 {
706 warn!(err; "Failed to drop allocated regions during repartition rollback, table_id: {}, regions: {:?}", table_id, allocated_region_ids);
707 }
708 }
709
710 let new_region_routes =
711 DeallocateRegion::generate_region_routes(¤t_region_routes, &allocated_region_ids);
712
713 if new_region_routes != *original_region_routes {
714 self.context
715 .update_table_route(&table_route_value, new_region_routes, HashMap::new())
716 .await
717 .map_err(BoxedError::new)
718 .with_context(|_| error::RetryLaterWithSourceSnafu {
719 reason: format!(
720 "Failed to rollback allocated region routes for repartition table: {}",
721 table_id
722 ),
723 })?;
724 }
725
726 if let Err(err) = self.context.invalidate_table_cache().await {
727 warn!(err; "Failed to invalidate table cache during repartition rollback, table_id: {}", table_id);
728 }
729
730 Ok(())
731 }
732}
733
734#[async_trait::async_trait]
735impl Procedure for RepartitionProcedure {
736 fn type_name(&self) -> &str {
737 Self::TYPE_NAME
738 }
739
740 #[tracing::instrument(skip_all, fields(
741 state = %self.state.name(),
742 table_id = %self.context.persistent_ctx.table_id
743 ))]
744 async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
745 let state = &mut self.state;
746 let state_name = state.name();
747 common_telemetry::info!(
749 "Repartition procedure executing state: {}, table_id: {}",
750 state_name,
751 self.context.persistent_ctx.table_id
752 );
753 match state.next(&mut self.context, _ctx).await {
754 Ok((next, status)) => {
755 *state = next;
756 Ok(status)
757 }
758 Err(e) => {
759 if e.is_retryable() {
760 Err(ProcedureError::retry_later(e))
761 } else {
762 error!(
763 e;
764 "Repartition procedure failed, table id: {}",
765 self.context.persistent_ctx.table_id,
766 );
767 Err(ProcedureError::external(e))
768 }
769 }
770 }
771 }
772
773 async fn rollback(&mut self, ctx: &ProcedureContext) -> ProcedureResult<()> {
774 self.rollback_inner(ctx)
775 .await
776 .map_err(ProcedureError::external)
777 }
778
779 fn rollback_supported(&self) -> bool {
780 true
781 }
782
783 fn dump(&self) -> ProcedureResult<String> {
784 let data = RepartitionData {
785 state: self.state.as_ref(),
786 persistent_ctx: &self.context.persistent_ctx,
787 };
788 serde_json::to_string(&data).context(ToJsonSnafu)
789 }
790
791 fn lock_key(&self) -> LockKey {
792 LockKey::new(self.context.persistent_ctx.lock_key())
793 }
794
795 fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn common_event_recorder::Event>> {
796 if !ctx.event_type_filter.allows(REPARTITION_EVENT_TYPE) {
797 return None;
798 }
799
800 let event = if matches!(ctx.trigger, EventTrigger::Submitted) {
801 let start = self.state.as_any().downcast_ref::<RepartitionStart>()?;
802 RepartitionEvent::submitted(&self.context.persistent_ctx, start)
803 } else {
804 RepartitionEvent::lifecycle(&self.context.persistent_ctx)
805 };
806 Some(Box::new(event))
807 }
808}
809
810pub struct DefaultRepartitionProcedureFactory {
811 mailbox: MailboxRef,
812 server_addr: String,
813 gc_requirement_manager: RepartitionGcRequirementManagerRef,
814}
815
816impl DefaultRepartitionProcedureFactory {
817 pub fn new(
818 mailbox: MailboxRef,
819 server_addr: String,
820 gc_requirement_manager: RepartitionGcRequirementManagerRef,
821 ) -> Self {
822 Self {
823 mailbox,
824 server_addr,
825 gc_requirement_manager,
826 }
827 }
828}
829
830pub struct GcDisabledRepartitionProcedureFactory {
835 enabled_factory: DefaultRepartitionProcedureFactory,
836}
837
838impl GcDisabledRepartitionProcedureFactory {
839 pub fn new(
840 mailbox: MailboxRef,
841 server_addr: String,
842 gc_requirement_manager: RepartitionGcRequirementManagerRef,
843 ) -> Self {
844 Self {
845 enabled_factory: DefaultRepartitionProcedureFactory::new(
846 mailbox,
847 server_addr,
848 gc_requirement_manager,
849 ),
850 }
851 }
852}
853
854#[async_trait::async_trait]
855impl RepartitionProcedureFactory for GcDisabledRepartitionProcedureFactory {
856 fn create(
857 &self,
858 _ddl_ctx: &DdlContext,
859 _table_name: TableName,
860 _table_id: TableId,
861 _source: RepartitionSource,
862 _to_exprs: Vec<String>,
863 _timeout: Option<Duration>,
864 ) -> std::result::Result<BoxedProcedure, BoxedError> {
865 Err(BoxedError::new(
866 error::InvalidArgumentsSnafu {
867 err_msg: "Repartition requires metasrv GC to be enabled".to_string(),
868 }
869 .build(),
870 ))
871 }
872
873 fn register_loaders(
874 &self,
875 ddl_ctx: &DdlContext,
876 procedure_manager: &ProcedureManagerRef,
877 ) -> std::result::Result<(), BoxedError> {
878 self.enabled_factory
879 .register_loaders(ddl_ctx, procedure_manager)
880 }
881
882 async fn ensure_gc_requirement(&self) -> std::result::Result<(), BoxedError> {
883 Err(BoxedError::new(
884 error::InvalidArgumentsSnafu {
885 err_msg: "Repartition requires metasrv GC to be enabled".to_string(),
886 }
887 .build(),
888 ))
889 }
890}
891
892#[async_trait::async_trait]
893impl RepartitionProcedureFactory for DefaultRepartitionProcedureFactory {
894 fn create(
895 &self,
896 ddl_ctx: &DdlContext,
897 table_name: TableName,
898 table_id: TableId,
899 source: RepartitionSource,
900 to_exprs: Vec<String>,
901 timeout: Option<Duration>,
902 ) -> std::result::Result<BoxedProcedure, BoxedError> {
903 let persistent_ctx = PersistentContext::new(table_name, table_id, timeout);
904 let from = match source {
905 RepartitionSource::Partitioned {
906 exprs,
907 target_partition_columns,
908 } => {
909 let exprs = exprs
910 .iter()
911 .map(|e| {
912 PartitionExpr::from_json_str(e)
913 .context(error::DeserializePartitionExprSnafu)?
914 .context(error::EmptyPartitionExprSnafu)
915 })
916 .collect::<Result<Vec<_>>>()
917 .map_err(BoxedError::new)?;
918 RepartitionFrom::Partitioned {
919 exprs,
920 target_partition_columns,
921 }
922 }
923 RepartitionSource::Unpartitioned { partition_columns } => {
924 RepartitionFrom::Unpartitioned { partition_columns }
925 }
926 };
927 let to_exprs = to_exprs
928 .iter()
929 .map(|e| {
930 PartitionExpr::from_json_str(e)
931 .context(error::DeserializePartitionExprSnafu)?
932 .context(error::EmptyPartitionExprSnafu)
933 })
934 .collect::<Result<Vec<_>>>()
935 .map_err(BoxedError::new)?;
936
937 let procedure = RepartitionProcedure::new(
938 from,
939 to_exprs,
940 Context::new(
941 ddl_ctx,
942 self.mailbox.clone(),
943 self.server_addr.clone(),
944 persistent_ctx,
945 ),
946 );
947
948 Ok(Box::new(procedure))
949 }
950
951 fn register_loaders(
952 &self,
953 ddl_ctx: &DdlContext,
954 procedure_manager: &ProcedureManagerRef,
955 ) -> std::result::Result<(), BoxedError> {
956 let mailbox = self.mailbox.clone();
958 let server_addr = self.server_addr.clone();
959 let moved_ddl_ctx = ddl_ctx.clone();
960 procedure_manager
961 .register_loader(
962 RepartitionProcedure::TYPE_NAME,
963 Box::new(move |json| {
964 let mailbox = mailbox.clone();
965 let server_addr = server_addr.clone();
966 let ddl_ctx = moved_ddl_ctx.clone();
967 let factory = move |persistent_ctx| {
968 Context::new(&ddl_ctx, mailbox, server_addr, persistent_ctx)
969 };
970 RepartitionProcedure::from_json(json, factory).map(|p| Box::new(p) as _)
971 }),
972 )
973 .map_err(BoxedError::new)?;
974
975 let mailbox = self.mailbox.clone();
977 let server_addr = self.server_addr.clone();
978 let moved_ddl_ctx = ddl_ctx.clone();
979 procedure_manager
980 .register_loader(
981 RepartitionGroupProcedure::TYPE_NAME,
982 Box::new(move |json| {
983 let mailbox = mailbox.clone();
984 let server_addr = server_addr.clone();
985 let ddl_ctx = moved_ddl_ctx.clone();
986 let factory = move |persistent_ctx| {
987 RepartitionGroupContext::new(&ddl_ctx, mailbox, server_addr, persistent_ctx)
988 };
989 RepartitionGroupProcedure::from_json(json, factory).map(|p| Box::new(p) as _)
990 }),
991 )
992 .map_err(BoxedError::new)?;
993
994 Ok(())
995 }
996
997 async fn ensure_gc_requirement(&self) -> std::result::Result<(), BoxedError> {
998 self.gc_requirement_manager
999 .require_gc()
1000 .await
1001 .map_err(BoxedError::new)
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use std::collections::HashMap;
1008 use std::sync::Arc;
1009 use std::sync::atomic::{AtomicBool, Ordering};
1010
1011 use common_error::ext::{BoxedError, ErrorExt};
1012 use common_error::mock::MockError;
1013 use common_error::status_code::StatusCode;
1014 use common_event_recorder::EventTypeFilter;
1015 use common_meta::ddl::test_util::datanode_handler::{
1016 DatanodeWatcher, NaiveDatanodeHandler, UnexpectedErrorDatanodeHandler,
1017 };
1018 use common_meta::error;
1019 use common_meta::peer::Peer;
1020 use common_meta::region_keeper::MemoryRegionKeeper;
1021 use common_meta::rpc::router::{LeaderState, Region, RegionRoute};
1022 use common_meta::state_store::KvStateStore;
1023 use common_meta::test_util::MockDatanodeManager;
1024 use common_procedure::local::{LocalManager, ManagerConfig};
1025 use common_procedure::{Error as ProcedureError, Procedure, ProcedureId, ProcedureState};
1026 use store_api::region_engine::RegionRole;
1027 use store_api::storage::RegionId;
1028 use table::table_name::TableName;
1029 use tokio::sync::mpsc;
1030 use uuid::Uuid;
1031
1032 use super::*;
1033 use crate::procedure::repartition::allocate_region::AllocateRegion;
1034 use crate::procedure::repartition::collect::Collect;
1035 use crate::procedure::repartition::deallocate_region::DeallocateRegion;
1036 use crate::procedure::repartition::dispatch::Dispatch;
1037 use crate::procedure::repartition::gc_requirement::RepartitionGcRequirementManager;
1038 use crate::procedure::repartition::group::update_metadata::UpdateMetadata;
1039 use crate::procedure::repartition::plan::{SourceRegionDescriptor, TargetRegionDescriptor};
1040 use crate::procedure::repartition::repartition_end::RepartitionEnd;
1041 use crate::procedure::repartition::test_util::{
1042 TestingEnv, assert_parent_state, current_parent_region_routes, extract_subprocedure_ids,
1043 new_parent_context, procedure_context_with_receivers, procedure_state_receiver, range_expr,
1044 test_region_route, test_region_wal_options,
1045 };
1046 use crate::procedure::repartition::update_partition_metadata::{
1047 PartitionMetadataUpdate, UpdatePartitionMetadata,
1048 };
1049
1050 fn test_plan(table_id: TableId) -> RepartitionPlanEntry {
1051 RepartitionPlanEntry {
1052 group_id: uuid::Uuid::new_v4(),
1053 source_regions: vec![SourceRegionDescriptor::partitioned(
1054 RegionId::new(table_id, 1),
1055 range_expr("x", 0, 100),
1056 )],
1057 target_regions: vec![
1058 TargetRegionDescriptor {
1059 region_id: RegionId::new(table_id, 1),
1060 partition_expr: range_expr("x", 0, 50),
1061 },
1062 TargetRegionDescriptor {
1063 region_id: RegionId::new(table_id, 3),
1064 partition_expr: range_expr("x", 50, 100),
1065 },
1066 ],
1067 allocated_region_ids: vec![RegionId::new(table_id, 3)],
1068 pending_deallocate_region_ids: vec![],
1069 transition_map: vec![vec![0, 1]],
1070 original_target_routes: vec![],
1071 }
1072 }
1073
1074 fn with_rollback_metadata(
1075 mut plan: RepartitionPlanEntry,
1076 original_target_routes: Vec<RegionRoute>,
1077 ) -> RepartitionPlanEntry {
1078 plan.original_target_routes = original_target_routes;
1079 plan
1080 }
1081
1082 fn apply_group_staging(
1083 plan: &RepartitionPlanEntry,
1084 current_region_routes: &[RegionRoute],
1085 ) -> Vec<RegionRoute> {
1086 UpdateMetadata::apply_staging_region_routes(
1087 plan.group_id,
1088 &plan.source_regions,
1089 &plan.target_regions,
1090 &plan.pending_deallocate_region_ids,
1091 current_region_routes,
1092 )
1093 .unwrap()
1094 }
1095
1096 fn exit_group_staging(
1097 plan: &RepartitionPlanEntry,
1098 current_region_routes: &[RegionRoute],
1099 ) -> Vec<RegionRoute> {
1100 UpdateMetadata::exit_staging_region_routes(
1101 plan.group_id,
1102 &plan.source_regions,
1103 &plan.target_regions,
1104 current_region_routes,
1105 )
1106 .unwrap()
1107 }
1108
1109 fn region_route_by_id(region_routes: &[RegionRoute], region_id: RegionId) -> &RegionRoute {
1110 region_routes
1111 .iter()
1112 .find(|route| route.region.id == region_id)
1113 .unwrap()
1114 }
1115
1116 async fn table_partition_key_indices(ctx: &Context) -> Vec<usize> {
1117 ctx.get_table_info_value()
1118 .await
1119 .unwrap()
1120 .table_info
1121 .meta
1122 .partition_key_indices
1123 }
1124
1125 fn test_procedure(state: Box<dyn State>, context: Context) -> RepartitionProcedure {
1126 RepartitionProcedure { state, context }
1127 }
1128
1129 fn test_context(env: &TestingEnv, table_id: TableId) -> Context {
1130 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1131 let ddl_ctx = env.ddl_context(node_manager);
1132 let persistent_ctx = PersistentContext::new(
1133 TableName::new("test_catalog", "test_schema", "test_table"),
1134 table_id,
1135 None,
1136 );
1137
1138 Context::new(
1139 &ddl_ctx,
1140 env.mailbox_ctx.mailbox().clone(),
1141 env.server_addr.clone(),
1142 persistent_ctx,
1143 )
1144 }
1145
1146 #[test]
1147 fn test_gc_disabled_factory_rejects_repartition_and_registers_loaders() {
1148 let env = TestingEnv::new();
1149 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1150 let ddl_ctx = env.ddl_context(node_manager);
1151 let factory = GcDisabledRepartitionProcedureFactory::new(
1152 env.mailbox_ctx.mailbox().clone(),
1153 env.server_addr.clone(),
1154 Arc::new(RepartitionGcRequirementManager::new(env.kv_backend.clone())),
1155 );
1156
1157 let err = factory
1158 .create(
1159 &ddl_ctx,
1160 TableName::new("test_catalog", "test_schema", "test_table"),
1161 1024,
1162 RepartitionSource::Unpartitioned {
1163 partition_columns: vec![],
1164 },
1165 vec![],
1166 None,
1167 )
1168 .err()
1169 .expect("GC-disabled factory must reject repartition");
1170
1171 assert_eq!(StatusCode::InvalidArguments, err.status_code());
1172 assert_eq!(
1173 "Invalid arguments: Repartition requires metasrv GC to be enabled",
1174 err.to_string()
1175 );
1176
1177 let state_store = Arc::new(KvStateStore::new(env.kv_backend));
1178 let procedure_manager = Arc::new(LocalManager::new(
1179 ManagerConfig::default(),
1180 state_store.clone(),
1181 state_store,
1182 None,
1183 None,
1184 ));
1185 let procedure_manager_ref: ProcedureManagerRef = procedure_manager.clone();
1186 factory
1187 .register_loaders(&ddl_ctx, &procedure_manager_ref)
1188 .unwrap();
1189 assert!(procedure_manager.contains_loader(RepartitionProcedure::TYPE_NAME));
1190 assert!(procedure_manager.contains_loader(RepartitionGroupProcedure::TYPE_NAME));
1191 }
1192
1193 #[test]
1194 fn test_filter_allocated_region_routes() {
1195 let table_id = 1024;
1196 let region_routes = vec![
1197 test_region_route(RegionId::new(table_id, 1), "a"),
1198 test_region_route(RegionId::new(table_id, 2), "b"),
1199 ];
1200 let allocated_region_ids = HashSet::from([RegionId::new(table_id, 2)]);
1201
1202 let new_region_routes =
1203 DeallocateRegion::generate_region_routes(®ion_routes, &allocated_region_ids);
1204
1205 assert_eq!(new_region_routes.len(), 1);
1206 assert_eq!(new_region_routes[0].region.id, RegionId::new(table_id, 1));
1207 }
1208
1209 #[test]
1210 fn test_should_rollback_after_metadata_update() {
1211 let env = TestingEnv::new();
1212 let table_id = 1024;
1213
1214 let procedure = test_procedure(
1215 Box::new(RepartitionStart::new(
1216 RepartitionFrom::Partitioned {
1217 exprs: vec![],
1218 target_partition_columns: None,
1219 },
1220 vec![],
1221 )),
1222 test_context(&env, table_id),
1223 );
1224 assert!(!procedure.should_rollback());
1225
1226 let procedure = test_procedure(
1227 Box::new(UpdatePartitionMetadata::new(vec![])),
1228 test_context(&env, table_id),
1229 );
1230 assert!(procedure.should_rollback());
1231
1232 let procedure = test_procedure(
1233 Box::new(AllocateRegion::new(vec![])),
1234 test_context(&env, table_id),
1235 );
1236 assert!(procedure.should_rollback());
1237
1238 let procedure = test_procedure(Box::new(Dispatch), test_context(&env, table_id));
1239 assert!(procedure.should_rollback());
1240
1241 let procedure =
1242 test_procedure(Box::new(Collect::new(vec![])), test_context(&env, table_id));
1243 assert!(procedure.should_rollback());
1244
1245 let procedure = test_procedure(Box::new(DeallocateRegion), test_context(&env, table_id));
1246 assert!(!procedure.should_rollback());
1247
1248 let procedure = test_procedure(Box::new(RepartitionEnd), test_context(&env, table_id));
1249 assert!(!procedure.should_rollback());
1250 }
1251
1252 #[test]
1253 fn test_event_hook_records_submitted_and_lightweight_lifecycle_events() {
1254 let env = TestingEnv::new();
1255 let procedure = test_procedure(
1256 Box::new(RepartitionStart::new(
1257 RepartitionFrom::Unpartitioned {
1258 partition_columns: vec!["x".to_string()],
1259 },
1260 vec![range_expr("x", 0, 100)],
1261 )),
1262 test_context(&env, 1024),
1263 );
1264 let state = ProcedureState::Running;
1265 let all = Arc::new(EventTypeFilter::All);
1266
1267 let submitted = procedure
1268 .event(&EventContext {
1269 procedure_id: ProcedureId::random(),
1270 lifecycle_state: &state,
1271 trigger: EventTrigger::Submitted,
1272 event_type_filter: all.clone(),
1273 event_context: None,
1274 })
1275 .unwrap();
1276 assert_eq!(submitted.event_type(), REPARTITION_EVENT_TYPE);
1277 assert_ne!(submitted.json_payload().unwrap(), serde_json::Value::Null);
1278
1279 let allowed = procedure
1280 .event(&EventContext {
1281 procedure_id: ProcedureId::random(),
1282 lifecycle_state: &state,
1283 trigger: EventTrigger::Submitted,
1284 event_type_filter: Arc::new(EventTypeFilter::Only(HashSet::from([
1285 REPARTITION_EVENT_TYPE.to_string(),
1286 ]))),
1287 event_context: None,
1288 })
1289 .unwrap();
1290 assert_eq!(allowed.event_type(), REPARTITION_EVENT_TYPE);
1291
1292 let succeeded = procedure
1293 .event(&EventContext {
1294 procedure_id: ProcedureId::random(),
1295 lifecycle_state: &state,
1296 trigger: EventTrigger::Succeeded,
1297 event_type_filter: all,
1298 event_context: None,
1299 })
1300 .unwrap();
1301 assert_eq!(succeeded.json_payload().unwrap(), serde_json::Value::Null);
1302
1303 let filtered = procedure.event(&EventContext {
1304 procedure_id: ProcedureId::random(),
1305 lifecycle_state: &state,
1306 trigger: EventTrigger::Submitted,
1307 event_type_filter: Arc::new(EventTypeFilter::Only(HashSet::from([
1308 "another_event".to_string()
1309 ]))),
1310 event_context: None,
1311 });
1312 assert!(filtered.is_none());
1313
1314 let empty = procedure.event(&EventContext {
1315 procedure_id: ProcedureId::random(),
1316 lifecycle_state: &state,
1317 trigger: EventTrigger::Submitted,
1318 event_type_filter: Arc::new(EventTypeFilter::Only(HashSet::new())),
1319 event_context: None,
1320 });
1321 assert!(empty.is_none());
1322 }
1323
1324 #[test]
1325 fn test_register_operating_regions_preserves_route_roles() {
1326 let keeper = Arc::new(MemoryRegionKeeper::new());
1327 let region_routes = vec![
1328 RegionRoute {
1329 region: Region::new_test(RegionId::new(1024, 1)),
1330 leader_peer: Some(Peer::empty(1)),
1331 follower_peers: vec![],
1332 leader_state: None,
1333 leader_down_since: None,
1334 write_route_policy: None,
1335 },
1336 RegionRoute {
1337 region: Region::new_test(RegionId::new(1024, 2)),
1338 leader_peer: Some(Peer::empty(2)),
1339 follower_peers: vec![],
1340 leader_state: Some(LeaderState::Staging),
1341 leader_down_since: None,
1342 write_route_policy: None,
1343 },
1344 RegionRoute {
1345 region: Region::new_test(RegionId::new(1024, 3)),
1346 leader_peer: Some(Peer::empty(3)),
1347 follower_peers: vec![],
1348 leader_state: Some(LeaderState::Downgrading),
1349 leader_down_since: None,
1350 write_route_policy: None,
1351 },
1352 ];
1353
1354 let _guards = Context::register_operating_regions(&keeper, ®ion_routes).unwrap();
1355
1356 let leader_roles =
1357 keeper.extract_operating_region_roles(1, &HashSet::from([RegionId::new(1024, 1)]));
1358 let staging_roles =
1359 keeper.extract_operating_region_roles(2, &HashSet::from([RegionId::new(1024, 2)]));
1360 let downgrading_roles =
1361 keeper.extract_operating_region_roles(3, &HashSet::from([RegionId::new(1024, 3)]));
1362
1363 assert_eq!(
1364 leader_roles.get(&RegionId::new(1024, 1)),
1365 Some(&RegionRole::Leader)
1366 );
1367 assert_eq!(
1368 staging_roles.get(&RegionId::new(1024, 2)),
1369 Some(&RegionRole::StagingLeader)
1370 );
1371 assert_eq!(
1372 downgrading_roles.get(&RegionId::new(1024, 3)),
1373 Some(&RegionRole::DowngradingLeader)
1374 );
1375 }
1376
1377 #[test]
1378 fn test_persistent_context_partition_metadata_update_serde_default() {
1379 let json = r#"{
1380 "catalog_name":"test_catalog",
1381 "schema_name":"test_schema",
1382 "table_name":"test_table",
1383 "table_id":1024,
1384 "plans":[],
1385 "timeout":"120s"
1386 }"#;
1387
1388 let persistent_ctx: PersistentContext = serde_json::from_str(json).unwrap();
1389
1390 assert!(persistent_ctx.partition_metadata_update.is_none());
1391 }
1392
1393 #[tokio::test]
1394 async fn test_repartition_rollback_removes_partition_metadata_indices() {
1395 let env = TestingEnv::new();
1396 let table_id = 1024;
1397 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1398 env.create_physical_table_metadata_for_repartition(
1399 table_id,
1400 vec![test_region_route(RegionId::new(table_id, 1), "")],
1401 test_region_wal_options(&[1]),
1402 )
1403 .await;
1404
1405 let mut context = new_parent_context(&env, node_manager, table_id);
1406 let current = context.get_raw_table_info_value().await.unwrap();
1407 let mut table_info = current.table_info.clone();
1408 table_info.meta.partition_key_indices = vec![0, 1];
1409 context
1410 .update_table_info(¤t, current.update(table_info))
1411 .await
1412 .unwrap();
1413 context.persistent_ctx.partition_metadata_update = Some(
1414 PartitionMetadataUpdate::from_partitioned(vec![1], vec![0, 1]),
1415 );
1416 let mut procedure = RepartitionProcedure {
1417 state: Box::new(UpdatePartitionMetadata::new(vec![])),
1418 context,
1419 };
1420
1421 procedure
1422 .rollback(&TestingEnv::procedure_context())
1423 .await
1424 .unwrap();
1425
1426 assert_eq!(
1427 procedure
1428 .context
1429 .get_table_info_value()
1430 .await
1431 .unwrap()
1432 .table_info
1433 .meta
1434 .partition_key_indices,
1435 vec![1]
1436 );
1437 }
1438
1439 #[tokio::test]
1440 async fn test_repartition_rollback_removes_allocated_routes_from_dispatch() {
1441 let env = TestingEnv::new();
1442 let table_id = 1024;
1443 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1444 let ddl_ctx = env.ddl_context(node_manager);
1445 let original_region_routes = vec![
1446 test_region_route(
1447 RegionId::new(table_id, 1),
1448 &range_expr("x", 0, 100).as_json_str().unwrap(),
1449 ),
1450 test_region_route(
1451 RegionId::new(table_id, 2),
1452 &range_expr("x", 50, 100).as_json_str().unwrap(),
1453 ),
1454 test_region_route(RegionId::new(table_id, 3), ""),
1455 ];
1456 env.create_physical_table_metadata_with_wal_options(
1457 table_id,
1458 original_region_routes,
1459 test_region_wal_options(&[1, 2]),
1460 )
1461 .await;
1462
1463 let mut persistent_ctx = PersistentContext::new(
1464 TableName::new("test_catalog", "test_schema", "test_table"),
1465 table_id,
1466 None,
1467 );
1468 persistent_ctx.plans = vec![with_rollback_metadata(
1469 test_plan(table_id),
1470 vec![
1471 test_region_route(
1472 RegionId::new(table_id, 1),
1473 &range_expr("x", 0, 100).as_json_str().unwrap(),
1474 ),
1475 test_region_route(RegionId::new(table_id, 3), ""),
1476 ],
1477 )];
1478 persistent_ctx.failed_procedures = vec![ProcedureMeta {
1479 plan_index: 0,
1480 group_id: Uuid::new_v4(),
1481 procedure_id: ProcedureId::random(),
1482 }];
1483 let context = Context::new(
1484 &ddl_ctx,
1485 env.mailbox_ctx.mailbox().clone(),
1486 env.server_addr.clone(),
1487 persistent_ctx,
1488 );
1489 let mut procedure = RepartitionProcedure {
1490 state: Box::new(Dispatch),
1491 context,
1492 };
1493
1494 procedure
1495 .rollback(&TestingEnv::procedure_context())
1496 .await
1497 .unwrap();
1498
1499 let region_routes = current_parent_region_routes(&procedure.context).await;
1500 assert_eq!(region_routes.len(), 2);
1501 assert_eq!(region_routes[0].region.id, RegionId::new(table_id, 1));
1502 assert_eq!(region_routes[1].region.id, RegionId::new(table_id, 2));
1503 }
1504
1505 #[tokio::test]
1506 async fn test_repartition_rollback_removes_allocated_routes_from_allocate() {
1507 let env = TestingEnv::new();
1508 let table_id = 1024;
1509 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1510 let ddl_ctx = env.ddl_context(node_manager);
1511 let original_region_routes = vec![
1512 test_region_route(
1513 RegionId::new(table_id, 1),
1514 &range_expr("x", 0, 100).as_json_str().unwrap(),
1515 ),
1516 test_region_route(
1517 RegionId::new(table_id, 2),
1518 &range_expr("x", 50, 100).as_json_str().unwrap(),
1519 ),
1520 test_region_route(RegionId::new(table_id, 3), ""),
1521 ];
1522 env.create_physical_table_metadata_with_wal_options(
1523 table_id,
1524 original_region_routes,
1525 test_region_wal_options(&[1, 2]),
1526 )
1527 .await;
1528
1529 let mut persistent_ctx = PersistentContext::new(
1530 TableName::new("test_catalog", "test_schema", "test_table"),
1531 table_id,
1532 None,
1533 );
1534 persistent_ctx.plans = vec![test_plan(table_id)];
1535 let context = Context::new(
1536 &ddl_ctx,
1537 env.mailbox_ctx.mailbox().clone(),
1538 env.server_addr.clone(),
1539 persistent_ctx,
1540 );
1541 let mut procedure = RepartitionProcedure {
1542 state: Box::new(AllocateRegion::new(vec![])),
1543 context,
1544 };
1545
1546 procedure
1547 .rollback(&TestingEnv::procedure_context())
1548 .await
1549 .unwrap();
1550
1551 let region_routes = current_parent_region_routes(&procedure.context).await;
1552 assert_eq!(region_routes.len(), 2);
1553 assert_eq!(region_routes[0].region.id, RegionId::new(table_id, 1));
1554 assert_eq!(region_routes[1].region.id, RegionId::new(table_id, 2));
1555 }
1556
1557 #[tokio::test]
1558 async fn test_repartition_rollback_from_collect_only_removes_failed_allocated_routes() {
1559 let env = TestingEnv::new();
1560 let table_id = 1024;
1561 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1562 let ddl_ctx = env.ddl_context(node_manager);
1563 let original_region_routes = vec![
1564 test_region_route(
1565 RegionId::new(table_id, 1),
1566 &range_expr("x", 0, 100).as_json_str().unwrap(),
1567 ),
1568 test_region_route(
1569 RegionId::new(table_id, 2),
1570 &range_expr("x", 100, 200).as_json_str().unwrap(),
1571 ),
1572 test_region_route(RegionId::new(table_id, 3), ""),
1573 test_region_route(RegionId::new(table_id, 4), ""),
1574 ];
1575 env.create_physical_table_metadata_with_wal_options(
1576 table_id,
1577 original_region_routes,
1578 test_region_wal_options(&[1, 2, 3, 4]),
1579 )
1580 .await;
1581
1582 let mut persistent_ctx = PersistentContext::new(
1583 TableName::new("test_catalog", "test_schema", "test_table"),
1584 table_id,
1585 None,
1586 );
1587 let failed_plan = test_plan(table_id);
1588 let failed_plan = with_rollback_metadata(
1589 failed_plan,
1590 vec![
1591 test_region_route(
1592 RegionId::new(table_id, 1),
1593 &range_expr("x", 0, 100).as_json_str().unwrap(),
1594 ),
1595 test_region_route(RegionId::new(table_id, 3), ""),
1596 ],
1597 );
1598 let succeeded_plan = RepartitionPlanEntry {
1599 group_id: Uuid::new_v4(),
1600 source_regions: vec![SourceRegionDescriptor::partitioned(
1601 RegionId::new(table_id, 2),
1602 range_expr("x", 100, 200),
1603 )],
1604 target_regions: vec![
1605 TargetRegionDescriptor {
1606 region_id: RegionId::new(table_id, 2),
1607 partition_expr: range_expr("x", 100, 150),
1608 },
1609 TargetRegionDescriptor {
1610 region_id: RegionId::new(table_id, 4),
1611 partition_expr: range_expr("x", 150, 200),
1612 },
1613 ],
1614 allocated_region_ids: vec![RegionId::new(table_id, 4)],
1615 pending_deallocate_region_ids: vec![],
1616 transition_map: vec![vec![0]],
1617 original_target_routes: vec![
1618 test_region_route(
1619 RegionId::new(table_id, 2),
1620 &range_expr("x", 100, 200).as_json_str().unwrap(),
1621 ),
1622 test_region_route(RegionId::new(table_id, 4), ""),
1623 ],
1624 };
1625 persistent_ctx.plans = vec![failed_plan, succeeded_plan];
1626 persistent_ctx.failed_procedures = vec![ProcedureMeta {
1627 plan_index: 0,
1628 group_id: persistent_ctx.plans[0].group_id,
1629 procedure_id: ProcedureId::random(),
1630 }];
1631
1632 let context = Context::new(
1633 &ddl_ctx,
1634 env.mailbox_ctx.mailbox().clone(),
1635 env.server_addr.clone(),
1636 persistent_ctx,
1637 );
1638 let mut procedure = RepartitionProcedure {
1639 state: Box::new(Collect::new(vec![])),
1640 context,
1641 };
1642
1643 procedure
1644 .rollback(&TestingEnv::procedure_context())
1645 .await
1646 .unwrap();
1647
1648 let region_routes = current_parent_region_routes(&procedure.context).await;
1649 assert_eq!(region_routes.len(), 3);
1650 assert_eq!(region_routes[0].region.id, RegionId::new(table_id, 1));
1651 assert_eq!(region_routes[1].region.id, RegionId::new(table_id, 2));
1652 assert_eq!(region_routes[2].region.id, RegionId::new(table_id, 4));
1653 }
1654
1655 #[tokio::test]
1656 async fn test_repartition_rollback_from_collect_restores_failed_group_metadata_only() {
1657 let env = TestingEnv::new();
1658 let table_id = 1024;
1659 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1660 let ddl_ctx = env.ddl_context(node_manager);
1661 let original_region_routes = vec![
1662 test_region_route(
1663 RegionId::new(table_id, 1),
1664 &range_expr("x", 0, 100).as_json_str().unwrap(),
1665 ),
1666 test_region_route(
1667 RegionId::new(table_id, 2),
1668 &range_expr("x", 100, 200).as_json_str().unwrap(),
1669 ),
1670 test_region_route(RegionId::new(table_id, 3), ""),
1671 test_region_route(RegionId::new(table_id, 4), ""),
1672 ];
1673
1674 let failed_plan = with_rollback_metadata(
1675 test_plan(table_id),
1676 vec![
1677 original_region_routes[0].clone(),
1678 original_region_routes[2].clone(),
1679 ],
1680 );
1681 let succeeded_plan = RepartitionPlanEntry {
1682 group_id: Uuid::new_v4(),
1683 source_regions: vec![SourceRegionDescriptor::partitioned(
1684 RegionId::new(table_id, 2),
1685 range_expr("x", 100, 200),
1686 )],
1687 target_regions: vec![
1688 TargetRegionDescriptor {
1689 region_id: RegionId::new(table_id, 2),
1690 partition_expr: range_expr("x", 100, 150),
1691 },
1692 TargetRegionDescriptor {
1693 region_id: RegionId::new(table_id, 4),
1694 partition_expr: range_expr("x", 150, 200),
1695 },
1696 ],
1697 allocated_region_ids: vec![RegionId::new(table_id, 4)],
1698 pending_deallocate_region_ids: vec![],
1699 transition_map: vec![vec![0, 1]],
1700 original_target_routes: vec![
1701 original_region_routes[1].clone(),
1702 original_region_routes[3].clone(),
1703 ],
1704 };
1705 let current_region_routes = apply_group_staging(&failed_plan, &original_region_routes);
1706 let current_region_routes = apply_group_staging(&succeeded_plan, ¤t_region_routes);
1707 let current_region_routes = exit_group_staging(&succeeded_plan, ¤t_region_routes);
1708 env.create_physical_table_metadata_with_wal_options(
1709 table_id,
1710 current_region_routes,
1711 test_region_wal_options(&[1, 2, 3, 4]),
1712 )
1713 .await;
1714
1715 let mut persistent_ctx = PersistentContext::new(
1716 TableName::new("test_catalog", "test_schema", "test_table"),
1717 table_id,
1718 None,
1719 );
1720 persistent_ctx.plans = vec![failed_plan, succeeded_plan.clone()];
1721 persistent_ctx.failed_procedures = vec![ProcedureMeta {
1722 plan_index: 0,
1723 group_id: persistent_ctx.plans[0].group_id,
1724 procedure_id: ProcedureId::random(),
1725 }];
1726
1727 let context = Context::new(
1728 &ddl_ctx,
1729 env.mailbox_ctx.mailbox().clone(),
1730 env.server_addr.clone(),
1731 persistent_ctx,
1732 );
1733 let mut procedure = RepartitionProcedure {
1734 state: Box::new(Collect::new(vec![])),
1735 context,
1736 };
1737
1738 procedure
1739 .rollback(&TestingEnv::procedure_context())
1740 .await
1741 .unwrap();
1742
1743 assert_eq!(
1744 current_parent_region_routes(&procedure.context).await,
1745 vec![
1746 test_region_route(
1747 RegionId::new(table_id, 1),
1748 &range_expr("x", 0, 100).as_json_str().unwrap(),
1749 ),
1750 RegionRoute {
1751 region: Region {
1752 id: RegionId::new(table_id, 2),
1753 partition_expr: range_expr("x", 100, 150).as_json_str().unwrap(),
1754 ..Default::default()
1755 },
1756 leader_peer: Some(Peer::empty(1)),
1757 ..Default::default()
1758 },
1759 RegionRoute {
1760 region: Region {
1761 id: RegionId::new(table_id, 4),
1762 partition_expr: range_expr("x", 150, 200).as_json_str().unwrap(),
1763 ..Default::default()
1764 },
1765 leader_peer: Some(Peer::empty(1)),
1766 ..Default::default()
1767 },
1768 ]
1769 );
1770 }
1771
1772 #[tokio::test]
1773 async fn test_repartition_rollback_from_collect_restores_unknown_group_metadata() {
1774 let env = TestingEnv::new();
1775 let table_id = 1024;
1776 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1777 let ddl_ctx = env.ddl_context(node_manager);
1778 let original_region_routes = vec![
1779 test_region_route(
1780 RegionId::new(table_id, 1),
1781 &range_expr("x", 0, 100).as_json_str().unwrap(),
1782 ),
1783 test_region_route(
1784 RegionId::new(table_id, 2),
1785 &range_expr("x", 100, 200).as_json_str().unwrap(),
1786 ),
1787 test_region_route(RegionId::new(table_id, 3), ""),
1788 ];
1789 let plan = with_rollback_metadata(
1790 test_plan(table_id),
1791 vec![
1792 original_region_routes[0].clone(),
1793 original_region_routes[2].clone(),
1794 ],
1795 );
1796 let staged_region_routes = apply_group_staging(&plan, &original_region_routes);
1797 assert_eq!(
1798 region_route_by_id(&staged_region_routes, RegionId::new(table_id, 1))
1799 .region
1800 .partition_expr(),
1801 range_expr("x", 0, 50).as_json_str().unwrap()
1802 );
1803 assert!(
1804 region_route_by_id(&staged_region_routes, RegionId::new(table_id, 1))
1805 .is_leader_staging()
1806 );
1807 assert_eq!(
1808 region_route_by_id(&staged_region_routes, RegionId::new(table_id, 3))
1809 .region
1810 .partition_expr(),
1811 range_expr("x", 50, 100).as_json_str().unwrap()
1812 );
1813 assert!(
1814 region_route_by_id(&staged_region_routes, RegionId::new(table_id, 3))
1815 .is_leader_staging()
1816 );
1817 env.create_physical_table_metadata_with_wal_options(
1818 table_id,
1819 staged_region_routes,
1820 test_region_wal_options(&[1, 2, 3]),
1821 )
1822 .await;
1823
1824 let mut persistent_ctx = PersistentContext::new(
1825 TableName::new("test_catalog", "test_schema", "test_table"),
1826 table_id,
1827 None,
1828 );
1829 persistent_ctx.plans = vec![plan.clone()];
1830 persistent_ctx.unknown_procedures = vec![ProcedureMeta {
1831 plan_index: 0,
1832 group_id: plan.group_id,
1833 procedure_id: ProcedureId::random(),
1834 }];
1835
1836 let context = Context::new(
1837 &ddl_ctx,
1838 env.mailbox_ctx.mailbox().clone(),
1839 env.server_addr.clone(),
1840 persistent_ctx,
1841 );
1842 let mut procedure = RepartitionProcedure {
1843 state: Box::new(Collect::new(vec![])),
1844 context,
1845 };
1846
1847 procedure
1848 .rollback(&TestingEnv::procedure_context())
1849 .await
1850 .unwrap();
1851
1852 assert_eq!(
1853 current_parent_region_routes(&procedure.context).await,
1854 vec![
1855 original_region_routes[0].clone(),
1856 original_region_routes[1].clone()
1857 ]
1858 );
1859 }
1860
1861 #[tokio::test]
1862 async fn test_repartition_rollback_is_idempotent() {
1863 let env = TestingEnv::new();
1864 let table_id = 1024;
1865 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1866 let ddl_ctx = env.ddl_context(node_manager);
1867 let original_region_routes = vec![
1868 test_region_route(
1869 RegionId::new(table_id, 1),
1870 &range_expr("x", 0, 100).as_json_str().unwrap(),
1871 ),
1872 test_region_route(
1873 RegionId::new(table_id, 2),
1874 &range_expr("x", 50, 100).as_json_str().unwrap(),
1875 ),
1876 test_region_route(RegionId::new(table_id, 3), ""),
1877 ];
1878 env.create_physical_table_metadata_with_wal_options(
1879 table_id,
1880 original_region_routes,
1881 test_region_wal_options(&[1, 2]),
1882 )
1883 .await;
1884
1885 let mut persistent_ctx = PersistentContext::new(
1886 TableName::new("test_catalog", "test_schema", "test_table"),
1887 table_id,
1888 None,
1889 );
1890 persistent_ctx.plans = vec![with_rollback_metadata(
1891 test_plan(table_id),
1892 vec![
1893 test_region_route(
1894 RegionId::new(table_id, 1),
1895 &range_expr("x", 0, 100).as_json_str().unwrap(),
1896 ),
1897 test_region_route(RegionId::new(table_id, 3), ""),
1898 ],
1899 )];
1900 persistent_ctx.failed_procedures = vec![ProcedureMeta {
1901 plan_index: 0,
1902 group_id: Uuid::new_v4(),
1903 procedure_id: ProcedureId::random(),
1904 }];
1905 let context = Context::new(
1906 &ddl_ctx,
1907 env.mailbox_ctx.mailbox().clone(),
1908 env.server_addr.clone(),
1909 persistent_ctx,
1910 );
1911 let mut procedure = RepartitionProcedure {
1912 state: Box::new(Dispatch),
1913 context,
1914 };
1915
1916 procedure
1917 .rollback(&TestingEnv::procedure_context())
1918 .await
1919 .unwrap();
1920 let once = current_parent_region_routes(&procedure.context).await;
1921
1922 procedure
1923 .rollback(&TestingEnv::procedure_context())
1924 .await
1925 .unwrap();
1926 let twice = current_parent_region_routes(&procedure.context).await;
1927
1928 assert_eq!(once, twice);
1929 assert_eq!(once.len(), 2);
1930 assert_eq!(once[0].region.id, RegionId::new(table_id, 1));
1931 assert_eq!(once[1].region.id, RegionId::new(table_id, 2));
1932 }
1933
1934 #[tokio::test]
1935 async fn test_repartition_rollback_from_collect_restores_failed_merge_group_metadata_only() {
1936 let env = TestingEnv::new();
1937 let table_id = 1024;
1938 let node_manager = Arc::new(MockDatanodeManager::new(UnexpectedErrorDatanodeHandler));
1939 let ddl_ctx = env.ddl_context(node_manager);
1940 let original_region_routes = vec![
1941 test_region_route(
1942 RegionId::new(table_id, 1),
1943 &range_expr("x", 0, 100).as_json_str().unwrap(),
1944 ),
1945 test_region_route(
1946 RegionId::new(table_id, 2),
1947 &range_expr("x", 100, 200).as_json_str().unwrap(),
1948 ),
1949 test_region_route(
1950 RegionId::new(table_id, 3),
1951 &range_expr("x", 200, 300).as_json_str().unwrap(),
1952 ),
1953 test_region_route(RegionId::new(table_id, 4), ""),
1954 ];
1955 let failed_merge_plan = RepartitionPlanEntry {
1956 group_id: Uuid::new_v4(),
1957 source_regions: vec![
1958 SourceRegionDescriptor::partitioned(
1959 RegionId::new(table_id, 1),
1960 range_expr("x", 0, 100),
1961 ),
1962 SourceRegionDescriptor::partitioned(
1963 RegionId::new(table_id, 2),
1964 range_expr("x", 100, 200),
1965 ),
1966 ],
1967 target_regions: vec![TargetRegionDescriptor {
1968 region_id: RegionId::new(table_id, 1),
1969 partition_expr: range_expr("x", 0, 200),
1970 }],
1971 allocated_region_ids: vec![],
1972 pending_deallocate_region_ids: vec![RegionId::new(table_id, 2)],
1973 transition_map: vec![vec![0], vec![0]],
1974 original_target_routes: vec![original_region_routes[0].clone()],
1975 };
1976 let succeeded_split_plan = RepartitionPlanEntry {
1977 group_id: Uuid::new_v4(),
1978 source_regions: vec![SourceRegionDescriptor::partitioned(
1979 RegionId::new(table_id, 3),
1980 range_expr("x", 200, 300),
1981 )],
1982 target_regions: vec![
1983 TargetRegionDescriptor {
1984 region_id: RegionId::new(table_id, 3),
1985 partition_expr: range_expr("x", 200, 250),
1986 },
1987 TargetRegionDescriptor {
1988 region_id: RegionId::new(table_id, 4),
1989 partition_expr: range_expr("x", 250, 300),
1990 },
1991 ],
1992 allocated_region_ids: vec![RegionId::new(table_id, 4)],
1993 pending_deallocate_region_ids: vec![],
1994 transition_map: vec![vec![0, 1]],
1995 original_target_routes: vec![
1996 original_region_routes[2].clone(),
1997 original_region_routes[3].clone(),
1998 ],
1999 };
2000 let current_region_routes =
2001 apply_group_staging(&failed_merge_plan, &original_region_routes);
2002 let current_region_routes =
2003 apply_group_staging(&succeeded_split_plan, ¤t_region_routes);
2004 let staged_region_routes =
2005 exit_group_staging(&succeeded_split_plan, ¤t_region_routes);
2006 env.create_physical_table_metadata_with_wal_options(
2007 table_id,
2008 staged_region_routes,
2009 test_region_wal_options(&[1, 2, 3, 4]),
2010 )
2011 .await;
2012
2013 let mut persistent_ctx = PersistentContext::new(
2014 TableName::new("test_catalog", "test_schema", "test_table"),
2015 table_id,
2016 None,
2017 );
2018 persistent_ctx.plans = vec![failed_merge_plan, succeeded_split_plan.clone()];
2019 persistent_ctx.failed_procedures = vec![ProcedureMeta {
2020 plan_index: 0,
2021 group_id: persistent_ctx.plans[0].group_id,
2022 procedure_id: ProcedureId::random(),
2023 }];
2024
2025 let context = Context::new(
2026 &ddl_ctx,
2027 env.mailbox_ctx.mailbox().clone(),
2028 env.server_addr.clone(),
2029 persistent_ctx,
2030 );
2031 let mut procedure = RepartitionProcedure {
2032 state: Box::new(Collect::new(vec![])),
2033 context,
2034 };
2035
2036 procedure
2037 .rollback(&TestingEnv::procedure_context())
2038 .await
2039 .unwrap();
2040
2041 let region_routes = current_parent_region_routes(&procedure.context).await;
2042 assert_eq!(
2043 region_routes,
2044 vec![
2045 test_region_route(
2046 RegionId::new(table_id, 1),
2047 &range_expr("x", 0, 100).as_json_str().unwrap(),
2048 ),
2049 test_region_route(
2050 RegionId::new(table_id, 2),
2051 &range_expr("x", 100, 200).as_json_str().unwrap(),
2052 ),
2053 RegionRoute {
2054 region: Region {
2055 id: RegionId::new(table_id, 3),
2056 partition_expr: range_expr("x", 200, 250).as_json_str().unwrap(),
2057 ..Default::default()
2058 },
2059 leader_peer: Some(Peer::empty(1)),
2060 ..Default::default()
2061 },
2062 RegionRoute {
2063 region: Region {
2064 id: RegionId::new(table_id, 4),
2065 partition_expr: range_expr("x", 250, 300).as_json_str().unwrap(),
2066 ..Default::default()
2067 },
2068 leader_peer: Some(Peer::empty(1)),
2069 ..Default::default()
2070 },
2071 ]
2072 );
2073 }
2074
2075 #[tokio::test]
2076 async fn test_repartition_procedure_flow_split_failed_and_full_rollback() {
2077 let env = TestingEnv::new();
2078 let table_id = 1024;
2079 let node_manager = Arc::new(MockDatanodeManager::new(NaiveDatanodeHandler));
2080
2081 env.create_physical_table_metadata_for_repartition(
2082 table_id,
2083 vec![
2084 test_region_route(
2085 RegionId::new(table_id, 1),
2086 &range_expr("x", 0, 100).as_json_str().unwrap(),
2087 ),
2088 test_region_route(
2089 RegionId::new(table_id, 2),
2090 &range_expr("x", 100, 200).as_json_str().unwrap(),
2091 ),
2092 ],
2093 test_region_wal_options(&[1, 2]),
2094 )
2095 .await;
2096
2097 let context = new_parent_context(&env, node_manager, table_id);
2098 let mut procedure = RepartitionProcedure::new(
2099 RepartitionFrom::Partitioned {
2100 exprs: vec![range_expr("x", 0, 100)],
2101 target_partition_columns: None,
2102 },
2103 vec![range_expr("x", 0, 50), range_expr("x", 50, 100)],
2104 context,
2105 );
2106
2107 let start_status = procedure
2108 .execute(&TestingEnv::procedure_context())
2109 .await
2110 .unwrap();
2111 assert!(!start_status.need_persist());
2112 let start_status = procedure
2113 .execute(&TestingEnv::procedure_context())
2114 .await
2115 .unwrap();
2116 assert!(start_status.need_persist());
2117 assert_parent_state::<AllocateRegion>(&procedure);
2118
2119 let allocate_status = procedure
2120 .execute(&TestingEnv::procedure_context())
2121 .await
2122 .unwrap();
2123 assert!(allocate_status.need_persist());
2124 assert_parent_state::<Dispatch>(&procedure);
2125 assert_eq!(procedure.context.persistent_ctx.plans.len(), 1);
2126 let plan = &procedure.context.persistent_ctx.plans[0];
2127 let expected_plan = test_plan(table_id);
2128 assert_eq!(plan.source_regions, expected_plan.source_regions);
2129 assert_eq!(plan.target_regions, expected_plan.target_regions);
2130 assert_eq!(
2131 plan.allocated_region_ids,
2132 expected_plan.allocated_region_ids
2133 );
2134 assert_eq!(
2135 plan.pending_deallocate_region_ids,
2136 expected_plan.pending_deallocate_region_ids
2137 );
2138 assert_eq!(plan.transition_map, expected_plan.transition_map);
2139 assert_eq!(
2140 current_parent_region_routes(&procedure.context).await,
2141 vec![
2142 test_region_route(
2143 RegionId::new(table_id, 1),
2144 &range_expr("x", 0, 100).as_json_str().unwrap(),
2145 ),
2146 test_region_route(
2147 RegionId::new(table_id, 2),
2148 &range_expr("x", 100, 200).as_json_str().unwrap(),
2149 ),
2150 RegionRoute {
2151 region: Region {
2152 id: RegionId::new(table_id, 3),
2153 partition_expr: range_expr("x", 50, 100).as_json_str().unwrap(),
2154 ..Default::default()
2155 },
2156 leader_peer: Some(Peer::empty(0)),
2157 ..Default::default()
2158 },
2159 ]
2160 );
2161
2162 let dispatch_status = procedure
2163 .execute(&TestingEnv::procedure_context())
2164 .await
2165 .unwrap();
2166 assert!(!dispatch_status.need_persist());
2167 let subprocedure_ids = extract_subprocedure_ids(dispatch_status);
2168 assert_eq!(subprocedure_ids.len(), 1);
2169 assert_parent_state::<Collect>(&procedure);
2170
2171 let failed_state = ProcedureState::failed(Arc::new(ProcedureError::external(
2172 MockError::new(StatusCode::Internal),
2173 )));
2174 let collect_ctx = procedure_context_with_receivers(HashMap::from([(
2175 subprocedure_ids[0],
2176 procedure_state_receiver(failed_state),
2177 )]));
2178
2179 let err = procedure.execute(&collect_ctx).await.unwrap_err();
2180 assert!(!err.is_retry_later());
2181 assert_parent_state::<Collect>(&procedure);
2182
2183 procedure
2184 .rollback(&TestingEnv::procedure_context())
2185 .await
2186 .unwrap();
2187
2188 let region_routes = current_parent_region_routes(&procedure.context).await;
2189 assert_eq!(
2190 region_routes,
2191 vec![
2192 test_region_route(
2193 RegionId::new(table_id, 1),
2194 &range_expr("x", 0, 100).as_json_str().unwrap(),
2195 ),
2196 test_region_route(
2197 RegionId::new(table_id, 2),
2198 &range_expr("x", 100, 200).as_json_str().unwrap(),
2199 ),
2200 ]
2201 );
2202 }
2203
2204 #[tokio::test]
2205 async fn test_repartition_procedure_flow_unpartitioned_failed_and_full_rollback() {
2206 let env = TestingEnv::new();
2207 let table_id = 1024;
2208 let node_manager = Arc::new(MockDatanodeManager::new(NaiveDatanodeHandler));
2209
2210 env.create_physical_table_metadata_for_repartition(
2211 table_id,
2212 vec![test_region_route(RegionId::new(table_id, 1), "")],
2213 test_region_wal_options(&[1]),
2214 )
2215 .await;
2216
2217 let context = new_parent_context(&env, node_manager, table_id);
2218 let to_exprs = vec![range_expr("col1", 0, 50), range_expr("col1", 50, 100)];
2219 let mut procedure = RepartitionProcedure::new(
2220 RepartitionFrom::Unpartitioned {
2221 partition_columns: vec!["col1".to_string()],
2222 },
2223 to_exprs.clone(),
2224 context,
2225 );
2226
2227 let start_status = procedure
2228 .execute(&TestingEnv::procedure_context())
2229 .await
2230 .unwrap();
2231 assert!(start_status.need_persist());
2232 assert_parent_state::<UpdatePartitionMetadata>(&procedure);
2233 assert_eq!(
2234 procedure
2235 .context
2236 .persistent_ctx
2237 .partition_metadata_update
2238 .as_ref()
2239 .unwrap()
2240 .target_partition_key_indices,
2241 vec![0]
2242 );
2243
2244 let update_status = procedure
2245 .execute(&TestingEnv::procedure_context())
2246 .await
2247 .unwrap();
2248 assert!(update_status.need_persist());
2249 assert_parent_state::<AllocateRegion>(&procedure);
2250 assert_eq!(
2251 table_partition_key_indices(&procedure.context).await,
2252 vec![0]
2253 );
2254
2255 let build_allocate_status = procedure
2256 .execute(&TestingEnv::procedure_context())
2257 .await
2258 .unwrap();
2259 assert!(build_allocate_status.need_persist());
2260 assert_parent_state::<AllocateRegion>(&procedure);
2261 assert_eq!(procedure.context.persistent_ctx.plans.len(), 1);
2262 let plan = &procedure.context.persistent_ctx.plans[0];
2263 assert_eq!(
2264 plan.source_regions,
2265 vec![SourceRegionDescriptor::Default {
2266 region_id: RegionId::new(table_id, 1)
2267 }]
2268 );
2269 assert_eq!(plan.target_regions.len(), 2);
2270 assert_eq!(plan.target_regions[0].region_id, RegionId::new(table_id, 1));
2271 assert_eq!(plan.target_regions[0].partition_expr, to_exprs[0]);
2272 assert_eq!(
2273 plan.allocated_region_ids,
2274 vec![plan.target_regions[1].region_id]
2275 );
2276 assert!(plan.pending_deallocate_region_ids.is_empty());
2277 assert_eq!(plan.transition_map, vec![vec![0, 1]]);
2278 let target_regions = plan.target_regions.clone();
2279
2280 let execute_allocate_status = procedure
2281 .execute(&TestingEnv::procedure_context())
2282 .await
2283 .unwrap();
2284 assert!(execute_allocate_status.need_persist());
2285 assert_parent_state::<Dispatch>(&procedure);
2286 let region_routes = current_parent_region_routes(&procedure.context).await;
2287 assert_eq!(region_routes.len(), 2);
2288 assert_eq!(
2289 region_route_by_id(®ion_routes, target_regions[0].region_id)
2290 .region
2291 .partition_expr(),
2292 ""
2293 );
2294 assert_eq!(
2295 region_route_by_id(®ion_routes, target_regions[1].region_id)
2296 .region
2297 .partition_expr(),
2298 to_exprs[1].as_json_str().unwrap()
2299 );
2300
2301 let dispatch_status = procedure
2302 .execute(&TestingEnv::procedure_context())
2303 .await
2304 .unwrap();
2305 let subprocedure_ids = extract_subprocedure_ids(dispatch_status);
2306 assert_eq!(subprocedure_ids.len(), 1);
2307 assert_parent_state::<Collect>(&procedure);
2308
2309 let failed_state = ProcedureState::failed(Arc::new(ProcedureError::external(
2310 MockError::new(StatusCode::Internal),
2311 )));
2312 let collect_ctx = procedure_context_with_receivers(HashMap::from([(
2313 subprocedure_ids[0],
2314 procedure_state_receiver(failed_state),
2315 )]));
2316 let err = procedure.execute(&collect_ctx).await.unwrap_err();
2317 assert!(!err.is_retry_later());
2318 assert_parent_state::<Collect>(&procedure);
2319
2320 procedure
2321 .rollback(&TestingEnv::procedure_context())
2322 .await
2323 .unwrap();
2324
2325 assert!(
2326 table_partition_key_indices(&procedure.context)
2327 .await
2328 .is_empty()
2329 );
2330 assert_eq!(
2331 current_parent_region_routes(&procedure.context).await,
2332 vec![test_region_route(RegionId::new(table_id, 1), "")]
2333 );
2334 }
2335
2336 #[tokio::test]
2337 async fn test_repartition_procedure_flow_unpartitioned_rollback_is_idempotent() {
2338 let env = TestingEnv::new();
2339 let table_id = 1024;
2340 let node_manager = Arc::new(MockDatanodeManager::new(NaiveDatanodeHandler));
2341
2342 env.create_physical_table_metadata_for_repartition(
2343 table_id,
2344 vec![test_region_route(RegionId::new(table_id, 1), "")],
2345 test_region_wal_options(&[1]),
2346 )
2347 .await;
2348
2349 let context = new_parent_context(&env, node_manager, table_id);
2350 let mut procedure = RepartitionProcedure::new(
2351 RepartitionFrom::Unpartitioned {
2352 partition_columns: vec!["col1".to_string()],
2353 },
2354 vec![range_expr("col1", 0, 50), range_expr("col1", 50, 100)],
2355 context,
2356 );
2357
2358 procedure
2359 .execute(&TestingEnv::procedure_context())
2360 .await
2361 .unwrap();
2362 procedure
2363 .execute(&TestingEnv::procedure_context())
2364 .await
2365 .unwrap();
2366 procedure
2367 .execute(&TestingEnv::procedure_context())
2368 .await
2369 .unwrap();
2370 procedure
2371 .execute(&TestingEnv::procedure_context())
2372 .await
2373 .unwrap();
2374 assert_eq!(
2375 table_partition_key_indices(&procedure.context).await,
2376 vec![0]
2377 );
2378 assert_eq!(
2379 current_parent_region_routes(&procedure.context).await.len(),
2380 2
2381 );
2382
2383 let dispatch_status = procedure
2384 .execute(&TestingEnv::procedure_context())
2385 .await
2386 .unwrap();
2387 let subprocedure_ids = extract_subprocedure_ids(dispatch_status);
2388 assert_eq!(subprocedure_ids.len(), 1);
2389 assert_parent_state::<Collect>(&procedure);
2390
2391 let failed_state = ProcedureState::failed(Arc::new(ProcedureError::external(
2392 MockError::new(StatusCode::Internal),
2393 )));
2394 let collect_ctx = procedure_context_with_receivers(HashMap::from([(
2395 subprocedure_ids[0],
2396 procedure_state_receiver(failed_state),
2397 )]));
2398 let err = procedure.execute(&collect_ctx).await.unwrap_err();
2399 assert!(!err.is_retry_later());
2400
2401 procedure
2402 .rollback(&TestingEnv::procedure_context())
2403 .await
2404 .unwrap();
2405 let once_indices = table_partition_key_indices(&procedure.context).await;
2406 let once_routes = current_parent_region_routes(&procedure.context).await;
2407
2408 procedure
2409 .rollback(&TestingEnv::procedure_context())
2410 .await
2411 .unwrap();
2412 let twice_indices = table_partition_key_indices(&procedure.context).await;
2413 let twice_routes = current_parent_region_routes(&procedure.context).await;
2414
2415 assert_eq!(once_indices, twice_indices);
2416 assert_eq!(once_routes, twice_routes);
2417 assert!(twice_indices.is_empty());
2418 assert_eq!(
2419 twice_routes,
2420 vec![test_region_route(RegionId::new(table_id, 1), "")]
2421 );
2422 }
2423
2424 #[tokio::test]
2425 async fn test_repartition_procedure_flow_split_allocate_retryable_then_resume() {
2426 common_telemetry::init_default_ut_logging();
2427 let env = TestingEnv::new();
2428 let table_id = 1024;
2429 let (tx, _rx) = mpsc::channel(8);
2430 let should_retry = Arc::new(AtomicBool::new(true));
2431 let datanode_handler = DatanodeWatcher::new(tx).with_handler(move |_, _| {
2432 if should_retry.swap(false, Ordering::SeqCst) {
2433 return Err(error::Error::RetryLater {
2434 source: BoxedError::new(
2435 error::UnexpectedSnafu {
2436 err_msg: "retry later",
2437 }
2438 .build(),
2439 ),
2440 clean_poisons: false,
2441 });
2442 }
2443
2444 Ok(api::region::RegionResponse::new(0))
2445 });
2446 let node_manager = Arc::new(MockDatanodeManager::new(datanode_handler));
2447
2448 env.create_physical_table_metadata_for_repartition(
2449 table_id,
2450 vec![
2451 test_region_route(
2452 RegionId::new(table_id, 1),
2453 &range_expr("x", 0, 100).as_json_str().unwrap(),
2454 ),
2455 test_region_route(
2456 RegionId::new(table_id, 2),
2457 &range_expr("x", 100, 200).as_json_str().unwrap(),
2458 ),
2459 ],
2460 test_region_wal_options(&[1, 2]),
2461 )
2462 .await;
2463
2464 let context = new_parent_context(&env, node_manager, table_id);
2465 let mut procedure = RepartitionProcedure::new(
2466 RepartitionFrom::Partitioned {
2467 exprs: vec![range_expr("x", 0, 100)],
2468 target_partition_columns: None,
2469 },
2470 vec![range_expr("x", 0, 50), range_expr("x", 50, 100)],
2471 context,
2472 );
2473
2474 let start_status = procedure
2475 .execute(&TestingEnv::procedure_context())
2476 .await
2477 .unwrap();
2478 assert!(!start_status.need_persist());
2479 let start_status = procedure
2480 .execute(&TestingEnv::procedure_context())
2481 .await
2482 .unwrap();
2483 assert!(start_status.need_persist());
2484 assert_parent_state::<AllocateRegion>(&procedure);
2485
2486 let err = procedure
2487 .execute(&TestingEnv::procedure_context())
2488 .await
2489 .unwrap_err();
2490 assert!(err.is_retry_later());
2491 assert_parent_state::<AllocateRegion>(&procedure);
2492 assert!(!procedure.context.persistent_ctx.plans.is_empty());
2493 assert_eq!(
2494 current_parent_region_routes(&procedure.context).await,
2495 vec![
2496 test_region_route(
2497 RegionId::new(table_id, 1),
2498 &range_expr("x", 0, 100).as_json_str().unwrap(),
2499 ),
2500 test_region_route(
2501 RegionId::new(table_id, 2),
2502 &range_expr("x", 100, 200).as_json_str().unwrap(),
2503 ),
2504 ]
2505 );
2506
2507 let allocate_status = procedure
2508 .execute(&TestingEnv::procedure_context())
2509 .await
2510 .unwrap();
2511 assert!(allocate_status.need_persist());
2512 assert_parent_state::<Dispatch>(&procedure);
2513
2514 assert_eq!(procedure.context.persistent_ctx.plans.len(), 1);
2515 let plan = &procedure.context.persistent_ctx.plans[0];
2516 let expected_plan = test_plan(table_id);
2517 assert_eq!(plan.source_regions, expected_plan.source_regions);
2518 assert_eq!(plan.target_regions, expected_plan.target_regions);
2519 assert_eq!(
2520 plan.allocated_region_ids,
2521 expected_plan.allocated_region_ids
2522 );
2523 assert_eq!(plan.transition_map, expected_plan.transition_map);
2524 assert_eq!(
2525 current_parent_region_routes(&procedure.context).await,
2526 vec![
2527 test_region_route(
2528 RegionId::new(table_id, 1),
2529 &range_expr("x", 0, 100).as_json_str().unwrap(),
2530 ),
2531 test_region_route(
2532 RegionId::new(table_id, 2),
2533 &range_expr("x", 100, 200).as_json_str().unwrap(),
2534 ),
2535 RegionRoute {
2536 region: Region {
2537 id: RegionId::new(table_id, 3),
2538 partition_expr: range_expr("x", 50, 100).as_json_str().unwrap(),
2539 ..Default::default()
2540 },
2541 leader_peer: Some(Peer::empty(0)),
2542 ..Default::default()
2543 },
2544 ]
2545 );
2546
2547 let dispatch_status = procedure
2548 .execute(&TestingEnv::procedure_context())
2549 .await
2550 .unwrap();
2551 assert!(!dispatch_status.need_persist());
2552 let subprocedure_ids = extract_subprocedure_ids(dispatch_status);
2553 assert_eq!(subprocedure_ids.len(), 1);
2554 assert_parent_state::<Collect>(&procedure);
2555 }
2556}