Skip to main content

datanode/
heartbeat.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
58/// The datanode heartbeat task which sending `[HeartbeatRequest]` to Metasrv periodically in background.
59pub 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    /// Create a new heartbeat task instance.
81    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            // We use datanode's start time millis as the node's epoch.
111            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                    // Resets the timer.
164                    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    /// Start heartbeat task, spawn background task.
215    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        // Update RegionAliveKeeper with the interval from Metasrv
264        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                    // TODO(zyy17): Remove these deprecated fields when the deprecated fields are removed from the proto.
310                    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(&region_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                    // If the heartbeat stream is broken, send a dummy heartbeat request to re-create the heartbeat stream.
382                    _ = 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                    // Resets the timer.
391                    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                                // Update retry_interval from new config
411                                retry_interval = new_config.retry_interval;
412                                // Update region_alive_keeper's heartbeat interval
413                                region_alive_keeper.update_heartbeat_interval(
414                                    new_config.interval.as_millis() as u64,
415                                );
416                                // Triggers to send heartbeat immediately.
417                                sleep.as_mut().reset(Instant::now());
418                            }
419                            Err(e) => {
420                                // Before the META_LEASE_SECS expires,
421                                // any retries are meaningless, it always reads the old meta leader address.
422                                // Triggers to retry after retry_interval from Metasrv config.
423                                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}