risingwave_storage/hummock/
observer_manager.rs1use 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}