(
ex: &MigrationExecutor,
state: &mut MigrationState,
group_id: u64,
req: &MigrationRequest,
migration_id: MigrationId,
)
| 216 | } |
| 217 | |
| 218 | pub(super) async fn phase3_cutover( |
| 219 | ex: &MigrationExecutor, |
| 220 | state: &mut MigrationState, |
| 221 | group_id: u64, |
| 222 | req: &MigrationRequest, |
| 223 | migration_id: MigrationId, |
| 224 | ) -> Result<()> { |
| 225 | let estimated_pause_us = 10_000; |
| 226 | |
| 227 | state.start_cutover(estimated_pause_us).map_err(|e| { |
| 228 | state.fail(format!("cutover rejected: {e}")); |
| 229 | e |
| 230 | })?; |
| 231 | |
| 232 | let cutover_start = std::time::Instant::now(); |
| 233 | |
| 234 | ex.propose_checkpoint( |
| 235 | migration_id, |
| 236 | 3, |
| 237 | MigrationCheckpointPayload::LeadershipTransfer { |
| 238 | vshard_id: req.vshard_id, |
| 239 | target_is_voter: true, |
| 240 | new_leader_node_id: req.target_node, |
| 241 | source_group: group_id, |
| 242 | }, |
| 243 | ) |
| 244 | .await?; |
| 245 | |
| 246 | info!( |
| 247 | vshard = req.vshard_id, |
| 248 | estimated_pause_us, "phase 3: atomic cut-over" |
| 249 | ); |
| 250 | |
| 251 | if let Some(proposer) = &ex.metadata_proposer { |
| 252 | let entry = Entry::RoutingChange(RoutingChange::LeadershipTransfer { |
| 253 | group_id, |
| 254 | new_leader_node_id: req.target_node, |
| 255 | }); |
| 256 | proposer.propose_and_wait(entry).await?; |
| 257 | } else { |
| 258 | let mut routing = ex.routing.write().unwrap_or_else(|p| p.into_inner()); |
| 259 | routing.set_leader(group_id, req.target_node); |
| 260 | } |
| 261 | |
| 262 | ex.propose_checkpoint( |
| 263 | migration_id, |
| 264 | 4, |
| 265 | MigrationCheckpointPayload::Cutover { |
| 266 | vshard_id: req.vshard_id, |
| 267 | new_leader_node_id: req.target_node, |
| 268 | source_group: group_id, |
| 269 | }, |
| 270 | ) |
| 271 | .await?; |
| 272 | |
| 273 | let ghost_stub = GhostStub { |
| 274 | node_id: format!("vshard-{}", req.vshard_id), |
| 275 | target_shard: req.vshard_id, |
no test coverage detected