1use std::collections::{BTreeMap, HashMap, HashSet};
16use std::ops::DerefMut;
17use std::sync::Arc;
18
19use itertools::Itertools;
20use risingwave_common::catalog::TableId;
21use risingwave_common::config::meta::default::compaction_config as default_compaction_config;
22use risingwave_common::util::epoch::INVALID_EPOCH;
23use risingwave_hummock_sdk::CompactionGroupId;
24use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId;
25use risingwave_hummock_sdk::compaction_group::hummock_version_ext::get_compaction_group_ids;
26use risingwave_hummock_sdk::filter_utils::{
27 parse_sstable_filter_layout, parse_sstable_filter_type,
28};
29use risingwave_hummock_sdk::version::{GroupDelta, HummockVersion};
30use risingwave_meta_model::compaction_config;
31use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig;
32use risingwave_pb::hummock::write_limits::WriteLimit;
33use risingwave_pb::hummock::{
34 CompactionConfig, CompactionGroupInfo, HummockVersionStats, PbGroupConstruct, PbGroupDestroy,
35 PbStateTableInfoDelta,
36};
37use sea_orm::EntityTrait;
38use tokio::sync::OnceCell;
39
40use super::CompactionGroupStatistic;
41use crate::hummock::compaction::compaction_config::{
42 CompactionConfigBuilder, validate_compaction_config,
43};
44use crate::hummock::error::{Error, Result};
45use crate::hummock::manager::transaction::HummockVersionTransaction;
46use crate::hummock::manager::versioning::Versioning;
47use crate::hummock::manager::{HummockManager, commit_multi_var};
48use crate::hummock::metrics_utils::remove_compaction_group_metrics;
49use crate::hummock::model::CompactionGroup;
50use crate::hummock::sequence::next_compaction_group_id;
51use crate::manager::MetaSrvEnv;
52use crate::model::{
53 BTreeMapTransaction, BTreeMapTransactionInner, DerefMutForward, MetadataModelError,
54};
55
56type CompactionGroupTransaction<'a> = BTreeMapTransaction<'a, CompactionGroupId, CompactionGroup>;
57
58impl CompactionGroupManager {
59 pub(crate) async fn new(env: &MetaSrvEnv) -> Result<CompactionGroupManager> {
60 let default_config = match env.opts.compaction_config.as_ref() {
61 None => CompactionConfigBuilder::new().build(),
62 Some(opt) => CompactionConfigBuilder::with_opt(opt).build(),
63 };
64 Self::new_with_config(env, default_config).await
65 }
66
67 pub(crate) async fn new_with_config(
68 env: &MetaSrvEnv,
69 default_config: CompactionConfig,
70 ) -> Result<CompactionGroupManager> {
71 let mut compaction_group_manager = CompactionGroupManager {
72 compaction_groups: BTreeMap::new(),
73 default_config: Arc::new(default_config),
74 write_limit: Default::default(),
75 };
76
77 let loaded_compaction_groups: BTreeMap<CompactionGroupId, CompactionGroup> =
78 compaction_config::Entity::find()
79 .all(&env.meta_store_ref().conn)
80 .await
81 .map_err(MetadataModelError::from)?
82 .into_iter()
83 .map(|m| (m.compaction_group_id as CompactionGroupId, m.into()))
84 .collect();
85
86 compaction_group_manager.init(loaded_compaction_groups);
87 Ok(compaction_group_manager)
88 }
89
90 fn init(&mut self, loaded_compaction_groups: BTreeMap<CompactionGroupId, CompactionGroup>) {
91 if !loaded_compaction_groups.is_empty() {
92 self.compaction_groups = loaded_compaction_groups;
93 }
94 }
95}
96
97impl HummockManager {
98 pub async fn compaction_group_ids(&self) -> Vec<CompactionGroupId> {
101 get_compaction_group_ids(&self.versioning.read().await.current_version).collect_vec()
102 }
103
104 pub async fn get_compaction_group_map(&self) -> BTreeMap<CompactionGroupId, CompactionGroup> {
106 self.compaction_group_manager
107 .read()
108 .await
109 .compaction_groups
110 .clone()
111 }
112
113 #[cfg(test)]
114 pub async fn register_table_fragments(
116 &self,
117 mv_table: Option<TableId>,
118 mut internal_tables: Vec<TableId>,
119 ) -> Result<()> {
120 let mut pairs = vec![];
121 if let Some(mv_table) = mv_table {
122 if internal_tables.extract_if(.., |t| *t == mv_table).count() > 0 {
123 tracing::warn!("`mv_table` {} found in `internal_tables`", mv_table);
124 }
125 pairs.push((mv_table, StaticCompactionGroupId::MaterializedView));
127 }
128 for table_id in internal_tables {
130 pairs.push((table_id, StaticCompactionGroupId::StateDefault));
131 }
132 self.register_table_ids_for_test(&pairs).await?;
133 Ok(())
134 }
135
136 #[cfg(test)]
137 pub async fn unregister_table_fragments_vec(
139 &self,
140 table_fragments: &[crate::model::StreamJobFragments],
141 ) {
142 self.unregister_table_ids(table_fragments.iter().flat_map(|t| t.all_table_ids()))
143 .await
144 .unwrap();
145 }
146
147 pub async fn purge(&self, valid_ids: &HashSet<TableId>) -> Result<()> {
151 let to_unregister = self
152 .versioning
153 .read()
154 .await
155 .current_version
156 .state_table_info
157 .info()
158 .keys()
159 .cloned()
160 .filter(|table_id| !valid_ids.contains(table_id))
161 .collect_vec();
162
163 self.unregister_table_ids(to_unregister).await
166 }
167
168 pub async fn register_table_ids_for_test(
173 &self,
174 pairs: &[(impl Into<TableId> + Copy, CompactionGroupId)],
175 ) -> Result<()> {
176 if pairs.is_empty() {
177 return Ok(());
178 }
179 let mut versioning_guard = self.versioning.write().await;
180 let versioning = versioning_guard.deref_mut();
181 let mut compaction_group_manager = self.compaction_group_manager.write().await;
182 let current_version = &versioning.current_version;
183 let default_config = compaction_group_manager.default_compaction_config();
184 let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn();
185
186 for (table_id, _) in pairs {
187 let table_id = (*table_id).into();
188 if let Some(info) = current_version.state_table_info.info().get(&table_id) {
189 return Err(Error::CompactionGroup(format!(
190 "table {} already {:?}",
191 table_id, info
192 )));
193 }
194 }
195 let new_compaction_group_id: OnceCell<CompactionGroupId> = OnceCell::new();
197 let mut version = HummockVersionTransaction::new(
198 &mut versioning.current_version,
199 &mut versioning.hummock_version_deltas,
200 &mut versioning.table_change_log,
201 self.env.notification_manager(),
202 None,
203 &self.metrics,
204 &self.env.opts,
205 &self.version_stat_tx,
206 );
207 let mut new_version_delta = version.new_delta();
208
209 let committed_epoch = new_version_delta
210 .latest_version()
211 .state_table_info
212 .info()
213 .values()
214 .map(|info| info.committed_epoch)
215 .max()
216 .unwrap_or(INVALID_EPOCH);
217
218 for (table_id, raw_group_id) in pairs {
219 let table_id = (*table_id).into();
220 let mut group_id = *raw_group_id;
221 if group_id == StaticCompactionGroupId::NewCompactionGroup {
222 let mut is_group_init = false;
223 group_id = *new_compaction_group_id
224 .get_or_try_init(|| async {
225 next_compaction_group_id(&self.env).await.inspect(|_| {
226 is_group_init = true;
227 })
228 })
229 .await?;
230 if is_group_init {
231 let group_deltas = &mut new_version_delta
232 .group_deltas
233 .entry(group_id)
234 .or_default()
235 .group_deltas;
236
237 let config =
238 match compaction_groups_txn.try_get_compaction_group_config(group_id) {
239 Some(config) => config.compaction_config.as_ref().clone(),
240 None => {
241 compaction_groups_txn
242 .create_compaction_groups(group_id, default_config.clone());
243 default_config.as_ref().clone()
244 }
245 };
246
247 let group_delta = GroupDelta::GroupConstruct(Box::new(PbGroupConstruct {
248 group_config: Some(config),
249 group_id,
250 ..Default::default()
251 }));
252
253 group_deltas.push(group_delta);
254 }
255 }
256 assert!(
257 new_version_delta
258 .state_table_info_delta
259 .insert(
260 table_id,
261 PbStateTableInfoDelta {
262 committed_epoch,
263 compaction_group_id: *raw_group_id,
264 }
265 )
266 .is_none()
267 );
268 }
269 new_version_delta.pre_apply();
270 commit_multi_var!(self.meta_store_ref(), version, compaction_groups_txn)?;
271
272 Ok(())
273 }
274
275 pub async fn unregister_table_ids(
276 &self,
277 table_ids: impl IntoIterator<Item = TableId>,
278 ) -> Result<()> {
279 let table_ids = table_ids.into_iter().collect_vec();
280 if table_ids.is_empty() {
281 return Ok(());
282 }
283
284 {
285 let mut table_write_throughput_statistic_manager =
289 self.table_write_throughput_statistic_manager.write();
290 for &table_id in table_ids.iter().unique() {
291 table_write_throughput_statistic_manager.remove_table(table_id);
292 }
293 }
294
295 let mut versioning_guard = self.versioning.write().await;
296 let versioning = versioning_guard.deref_mut();
297 let mut version = HummockVersionTransaction::new(
298 &mut versioning.current_version,
299 &mut versioning.hummock_version_deltas,
300 &mut versioning.table_change_log,
301 self.env.notification_manager(),
302 None,
303 &self.metrics,
304 &self.env.opts,
305 &self.version_stat_tx,
306 );
307 let mut new_version_delta = version.new_delta();
308 struct UnregisterGroupChange {
309 remaining_member_count: usize,
310 removed_table_ids: HashSet<TableId>,
311 }
312 let mut group_changes: HashMap<CompactionGroupId, UnregisterGroupChange> = HashMap::new();
313 for table_id in table_ids.into_iter().unique() {
315 let version = new_version_delta.latest_version();
316 let Some(info) = version.state_table_info.info().get(&table_id) else {
317 continue;
318 };
319 let compaction_group_id = info.compaction_group_id;
320
321 let group_change =
322 group_changes
323 .entry(compaction_group_id)
324 .or_insert_with(|| UnregisterGroupChange {
325 remaining_member_count: version
326 .state_table_info
327 .compaction_group_member_tables()
328 .get(&compaction_group_id)
329 .expect("should exist")
330 .len(),
331 removed_table_ids: HashSet::new(),
332 });
333 group_change.remaining_member_count = group_change
334 .remaining_member_count
335 .checked_sub(1)
336 .expect("member table count should be positive");
337 assert!(group_change.removed_table_ids.insert(table_id));
338 new_version_delta.removed_table_ids.insert(table_id);
339 }
340
341 for (group_id, change) in group_changes {
342 if change.remaining_member_count == 0 && group_id > StaticCompactionGroupId::End {
343 let max_level = new_version_delta
344 .latest_version()
345 .get_compaction_group_levels(group_id)
346 .levels
347 .len();
348 new_version_delta
349 .group_deltas
350 .entry(group_id)
351 .or_default()
352 .group_deltas
353 .push(GroupDelta::GroupDestroy(PbGroupDestroy {}));
354 remove_compaction_group_metrics(&self.metrics, group_id, max_level);
355 self.compaction_state.remove_compaction_group(group_id);
357 } else {
358 new_version_delta
359 .group_deltas
360 .entry(group_id)
361 .or_default()
362 .group_deltas
363 .push(GroupDelta::PruneTableIdsFromSsts(change.removed_table_ids));
364 }
365 }
366
367 new_version_delta.pre_apply();
368
369 let mut compaction_group_manager = self.compaction_group_manager.write().await;
372 let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn();
373
374 compaction_groups_txn.purge(HashSet::from_iter(get_compaction_group_ids(
375 version.latest_version(),
376 )));
377 commit_multi_var!(self.meta_store_ref(), version, compaction_groups_txn)?;
378
379 Ok(())
381 }
382
383 pub async fn update_compaction_config(
384 &self,
385 compaction_group_ids: &[CompactionGroupId],
386 config_to_update: &[MutableConfig],
387 ) -> Result<()> {
388 {
389 let mut compaction_group_manager = self.compaction_group_manager.write().await;
391 let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn();
392 compaction_groups_txn
393 .update_compaction_config(compaction_group_ids, config_to_update)?;
394 commit_multi_var!(self.meta_store_ref(), compaction_groups_txn)?;
395 }
396
397 if config_to_update
398 .iter()
399 .any(|c| matches!(c, MutableConfig::Level0StopWriteThresholdSubLevelNumber(_)))
400 {
401 self.try_update_write_limits(compaction_group_ids).await;
403 }
404
405 Ok(())
406 }
407
408 pub async fn list_compaction_group(&self) -> Vec<CompactionGroupInfo> {
411 let mut versioning_guard = self.versioning.write().await;
412 let versioning = versioning_guard.deref_mut();
413 let current_version = &versioning.current_version;
414 let mut results = vec![];
415 let compaction_group_manager = self.compaction_group_manager.read().await;
416
417 for levels in current_version.levels.values() {
418 let compaction_config = compaction_group_manager
419 .try_get_compaction_group_config(levels.group_id)
420 .unwrap()
421 .compaction_config
422 .as_ref()
423 .clone();
424 let group = CompactionGroupInfo {
425 id: levels.group_id,
426 parent_id: levels.parent_group_id,
427 member_table_ids: current_version
428 .state_table_info
429 .compaction_group_member_table_ids(levels.group_id)
430 .iter()
431 .copied()
432 .collect_vec(),
433 compaction_config: Some(compaction_config),
434 };
435 results.push(group);
436 }
437 results
438 }
439
440 pub(crate) fn calculate_compaction_group_statistic_from_snapshot(
441 current_version: &HummockVersion,
442 version_stats: &HummockVersionStats,
443 id_to_config: &BTreeMap<CompactionGroupId, CompactionGroup>,
444 ) -> Vec<CompactionGroupStatistic> {
445 let mut infos = vec![];
446 for group_id in current_version.levels.keys() {
447 let compaction_group_config = id_to_config
448 .get(group_id)
449 .expect("compaction group config should exist for every group in current version")
450 .clone();
451 let mut group_info = CompactionGroupStatistic {
452 group_id: *group_id,
453 compaction_group_config,
454 ..Default::default()
455 };
456
457 for table_id in current_version
458 .state_table_info
459 .compaction_group_member_table_ids(*group_id)
460 {
461 let stats_size = version_stats
462 .table_stats
463 .get(table_id)
464 .map(|stats| stats.total_key_size + stats.total_value_size)
465 .unwrap_or(0);
466 let table_size = stats_size.max(0) as u64;
467 group_info.group_size += table_size;
468 group_info.table_statistic.insert(*table_id, table_size);
469 }
470
471 infos.push(group_info);
472 }
473 infos
474 }
475
476 pub async fn calculate_compaction_group_statistic(&self) -> Vec<CompactionGroupStatistic> {
477 let versioning_guard = self.versioning.read().await;
478 let manager = self.compaction_group_manager.read().await;
479 Self::calculate_compaction_group_statistic_from_snapshot(
480 &versioning_guard.current_version,
481 &versioning_guard.version_stats,
482 &manager.compaction_groups,
483 )
484 }
485
486 pub(crate) async fn initial_compaction_group_config_after_load(
487 &self,
488 versioning_guard: &Versioning,
489 compaction_group_manager: &mut CompactionGroupManager,
490 ) -> Result<()> {
491 let current_version = &versioning_guard.current_version;
493 let all_group_ids = get_compaction_group_ids(current_version).collect_vec();
494 let default_config = compaction_group_manager.default_compaction_config();
495 let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn();
496 compaction_groups_txn.try_create_compaction_groups(&all_group_ids, default_config);
497 commit_multi_var!(self.meta_store_ref(), compaction_groups_txn)?;
498
499 Ok(())
500 }
501}
502
503pub(crate) struct CompactionGroupManager {
511 compaction_groups: BTreeMap<CompactionGroupId, CompactionGroup>,
512 default_config: Arc<CompactionConfig>,
513 pub write_limit: HashMap<CompactionGroupId, WriteLimit>,
515}
516
517impl CompactionGroupManager {
518 pub fn start_compaction_groups_txn(&mut self) -> CompactionGroupTransaction<'_> {
520 CompactionGroupTransaction::new(&mut self.compaction_groups)
521 }
522
523 #[expect(clippy::type_complexity)]
524 pub fn start_owned_compaction_groups_txn<P: DerefMut<Target = Self>>(
525 inner: P,
526 ) -> BTreeMapTransactionInner<
527 CompactionGroupId,
528 CompactionGroup,
529 DerefMutForward<
530 Self,
531 BTreeMap<CompactionGroupId, CompactionGroup>,
532 P,
533 impl Fn(&Self) -> &BTreeMap<CompactionGroupId, CompactionGroup>,
534 impl Fn(&mut Self) -> &mut BTreeMap<CompactionGroupId, CompactionGroup>,
535 >,
536 > {
537 BTreeMapTransactionInner::new(DerefMutForward::new(
538 inner,
539 |mgr| &mgr.compaction_groups,
540 |mgr| &mut mgr.compaction_groups,
541 ))
542 }
543
544 pub(crate) fn try_get_compaction_group_config(
546 &self,
547 compaction_group_id: impl Into<CompactionGroupId>,
548 ) -> Option<CompactionGroup> {
549 self.compaction_groups
550 .get(&compaction_group_id.into())
551 .cloned()
552 }
553
554 pub(crate) fn default_compaction_config(&self) -> Arc<CompactionConfig> {
556 self.default_config.clone()
557 }
558}
559
560fn update_compaction_config(target: &mut CompactionConfig, items: &[MutableConfig]) -> Result<()> {
561 for item in items {
562 match item {
563 MutableConfig::MaxBytesForLevelBase(c) => {
564 target.max_bytes_for_level_base = *c;
565 }
566 MutableConfig::MaxBytesForLevelMultiplier(c) => {
567 target.max_bytes_for_level_multiplier = *c;
568 }
569 MutableConfig::MaxCompactionBytes(c) => {
570 target.max_compaction_bytes = *c;
571 }
572 MutableConfig::SubLevelMaxCompactionBytes(c) => {
573 target.sub_level_max_compaction_bytes = *c;
574 }
575 MutableConfig::Level0TierCompactFileNumber(c) => {
576 target.level0_tier_compact_file_number = *c;
577 }
578 MutableConfig::TargetFileSizeBase(c) => {
579 target.target_file_size_base = *c;
580 }
581 MutableConfig::CompactionFilterMask(c) => {
582 target.compaction_filter_mask = *c;
583 }
584 MutableConfig::MaxSubCompaction(c) => {
585 target.max_sub_compaction = *c;
586 }
587 MutableConfig::Level0StopWriteThresholdSubLevelNumber(c) => {
588 target.level0_stop_write_threshold_sub_level_number = *c;
589 }
590 MutableConfig::Level0SubLevelCompactLevelCount(c) => {
591 target.level0_sub_level_compact_level_count = *c;
592 }
593 MutableConfig::Level0OverlappingSubLevelCompactLevelCount(c) => {
594 target.level0_overlapping_sub_level_compact_level_count = *c;
595 }
596 MutableConfig::MaxSpaceReclaimBytes(c) => {
597 target.max_space_reclaim_bytes = *c;
598 }
599 MutableConfig::Level0MaxCompactFileNumber(c) => {
600 target.level0_max_compact_file_number = *c;
601 }
602 MutableConfig::EnableEmergencyPicker(c) => {
603 target.enable_emergency_picker = *c;
604 }
605 MutableConfig::TombstoneReclaimRatio(c) => {
606 target.tombstone_reclaim_ratio = *c;
607 }
608 MutableConfig::CompressionAlgorithm(c) => {
609 let level = c.get_level();
610 let max_level = try_u32_max_level(target.max_level)?;
611 if level > max_level {
612 return Err(Error::CompactionGroup(format!(
613 "invalid compression_algorithm level {}, max_level is {}",
614 level, target.max_level
615 )));
616 }
617
618 let Some(algorithm) = target.compression_algorithm.get_mut(level as usize) else {
619 return Err(Error::CompactionGroup(format!(
620 "invalid compression_algorithm level {}, compression_algorithm len is {}",
621 level,
622 target.compression_algorithm.len()
623 )));
624 };
625 algorithm.clone_from(&c.compression_algorithm);
626 }
627 MutableConfig::ResetCompressionAlgorithm(reset) => {
628 if *reset {
629 target.compression_algorithm =
630 default_compaction_config::compression_algorithm_vec(try_u32_max_level(
631 target.max_level,
632 )?);
633 }
634 }
635 MutableConfig::MaxL0CompactLevelCount(c) => {
636 target.max_l0_compact_level_count = Some(*c);
637 }
638 MutableConfig::SstAllowedTrivialMoveMinSize(c) => {
639 target.sst_allowed_trivial_move_min_size = Some(*c);
640 }
641 MutableConfig::SplitWeightByVnode(c) => {
642 target.split_weight_by_vnode = *c;
643 }
644 MutableConfig::DisableAutoGroupScheduling(c) => {
645 target.disable_auto_group_scheduling = Some(*c);
646 }
647 MutableConfig::MaxOverlappingLevelSize(c) => {
648 target.max_overlapping_level_size = Some(*c);
649 }
650 MutableConfig::SstAllowedTrivialMoveMaxCount(c) => {
651 target.sst_allowed_trivial_move_max_count = Some(*c);
652 }
653 MutableConfig::EmergencyLevel0SstFileCount(c) => {
654 target.emergency_level0_sst_file_count = Some(*c);
655 }
656 MutableConfig::EmergencyLevel0SubLevelPartition(c) => {
657 target.emergency_level0_sub_level_partition = Some(*c);
658 }
659 MutableConfig::Level0StopWriteThresholdMaxSstCount(c) => {
660 target.level0_stop_write_threshold_max_sst_count = Some(*c);
661 }
662 MutableConfig::Level0StopWriteThresholdMaxSize(c) => {
663 target.level0_stop_write_threshold_max_size = Some(*c);
664 }
665 MutableConfig::EnableOptimizeL0IntervalSelection(c) => {
666 target.enable_optimize_l0_interval_selection = Some(*c);
667 }
668 #[expect(deprecated)]
669 MutableConfig::VnodeAlignedLevelSizeThreshold(_) => {
670 }
672 MutableConfig::MaxKvCountForXor16(c) => {
673 target.max_kv_count_for_xor16 = optional_non_sentinel_u64_config(*c);
674 }
675 MutableConfig::MaxVnodeKeyRangeBytes(c) => {
676 target.max_vnode_key_range_bytes = optional_positive_u64_config(*c);
677 }
678 MutableConfig::SstableFilterType(c) => {
679 parse_sstable_filter_type(&c.filter_type).map_err(Error::CompactionGroup)?;
680 if target.sstable_filter_type.is_empty() {
681 target.sstable_filter_type = default_compaction_config::sstable_filter_type();
682 target
683 .sstable_filter_type
684 .resize(target.max_level as usize + 1, "xor16".to_owned());
685 }
686 let idx = c.get_level() as usize;
687 let level_entry = target.sstable_filter_type.get_mut(idx).ok_or_else(|| {
688 Error::CompactionGroup(format!(
689 "sstable_filter_type level {} is out of range",
690 idx
691 ))
692 })?;
693 level_entry.clone_from(&c.filter_type);
694 }
695 MutableConfig::SstableFilterLayout(c) => {
696 parse_sstable_filter_layout(&c.layout).map_err(Error::CompactionGroup)?;
697 if target.sstable_filter_layout.is_empty() {
698 target.sstable_filter_layout =
699 default_compaction_config::sstable_filter_layout();
700 target
701 .sstable_filter_layout
702 .resize(target.max_level as usize + 1, "blocked".to_owned());
703 }
704 let idx = c.get_level() as usize;
705 let level_entry = target.sstable_filter_layout.get_mut(idx).ok_or_else(|| {
706 Error::CompactionGroup(format!(
707 "sstable_filter_layout level {} is out of range",
708 idx
709 ))
710 })?;
711 level_entry.clone_from(&c.layout);
712 }
713 }
714 }
715 Ok(())
716}
717
718fn optional_u64_config(value: u64) -> Option<u64> {
719 (value != u64::MIN && value != u64::MAX).then_some(value)
720}
721
722fn optional_non_sentinel_u64_config(value: u64) -> Option<u64> {
723 (value != u64::MAX).then_some(value)
724}
725
726fn optional_positive_u64_config(value: u64) -> Option<u64> {
727 optional_u64_config(value).filter(|value| *value > 0)
728}
729
730fn try_u32_max_level(max_level: u64) -> Result<u32> {
731 u32::try_from(max_level).map_err(|_| {
732 Error::CompactionGroup(format!(
733 "invalid max_level {}, expect <= {}",
734 max_level,
735 u32::MAX
736 ))
737 })
738}
739
740impl CompactionGroupTransaction<'_> {
741 pub fn try_create_compaction_groups(
743 &mut self,
744 compaction_group_ids: &[CompactionGroupId],
745 config: Arc<CompactionConfig>,
746 ) -> bool {
747 let mut trivial = true;
748 for id in compaction_group_ids {
749 if self.contains_key(id) {
750 continue;
751 }
752 let new_entry = CompactionGroup::new(*id, config.as_ref().clone());
753 self.insert(*id, new_entry);
754
755 trivial = false;
756 }
757
758 !trivial
759 }
760
761 pub fn create_compaction_groups(
762 &mut self,
763 compaction_group_id: CompactionGroupId,
764 config: Arc<CompactionConfig>,
765 ) {
766 self.try_create_compaction_groups(&[compaction_group_id], config);
767 }
768
769 pub(crate) fn try_get_compaction_group_config(
771 &self,
772 compaction_group_id: CompactionGroupId,
773 ) -> Option<&CompactionGroup> {
774 self.get(&compaction_group_id)
775 }
776
777 pub fn purge(&mut self, existing_groups: HashSet<CompactionGroupId>) {
779 let stale_group = self
780 .tree_ref()
781 .keys()
782 .cloned()
783 .filter(|k| !existing_groups.contains(k))
784 .collect_vec();
785 if stale_group.is_empty() {
786 return;
787 }
788 for group in stale_group {
789 self.remove(group);
790 }
791 }
792
793 pub(crate) fn update_compaction_config(
794 &mut self,
795 compaction_group_ids: &[CompactionGroupId],
796 config_to_update: &[MutableConfig],
797 ) -> Result<HashMap<CompactionGroupId, CompactionGroup>> {
798 let mut results = HashMap::default();
799 for compaction_group_id in compaction_group_ids.iter().unique() {
800 let group = self.get(compaction_group_id).ok_or_else(|| {
801 Error::CompactionGroup(format!("invalid group {}", *compaction_group_id))
802 })?;
803 let mut config = group.compaction_config.as_ref().clone();
804 update_compaction_config(&mut config, config_to_update)?;
805 if let Err(reason) = validate_compaction_config(&config) {
806 return Err(Error::CompactionGroup(reason));
807 }
808 let mut new_group = group.clone();
809 new_group.compaction_config = Arc::new(config);
810 self.insert(*compaction_group_id, new_group.clone());
811 results.insert(new_group.group_id(), new_group);
812 }
813
814 Ok(results)
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use std::collections::{BTreeMap, HashSet};
821 use std::sync::Arc;
822
823 use itertools::Itertools;
824 use risingwave_common::id::JobId;
825 use risingwave_hummock_sdk::CompactionGroupId;
826 use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig;
827 use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::{
828 CompressionAlgorithm, SstableFilterLayout, SstableFilterType,
829 };
830
831 use crate::controller::SqlMetaStore;
832 use crate::hummock::commit_multi_var;
833 use crate::hummock::compaction::compaction_config::CompactionConfigBuilder;
834 use crate::hummock::error::Result;
835 use crate::hummock::manager::compaction_group_manager::CompactionGroupManager;
836 use crate::hummock::test_utils::setup_compute_env;
837 use crate::model::{Fragment, StreamJobFragments};
838
839 #[test]
840 fn test_update_compaction_config_filter_type_layout_backward_compat() {
841 let mut config = CompactionConfigBuilder::new().build();
842 config.sstable_filter_type.clear();
843 config.sstable_filter_layout.clear();
844
845 super::update_compaction_config(
846 &mut config,
847 &[MutableConfig::SstableFilterType(SstableFilterType {
848 level: 0,
849 filter_type: "xor8".to_owned(),
850 })],
851 )
852 .unwrap();
853 assert_eq!(
854 config.sstable_filter_type.len(),
855 config.max_level as usize + 1
856 );
857 assert_eq!(config.sstable_filter_type[0], "xor8");
858 assert_eq!(config.sstable_filter_type[5], "xor8");
859
860 super::update_compaction_config(
861 &mut config,
862 &[MutableConfig::SstableFilterType(SstableFilterType {
863 level: 1,
864 filter_type: "none".to_owned(),
865 })],
866 )
867 .unwrap();
868 assert_eq!(config.sstable_filter_type[1], "none");
869
870 super::update_compaction_config(
871 &mut config,
872 &[MutableConfig::SstableFilterLayout(SstableFilterLayout {
873 level: 1,
874 layout: "plain".to_owned(),
875 })],
876 )
877 .unwrap();
878 assert_eq!(
879 config.sstable_filter_layout.len(),
880 config.max_level as usize + 1
881 );
882 assert_eq!(config.sstable_filter_layout[1], "plain");
883 assert_eq!(config.sstable_filter_layout[2], "blocked");
884 }
885
886 #[test]
887 fn test_update_compaction_config_rejects_out_of_range_level() {
888 let mut config = CompactionConfigBuilder::new().build();
889 let oob = config.max_level as u32 + 1;
890
891 assert!(
892 super::update_compaction_config(
893 &mut config,
894 &[MutableConfig::SstableFilterType(SstableFilterType {
895 level: oob,
896 filter_type: "xor8".to_owned(),
897 })],
898 )
899 .is_err()
900 );
901
902 assert!(
903 super::update_compaction_config(
904 &mut config,
905 &[MutableConfig::SstableFilterLayout(SstableFilterLayout {
906 level: oob,
907 layout: "plain".to_owned(),
908 })],
909 )
910 .is_err()
911 );
912
913 assert!(
914 super::update_compaction_config(
915 &mut config,
916 &[MutableConfig::CompressionAlgorithm(CompressionAlgorithm {
917 level: oob,
918 compression_algorithm: "Zstd".to_owned(),
919 })],
920 )
921 .is_err()
922 );
923 }
924
925 #[test]
926 fn test_update_compaction_config_rejects_invalid_filter_metadata() {
927 let mut config = CompactionConfigBuilder::new().build();
928
929 assert!(
930 super::update_compaction_config(
931 &mut config,
932 &[MutableConfig::SstableFilterType(SstableFilterType {
933 level: 0,
934 filter_type: "unknown".to_owned(),
935 })],
936 )
937 .is_err()
938 );
939
940 let mut config = CompactionConfigBuilder::new().build();
941
942 assert!(
943 super::update_compaction_config(
944 &mut config,
945 &[MutableConfig::SstableFilterLayout(SstableFilterLayout {
946 level: 0,
947 layout: "unknown".to_owned(),
948 })],
949 )
950 .is_err()
951 );
952 }
953
954 #[test]
955 fn test_reset_compression_algorithm_false_is_noop() {
956 let mut config = CompactionConfigBuilder::new().build();
957 config.compression_algorithm[3] = "Zstd".to_owned();
958
959 super::update_compaction_config(
960 &mut config,
961 &[MutableConfig::ResetCompressionAlgorithm(false)],
962 )
963 .unwrap();
964
965 assert_eq!(config.compression_algorithm[3], "Zstd");
966 }
967
968 #[tokio::test]
969 async fn test_inner() {
970 let (env, ..) = setup_compute_env(8080).await;
971 let mut inner = CompactionGroupManager::new(&env).await.unwrap();
972 assert_eq!(inner.compaction_groups.len(), 2);
973
974 async fn update_compaction_config(
975 meta: &SqlMetaStore,
976 inner: &mut CompactionGroupManager,
977 cg_ids: &[impl Into<CompactionGroupId> + Copy],
978 config_to_update: &[MutableConfig],
979 ) -> Result<()> {
980 let cg_ids = cg_ids.iter().copied().map_into().collect_vec();
981 let mut compaction_groups_txn = inner.start_compaction_groups_txn();
982 compaction_groups_txn.update_compaction_config(&cg_ids, config_to_update)?;
983 commit_multi_var!(meta, compaction_groups_txn)
984 }
985
986 async fn insert_compaction_group_configs(
987 meta: &SqlMetaStore,
988 inner: &mut CompactionGroupManager,
989 cg_ids: &[u64],
990 ) {
991 let default_config = inner.default_compaction_config();
992 let mut compaction_groups_txn = inner.start_compaction_groups_txn();
993 if compaction_groups_txn.try_create_compaction_groups(
994 &cg_ids.iter().copied().map_into().collect_vec(),
995 default_config,
996 ) {
997 commit_multi_var!(meta, compaction_groups_txn).unwrap();
998 }
999 }
1000
1001 async fn insert_compaction_group_config_with_max_level(
1002 meta: &SqlMetaStore,
1003 inner: &mut CompactionGroupManager,
1004 cg_id: u64,
1005 max_level: u64,
1006 ) {
1007 let mut config = inner.default_compaction_config().as_ref().clone();
1008 config.max_level = max_level;
1009 config.compression_algorithm =
1010 super::default_compaction_config::compression_algorithm_vec(
1011 super::try_u32_max_level(max_level).expect("max_level should fit u32 in test"),
1012 );
1013 let mut compaction_groups_txn = inner.start_compaction_groups_txn();
1014 compaction_groups_txn.create_compaction_groups(cg_id.into(), Arc::new(config));
1015 commit_multi_var!(meta, compaction_groups_txn).unwrap();
1016 }
1017
1018 update_compaction_config(env.meta_store_ref(), &mut inner, &[100, 200], &[])
1019 .await
1020 .unwrap_err();
1021 insert_compaction_group_configs(env.meta_store_ref(), &mut inner, &[100, 200]).await;
1022 assert_eq!(inner.compaction_groups.len(), 4);
1023 let mut inner = CompactionGroupManager::new(&env).await.unwrap();
1024 assert_eq!(inner.compaction_groups.len(), 4);
1025
1026 update_compaction_config(
1027 env.meta_store_ref(),
1028 &mut inner,
1029 &[100, 200],
1030 &[MutableConfig::MaxSubCompaction(123)],
1031 )
1032 .await
1033 .unwrap();
1034 assert_eq!(inner.compaction_groups.len(), 4);
1035 assert_eq!(
1036 inner
1037 .try_get_compaction_group_config(100)
1038 .unwrap()
1039 .compaction_config
1040 .max_sub_compaction,
1041 123
1042 );
1043 assert_eq!(
1044 inner
1045 .try_get_compaction_group_config(200)
1046 .unwrap()
1047 .compaction_config
1048 .max_sub_compaction,
1049 123
1050 );
1051
1052 insert_compaction_group_config_with_max_level(env.meta_store_ref(), &mut inner, 300, 4)
1053 .await;
1054 update_compaction_config(
1055 env.meta_store_ref(),
1056 &mut inner,
1057 &[300],
1058 &[MutableConfig::ResetCompressionAlgorithm(true)],
1059 )
1060 .await
1061 .unwrap();
1062 assert_eq!(
1063 inner
1064 .try_get_compaction_group_config(300)
1065 .unwrap()
1066 .compaction_config
1067 .compression_algorithm,
1068 super::default_compaction_config::compression_algorithm_vec(4)
1069 );
1070 let err = update_compaction_config(
1071 env.meta_store_ref(),
1072 &mut inner,
1073 &[300],
1074 &[MutableConfig::CompressionAlgorithm(CompressionAlgorithm {
1075 level: 6,
1076 compression_algorithm: "Zstd".to_owned(),
1077 })],
1078 )
1079 .await
1080 .unwrap_err();
1081 assert!(
1082 err.to_string()
1083 .contains("invalid compression_algorithm level 6")
1084 );
1085
1086 update_compaction_config(
1087 env.meta_store_ref(),
1088 &mut inner,
1089 &[100],
1090 &[MutableConfig::MaxKvCountForXor16(0)],
1091 )
1092 .await
1093 .unwrap();
1094 assert_eq!(
1095 inner
1096 .try_get_compaction_group_config(100)
1097 .unwrap()
1098 .compaction_config
1099 .max_kv_count_for_xor16,
1100 Some(0)
1101 );
1102 update_compaction_config(
1103 env.meta_store_ref(),
1104 &mut inner,
1105 &[100],
1106 &[MutableConfig::MaxKvCountForXor16(1024)],
1107 )
1108 .await
1109 .unwrap();
1110 assert_eq!(
1111 inner
1112 .try_get_compaction_group_config(100)
1113 .unwrap()
1114 .compaction_config
1115 .max_kv_count_for_xor16,
1116 Some(1024)
1117 );
1118 update_compaction_config(
1119 env.meta_store_ref(),
1120 &mut inner,
1121 &[100],
1122 &[MutableConfig::MaxKvCountForXor16(u64::MAX)],
1123 )
1124 .await
1125 .unwrap();
1126 assert_eq!(
1127 inner
1128 .try_get_compaction_group_config(100)
1129 .unwrap()
1130 .compaction_config
1131 .max_kv_count_for_xor16,
1132 None
1133 );
1134
1135 update_compaction_config(
1136 env.meta_store_ref(),
1137 &mut inner,
1138 &[100],
1139 &[MutableConfig::MaxVnodeKeyRangeBytes(0)],
1140 )
1141 .await
1142 .unwrap();
1143 assert_eq!(
1144 inner
1145 .try_get_compaction_group_config(100)
1146 .unwrap()
1147 .compaction_config
1148 .max_vnode_key_range_bytes,
1149 None
1150 );
1151 update_compaction_config(
1152 env.meta_store_ref(),
1153 &mut inner,
1154 &[100],
1155 &[MutableConfig::MaxVnodeKeyRangeBytes(1024)],
1156 )
1157 .await
1158 .unwrap();
1159 assert_eq!(
1160 inner
1161 .try_get_compaction_group_config(100)
1162 .unwrap()
1163 .compaction_config
1164 .max_vnode_key_range_bytes,
1165 Some(1024)
1166 );
1167 }
1168
1169 #[tokio::test]
1170 async fn test_manager() {
1171 let (_, compaction_group_manager, ..) = setup_compute_env(8080).await;
1172 let table_fragment_1 = StreamJobFragments::for_test(
1173 JobId::new(10),
1174 BTreeMap::from([(
1175 1.into(),
1176 Fragment {
1177 fragment_id: 1.into(),
1178 state_table_ids: vec![10.into(), 11.into(), 12.into(), 13.into()],
1179 ..Default::default()
1180 },
1181 )]),
1182 );
1183 let table_fragment_2 = StreamJobFragments::for_test(
1184 JobId::new(20),
1185 BTreeMap::from([(
1186 2.into(),
1187 Fragment {
1188 fragment_id: 2.into(),
1189 state_table_ids: vec![20.into(), 21.into(), 22.into(), 23.into()],
1190 ..Default::default()
1191 },
1192 )]),
1193 );
1194
1195 let registered_number = || async {
1197 compaction_group_manager
1198 .list_compaction_group()
1199 .await
1200 .iter()
1201 .map(|cg| cg.member_table_ids.len())
1202 .sum::<usize>()
1203 };
1204 let group_number =
1205 || async { compaction_group_manager.list_compaction_group().await.len() };
1206 assert_eq!(registered_number().await, 0);
1207
1208 compaction_group_manager
1209 .register_table_fragments(
1210 Some(table_fragment_1.stream_job_id().as_mv_table_id()),
1211 table_fragment_1
1212 .internal_table_ids()
1213 .into_iter()
1214 .map_into()
1215 .collect(),
1216 )
1217 .await
1218 .unwrap();
1219 assert_eq!(registered_number().await, 4);
1220 compaction_group_manager
1221 .register_table_fragments(
1222 Some(table_fragment_2.stream_job_id().as_mv_table_id()),
1223 table_fragment_2
1224 .internal_table_ids()
1225 .into_iter()
1226 .map_into()
1227 .collect(),
1228 )
1229 .await
1230 .unwrap();
1231 assert_eq!(registered_number().await, 8);
1232
1233 compaction_group_manager
1235 .unregister_table_fragments_vec(std::slice::from_ref(&table_fragment_1))
1236 .await;
1237 assert_eq!(registered_number().await, 4);
1238
1239 compaction_group_manager
1241 .purge(&table_fragment_2.all_table_ids().collect())
1242 .await
1243 .unwrap();
1244 assert_eq!(registered_number().await, 4);
1245 compaction_group_manager
1246 .purge(&HashSet::new())
1247 .await
1248 .unwrap();
1249 assert_eq!(registered_number().await, 0);
1250
1251 assert_eq!(group_number().await, 2);
1252
1253 compaction_group_manager
1254 .register_table_fragments(
1255 Some(table_fragment_1.stream_job_id().as_mv_table_id()),
1256 table_fragment_1
1257 .internal_table_ids()
1258 .into_iter()
1259 .map_into()
1260 .collect(),
1261 )
1262 .await
1263 .unwrap();
1264 assert_eq!(registered_number().await, 4);
1265 assert_eq!(group_number().await, 2);
1266
1267 compaction_group_manager
1268 .unregister_table_fragments_vec(&[table_fragment_1])
1269 .await;
1270 assert_eq!(registered_number().await, 0);
1271 assert_eq!(group_number().await, 2);
1272 }
1273}