1use std::collections::HashMap;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::time::Duration;
19
20use api::v1::meta::heartbeat_request::NodeWorkloads;
21use api::v1::meta::mailbox_message::Payload;
22use api::v1::meta::{DatanodeWorkloads, HeartbeatRequest, NodeInfo, Peer, RegionRole, RegionStat};
23use common_base::Plugins;
24use common_meta::cache_invalidator::CacheInvalidatorRef;
25use common_meta::datanode::{EnvVars, REGION_STATISTIC_KEY};
26use common_meta::distributed_time_constants::BASE_HEARTBEAT_INTERVAL;
27use common_meta::heartbeat::handler::invalidate_table_cache::InvalidateCacheHandler;
28use common_meta::heartbeat::handler::parse_mailbox_message::ParseMailboxMessageHandler;
29use common_meta::heartbeat::handler::suspend::SuspendHandler;
30use common_meta::heartbeat::handler::{
31 HandlerGroupExecutor, HeartbeatResponseHandlerContext, HeartbeatResponseHandlerExecutorRef,
32};
33use common_meta::heartbeat::mailbox::{HeartbeatMailbox, MailboxRef};
34use common_meta::heartbeat::utils::outgoing_message_to_mailbox_message;
35use common_meta::kv_backend::KvBackendRef;
36use common_stat::ResourceStatRef;
37use common_telemetry::{debug, error, info, trace, warn};
38use common_workload::DatanodeWorkloadType;
39use meta_client::MetaClientRef;
40use meta_client::client::heartbeat::HeartbeatConfig;
41use meta_client::client::{HeartbeatSender, MetaClient};
42use servers::addrs;
43use snafu::{OptionExt as _, ResultExt};
44use tokio::sync::{Notify, mpsc};
45use tokio::time::Instant;
46
47use self::handler::RegionHeartbeatResponseHandler;
48use crate::alive_keeper::{CountdownTaskHandlerExtRef, RegionAliveKeeper};
49use crate::config::DatanodeOptions;
50use crate::error::{self, MetaClientInitSnafu, RegionEngineNotFoundSnafu, Result};
51use crate::event_listener::RegionServerEventReceiver;
52use crate::metrics::{self, HEARTBEAT_RECV_COUNT, HEARTBEAT_SENT_COUNT};
53use crate::region_server::RegionServer;
54
55pub(crate) mod handler;
56pub(crate) mod task_tracker;
57
58pub struct HeartbeatTask {
60 node_id: u64,
61 workload_types: Vec<DatanodeWorkloadType>,
62 node_epoch: u64,
63 peer_addr: String,
64 running: Arc<AtomicBool>,
65 meta_client: MetaClientRef,
66 region_server: RegionServer,
67 resp_handler_executor: HeartbeatResponseHandlerExecutorRef,
68 region_alive_keeper: Arc<RegionAliveKeeper>,
69 resource_stat: ResourceStatRef,
70 env_vars: EnvVars,
71}
72
73impl Drop for HeartbeatTask {
74 fn drop(&mut self) {
75 self.running.store(false, Ordering::Release);
76 }
77}
78
79impl HeartbeatTask {
80 pub async fn try_new(
82 opts: &DatanodeOptions,
83 region_server: RegionServer,
84 meta_client: MetaClientRef,
85 kv_backend: KvBackendRef,
86 cache_invalidator: CacheInvalidatorRef,
87 plugins: Plugins,
88 resource_stat: ResourceStatRef,
89 ) -> Result<Self> {
90 let countdown_task_handler_ext = plugins.get::<CountdownTaskHandlerExtRef>();
91 let region_alive_keeper = Arc::new(RegionAliveKeeper::new(
92 region_server.clone(),
93 countdown_task_handler_ext,
94 BASE_HEARTBEAT_INTERVAL,
95 ));
96 let resp_handler_executor = Arc::new(HandlerGroupExecutor::new(vec![
97 region_alive_keeper.clone(),
98 Arc::new(ParseMailboxMessageHandler),
99 Arc::new(SuspendHandler::new(region_server.suspend_state())),
100 Arc::new(
101 RegionHeartbeatResponseHandler::new(region_server.clone(), kv_backend)
102 .with_open_region_parallelism(opts.init_regions_parallelism),
103 ),
104 Arc::new(InvalidateCacheHandler::new(cache_invalidator)),
105 ]));
106
107 Ok(Self {
108 node_id: opts.node_id.unwrap_or(0),
109 workload_types: opts.workload_types.clone(),
110 node_epoch: common_time::util::current_time_millis() as u64,
112 peer_addr: addrs::resolve_addr(&opts.grpc.bind_addr, Some(&opts.grpc.server_addr)),
113 running: Arc::new(AtomicBool::new(false)),
114 meta_client,
115 region_server,
116 resp_handler_executor,
117 region_alive_keeper,
118 resource_stat,
119 env_vars: EnvVars::from_config(&opts.heartbeat_env_vars),
120 })
121 }
122
123 pub async fn create_streams(
124 meta_client: &MetaClient,
125 local_gc_enabled: bool,
126 running: Arc<AtomicBool>,
127 handler_executor: HeartbeatResponseHandlerExecutorRef,
128 mailbox: MailboxRef,
129 mut notify: Option<Arc<Notify>>,
130 quit_signal: Arc<Notify>,
131 ) -> Result<(HeartbeatSender, HeartbeatConfig)> {
132 let client_id = meta_client.id();
133 let (tx, mut rx, config) = meta_client.heartbeat().await.context(MetaClientInitSnafu)?;
134 if config.gc_enabled != local_gc_enabled {
135 return error::GcConfigMismatchSnafu {
136 metasrv_gc_enabled: config.gc_enabled,
137 datanode_gc_enabled: local_gc_enabled,
138 }
139 .fail();
140 }
141
142 let mut last_received_lease = Instant::now();
143
144 let _handle = common_runtime::spawn_hb(async move {
145 while let Some(res) = rx.message().await.unwrap_or_else(|e| {
146 error!(e; "Error while reading heartbeat response");
147 None
148 }) {
149 if let Some(msg) = res.mailbox_message.as_ref() {
150 let payload_len = msg
151 .payload
152 .as_ref()
153 .map(|Payload::Json(payload)| payload.len())
154 .unwrap_or_default();
155 info!(
156 message_id = msg.id,
157 payload_len, client_id, "Received mailbox message"
158 );
159 }
160 if let Some(lease) = res.region_lease.as_ref() {
161 metrics::LAST_RECEIVED_HEARTBEAT_ELAPSED
162 .set(last_received_lease.elapsed().as_millis() as i64);
163 last_received_lease = Instant::now();
165
166 let mut leader_region_lease_count = 0;
167 let mut follower_region_lease_count = 0;
168 for lease in &lease.regions {
169 match lease.role() {
170 RegionRole::Leader
171 | RegionRole::StagingLeader
172 | RegionRole::DowngradingLeader => leader_region_lease_count += 1,
173 RegionRole::Follower => follower_region_lease_count += 1,
174 }
175 }
176
177 metrics::HEARTBEAT_REGION_LEASES
178 .with_label_values(&["leader"])
179 .set(leader_region_lease_count);
180 metrics::HEARTBEAT_REGION_LEASES
181 .with_label_values(&["follower"])
182 .set(follower_region_lease_count);
183 }
184 let ctx = HeartbeatResponseHandlerContext::new(mailbox.clone(), res);
185 if let Err(e) = Self::handle_response(ctx, handler_executor.clone()).await {
186 error!(e; "Error while handling heartbeat response");
187 }
188 if let Some(notify) = notify.take() {
189 notify.notify_one();
190 }
191 if !running.load(Ordering::Acquire) {
192 info!("Heartbeat task shutdown");
193 }
194 }
195 quit_signal.notify_one();
196 info!("Heartbeat handling loop exit.");
197 });
198 Ok((tx, config))
199 }
200
201 async fn handle_response(
202 ctx: HeartbeatResponseHandlerContext,
203 handler_executor: HeartbeatResponseHandlerExecutorRef,
204 ) -> Result<()> {
205 let mailbox_message_id = ctx.response.mailbox_message.as_ref().map(|msg| msg.id);
206 trace!(?mailbox_message_id, "Handling heartbeat response");
207 handler_executor
208 .handle(ctx)
209 .await
210 .context(error::HandleHeartbeatResponseSnafu)
211 }
212
213 #[allow(deprecated)]
214 pub async fn start(
216 &self,
217 event_receiver: RegionServerEventReceiver,
218 notify: Option<Arc<Notify>>,
219 ) -> Result<()> {
220 let running = self.running.clone();
221 if running
222 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
223 .is_err()
224 {
225 warn!("Heartbeat task started multiple times");
226 return Ok(());
227 }
228 let node_id = self.node_id;
229 let node_epoch = self.node_epoch;
230 let addr = &self.peer_addr;
231
232 let meta_client = self.meta_client.clone();
233 let region_server_clone = self.region_server.clone();
234
235 let handler_executor = self.resp_handler_executor.clone();
236
237 let (outgoing_tx, mut outgoing_rx) = mpsc::channel(16);
238 let mailbox = Arc::new(HeartbeatMailbox::new(outgoing_tx));
239
240 let quit_signal = Arc::new(Notify::new());
241 let local_gc_enabled = self
242 .region_server
243 .mito_engine()
244 .context(RegionEngineNotFoundSnafu { name: "mito" })?
245 .mito_config()
246 .gc
247 .enable;
248
249 let (mut tx, config) = Self::create_streams(
250 &meta_client,
251 local_gc_enabled,
252 running.clone(),
253 handler_executor.clone(),
254 mailbox.clone(),
255 notify,
256 quit_signal.clone(),
257 )
258 .await?;
259
260 let interval = config.interval.as_millis() as u64;
261 let mut retry_interval = config.retry_interval;
262
263 self.region_alive_keeper.update_heartbeat_interval(interval);
265
266 info!(
267 "Starting heartbeat to Metasrv with config: {}. My node id is {}, address is {}.",
268 config, node_id, addr
269 );
270
271 let self_peer = Some(Peer {
272 id: node_id,
273 addr: addr.clone(),
274 });
275 let epoch = self.region_alive_keeper.epoch();
276 let workload_types = self.workload_types.clone();
277
278 self.region_alive_keeper.start(Some(event_receiver)).await?;
279 let mut last_sent = Instant::now();
280 let total_cpu_millicores = self.resource_stat.get_total_cpu_millicores();
281 let total_memory_bytes = self.resource_stat.get_total_memory_bytes();
282 let resource_stat = self.resource_stat.clone();
283 let region_alive_keeper = self.region_alive_keeper.clone();
284 let gc_limiter = self
285 .region_server
286 .mito_engine()
287 .context(RegionEngineNotFoundSnafu { name: "mito" })?
288 .gc_limiter();
289 let mut env_var_extensions = HashMap::new();
290 self.env_vars.into_extensions(&mut env_var_extensions);
291
292 common_runtime::spawn_hb(async move {
293 let sleep = tokio::time::sleep(Duration::from_millis(0));
294 tokio::pin!(sleep);
295
296 let build_info = common_version::build_info();
297
298 let heartbeat_request = HeartbeatRequest {
299 peer: self_peer,
300 node_epoch,
301 info: Some(NodeInfo {
302 version: build_info.version.to_string(),
303 git_commit: build_info.commit_short.to_string(),
304 start_time_ms: node_epoch,
305 total_cpu_millicores,
306 total_memory_bytes,
307 cpu_usage_millicores: 0,
308 memory_usage_bytes: 0,
309 cpus: total_cpu_millicores as u32,
311 memory_bytes: total_memory_bytes as u64,
312 hostname: hostname::get()
313 .unwrap_or_default()
314 .to_string_lossy()
315 .to_string(),
316 }),
317 node_workloads: Some(NodeWorkloads::Datanode(DatanodeWorkloads {
318 types: workload_types.iter().map(|w| w.to_i32()).collect(),
319 })),
320 ..Default::default()
321 };
322
323 loop {
324 if !running.load(Ordering::Relaxed) {
325 info!("shutdown heartbeat task");
326 break;
327 }
328 let req = tokio::select! {
329 message = outgoing_rx.recv() => {
330 if let Some(message) = message {
331 match outgoing_message_to_mailbox_message(message) {
332 Ok(message) => {
333 let mut extensions = env_var_extensions.clone();
334 let gc_stat = gc_limiter.gc_stat();
335 gc_stat.into_extensions(&mut extensions);
336
337 let req = HeartbeatRequest {
338 mailbox_message: Some(message),
339 extensions,
340 ..heartbeat_request.clone()
341 };
342 HEARTBEAT_RECV_COUNT.with_label_values(&["success"]).inc();
343 Some(req)
344 }
345 Err(e) => {
346 error!(e; "Failed to encode mailbox messages!");
347 HEARTBEAT_RECV_COUNT.with_label_values(&["error"]).inc();
348 None
349 }
350 }
351 } else {
352 None
353 }
354 }
355 _ = &mut sleep => {
356 let region_stats = Self::load_region_stats(®ion_server_clone);
357 let topic_stats = region_server_clone.topic_stats();
358 let now = Instant::now();
359 let duration_since_epoch = (now - epoch).as_millis() as u64;
360
361 let mut extensions = env_var_extensions.clone();
362 let gc_stat = gc_limiter.gc_stat();
363 gc_stat.into_extensions(&mut extensions);
364
365 let mut req = HeartbeatRequest {
366 region_stats,
367 topic_stats,
368 duration_since_epoch,
369 extensions,
370 ..heartbeat_request.clone()
371 };
372
373 if let Some(info) = req.info.as_mut() {
374 info.cpu_usage_millicores = resource_stat.get_cpu_usage_millicores();
375 info.memory_usage_bytes = resource_stat.get_memory_usage_bytes();
376 }
377
378 sleep.as_mut().reset(now + Duration::from_millis(interval));
379 Some(req)
380 }
381 _ = quit_signal.notified() => {
383 let req = HeartbeatRequest::default();
384 Some(req)
385 }
386 };
387 if let Some(req) = req {
388 metrics::LAST_SENT_HEARTBEAT_ELAPSED
389 .set(last_sent.elapsed().as_millis() as i64);
390 last_sent = Instant::now();
392 let mailbox_message_id = req.mailbox_message.as_ref().map(|msg| msg.id);
393 debug!(?mailbox_message_id, "Sending heartbeat request");
394 if let Err(e) = tx.send(req).await {
395 error!(e; "Failed to send heartbeat to metasrv");
396 match Self::create_streams(
397 &meta_client,
398 local_gc_enabled,
399 running.clone(),
400 handler_executor.clone(),
401 mailbox.clone(),
402 None,
403 quit_signal.clone(),
404 )
405 .await
406 {
407 Ok((new_tx, new_config)) => {
408 info!("Reconnected to metasrv, heartbeat config: {}", new_config);
409 tx = new_tx;
410 retry_interval = new_config.retry_interval;
412 region_alive_keeper.update_heartbeat_interval(
414 new_config.interval.as_millis() as u64,
415 );
416 sleep.as_mut().reset(Instant::now());
418 }
419 Err(e) => {
420 sleep.as_mut().reset(Instant::now() + retry_interval);
424 error!(e; "Failed to reconnect to metasrv!");
425 }
426 }
427 } else {
428 HEARTBEAT_SENT_COUNT.inc();
429 }
430 }
431 }
432 });
433
434 Ok(())
435 }
436
437 fn load_region_stats(region_server: &RegionServer) -> Vec<RegionStat> {
438 region_server
439 .reportable_regions()
440 .into_iter()
441 .map(|stat| {
442 let region_stat = region_server
443 .region_statistic(stat.region_id)
444 .unwrap_or_default();
445 let mut extensions = HashMap::new();
446 if let Some(serialized) = region_stat.serialize_to_vec() {
447 extensions.insert(REGION_STATISTIC_KEY.to_string(), serialized);
448 }
449 RegionStat {
450 region_id: stat.region_id.as_u64(),
451 engine: stat.engine,
452 role: RegionRole::from(stat.role).into(),
453 rcus: 0,
454 wcus: 0,
455 approximate_bytes: region_stat.estimated_disk_size() as i64,
456 extensions,
457 }
458 })
459 .collect()
460 }
461
462 pub fn close(&self) -> Result<()> {
463 let running = self.running.clone();
464 if running
465 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)
466 .is_err()
467 {
468 warn!("Call close heartbeat task multiple times");
469 }
470
471 Ok(())
472 }
473}