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_meta_model::ObjectId;
22use risingwave_pb::common::{WorkerNode, WorkerType};
23use risingwave_pb::meta::object::PbObjectInfo;
24use risingwave_pb::meta::subscribe_response::{Info, Operation};
25use risingwave_pb::meta::{
26    MetaSnapshot, PbObject, PbObjectGroup, SubscribeResponse, SubscribeType,
27};
28use thiserror_ext::AsReport;
29use tokio::sync::Mutex;
30use tokio::sync::mpsc::{self, UnboundedSender};
31use tonic::Status;
32
33use crate::controller::SqlMetaStore;
34use crate::manager::WorkerKey;
35use crate::manager::notification_version::NotificationVersionGenerator;
36use crate::model::FragmentId;
37
38pub type MessageStatus = Status;
39pub type Notification = Result<SubscribeResponse, Status>;
40pub type NotificationManagerRef = Arc<NotificationManager>;
41pub type NotificationVersion = u64;
42/// NOTE(kwannoel): This is just ignored, used in background DDL
43pub const IGNORED_NOTIFICATION_VERSION: u64 = 0;
44
45#[derive(Clone, Debug)]
46pub enum LocalNotification {
47    WorkerNodeDeleted(WorkerNode),
48    WorkerNodeActivated(WorkerNode),
49    SystemParamsChange(SystemParamsReader),
50    BatchParallelismChange,
51    ServingFragmentMappingsUpsert(Vec<FragmentId>),
52    ServingFragmentMappingsDelete(Vec<FragmentId>),
53    SourceDropped(ObjectId),
54    StreamingJobBackfillFinished(JobId),
55}
56
57#[derive(Debug)]
58struct Target {
59    subscribe_type: SubscribeType,
60    // `None` indicates sending to all subscribers of `subscribe_type`.
61    worker_key: Option<WorkerKey>,
62}
63
64impl From<SubscribeType> for Target {
65    fn from(value: SubscribeType) -> Self {
66        Self {
67            subscribe_type: value,
68            worker_key: None,
69        }
70    }
71}
72
73#[derive(Debug)]
74struct Task {
75    target: Target,
76    operation: Operation,
77    info: Info,
78    version: Option<NotificationVersion>,
79}
80
81/// [`NotificationManager`] is used to send notification to frontends and compute nodes.
82pub struct NotificationManager {
83    core: Arc<parking_lot::Mutex<NotificationManagerCore>>,
84    /// Sender used to add a notification into the waiting queue.
85    task_tx: UnboundedSender<Task>,
86    /// The current notification version generator.
87    version_generator: Mutex<NotificationVersionGenerator>,
88}
89
90impl NotificationManager {
91    pub async fn new(meta_store_impl: SqlMetaStore) -> Self {
92        // notification waiting queue.
93        let (task_tx, mut task_rx) = mpsc::unbounded_channel::<Task>();
94        let core = Arc::new(parking_lot::Mutex::new(NotificationManagerCore::new()));
95        let core_clone = core.clone();
96        let version_generator = NotificationVersionGenerator::new(meta_store_impl)
97            .await
98            .unwrap();
99
100        tokio::spawn(async move {
101            while let Some(task) = task_rx.recv().await {
102                let response = SubscribeResponse {
103                    status: None,
104                    operation: task.operation as i32,
105                    info: Some(task.info),
106                    version: task.version.unwrap_or_default(),
107                };
108                core.lock().notify(task.target, response);
109            }
110        });
111
112        Self {
113            core: core_clone,
114            task_tx,
115            version_generator: Mutex::new(version_generator),
116        }
117    }
118
119    pub fn abort_all(&self) {
120        let mut guard = self.core.lock();
121        *guard = NotificationManagerCore::new();
122        guard.exiting = true;
123    }
124
125    #[inline(always)]
126    fn notify(
127        &self,
128        target: Target,
129        operation: Operation,
130        info: Info,
131        version: Option<NotificationVersion>,
132    ) {
133        let task = Task {
134            target,
135            operation,
136            info,
137            version,
138        };
139        self.task_tx.send(task).unwrap();
140    }
141
142    /// Add a notification to the waiting queue and increase notification version.
143    async fn notify_with_version(
144        &self,
145        target: Target,
146        operation: Operation,
147        info: Info,
148    ) -> NotificationVersion {
149        let mut version_guard = self.version_generator.lock().await;
150        version_guard.increase_version().await;
151        let version = version_guard.current_version();
152        self.notify(target, operation, info, Some(version));
153        version
154    }
155
156    /// Add a notification to the waiting queue and return immediately
157    #[inline(always)]
158    fn notify_without_version(&self, target: Target, operation: Operation, info: Info) {
159        self.notify(target, operation, info, None);
160    }
161
162    pub fn notify_snapshot(
163        &self,
164        worker_key: WorkerKey,
165        subscribe_type: SubscribeType,
166        meta_snapshot: MetaSnapshot,
167    ) {
168        self.notify_without_version(
169            Target {
170                subscribe_type,
171                worker_key: Some(worker_key),
172            },
173            Operation::Snapshot,
174            Info::Snapshot(meta_snapshot),
175        )
176    }
177
178    pub fn notify_all_without_version(&self, operation: Operation, info: Info) {
179        for subscribe_type in [
180            SubscribeType::Frontend,
181            SubscribeType::Hummock,
182            SubscribeType::Compactor,
183            SubscribeType::Compute,
184        ] {
185            self.notify_without_version(subscribe_type.into(), operation, info.clone());
186        }
187    }
188
189    pub async fn notify_frontend(&self, operation: Operation, info: Info) -> NotificationVersion {
190        self.notify_with_version(SubscribeType::Frontend.into(), operation, info)
191            .await
192    }
193
194    pub async fn notify_frontend_object_info(
195        &self,
196        operation: Operation,
197        object_info: PbObjectInfo,
198    ) -> NotificationVersion {
199        self.notify_with_version(
200            SubscribeType::Frontend.into(),
201            operation,
202            Info::ObjectGroup(PbObjectGroup {
203                objects: vec![PbObject {
204                    object_info: object_info.into(),
205                }],
206                dependencies: vec![],
207            }),
208        )
209        .await
210    }
211
212    pub async fn notify_hummock(&self, operation: Operation, info: Info) -> NotificationVersion {
213        self.notify_with_version(SubscribeType::Hummock.into(), operation, info)
214            .await
215    }
216
217    pub(crate) async fn notify_hummock_targeted_update(
218        &self,
219        worker_key: WorkerKey,
220        info: Info,
221    ) -> NotificationVersion {
222        self.notify_with_version(
223            Target {
224                subscribe_type: SubscribeType::Hummock,
225                worker_key: Some(worker_key),
226            },
227            Operation::Update,
228            info,
229        )
230        .await
231    }
232
233    pub async fn notify_compactor(&self, operation: Operation, info: Info) -> NotificationVersion {
234        self.notify_with_version(SubscribeType::Compactor.into(), operation, info)
235            .await
236    }
237
238    pub fn notify_compute_without_version(&self, operation: Operation, info: Info) {
239        self.notify_without_version(SubscribeType::Compute.into(), operation, info)
240    }
241
242    pub fn notify_frontend_without_version(&self, operation: Operation, info: Info) {
243        self.notify_without_version(SubscribeType::Frontend.into(), operation, info)
244    }
245
246    pub fn notify_hummock_without_version(&self, operation: Operation, info: Info) {
247        self.notify_without_version(SubscribeType::Hummock.into(), operation, info)
248    }
249
250    pub fn notify_compactor_without_version(&self, operation: Operation, info: Info) {
251        self.notify_without_version(SubscribeType::Compactor.into(), operation, info)
252    }
253
254    #[cfg(any(test, feature = "test"))]
255    pub fn notify_hummock_with_version(
256        &self,
257        operation: Operation,
258        info: Info,
259        version: Option<NotificationVersion>,
260    ) {
261        self.notify(SubscribeType::Hummock.into(), operation, info, version)
262    }
263
264    pub fn notify_local_subscribers(&self, notification: LocalNotification) {
265        let mut core_guard = self.core.lock();
266        core_guard.local_senders.retain(|sender| {
267            if let Err(err) = sender.send(notification.clone()) {
268                tracing::warn!(error = %err.as_report(), "Failed to notify local subscriber");
269                return false;
270            }
271            true
272        });
273    }
274
275    /// Tell `NotificationManagerCore` to delete sender.
276    pub fn delete_sender(&self, worker_type: WorkerType, worker_key: WorkerKey) {
277        let mut core_guard = self.core.lock();
278        // TODO: we may avoid passing the worker_type and remove the `worker_key` in all sender
279        // holders anyway
280        match worker_type {
281            WorkerType::Frontend => core_guard.frontend_senders.remove(&worker_key),
282            WorkerType::ComputeNode | WorkerType::RiseCtl => {
283                core_guard.hummock_senders.remove(&worker_key)
284            }
285            WorkerType::Compactor => core_guard.compactor_senders.remove(&worker_key),
286            _ => unreachable!(),
287        };
288    }
289
290    /// Tell `NotificationManagerCore` to insert sender by `worker_type`.
291    pub fn insert_sender(
292        &self,
293        subscribe_type: SubscribeType,
294        worker_key: WorkerKey,
295        sender: UnboundedSender<Notification>,
296    ) {
297        let mut core_guard = self.core.lock();
298        if core_guard.exiting {
299            tracing::warn!("notification manager exiting.");
300            return;
301        }
302        let senders = core_guard.senders_of(subscribe_type);
303
304        senders.insert(worker_key, sender);
305    }
306
307    pub fn insert_local_sender(&self, sender: UnboundedSender<LocalNotification>) {
308        let mut core_guard = self.core.lock();
309        if core_guard.exiting {
310            tracing::warn!("notification manager exiting.");
311            return;
312        }
313        core_guard.local_senders.push(sender);
314    }
315
316    #[cfg(test)]
317    pub fn clear_local_sender(&self) {
318        self.core.lock().local_senders.clear();
319    }
320
321    pub async fn current_version(&self) -> NotificationVersion {
322        let version_guard = self.version_generator.lock().await;
323        version_guard.current_version()
324    }
325}
326
327type SenderMap = HashMap<WorkerKey, UnboundedSender<Notification>>;
328
329struct NotificationManagerCore {
330    /// The notification sender to frontends.
331    frontend_senders: SenderMap,
332    /// The notification sender to nodes that subscribes the hummock.
333    hummock_senders: SenderMap,
334    /// The notification sender to compactor nodes.
335    compactor_senders: SenderMap,
336    /// The notification sender to compute nodes.
337    compute_senders: SenderMap,
338    /// The notification sender to local subscribers.
339    local_senders: Vec<UnboundedSender<LocalNotification>>,
340    exiting: bool,
341}
342
343impl NotificationManagerCore {
344    fn new() -> Self {
345        Self {
346            frontend_senders: HashMap::new(),
347            hummock_senders: HashMap::new(),
348            compactor_senders: HashMap::new(),
349            compute_senders: HashMap::new(),
350            local_senders: vec![],
351            exiting: false,
352        }
353    }
354
355    fn notify(&mut self, target: Target, response: SubscribeResponse) {
356        macro_rules! warn_send_failure {
357            ($subscribe_type:expr, $worker_key:expr, $err:expr) => {
358                tracing::warn!(
359                    "Failed to notify {:?} {:?}: {}",
360                    $subscribe_type,
361                    $worker_key,
362                    $err
363                );
364            };
365        }
366
367        let senders = self.senders_of(target.subscribe_type);
368
369        if let Some(worker_key) = target.worker_key {
370            match senders.entry(worker_key.clone()) {
371                Entry::Occupied(entry) => {
372                    let _ = entry.get().send(Ok(response)).inspect_err(|err| {
373                        warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
374                        entry.remove_entry();
375                    });
376                }
377                Entry::Vacant(_) => {
378                    tracing::warn!("Failed to find notification sender of {:?}", worker_key)
379                }
380            }
381        } else {
382            senders.retain(|worker_key, sender| {
383                sender
384                    .send(Ok(response.clone()))
385                    .inspect_err(|err| {
386                        warn_send_failure!(target.subscribe_type, &worker_key, err.as_report());
387                    })
388                    .is_ok()
389            });
390        }
391    }
392
393    fn senders_of(&mut self, subscribe_type: SubscribeType) -> &mut SenderMap {
394        match subscribe_type {
395            SubscribeType::Frontend => &mut self.frontend_senders,
396            SubscribeType::Hummock => &mut self.hummock_senders,
397            SubscribeType::Compactor => &mut self.compactor_senders,
398            SubscribeType::Compute => &mut self.compute_senders,
399            SubscribeType::Unspecified => unreachable!(),
400        }
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use risingwave_common::id::JobId;
407    use risingwave_pb::common::HostAddress;
408
409    use super::*;
410    use crate::manager::WorkerKey;
411
412    #[tokio::test]
413    async fn test_multiple_subscribers_one_worker() {
414        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
415        let worker_key1 = WorkerKey(HostAddress {
416            host: "a".to_owned(),
417            port: 1,
418        });
419        let worker_key2 = WorkerKey(HostAddress {
420            host: "a".to_owned(),
421            port: 2,
422        });
423        let (tx1, mut rx1) = mpsc::unbounded_channel();
424        let (tx2, mut rx2) = mpsc::unbounded_channel();
425        let (tx3, mut rx3) = mpsc::unbounded_channel();
426        mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), tx1);
427        mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), tx2);
428        mgr.insert_sender(SubscribeType::Frontend, worker_key2, tx3);
429        mgr.notify_snapshot(
430            worker_key1.clone(),
431            SubscribeType::Hummock,
432            MetaSnapshot::default(),
433        );
434        assert!(rx1.recv().await.is_some());
435        assert!(rx2.try_recv().is_err());
436        assert!(rx3.try_recv().is_err());
437
438        mgr.notify_frontend(Operation::Add, Info::Database(Default::default()))
439            .await;
440        assert!(rx1.try_recv().is_err());
441        assert!(rx2.recv().await.is_some());
442        assert!(rx3.recv().await.is_some());
443    }
444
445    #[tokio::test]
446    async fn test_notify_hummock_targeted_update() {
447        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
448        let worker_key1 = WorkerKey(HostAddress {
449            host: "a".to_owned(),
450            port: 1,
451        });
452        let worker_key2 = WorkerKey(HostAddress {
453            host: "a".to_owned(),
454            port: 2,
455        });
456        let (hummock_tx1, mut hummock_rx1) = mpsc::unbounded_channel();
457        let (hummock_tx2, mut hummock_rx2) = mpsc::unbounded_channel();
458        let (frontend_tx1, mut frontend_rx1) = mpsc::unbounded_channel();
459        mgr.insert_sender(SubscribeType::Hummock, worker_key1.clone(), hummock_tx1);
460        mgr.insert_sender(SubscribeType::Hummock, worker_key2.clone(), hummock_tx2);
461        mgr.insert_sender(SubscribeType::Frontend, worker_key1.clone(), frontend_tx1);
462
463        mgr.notify_hummock_targeted_update(worker_key1, Info::Database(Default::default()))
464            .await;
465
466        assert!(hummock_rx1.recv().await.is_some());
467        assert!(hummock_rx2.try_recv().is_err());
468        assert!(frontend_rx1.try_recv().is_err());
469    }
470
471    #[tokio::test]
472    async fn test_local_notification_backfill_finished() {
473        let mgr = NotificationManager::new(SqlMetaStore::for_test().await).await;
474        let (tx, mut rx) = mpsc::unbounded_channel();
475        mgr.insert_local_sender(tx);
476
477        let job_id = JobId::new(42);
478        mgr.notify_local_subscribers(LocalNotification::StreamingJobBackfillFinished(job_id));
479
480        match rx.recv().await.expect("should receive notification") {
481            LocalNotification::StreamingJobBackfillFinished(received) => {
482                assert_eq!(received, job_id);
483            }
484            other => panic!("unexpected notification: {other:?}"),
485        }
486    }
487}