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