1use async_trait::async_trait;
16use common_meta::error::{InvalidHeartbeatResponseSnafu, Result as MetaResult};
17use common_meta::heartbeat::handler::{
18 HandleControl, HeartbeatResponseHandler, HeartbeatResponseHandlerContext,
19};
20use common_meta::instruction::{Instruction, InstructionReply};
21use common_meta::kv_backend::KvBackendRef;
22use common_telemetry::error;
23use common_telemetry::tracing_context::FutureExt;
24use snafu::OptionExt;
25use store_api::storage::GcReport;
26use strum::AsRefStr;
27
28mod apply_staging_manifest;
29mod close_region;
30mod downgrade_region;
31mod enter_staging;
32mod file_ref;
33mod flush_region;
34mod gc_worker;
35mod open_region;
36mod remap_manifest;
37mod sync_region;
38mod upgrade_region;
39
40use crate::heartbeat::handler::apply_staging_manifest::ApplyStagingManifestsHandler;
41use crate::heartbeat::handler::close_region::CloseRegionsHandler;
42use crate::heartbeat::handler::downgrade_region::DowngradeRegionsHandler;
43use crate::heartbeat::handler::enter_staging::EnterStagingRegionsHandler;
44use crate::heartbeat::handler::file_ref::{GetFileRefsHandler, GetPackedFileRefsHandler};
45use crate::heartbeat::handler::flush_region::FlushRegionsHandler;
46use crate::heartbeat::handler::gc_worker::{GcRegionsHandler, PackedGcRegionsHandler};
47use crate::heartbeat::handler::open_region::OpenRegionsHandler;
48use crate::heartbeat::handler::remap_manifest::RemapManifestHandler;
49use crate::heartbeat::handler::sync_region::SyncRegionHandler;
50use crate::heartbeat::handler::upgrade_region::UpgradeRegionsHandler;
51use crate::heartbeat::task_tracker::TaskTracker;
52use crate::region_server::RegionServer;
53
54#[derive(Clone)]
56pub struct RegionHeartbeatResponseHandler {
57 region_server: RegionServer,
58 downgrade_tasks: TaskTracker<()>,
59 flush_tasks: TaskTracker<()>,
60 open_region_parallelism: usize,
61 gc_tasks: TaskTracker<GcReport>,
62 kv_backend: KvBackendRef,
63}
64
65#[async_trait::async_trait]
66pub trait InstructionHandler: Send + Sync {
67 type Instruction;
68 async fn handle(
69 &self,
70 ctx: &HandlerContext,
71 instruction: Self::Instruction,
72 ) -> Option<InstructionReply>;
73}
74
75#[derive(Clone)]
76pub struct HandlerContext {
77 pub region_server: RegionServer,
78 pub downgrade_tasks: TaskTracker<()>,
79 pub flush_tasks: TaskTracker<()>,
80 pub gc_tasks: TaskTracker<GcReport>,
81 pub kv_backend: KvBackendRef,
82}
83
84impl HandlerContext {
85 #[cfg(test)]
86 pub fn new_for_test(region_server: RegionServer, kv_backend: KvBackendRef) -> Self {
87 Self {
88 region_server,
89 downgrade_tasks: TaskTracker::new(),
90 flush_tasks: TaskTracker::new(),
91 gc_tasks: TaskTracker::new(),
92 kv_backend,
93 }
94 }
95}
96
97impl RegionHeartbeatResponseHandler {
98 pub fn new(region_server: RegionServer, kv_backend: KvBackendRef) -> Self {
100 Self {
101 region_server,
102 downgrade_tasks: TaskTracker::new(),
103 flush_tasks: TaskTracker::new(),
104 open_region_parallelism: (num_cpus::get() / 2).max(1),
106 gc_tasks: TaskTracker::new(),
107 kv_backend,
108 }
109 }
110
111 pub fn with_open_region_parallelism(mut self, parallelism: usize) -> Self {
113 self.open_region_parallelism = parallelism;
114 self
115 }
116
117 fn build_handler(
118 &self,
119 instruction: &Instruction,
120 ) -> MetaResult<Option<Box<InstructionHandlers>>> {
121 match instruction {
122 Instruction::CloseRegions(_) => Ok(Some(Box::new(CloseRegionsHandler.into()))),
123 Instruction::OpenRegions(_) => Ok(Some(Box::new(
124 OpenRegionsHandler {
125 open_region_parallelism: self.open_region_parallelism,
126 }
127 .into(),
128 ))),
129 Instruction::FlushRegions(_) => Ok(Some(Box::new(FlushRegionsHandler.into()))),
130 Instruction::DowngradeRegions(_) => Ok(Some(Box::new(DowngradeRegionsHandler.into()))),
131 Instruction::UpgradeRegions(_) => Ok(Some(Box::new(
132 UpgradeRegionsHandler {
133 upgrade_region_parallelism: self.open_region_parallelism,
134 }
135 .into(),
136 ))),
137 Instruction::GetFileRefs(_) => Ok(Some(Box::new(GetFileRefsHandler.into()))),
138 Instruction::GetPackedFileRefs(_) => {
139 Ok(Some(Box::new(GetPackedFileRefsHandler.into())))
140 }
141 Instruction::GcRegions(_) => Ok(Some(Box::new(GcRegionsHandler.into()))),
142 Instruction::PackedGcRegions(_) => Ok(Some(Box::new(PackedGcRegionsHandler.into()))),
143 Instruction::InvalidateCaches(_) => InvalidHeartbeatResponseSnafu.fail(),
144 Instruction::Suspend => Ok(None),
145 Instruction::EnterStagingRegions(_) => {
146 Ok(Some(Box::new(EnterStagingRegionsHandler.into())))
147 }
148 Instruction::SyncRegions(_) => Ok(Some(Box::new(SyncRegionHandler.into()))),
149 Instruction::RemapManifest(_) => Ok(Some(Box::new(RemapManifestHandler.into()))),
150 Instruction::ApplyStagingManifests(_) => {
151 Ok(Some(Box::new(ApplyStagingManifestsHandler.into())))
152 }
153 }
154 }
155}
156
157#[allow(clippy::enum_variant_names)]
158#[derive(AsRefStr)]
159pub enum InstructionHandlers {
160 CloseRegions(CloseRegionsHandler),
161 OpenRegions(OpenRegionsHandler),
162 FlushRegions(FlushRegionsHandler),
163 DowngradeRegions(DowngradeRegionsHandler),
164 UpgradeRegions(UpgradeRegionsHandler),
165 GetFileRefs(GetFileRefsHandler),
166 GetPackedFileRefs(GetPackedFileRefsHandler),
167 GcRegions(GcRegionsHandler),
168 PackedGcRegions(PackedGcRegionsHandler),
169 EnterStagingRegions(EnterStagingRegionsHandler),
170 SyncRegions(SyncRegionHandler),
171 RemapManifest(RemapManifestHandler),
172 ApplyStagingManifests(ApplyStagingManifestsHandler),
173}
174
175impl InstructionHandlers {
176 pub fn as_ref_str(&self) -> &str {
178 self.as_ref()
179 }
180}
181
182macro_rules! impl_from_handler {
183 ($($handler:ident => $variant:ident),*) => {
184 $(
185 impl From<$handler> for InstructionHandlers {
186 fn from(handler: $handler) -> Self {
187 InstructionHandlers::$variant(handler)
188 }
189 }
190 )*
191 };
192}
193
194impl_from_handler!(
195 CloseRegionsHandler => CloseRegions,
196 OpenRegionsHandler => OpenRegions,
197 FlushRegionsHandler => FlushRegions,
198 DowngradeRegionsHandler => DowngradeRegions,
199 UpgradeRegionsHandler => UpgradeRegions,
200 GetFileRefsHandler => GetFileRefs,
201 GetPackedFileRefsHandler => GetPackedFileRefs,
202 GcRegionsHandler => GcRegions,
203 PackedGcRegionsHandler => PackedGcRegions,
204 EnterStagingRegionsHandler => EnterStagingRegions,
205 SyncRegionHandler => SyncRegions,
206 RemapManifestHandler => RemapManifest,
207 ApplyStagingManifestsHandler => ApplyStagingManifests
208);
209
210macro_rules! dispatch_instr {
211 (
212 $( $instr_variant:ident => $handler_variant:ident ),* $(,)?
213 ) => {
214 impl InstructionHandlers {
215 pub async fn handle(
216 &self,
217 ctx: &HandlerContext,
218 instruction: Instruction,
219 ) -> Option<InstructionReply> {
220 match (self, instruction) {
221 $(
222 (
223 InstructionHandlers::$handler_variant(handler),
224 Instruction::$instr_variant(instr),
225 ) => handler.handle(ctx, instr).await,
226 )*
227 _ => unreachable!(),
229 }
230 }
231 pub fn is_acceptable(instruction: &Instruction) -> bool {
233 matches!(
234 instruction,
235 $(
236 Instruction::$instr_variant { .. }
237 )|*
238 )
239 }
240 }
241 };
242}
243
244dispatch_instr!(
245 CloseRegions => CloseRegions,
246 OpenRegions => OpenRegions,
247 FlushRegions => FlushRegions,
248 DowngradeRegions => DowngradeRegions,
249 UpgradeRegions => UpgradeRegions,
250 GetFileRefs => GetFileRefs,
251 GetPackedFileRefs => GetPackedFileRefs,
252 GcRegions => GcRegions,
253 PackedGcRegions => PackedGcRegions,
254 EnterStagingRegions => EnterStagingRegions,
255 SyncRegions => SyncRegions,
256 RemapManifest => RemapManifest,
257 ApplyStagingManifests => ApplyStagingManifests,
258);
259
260#[async_trait]
261impl HeartbeatResponseHandler for RegionHeartbeatResponseHandler {
262 fn is_acceptable(&self, ctx: &HeartbeatResponseHandlerContext) -> bool {
263 if let Some((_, _, instruction)) = ctx.incoming_message.as_ref() {
264 return InstructionHandlers::is_acceptable(instruction);
265 }
266 false
267 }
268
269 async fn handle(&self, ctx: &mut HeartbeatResponseHandlerContext) -> MetaResult<HandleControl> {
270 let (meta, tracing_ctx, instruction) = ctx
271 .incoming_message
272 .take()
273 .context(InvalidHeartbeatResponseSnafu)?;
274
275 let mailbox = ctx.mailbox.clone();
276 if let Some(handler) = self.build_handler(&instruction)? {
277 let context = HandlerContext {
278 region_server: self.region_server.clone(),
279 downgrade_tasks: self.downgrade_tasks.clone(),
280 flush_tasks: self.flush_tasks.clone(),
281 gc_tasks: self.gc_tasks.clone(),
282 kv_backend: self.kv_backend.clone(),
283 };
284 let span = tracing_ctx.attach(tracing::info_span!(
285 "RegionHeartbeatResponseHandler::handle",
286 from = %meta.from,
287 to = %meta.to,
288 handler = %handler.as_ref_str(),
289 ));
290 let _handle = common_runtime::spawn_global(async move {
291 let reply = handler.handle(&context, instruction).trace(span).await;
292 if let Some(reply) = reply
293 && let Err(e) = mailbox.send((meta, reply)).await
294 {
295 let error = e.to_string();
296 let (meta, reply) = e.0;
297 error!("Failed to send reply {reply} to {meta:?}: {error}");
298 }
299 });
300 }
301
302 Ok(HandleControl::Continue)
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use std::assert_matches;
309 use std::collections::HashMap;
310 use std::sync::Arc;
311 use std::time::Duration;
312
313 use common_meta::RegionIdent;
314 use common_meta::heartbeat::mailbox::{
315 HeartbeatMailbox, IncomingMessage, MailboxRef, MessageMeta,
316 };
317 use common_meta::instruction::{
318 DowngradeRegion, EnterStagingRegion, OpenRegion, StagingPartitionDirective, UpgradeRegion,
319 };
320 use common_meta::kv_backend::memory::MemoryKvBackend;
321 use mito2::config::MitoConfig;
322 use mito2::engine::MITO_ENGINE_NAME;
323 use mito2::test_util::{CreateRequestBuilder, TestEnv};
324 use store_api::path_utils::table_dir;
325 use store_api::region_engine::RegionRole;
326 use store_api::region_request::{RegionCloseRequest, RegionRequest, RegionRequirements};
327 use store_api::storage::RegionId;
328 use tokio::sync::mpsc::{self, Receiver};
329
330 use super::*;
331 use crate::error;
332 use crate::tests::mock_region_server;
333
334 pub struct HeartbeatResponseTestEnv {
335 pub(crate) mailbox: MailboxRef,
336 pub(crate) receiver: Receiver<(MessageMeta, InstructionReply)>,
337 }
338
339 impl HeartbeatResponseTestEnv {
340 pub fn new() -> Self {
341 let (tx, rx) = mpsc::channel(8);
342 let mailbox = Arc::new(HeartbeatMailbox::new(tx));
343
344 HeartbeatResponseTestEnv {
345 mailbox,
346 receiver: rx,
347 }
348 }
349
350 pub fn create_handler_ctx(
351 &self,
352 incoming_message: IncomingMessage,
353 ) -> HeartbeatResponseHandlerContext {
354 HeartbeatResponseHandlerContext {
355 mailbox: self.mailbox.clone(),
356 response: Default::default(),
357 incoming_message: Some(incoming_message),
358 }
359 }
360 }
361
362 #[test]
363 fn test_is_acceptable() {
364 common_telemetry::init_default_ut_logging();
365 let region_server = mock_region_server();
366 let kv_backend = Arc::new(MemoryKvBackend::new());
367 let heartbeat_handler =
368 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend);
369 let heartbeat_env = HeartbeatResponseTestEnv::new();
370 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
371
372 let region_id = RegionId::new(1024, 1);
374 let storage_path = "test";
375 let instruction = open_region_instruction(region_id, storage_path);
376 assert!(
377 heartbeat_handler.is_acceptable(&heartbeat_env.create_handler_ctx((
378 meta.clone(),
379 Default::default(),
380 instruction
381 )))
382 );
383
384 let instruction = close_region_instruction(region_id);
386 assert!(
387 heartbeat_handler.is_acceptable(&heartbeat_env.create_handler_ctx((
388 meta.clone(),
389 Default::default(),
390 instruction
391 )))
392 );
393
394 let instruction = Instruction::DowngradeRegions(vec![DowngradeRegion {
396 region_id: RegionId::new(2048, 1),
397 flush_timeout: Some(Duration::from_secs(1)),
398 }]);
399 assert!(
400 heartbeat_handler.is_acceptable(&heartbeat_env.create_handler_ctx((
401 meta.clone(),
402 Default::default(),
403 instruction
404 )))
405 );
406
407 let instruction = Instruction::UpgradeRegions(vec![UpgradeRegion {
409 region_id,
410 ..Default::default()
411 }]);
412 assert!(
413 heartbeat_handler.is_acceptable(&heartbeat_env.create_handler_ctx((
414 meta.clone(),
415 Default::default(),
416 instruction
417 )))
418 );
419
420 let instruction = Instruction::EnterStagingRegions(vec![EnterStagingRegion {
422 region_id,
423 partition_directive: StagingPartitionDirective::UpdatePartitionExpr("".to_string()),
424 }]);
425 assert!(
426 heartbeat_handler.is_acceptable(&heartbeat_env.create_handler_ctx((
427 meta,
428 Default::default(),
429 instruction
430 )))
431 );
432 }
433
434 fn close_region_instruction(region_id: RegionId) -> Instruction {
435 Instruction::CloseRegions(vec![RegionIdent {
436 table_id: region_id.table_id(),
437 region_number: region_id.region_number(),
438 datanode_id: 2,
439 engine: MITO_ENGINE_NAME.to_string(),
440 }])
441 }
442
443 fn open_region_instruction(region_id: RegionId, path: &str) -> Instruction {
444 Instruction::OpenRegions(vec![OpenRegion::new(
445 RegionIdent {
446 table_id: region_id.table_id(),
447 region_number: region_id.region_number(),
448 datanode_id: 2,
449 engine: MITO_ENGINE_NAME.to_string(),
450 },
451 path,
452 HashMap::new(),
453 HashMap::new(),
454 false,
455 None,
456 RegionRequirements::empty(),
457 )])
458 }
459
460 #[tokio::test]
461 async fn test_close_region() {
462 common_telemetry::init_default_ut_logging();
463
464 let mut region_server = mock_region_server();
465 let kv_backend = Arc::new(MemoryKvBackend::new());
466 let heartbeat_handler =
467 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend);
468
469 let mut engine_env = TestEnv::with_prefix("close-region").await;
470 let engine = engine_env.create_engine(MitoConfig::default()).await;
471 region_server.register_engine(Arc::new(engine));
472 let region_id = RegionId::new(1024, 1);
473
474 let builder = CreateRequestBuilder::new();
475 let create_req = builder.build();
476 region_server
477 .handle_request(region_id, RegionRequest::Create(create_req))
478 .await
479 .unwrap();
480
481 let mut heartbeat_env = HeartbeatResponseTestEnv::new();
482
483 for _ in 0..2 {
485 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
486 let instruction = close_region_instruction(region_id);
487
488 let mut ctx = heartbeat_env.create_handler_ctx((meta, Default::default(), instruction));
489 let control = heartbeat_handler.handle(&mut ctx).await.unwrap();
490 assert_matches!(control, HandleControl::Continue);
491
492 let (_, reply) = heartbeat_env.receiver.recv().await.unwrap();
493
494 if let InstructionReply::CloseRegions(reply) = reply {
495 assert!(reply.result);
496 assert!(reply.error.is_none());
497 } else {
498 unreachable!()
499 }
500
501 assert_matches!(
502 region_server
503 .set_region_role(region_id, RegionRole::Leader)
504 .unwrap_err(),
505 error::Error::RegionNotFound { .. }
506 );
507 }
508 }
509
510 #[tokio::test]
511 async fn test_open_region_ok() {
512 common_telemetry::init_default_ut_logging();
513
514 let mut region_server = mock_region_server();
515 let kv_backend = Arc::new(MemoryKvBackend::new());
516 let heartbeat_handler =
517 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend);
518
519 let mut engine_env = TestEnv::with_prefix("open-region").await;
520 let engine = engine_env.create_engine(MitoConfig::default()).await;
521 region_server.register_engine(Arc::new(engine));
522 let region_id = RegionId::new(1024, 1);
523
524 let builder = CreateRequestBuilder::new();
525 let mut create_req = builder.build();
526 let storage_path = "test";
527 create_req.table_dir = table_dir(storage_path, region_id.table_id());
528
529 region_server
530 .handle_request(region_id, RegionRequest::Create(create_req))
531 .await
532 .unwrap();
533
534 region_server
535 .handle_request(
536 region_id,
537 RegionRequest::Close(RegionCloseRequest::default()),
538 )
539 .await
540 .unwrap();
541 let mut heartbeat_env = HeartbeatResponseTestEnv::new();
542
543 for _ in 0..2 {
545 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
546 let instruction = open_region_instruction(region_id, storage_path);
547
548 let mut ctx = heartbeat_env.create_handler_ctx((meta, Default::default(), instruction));
549 let control = heartbeat_handler.handle(&mut ctx).await.unwrap();
550 assert_matches!(control, HandleControl::Continue);
551
552 let (_, reply) = heartbeat_env.receiver.recv().await.unwrap();
553
554 if let InstructionReply::OpenRegions(reply) = reply {
555 assert!(reply.result);
556 assert!(reply.error.is_none());
557 } else {
558 unreachable!()
559 }
560 }
561 }
562
563 #[tokio::test]
564 async fn test_open_not_exists_region() {
565 common_telemetry::init_default_ut_logging();
566
567 let mut region_server = mock_region_server();
568 let kv_backend = Arc::new(MemoryKvBackend::new());
569 let heartbeat_handler =
570 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend);
571
572 let mut engine_env = TestEnv::with_prefix("open-not-exists-region").await;
573 let engine = engine_env.create_engine(MitoConfig::default()).await;
574 region_server.register_engine(Arc::new(engine));
575 let region_id = RegionId::new(1024, 1);
576 let storage_path = "test";
577
578 let mut heartbeat_env = HeartbeatResponseTestEnv::new();
579
580 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
581 let instruction = open_region_instruction(region_id, storage_path);
582
583 let mut ctx = heartbeat_env.create_handler_ctx((meta, Default::default(), instruction));
584 let control = heartbeat_handler.handle(&mut ctx).await.unwrap();
585 assert_matches!(control, HandleControl::Continue);
586
587 let (_, reply) = heartbeat_env.receiver.recv().await.unwrap();
588
589 if let InstructionReply::OpenRegions(reply) = reply {
590 assert!(!reply.result);
591 assert!(reply.error.is_some());
592 } else {
593 unreachable!()
594 }
595 }
596
597 #[tokio::test]
598 async fn test_downgrade_region() {
599 common_telemetry::init_default_ut_logging();
600
601 let mut region_server = mock_region_server();
602 let kv_backend = Arc::new(MemoryKvBackend::new());
603 let heartbeat_handler =
604 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend);
605
606 let mut engine_env = TestEnv::with_prefix("downgrade-region").await;
607 let engine = engine_env.create_engine(MitoConfig::default()).await;
608 region_server.register_engine(Arc::new(engine));
609 let region_id = RegionId::new(1024, 1);
610
611 let builder = CreateRequestBuilder::new();
612 let mut create_req = builder.build();
613 let storage_path = "test";
614 create_req.table_dir = table_dir(storage_path, region_id.table_id());
615
616 region_server
617 .handle_request(region_id, RegionRequest::Create(create_req))
618 .await
619 .unwrap();
620
621 let mut heartbeat_env = HeartbeatResponseTestEnv::new();
622
623 for _ in 0..2 {
625 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
626 let instruction = Instruction::DowngradeRegions(vec![DowngradeRegion {
627 region_id,
628 flush_timeout: Some(Duration::from_secs(1)),
629 }]);
630
631 let mut ctx = heartbeat_env.create_handler_ctx((meta, Default::default(), instruction));
632 let control = heartbeat_handler.handle(&mut ctx).await.unwrap();
633 assert_matches!(control, HandleControl::Continue);
634
635 let (_, reply) = heartbeat_env.receiver.recv().await.unwrap();
636
637 let reply = &reply.expect_downgrade_regions_reply()[0];
638 assert!(reply.exists);
639 assert!(reply.error.is_none());
640 assert_eq!(reply.last_entry_id.unwrap(), 0);
641 }
642
643 let meta = MessageMeta::new_test(1, "test", "dn-1", "me-0");
645 let instruction = Instruction::DowngradeRegions(vec![DowngradeRegion {
646 region_id: RegionId::new(2048, 1),
647 flush_timeout: Some(Duration::from_secs(1)),
648 }]);
649 let mut ctx = heartbeat_env.create_handler_ctx((meta, Default::default(), instruction));
650 let control = heartbeat_handler.handle(&mut ctx).await.unwrap();
651 assert_matches!(control, HandleControl::Continue);
652
653 let (_, reply) = heartbeat_env.receiver.recv().await.unwrap();
654
655 let reply = reply.expect_downgrade_regions_reply();
656 assert!(!reply[0].exists);
657 assert!(reply[0].error.is_none());
658 assert!(reply[0].last_entry_id.is_none());
659 }
660}