1use common_error::ext::{BoxedError, ErrorExt};
16use common_error::status_code::StatusCode;
17use common_meta::instruction::{
18 InstructionError, InstructionReply, UpgradeRegion, UpgradeRegionReply, UpgradeRegionsReply,
19};
20use common_telemetry::{debug, info, warn};
21use store_api::region_request::{RegionCatchupRequest, ReplayCheckpoint};
22use store_api::storage::RegionId;
23
24use crate::error::Result;
25use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
26
27#[derive(Debug, Clone, Copy, Default)]
28pub struct UpgradeRegionsHandler {
29 pub upgrade_region_parallelism: usize,
30}
31
32#[cfg(test)]
33impl UpgradeRegionsHandler {
34 fn new_test() -> UpgradeRegionsHandler {
35 UpgradeRegionsHandler {
36 upgrade_region_parallelism: 8,
37 }
38 }
39}
40
41impl UpgradeRegionsHandler {
42 fn convert_responses_to_replies(
43 responses: Result<Vec<(RegionId, std::result::Result<(), BoxedError>)>>,
44 catchup_regions: &[RegionId],
45 ) -> Vec<UpgradeRegionReply> {
46 match responses {
47 Ok(responses) => responses
48 .into_iter()
49 .map(|(region_id, result)| match result {
50 Ok(()) => UpgradeRegionReply {
51 region_id,
52 ready: true,
53 exists: true,
54 error: None,
55 },
56 Err(err) => {
57 if err.status_code() == StatusCode::RegionNotFound {
58 UpgradeRegionReply {
59 region_id,
60 ready: false,
61 exists: false,
62 error: Some(InstructionError::from_error(&err)),
63 }
64 } else {
65 UpgradeRegionReply {
66 region_id,
67 ready: false,
68 exists: true,
69 error: Some(InstructionError::from_error(&err)),
70 }
71 }
72 }
73 })
74 .collect::<Vec<_>>(),
75 Err(err) => catchup_regions
76 .iter()
77 .map(|region_id| UpgradeRegionReply {
78 region_id: *region_id,
79 ready: false,
80 exists: true,
81 error: Some(InstructionError::from_error(&err)),
82 })
83 .collect::<Vec<_>>(),
84 }
85 }
86}
87
88impl UpgradeRegionsHandler {
89 async fn handle_upgrade_regions(
93 &self,
94 ctx: &HandlerContext,
95 upgrade_regions: Vec<UpgradeRegion>,
96 ) -> Vec<UpgradeRegionReply> {
97 let num_upgrade_regions = upgrade_regions.len();
98 let mut replies = Vec::with_capacity(num_upgrade_regions);
99 let mut catchup_requests = Vec::with_capacity(num_upgrade_regions);
100 let mut catchup_regions = Vec::with_capacity(num_upgrade_regions);
101 let mut timeout = None;
102
103 for upgrade_region in upgrade_regions {
104 let Some(writable) = ctx.region_server.is_region_leader(upgrade_region.region_id)
105 else {
106 debug!("Region {} is not found", upgrade_region.region_id);
108 replies.push(UpgradeRegionReply {
109 region_id: upgrade_region.region_id,
110 ready: false,
111 exists: false,
112 error: None,
113 });
114 continue;
115 };
116
117 if writable {
119 warn!(
120 "Region {} is writable, ignores the catchup request",
121 upgrade_region.region_id
122 );
123 replies.push(UpgradeRegionReply {
124 region_id: upgrade_region.region_id,
125 ready: true,
126 exists: true,
127 error: None,
128 });
129 } else {
130 let UpgradeRegion {
131 last_entry_id,
132 metadata_last_entry_id,
133 replay_entry_id,
134 metadata_replay_entry_id,
135 replay_timeout,
136 ..
137 } = upgrade_region;
138 match timeout {
139 Some(timeout) => {
140 debug_assert_eq!(timeout, replay_timeout);
141 }
142 None => {
143 timeout = Some(replay_timeout);
145 }
146 }
147
148 let checkpoint = match (replay_entry_id, metadata_replay_entry_id) {
149 (Some(entry_id), metadata_entry_id) => Some(ReplayCheckpoint {
150 entry_id,
151 metadata_entry_id,
152 }),
153 _ => None,
154 };
155
156 catchup_regions.push(upgrade_region.region_id);
157 catchup_requests.push((
158 upgrade_region.region_id,
159 RegionCatchupRequest {
160 set_writable: true,
161 entry_id: last_entry_id,
162 metadata_entry_id: metadata_last_entry_id,
163 checkpoint,
164 },
165 ));
166 }
167 }
168
169 let Some(timeout) = timeout else {
170 info!("All regions are writable, no need to catchup");
172 debug_assert_eq!(replies.len(), num_upgrade_regions);
173 return replies;
174 };
175
176 match tokio::time::timeout(
177 timeout,
178 ctx.region_server
179 .handle_batch_catchup_requests(self.upgrade_region_parallelism, catchup_requests),
180 )
181 .await
182 {
183 Ok(responses) => {
184 replies.extend(Self::convert_responses_to_replies(
185 responses,
186 &catchup_regions,
187 ));
188 }
189 Err(_) => {
190 replies.extend(catchup_regions.iter().map(|region_id| UpgradeRegionReply {
191 region_id: *region_id,
192 ready: false,
193 exists: true,
194 error: None,
195 }));
196 }
197 }
198
199 replies
200 }
201}
202
203#[async_trait::async_trait]
204impl InstructionHandler for UpgradeRegionsHandler {
205 type Instruction = Vec<UpgradeRegion>;
206
207 async fn handle(
208 &self,
209 ctx: &HandlerContext,
210 upgrade_regions: Self::Instruction,
211 ) -> Option<InstructionReply> {
212 let replies = self.handle_upgrade_regions(ctx, upgrade_regions).await;
213
214 Some(InstructionReply::UpgradeRegions(UpgradeRegionsReply::new(
215 replies,
216 )))
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use std::sync::Arc;
223 use std::time::Duration;
224
225 use common_meta::instruction::UpgradeRegion;
226 use common_meta::kv_backend::memory::MemoryKvBackend;
227 use mito2::engine::MITO_ENGINE_NAME;
228 use store_api::region_engine::RegionRole;
229 use store_api::storage::RegionId;
230
231 use crate::error;
232 use crate::heartbeat::handler::upgrade_region::UpgradeRegionsHandler;
233 use crate::heartbeat::handler::{HandlerContext, InstructionHandler};
234 use crate::tests::{MockRegionEngine, mock_region_server};
235
236 #[tokio::test]
237 async fn test_region_not_exist() {
238 let mut mock_region_server = mock_region_server();
239 let (mock_engine, _) = MockRegionEngine::new(MITO_ENGINE_NAME);
240 mock_region_server.register_engine(mock_engine);
241 let kv_backend = Arc::new(MemoryKvBackend::new());
242 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
243
244 let region_id = RegionId::new(1024, 1);
245 let region_id2 = RegionId::new(1024, 2);
246 let replay_timeout = Duration::from_millis(100u64);
247 let reply = UpgradeRegionsHandler::new_test()
248 .handle(
249 &handler_context,
250 vec![
251 UpgradeRegion {
252 region_id,
253 replay_timeout,
254 ..Default::default()
255 },
256 UpgradeRegion {
257 region_id: region_id2,
258 replay_timeout,
259 ..Default::default()
260 },
261 ],
262 )
263 .await;
264
265 let replies = &reply.unwrap().expect_upgrade_regions_reply();
266 assert_eq!(replies[0].region_id, region_id);
267 assert_eq!(replies[1].region_id, region_id2);
268 for reply in replies {
269 assert!(!reply.exists);
270 assert!(reply.error.is_none());
271 }
272 }
273
274 #[tokio::test]
275 async fn test_region_writable() {
276 let mock_region_server = mock_region_server();
277 let region_id = RegionId::new(1024, 1);
278 let region_id2 = RegionId::new(1024, 2);
279
280 let (mock_engine, _) =
281 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
282 region_engine.mock_role = Some(Some(RegionRole::Leader));
283 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| {
284 unreachable!();
286 }));
287 });
288 mock_region_server.register_test_region(region_id, mock_engine.clone());
289 mock_region_server.register_test_region(region_id2, mock_engine);
290 let kv_backend = Arc::new(MemoryKvBackend::new());
291 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
292 let replay_timeout = Duration::from_millis(100u64);
293 let reply = UpgradeRegionsHandler::new_test()
294 .handle(
295 &handler_context,
296 vec![
297 UpgradeRegion {
298 region_id,
299 replay_timeout,
300 ..Default::default()
301 },
302 UpgradeRegion {
303 region_id: region_id2,
304 replay_timeout,
305 ..Default::default()
306 },
307 ],
308 )
309 .await;
310
311 let replies = &reply.unwrap().expect_upgrade_regions_reply();
312 assert_eq!(replies[0].region_id, region_id);
313 assert_eq!(replies[1].region_id, region_id2);
314 for reply in replies {
315 assert!(reply.ready);
316 assert!(reply.exists);
317 assert!(reply.error.is_none());
318 }
319 }
320
321 #[tokio::test]
322 async fn test_legacy_wal_index_hint() {
323 for location_id in [None, Some(42)] {
324 let mock_region_server = mock_region_server();
325 let region_id = RegionId::new(1024, 1);
326 let (mock_engine, _) =
327 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |engine| {
328 engine.mock_role = Some(Some(RegionRole::Follower));
329 engine.handle_request_mock_fn = Some(Box::new(|_, request| {
330 let store_api::region_request::RegionRequest::Catchup(request) = request
331 else {
332 panic!("expected catchup request");
333 };
334 assert!(request.set_writable);
335 assert_eq!(request.entry_id, Some(10));
336 assert_eq!(request.metadata_entry_id, Some(5));
337 Ok(0)
338 }));
339 });
340 mock_region_server.register_test_region(region_id, mock_engine);
341 let ctx =
342 HandlerContext::new_for_test(mock_region_server, Arc::new(MemoryKvBackend::new()));
343 let reply = UpgradeRegionsHandler::new_test()
344 .handle(
345 &ctx,
346 vec![UpgradeRegion {
347 region_id,
348 last_entry_id: Some(10),
349 metadata_last_entry_id: Some(5),
350 location_id,
351 replay_timeout: Duration::from_secs(1),
352 ..Default::default()
353 }],
354 )
355 .await
356 .unwrap();
357 let reply = &reply.expect_upgrade_regions_reply()[0];
358 assert!(reply.ready);
359 assert!(reply.exists);
360 assert!(reply.error.is_none());
361 }
362 }
363
364 #[tokio::test]
365 async fn test_region_not_ready() {
366 let mock_region_server = mock_region_server();
367 let region_id = RegionId::new(1024, 1);
368
369 let (mock_engine, _) =
370 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
371 region_engine.mock_role = Some(Some(RegionRole::Follower));
373 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| Ok(0)));
374 region_engine.handle_request_delay = Some(Duration::from_secs(100));
376 });
377 mock_region_server.register_test_region(region_id, mock_engine);
378 let kv_backend = Arc::new(MemoryKvBackend::new());
379 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
380 let replay_timeout = Duration::from_millis(100u64);
381 let reply = UpgradeRegionsHandler::new_test()
382 .handle(
383 &handler_context,
384 vec![UpgradeRegion {
385 region_id,
386 replay_timeout,
387 ..Default::default()
388 }],
389 )
390 .await;
391
392 let reply = &reply.unwrap().expect_upgrade_regions_reply()[0];
393 assert!(!reply.ready);
394 assert!(reply.exists);
395 assert!(reply.error.is_none(), "error: {:?}", reply.error);
396 }
397
398 #[tokio::test]
399 async fn test_region_not_ready_with_retry() {
400 common_telemetry::init_default_ut_logging();
401 let mock_region_server = mock_region_server();
402 let region_id = RegionId::new(1024, 1);
403
404 let (mock_engine, _) =
405 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
406 region_engine.mock_role = Some(Some(RegionRole::Follower));
408 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| Ok(0)));
409 region_engine.handle_request_delay = Some(Duration::from_millis(300));
410 });
411 mock_region_server.register_test_region(region_id, mock_engine);
412 let waits = vec![Duration::from_millis(100u64), Duration::from_millis(100u64)];
413 let kv_backend = Arc::new(MemoryKvBackend::new());
414 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
415 for replay_timeout in waits {
416 let reply = UpgradeRegionsHandler::new_test()
417 .handle(
418 &handler_context,
419 vec![UpgradeRegion {
420 region_id,
421 replay_timeout,
422 ..Default::default()
423 }],
424 )
425 .await;
426
427 let reply = &reply.unwrap().expect_upgrade_regions_reply()[0];
428 assert!(!reply.ready);
429 assert!(reply.exists);
430 assert!(reply.error.is_none(), "error: {:?}", reply.error);
431 }
432
433 let reply = UpgradeRegionsHandler::new_test()
434 .handle(
435 &handler_context,
436 vec![UpgradeRegion {
437 region_id,
438 replay_timeout: Duration::from_millis(500),
439 ..Default::default()
440 }],
441 )
442 .await;
443 let reply = &reply.unwrap().expect_upgrade_regions_reply()[0];
444 assert!(reply.ready);
445 assert!(reply.exists);
446 assert!(reply.error.is_none(), "error: {:?}", reply.error);
447 }
448
449 #[tokio::test]
450 async fn test_region_error() {
451 common_telemetry::init_default_ut_logging();
452 let region_id = RegionId::new(1024, 1);
453
454 let region_server = mock_region_server();
458 let (mock_engine, _) =
459 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
460 region_engine.mock_role = Some(Some(RegionRole::Follower));
462 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| {
463 error::UnexpectedSnafu {
464 violated: "mock_error".to_string(),
465 }
466 .fail()
467 }));
468 region_engine.handle_request_delay = Some(Duration::from_millis(100));
470 });
471 region_server.register_test_region(region_id, mock_engine);
472 let kv_backend = Arc::new(MemoryKvBackend::new());
473 let handler_context = HandlerContext::new_for_test(region_server, kv_backend);
474 let reply = UpgradeRegionsHandler::new_test()
475 .handle(
476 &handler_context,
477 vec![UpgradeRegion {
478 region_id,
479 ..Default::default()
480 }],
481 )
482 .await;
483
484 let reply = &reply.unwrap().expect_upgrade_regions_reply()[0];
485 assert!(!reply.ready);
486 assert!(reply.exists);
487 assert!(reply.error.is_none());
488
489 let mock_region_server = mock_region_server();
496 let (mock_engine, _) =
497 MockRegionEngine::with_custom_apply_fn(MITO_ENGINE_NAME, |region_engine| {
498 region_engine.mock_role = Some(Some(RegionRole::Follower));
500 region_engine.handle_request_mock_fn = Some(Box::new(|_, _| {
501 error::UnexpectedSnafu {
502 violated: "mock_error".to_string(),
503 }
504 .fail()
505 }));
506 });
507 mock_region_server.register_test_region(region_id, mock_engine);
508 let kv_backend = Arc::new(MemoryKvBackend::new());
509 let handler_context = HandlerContext::new_for_test(mock_region_server, kv_backend);
510 let reply = UpgradeRegionsHandler::new_test()
511 .handle(
512 &handler_context,
513 vec![UpgradeRegion {
514 region_id,
515 replay_timeout: Duration::from_millis(200),
516 ..Default::default()
517 }],
518 )
519 .await;
520
521 let reply = &reply.unwrap().expect_upgrade_regions_reply()[0];
522 assert!(!reply.ready);
523 assert!(reply.exists);
524 assert!(reply.error.is_some());
525 assert!(reply.error.as_ref().unwrap().message.contains("mock_error"));
526 }
527}