Skip to main content

risingwave_meta/manager/
notification.rs

1// Copyright 2022 RisingWave Labs
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::collections::hash_map::Entry;
17use std::sync::Arc;
18
19use risingwave_common::id::JobId;
20use risingwave_common::system_param::reader::SystemParamsReader;
21use risingwave_common::util::iter_util::ZipEqDebug;
22use risingwave_meta_model::ObjectId;
23use risingwave_pb::common::{WorkerNode, WorkerType};
24use risingwave_pb::meta::object::PbObjectInfo;
25use risingwave_pb::meta::subscribe_response::{Info, Operation};
26use risingwave_pb::meta::{
27    MetaSnapshot, PbObject, PbObjectGroup, SubscribeResponse, SubscribeType,
28};
29use thiserror_ext::AsReport;
30use tokio::sync::Mutex;
31use tokio::sync::mpsc::{self, UnboundedSender};
32use tonic::Status;
33
34use crate::controller::SqlMetaStore;
35use crate::manager::WorkerKey;
36use crate::manager::notification_version::NotificationVersionGenerator;
37use crate::model::FragmentId;
38
39pub type MessageStatus = Status;
40pub type Notification = Result<SubscribeResponse, Status>;
41pub type NotificationManagerRef = Arc<NotificationManager>;
42pub type NotificationVersion = u64;
43/// NOTE(kwannoel): This is just ignored, used in background DDL
44pub const IGNORED_NOTIFICATION_VERSION: u64 = 0;
45
46/// A batch of frontend notifications that belong to the same catalog update.
47///
48/// Publishing the batch assigns consecutive catalog notification versions to its notifications.
49/// Therefore, waiting for the returned final version also waits for every earlier notification in
50/// the batch to be processed.
51pub struct FrontendNotificationBatch<'a> {
52    notification_manager: &'a NotificationManager,
53    pending: Vec<(Operation, Info)>,
54}
55
56impl<'a> FrontendNotificationBatch<'a> {
57    pub fn new(notification_manager: &'a NotificationManager) -> Self {
58        Self {
59            notification_manager,
60            pending: Vec::new(),
61        }
62    }
63
64    pub fn add(&mut self, operation: Operation, info: Info) {
65        self.pending.push((operation, info));
66    }
67
68    #[must_use]
69    pub async fn publish(self) -> Option<NotificationVersion> {
70        self.notification_manager
71            .notify_frontend_batch(self.pending)
72            .await
73    }
74}
75
76#[derive(Clone, Debug)]
77pub enum LocalNotification {
78    WorkerNodeDeleted(WorkerNode),
79    WorkerNodeActivated(WorkerNode),
80    SystemParamsChange(SystemParamsReader),
81    BatchParallelismChange,
82    ServingFragmentMappingsUpsert(Vec<FragmentId>),
83    ServingFragmentMappingsDelete(Vec<FragmentId>),
84    SourceDropped(ObjectId),
85    StreamingJobBackfillFinished(JobId),
86}
87
88#[derive(Debug)]
89struct Target {
90    subscribe_type: SubscribeType,
91    // `None` indicates sending to all subscribers of `subscribe_type`.
92    worker_key: Option<WorkerKey>,
93}
94
95impl From<SubscribeType> for Target {
96    fn from(value: SubscribeType) -> Self {
97        Self {
98            subscribe_type: value,
99            worker_key: None,
100        }
101    }
102}
103
104#[derive(Debug)]
105struct Task {
106    target: Target,
107    operation: Operation,
108    info: Info,
109    /// `None` means using the latest version published to the notification queue without
110    /// incrementing it.
111    version: Option<NotificationVersion>,
112}
113
114/// [`NotificationManager`] is used to send notification to frontends and compute nodes.
115pub struct NotificationManager {
116    core: Arc<parking_lot::Mutex<NotificationManagerCore>>,
117    /// Sender used to add a notification into the waiting queue.
118    task_tx: UnboundedSender<Task>,
119    /// The current notification version generator.
120    version_generator: Mutex<NotificationVersionGenerator>,
121}
122
123impl NotificationManager {
124    pub async fn new(meta_store_impl: SqlMetaStore) -> Self {
125        // notification waiting queue.
126        let (task_tx, mut task_rx) = mpsc::unbounded_channel::<Task>();
127        let core = Arc::new(parking_lot::Mutex::new(NotificationManagerCore::new()));
128        let core_clone = core.clone();
129        let version_generator = NotificationVersionGenerator::new(meta_store_impl)
130            .await
131            .unwrap();
132        let mut latest_version = version_generator.current_version();
133
134        tokio::spawn(async move {
135            while let Some(task) = task_rx.recv().await {
136                if let Some(version) = task.version {
137                    latest_version = version;
138                }
139                let response = SubscribeResponse {
140                    status: None,
141                    operation: task.operation as i32,
142                    info: Some(task.info),
143                    version: latest_version,
144                };
145                core.lock().notify(task.target, response);
146            }
147        });
148
149        Self {
150            core: core_clone,
151            task_tx,
152            version_generator: Mutex::new(version_generator),
153        }
154    }
155
156    pub fn abort_all(&self) {
157        let mut guard = self.core.lock();
158        *guard = NotificationManagerCore::new();
159        guard.exiting = true;
160    }
161
162    #[inline(always)]
163    fn notify(
164        &self,
165        target: Target,
166        operation: Operation,
167        info: Info,
168        version: Option<NotificationVersion>,
169    ) {
170        let task = Task {
171            target,
172            operation,
173            info,
174            version,
175        };
176        self.task_tx.send(task).unwrap();
177    }
178
179    /// Add a notification to the waiting queue and increase notification version.
180    async fn notify_with_version(
181        &self,
182        target: Target,
183        operation: Operation,
184        info: Info,
185    ) -> NotificationVersion {
186        let mut version_guard = self.version_generator.lock().await;
187        version_guard.increase_version().await;
188        let version = version_guard.current_version();
189        self.notify(target, operation, info, Some(version));
190        version
191    }
192
193    /// Assign consecutive versions to a frontend notification batch and enqueue the complete batch
194    /// while holding the version generator lock.
195    async fn notify_frontend_batch(
196        &self,
197        notifications: Vec<(Operation, Info)>,
198    ) -> Option<NotificationVersion> {
199        let batch_len: NotificationVersion = notifications.len().try_into().unwrap();
200        if batch_len == 0 {
201            return None;
202        }
203
204        let mut version_guard = self.version_generator.lock().await;
205        let first_version = version_guard.current_version() + 1;
206        version_guard.increase_version_by(batch_len).await;
207        let final_version = version_guard.current_version();
208
209        for ((operation, info), version) in notifications
210            .into_iter()
211            .zip_eq_debug(first_version..=final_version)
212        {
213            self.notify(
214                SubscribeType::Frontend.into(),
215                operation,
216                info,
217                Some(version),
218            );
219        }
220
221        Some(final_version)
222    }
223
224    /// Add a notification using the latest version published to the queue without incrementing it.
225    #[inline(always)]
226    fn notify_without_version(&self, target: Target, operation: Operation, info: Info) {
227        self.notify(target, operation, info, None);
228    }
229
230    pub fn notify_snapshot(
231        &self,
232        sender: UnboundedSender<Notification>,
233        meta_snapshot: MetaSnapshot,
234    ) {
235        let response = SubscribeResponse {
236            status: None,
237            operation: Operation::Snapshot as i32,
238            info: Some(Info::Snapshot(meta_snapshot)),
239            version: Default::default(),
240        };
241        let _ = sender.send(Ok(response)).inspect_err(|err| {
242            tracing::warn!("Failed to notify direct subscriber: {}", err.as_report());
243        });
244    }
245
246    pub fn notify_all_without_version(&self, operation: Operation, info: Info) {
247        for subscribe_type in [
248            SubscribeType::Frontend,
249            SubscribeType::Hummock,
250            SubscribeType::Compactor,
251            SubscribeType::Compute,
252        ] {
253            self.notify_without_version(subscribe_type.into(), operation, info.clone());
254        }
255    }
256
257    #[must_use]
258    pub async fn notify_frontend(&self, operation: Operation, info: Info) -> NotificationVersion {
259        self.notify_with_version(SubscribeType::Frontend.into(), operation, info)
260            .await
261    }
262
263    #[must_use]
264    pub async fn notify_frontend_object_info(
265        &self,
266        operation: Operation,
267        object_info: PbObjectInfo,
268    ) -> NotificationVersion {
269        self.notify_with_version(
270            SubscribeType::Frontend.into(),
271            operation,
272            Info::ObjectGroup(PbObjectGroup {
273                objects: vec![PbObject {
274                    object_info: object_info.into(),
275                }],
276                dependencies: vec![],
277            }),
278        )
279        .await
280    }
281
282    pub(crate) fn notify_hummock_targeted_update(&self, worker_key: WorkerKey, info: Info) {
283        self.notify_without_version(
284            Target {
285                subscribe_type: SubscribeType::Hummock,
286                worker_key: Some(worker_key),
287            },
288            Operation::Update,
289            info,
290        );
291    }
292
293    pub fn notify_compute_without_version(&self, operation: Operation, info: Info) {
294        self.notify_without_version(SubscribeType::Compute.into(), operation, info)
295    }
296
297    pub fn notify_frontend_without_version(&self, operation: Operation, info: Info) {
298        self.notify_without_version(SubscribeType::Frontend.into(), operation, info)
299    }
300
301    pub fn notify_hummock(&self, operation: Operation, info: Info) {
302        self.notify_without_version(SubscribeType::Hummock.into(), operation, info)
303    }
304
305    pub fn notify_compactor(&self, operation: Operation, info: Info) {
306        self.notify_without_version(SubscribeType::Compactor.into(), operation, info)
307    }
308
309    #[cfg(any(test, feature = "test"))]
310    pub fn notify_hummock_with_version(
311        &self,
312        operation: Operation,
313        info: Info,
314        version: Option<NotificationVersion>,
315    ) {
316        self.notify(SubscribeType::Hummock.into(), operation, info, version)
317    }
318
319    pub fn notify_local_subscribers(&self, notification: LocalNotification) {
320        let mut core_guard = self.core.lock();
321        core_guard.local_senders.retain(|sender| {
322            if let Err(err) = sender.send(notification.clone()) {
323                tracing::warn!(error = %err.as_report(), "Failed to notify local subscriber");
324                return false;
325            }
326            true
327        });
328    }
329
330    /// Tell `NotificationManagerCore` to delete sender.
331    pub fn delete_sender(&self, worker_type: WorkerType, worker_key: WorkerKey) {
332        let mut core_guard = self.core.lock();
333        // TODO: we may avoid passing the worker_type and remove the `worker_key` in all sender
334        // holders anyway
335        match worker_type {
336            WorkerType::Frontend => core_guard.frontend_senders.remove(&worker_key),
337            WorkerType::ComputeNode | WorkerType::RiseCtl => {
338                core_guard.hummock_senders.remove(&worker_key)
339            }
340            WorkerType::Compactor => core_guard.compactor_senders.remove(&worker_key),
341            _ => unreachable!(),
342        };
343    }
344
345    /// Tell `NotificationManagerCore` to insert sender by `worker_type`.
346    pub fn insert_sender(
347        &self,
348        subscribe_type: SubscribeType,
349        worker_key: WorkerKey,
350        sender: UnboundedSender<Notification>,
351    ) {
352        let mut core_guard = self.core.lock();
353        if core_guard.exiting {
354            tracing::warn!("notification manager exiting.");
355            return;
356        }
357        let senders = core_guard.senders_of(subscribe_type);
358
359        senders.insert(worker_key, sender);
360    }
361
362    pub fn insert_local_sender(&self, sender: UnboundedSender<LocalNotification>) {
363        let mut core_guard = self.core.lock();
364        if core_guard.exiting {
365            tracing::warn!("notification manager exiting.");
366            return;
367        }
368        core_guard.local_senders.push(sender);
369    }
370
371    #[cfg(test)]
372    pub fn clear_local_sender(&self) {
373        self.core.lock().local_senders.clear();
374    }
375
376    pub async fn current_version(&self) -> NotificationVersion {
377        let version_guard = self.version_generator.lock().await;
378        version_guard.current_version()
379    }
380}
381
382type SenderMap = HashMap<WorkerKey, UnboundedSender<Notification>>;
383
384struct NotificationManagerCore {
385    /// The notification sender to frontends.
386    frontend_senders: SenderMap,
387    /// The notification sender to nodes that subscribes the hummock.
388    hummock_senders: SenderMap,
389    /// The notification sender to compactor nodes.
390    compactor_senders: SenderMap,
391    /// The notification sender to compute nodes.
392    compute_senders: SenderMap,
393    /// The notification sender to local subscribers.
394    local_senders: Vec<UnboundedSender<LocalNotification>>,
395    exiting: bool,
396}
397
398impl NotificationManagerCore {
399    fn new() -> Self {
400        Self {
401            frontend_senders: HashMap::new(),
402            hummock_senders: HashMap::new(),
403            compactor_senders: HashMap::new(),
404            compute_senders: HashMap::new(),
405            local_senders: vec![],
406            exiting: false,
407        }
408    }
409
410    fn notify(&mut self, target: Target, response: SubscribeResponse) {
411        macro_rules! warn_send_failure {
412            ($subscribe_type:expr, $worker_key:expr, $err:expr) => {
413                tracing::warn!(
414                    "Failed to notify {:?} {:?}: {}",
415                    $subscribe_type,
416                    $worker_key,
417                    $err
418                );
419            };
420        }
421
422        let senders = self.senders_of(target.subscribe_type);
423
424        if let Some(worker_key) = target.worker_key {
425            match senders.entry(worker_key.clone()) {
426                Entry::Occupied(entry) => {
427                    let _ = entry.get().send(Ok(response)).inspect_err(|err| {
428                        warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
429                        entry.remove_entry();
430                    });
431                }
432                Entry::Vacant(_) => {
433                    tracing::warn!("Failed to find notification sender of {:?}", worker_key)
434                }
435            }
436        } else {
437            senders.retain(|worker_key, sender| {
438                sender
439                    .send(Ok(response.clone()))
440                    .inspect_err(|err| {
441                        warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
442                    })
443                    .is_ok()
444            });
445        }
446    }
447
448    fn senders_of(&mut self, subscribe_type: SubscribeType) -> &mut SenderMap {
449        match subscribe_type {
450            SubscribeType::Frontend => &mut self.frontend_senders,
451            SubscribeType::Hummock => &mut self.hummock_senders,
452            SubscribeType::Compactor => &mut self.compactor_senders,
453            SubscribeType::Compute => &mut self.compute_senders,
454            SubscribeType::Unspecified => unreachable!(),
455        }
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    use risingwave_common::id::JobId;
462    use risingwave_pb::common::HostAddress;
463
464    use super::*;
465    use crate::manager::WorkerKey;
466
467    #[tokio::test]
468    async fn test_multiple_subscribers_one_worker() {
469        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
470        let worker_key1 = WorkerKey(HostAddress {
471            host: "a".to_owned(),
472            port: 1,
473        });
474        let worker_key2 = WorkerKey(HostAddress {
475            host: "a".to_owned(),
476            port: 2,
477        });
478        let (tx1, mut rx1) = mpsc::unbounded_channel();
479        let (tx2, mut rx2) = mpsc::unbounded_channel();
480        let (tx3, mut rx3) = mpsc::unbounded_channel();
481        mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), tx1.clone());
482        mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), tx2);
483        mgr.insert_sender(SubscribeType::Frontend, worker_key2, tx3);
484        mgr.notify_snapshot(tx1, MetaSnapshot::default());
485        assert!(rx1.recv().await.is_some());
486        assert!(rx2.try_recv().is_err());
487        assert!(rx3.try_recv().is_err());
488
489        let version = mgr
490            .notify_frontend(Operation::Add, Info::Database(Default::default()))
491            .await;
492        assert!(rx1.try_recv().is_err());
493        let frontend_notification = rx2.recv().await.unwrap().unwrap();
494        assert_eq!(frontend_notification.version, version);
495        assert_eq!(
496            rx3.recv().await.unwrap().unwrap().version,
497            frontend_notification.version
498        );
499
500        mgr.notify_hummock(Operation::Add, Info::Database(Default::default()));
501        assert_eq!(
502            rx1.recv().await.unwrap().unwrap().version,
503            frontend_notification.version
504        );
505    }
506
507    #[tokio::test]
508    async fn test_frontend_notification_batch_assigns_consecutive_versions() {
509        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
510        let worker_key = WorkerKey(HostAddress {
511            host: "a".to_owned(),
512            port: 1,
513        });
514        let (tx, mut rx) = mpsc::unbounded_channel();
515        mgr.insert_sender(SubscribeType::Frontend, worker_key, tx);
516
517        let initial_version = mgr.current_version().await;
518        let mut batch = FrontendNotificationBatch::new(&mgr);
519        batch.add(Operation::Add, Info::Database(Default::default()));
520        batch.add(Operation::Add, Info::Schema(Default::default()));
521        let published_version = batch
522            .publish()
523            .await
524            .expect("the test notification batch contains two notifications");
525
526        assert_eq!(published_version, initial_version + 2);
527        assert_eq!(mgr.current_version().await, published_version);
528        assert_eq!(
529            rx.recv().await.unwrap().unwrap().version,
530            initial_version + 1
531        );
532        assert_eq!(rx.recv().await.unwrap().unwrap().version, published_version);
533    }
534
535    #[tokio::test]
536    async fn test_notify_hummock_targeted_update() {
537        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
538        let worker_key1 = WorkerKey(HostAddress {
539            host: "a".to_owned(),
540            port: 1,
541        });
542        let worker_key2 = WorkerKey(HostAddress {
543            host: "a".to_owned(),
544            port: 2,
545        });
546        let (hummock_tx1, mut hummock_rx1) = mpsc::unbounded_channel();
547        let (hummock_tx2, mut hummock_rx2) = mpsc::unbounded_channel();
548        let (frontend_tx1, mut frontend_rx1) = mpsc::unbounded_channel();
549        mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), hummock_tx1);
550        mgr.insert_sender(SubscribeType::Hummock, worker_key2.clone(), hummock_tx2);
551        mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), frontend_tx1);
552
553        let version = mgr.current_version().await;
554        mgr.notify_hummock_targeted_update(worker_key1, Info::Database(Default::default()));
555
556        let notification = hummock_rx1.recv().await.unwrap().unwrap();
557        assert_eq!(notification.version, version);
558        assert_eq!(mgr.current_version().await, version);
559        assert!(hummock_rx2.try_recv().is_err());
560        assert!(frontend_rx1.try_recv().is_err());
561    }
562
563    #[tokio::test]
564    async fn test_snapshot_uses_original_sender_after_resubscribe() {
565        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
566        let worker_key = WorkerKey(HostAddress {
567            host: "a".to_owned(),
568            port: 1,
569        });
570        let (tx1, mut rx1) = mpsc::unbounded_channel();
571        mgr.insert_sender(SubscribeType::Hummock, worker_key.clone(), tx1.clone());
572        mgr.notify_snapshot(tx1, MetaSnapshot::default());
573
574        let (tx2, mut rx2) = mpsc::unbounded_channel();
575        mgr.insert_sender(SubscribeType::Hummock, worker_key, tx2);
576
577        assert!(rx1.recv().await.is_some());
578        assert!(rx2.try_recv().is_err());
579
580        let version = mgr.current_version().await;
581        mgr.notify_hummock(Operation::Add, Info::Database(Default::default()));
582        assert!(rx1.try_recv().is_err());
583        let notification = rx2.recv().await.unwrap().unwrap();
584        assert_eq!(notification.version, version);
585        assert_eq!(mgr.current_version().await, version);
586    }
587
588    #[tokio::test]
589    async fn test_local_notification_backfill_finished() {
590        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
591        let (tx, mut rx) = mpsc::unbounded_channel();
592        mgr.insert_local_sender(tx);
593
594        let job_id = JobId::new(42);
595        mgr.notify_local_subscribers(LocalNotification::StreamingJobBackfillFinished(job_id));
596
597        match rx.recv().await.expect("should receive notification") {
598            LocalNotification::StreamingJobBackfillFinished(received) => {
599                assert_eq!(received, job_id);
600            }
601            other => panic!("unexpected notification: {other:?}"),
602        }
603    }
604}