Skip to main content

risingwave_storage/hummock/
observer_manager.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 risingwave_common::config::Role;
16use risingwave_common::license::LicenseManager;
17use risingwave_common_service::ObserverState;
18use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta};
19use risingwave_hummock_trace::TraceSpan;
20use risingwave_pb::catalog::Table;
21use risingwave_pb::meta::object::PbObjectInfo;
22use risingwave_pb::meta::subscribe_response::{Info, Operation};
23use risingwave_pb::meta::{PbTableRefillRuntimeConfig, SubscribeResponse};
24use tokio::sync::mpsc::UnboundedSender;
25
26use crate::compaction_catalog_manager::CompactionCatalogManagerRef;
27use crate::hummock::backup_reader::BackupReaderRef;
28use crate::hummock::event_handler::{HummockObserverEvent, HummockVersionUpdate};
29use crate::hummock::write_limiter::WriteLimiterRef;
30
31pub struct HummockObserverNode {
32    role: Role,
33    compaction_catalog_manager: CompactionCatalogManagerRef,
34    backup_reader: BackupReaderRef,
35    write_limiter: WriteLimiterRef,
36    observer_event_sender: UnboundedSender<HummockObserverEvent>,
37    version: u64,
38}
39
40impl ObserverState for HummockObserverNode {
41    fn subscribe_type() -> risingwave_pb::meta::SubscribeType {
42        risingwave_pb::meta::SubscribeType::Hummock
43    }
44
45    fn handle_notification(&mut self, resp: SubscribeResponse) {
46        let Some(info) = resp.info.as_ref() else {
47            return;
48        };
49
50        let _span: risingwave_hummock_trace::MayTraceSpan =
51            TraceSpan::new_meta_message_span(resp.clone());
52
53        match info.to_owned() {
54            Info::ObjectGroup(object_group) => {
55                for object in object_group.objects {
56                    match object.object_info.unwrap() {
57                        PbObjectInfo::Table(table_catalog) => {
58                            self.handle_catalog_notification(resp.operation(), table_catalog);
59                        }
60                        info => panic!("invalid notification info: {info}"),
61                    };
62                }
63                assert!(
64                    resp.version > self.version,
65                    "resp version={:?}, current version={:?}",
66                    resp.version,
67                    self.version
68                );
69                self.version = resp.version;
70            }
71            Info::HummockVersionDeltas(hummock_version_deltas) => {
72                let _ = self
73                    .observer_event_sender
74                    .send(HummockObserverEvent::VersionUpdate(
75                        HummockVersionUpdate::VersionDeltas(
76                            hummock_version_deltas
77                                .version_deltas
78                                .iter()
79                                .map(HummockVersionDelta::from_rpc_protobuf)
80                                .collect(),
81                        ),
82                    ))
83                    .inspect_err(|e| {
84                        tracing::error!(event = ?e.0, "unable to send version delta");
85                    });
86            }
87
88            Info::MetaBackupManifestId(id) => {
89                self.backup_reader.try_refresh_manifest(id.id);
90            }
91
92            Info::HummockWriteLimits(write_limits) => {
93                self.write_limiter
94                    .update_write_limits(write_limits.write_limits);
95            }
96
97            Info::ClusterResource(resource) => {
98                LicenseManager::get().update_cluster_resource(resource);
99            }
100            Info::TableRefillRuntimeConfig(config) => {
101                self.handle_table_refill_runtime_config(resp.operation(), config);
102            }
103            info => {
104                panic!("invalid notification info: {info}");
105            }
106        }
107    }
108
109    fn handle_initialization_notification(&mut self, resp: SubscribeResponse) {
110        let _span: risingwave_hummock_trace::MayTraceSpan =
111            TraceSpan::new_meta_message_span(resp.clone());
112
113        let Some(Info::Snapshot(snapshot)) = resp.info else {
114            unreachable!();
115        };
116
117        self.handle_catalog_snapshot(snapshot.tables);
118        self.backup_reader.try_refresh_manifest(
119            snapshot
120                .meta_backup_manifest_id
121                .expect("should get meta backup manifest id")
122                .id,
123        );
124        self.write_limiter.update_write_limits(
125            snapshot
126                .hummock_write_limits
127                .expect("should get hummock_write_limits")
128                .write_limits,
129        );
130        let _ = self
131            .observer_event_sender
132            .send(HummockObserverEvent::VersionUpdate(
133                HummockVersionUpdate::PinnedVersion(Box::new(HummockVersion::from_rpc_protobuf(
134                    &snapshot
135                        .hummock_version
136                        .expect("should get hummock version"),
137                ))),
138            ))
139            .inspect_err(|e| {
140                tracing::error!(event = ?e.0, "unable to send full version");
141            });
142        let snapshot_version = snapshot.version.unwrap();
143        self.version = snapshot_version.catalog_version;
144        LicenseManager::get().update_cluster_resource(snapshot.cluster_resource.unwrap());
145
146        self.handle_table_refill_runtime_config(
147            Operation::Snapshot,
148            snapshot.table_refill_runtime_config.unwrap_or_default(),
149        );
150    }
151}
152
153impl HummockObserverNode {
154    pub fn new(
155        role: Role,
156        compaction_catalog_manager: CompactionCatalogManagerRef,
157        backup_reader: BackupReaderRef,
158        observer_event_sender: UnboundedSender<HummockObserverEvent>,
159        write_limiter: WriteLimiterRef,
160    ) -> Self {
161        Self {
162            role,
163            compaction_catalog_manager,
164            backup_reader,
165            observer_event_sender,
166            version: 0,
167            write_limiter,
168        }
169    }
170
171    fn handle_catalog_snapshot(&mut self, tables: Vec<Table>) {
172        self.compaction_catalog_manager
173            .sync(tables.into_iter().map(|t| (t.id, t)).collect());
174    }
175
176    fn handle_table_refill_runtime_config(
177        &self,
178        operation: Operation,
179        mut config: PbTableRefillRuntimeConfig,
180    ) {
181        if self.role.for_serving()
182            && !self.role.for_streaming()
183            && let Some(policies) = &mut config.table_cache_refill_policies
184        {
185            policies.internal_table_policies.clear();
186        }
187
188        tracing::debug!(
189            ?operation,
190            ?config,
191            "receive table refill runtime config updates"
192        );
193
194        let _ = self
195            .observer_event_sender
196            .send(HummockObserverEvent::TableRefillRuntimeConfig(
197                operation, config,
198            ))
199            .inspect_err(|e| {
200                tracing::error!(
201                    event = ?e.0,
202                    "unable to send table refill runtime config"
203                );
204            });
205    }
206
207    fn handle_catalog_notification(&mut self, operation: Operation, table_catalog: Table) {
208        match operation {
209            Operation::Add | Operation::Update => {
210                self.compaction_catalog_manager
211                    .update(table_catalog.id, table_catalog);
212            }
213
214            Operation::Delete => {
215                self.compaction_catalog_manager.remove(table_catalog.id);
216            }
217
218            _ => panic!("receive an unsupported notify {:?}", operation),
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use std::collections::HashMap;
226    use std::sync::Arc;
227
228    use risingwave_common::bitmap::Bitmap;
229    use risingwave_common::config::Role;
230    use risingwave_common::hash::VirtualNode;
231    use risingwave_common_service::ObserverState;
232    use risingwave_pb::backup_service::MetaBackupManifestId;
233    use risingwave_pb::hummock::{PbHummockVersion, WriteLimits};
234    use risingwave_pb::meta::meta_snapshot::SnapshotVersion;
235    use risingwave_pb::meta::serving_table_vnode_mappings::PbServingTableVnodeMapping;
236    use risingwave_pb::meta::subscribe_response::{Info, Operation};
237    use risingwave_pb::meta::table_cache_refill_policies::PbTableCacheRefillPolicy;
238    use risingwave_pb::meta::table_cache_refill_policies::table_cache_refill_policy::PbCacheRefillPolicy;
239    use risingwave_pb::meta::{
240        MetaSnapshot, PbServingTableVnodeMappings, PbTableRefillRuntimeConfig, SubscribeResponse,
241        TableCacheRefillPolicies,
242    };
243    use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
244
245    use super::HummockObserverNode;
246    use crate::compaction_catalog_manager::CompactionCatalogManager;
247    use crate::hummock::backup_reader::BackupReader;
248    use crate::hummock::event_handler::{HummockObserverEvent, HummockVersionUpdate};
249    use crate::hummock::write_limiter::WriteLimiter;
250
251    fn serving_vnodes() -> Bitmap {
252        Bitmap::from_indices(VirtualNode::COUNT_FOR_TEST, [1, 3])
253    }
254
255    fn runtime_config(table_id: u32, version: u64) -> PbTableRefillRuntimeConfig {
256        PbTableRefillRuntimeConfig {
257            table_cache_refill_policies: Some(TableCacheRefillPolicies {
258                table_policies: vec![PbTableCacheRefillPolicy {
259                    table_id,
260                    policy: PbCacheRefillPolicy::Serving as i32,
261                }],
262                internal_table_policies: vec![PbTableCacheRefillPolicy {
263                    table_id: table_id + 1,
264                    policy: PbCacheRefillPolicy::Both as i32,
265                }],
266            }),
267            serving_table_vnode_mappings: Some(PbServingTableVnodeMappings {
268                mappings: vec![PbServingTableVnodeMapping {
269                    table_id,
270                    bitmap: Some(serving_vnodes().to_protobuf()),
271                }],
272            }),
273            version,
274        }
275    }
276
277    fn runtime_snapshot(catalog_version: u64, table_id: u32, version: u64) -> SubscribeResponse {
278        SubscribeResponse {
279            info: Some(Info::Snapshot(MetaSnapshot {
280                hummock_version: Some(PbHummockVersion::default()),
281                version: Some(SnapshotVersion {
282                    catalog_version,
283                    ..Default::default()
284                }),
285                meta_backup_manifest_id: Some(MetaBackupManifestId { id: 0 }),
286                hummock_write_limits: Some(WriteLimits {
287                    write_limits: HashMap::new(),
288                }),
289                cluster_resource: Some(Default::default()),
290                table_refill_runtime_config: Some(runtime_config(table_id, version)),
291                ..Default::default()
292            })),
293            ..Default::default()
294        }
295    }
296
297    async fn recv_runtime_config(
298        receiver: &mut UnboundedReceiver<HummockObserverEvent>,
299    ) -> (Operation, PbTableRefillRuntimeConfig) {
300        let HummockObserverEvent::TableRefillRuntimeConfig(operation, config) =
301            receiver.recv().await.unwrap()
302        else {
303            panic!("expect table refill runtime config");
304        };
305        (operation, config)
306    }
307
308    #[tokio::test]
309    async fn test_resubscribe_snapshot_refreshes_table_refill_runtime_config() {
310        let (observer_event_tx, mut observer_event_rx) = unbounded_channel();
311        let mut observer = HummockObserverNode::new(
312            Role::Streaming,
313            Arc::new(CompactionCatalogManager::default()),
314            BackupReader::unused().await,
315            observer_event_tx,
316            WriteLimiter::unused(),
317        );
318
319        for (catalog_version, table_id, config_version) in [(1, 233, 10), (2, 333, 20)] {
320            observer.handle_initialization_notification(runtime_snapshot(
321                catalog_version,
322                table_id,
323                config_version,
324            ));
325            assert!(matches!(
326                observer_event_rx.recv().await.unwrap(),
327                HummockObserverEvent::VersionUpdate(HummockVersionUpdate::PinnedVersion(_))
328            ));
329            let (operation, config) = recv_runtime_config(&mut observer_event_rx).await;
330            assert_eq!(operation, Operation::Snapshot);
331            assert_eq!(config.version, config_version);
332            let policies = config.table_cache_refill_policies.unwrap();
333            assert_eq!(policies.table_policies[0].table_id, table_id);
334            assert_eq!(policies.internal_table_policies[0].table_id, table_id + 1);
335            let mapping = &config.serving_table_vnode_mappings.unwrap().mappings[0];
336            assert_eq!(mapping.table_id, table_id);
337            assert_eq!(
338                Bitmap::from(mapping.bitmap.clone().unwrap()),
339                serving_vnodes()
340            );
341        }
342    }
343
344    #[tokio::test]
345    async fn test_table_refill_policies_are_scoped_at_notification_boundary() {
346        let table_id = 233;
347        let internal_table_id = 234;
348        let config = runtime_config(table_id, 42);
349        let expected_serving_mappings = config.serving_table_vnode_mappings.clone();
350
351        for (role, expects_internal_policy) in [
352            (Role::Serving, false),
353            (Role::Streaming, true),
354            (Role::Both, true),
355            (Role::None, true),
356        ] {
357            let (observer_event_tx, mut observer_event_rx) = unbounded_channel();
358            let observer = HummockObserverNode::new(
359                role,
360                Arc::new(CompactionCatalogManager::default()),
361                BackupReader::unused().await,
362                observer_event_tx,
363                WriteLimiter::unused(),
364            );
365
366            observer.handle_table_refill_runtime_config(Operation::Update, config.clone());
367            let (operation, config) = recv_runtime_config(&mut observer_event_rx).await;
368            assert_eq!(operation, Operation::Update);
369            assert_eq!(config.version, 42);
370            assert_eq!(
371                config.serving_table_vnode_mappings,
372                expected_serving_mappings
373            );
374            let policies = config.table_cache_refill_policies.unwrap();
375            assert_eq!(policies.table_policies[0].table_id, table_id);
376            assert_eq!(
377                policies.internal_table_policies.first().map(|p| p.table_id),
378                expects_internal_policy.then_some(internal_table_id)
379            );
380        }
381    }
382}