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