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