risingwave_meta/hummock/manager/compaction/
compaction_group_schedule.rs1use std::collections::HashSet;
19
20use itertools::Itertools;
21use risingwave_common::catalog::TableId;
22use thiserror_ext::AsReport;
23
24use super::CompactionGroupStatistic;
25use crate::hummock::error::Result;
26use crate::hummock::manager::HummockManager;
27use crate::hummock::table_write_throughput_statistic::TableWriteThroughputStatisticManager;
28
29mod merge_policy;
30mod normalize;
31mod topology;
32
33#[cfg(test)]
34mod tests;
35
36const TRACE_TARGET: &str = module_path!();
38
39impl HummockManager {
40 pub async fn try_split_compaction_group(
42 &self,
43 table_write_throughput_statistic_manager: &TableWriteThroughputStatisticManager,
44 group: CompactionGroupStatistic,
45 ) {
46 if group
47 .compaction_group_config
48 .compaction_config
49 .disable_auto_group_scheduling
50 .unwrap_or(false)
51 {
52 return;
53 }
54 let mut hot_tables = group
55 .table_statistic
56 .keys()
57 .copied()
58 .filter(|&table_id| {
59 table_write_throughput_statistic_manager
60 .latest_table_throughput(table_id)
61 .is_some_and(|rate| rate > self.env.opts.table_high_write_throughput_threshold)
62 })
63 .peekable();
64 let group_max_size = (group.compaction_group_config.max_estimated_group_size() as f64
65 * self.env.opts.split_group_size_ratio) as u64;
66 if hot_tables.peek().is_none()
67 && (group.table_statistic.len() < 2 || group.group_size <= group_max_size)
68 {
69 return;
70 }
71
72 for table_id in hot_tables {
73 self.try_move_high_throughput_table_to_dedicated_cg(table_id)
74 .await;
75 }
76
77 let table_ids = group.table_statistic.keys().copied().collect_vec();
80 for current in self
81 .calculate_compaction_group_statistic_for_tables(&table_ids)
82 .await
83 {
84 if !current
85 .compaction_group_config
86 .compaction_config
87 .disable_auto_group_scheduling
88 .unwrap_or(false)
89 {
90 self.try_split_huge_compaction_group(current).await;
91 }
92 }
93 }
94
95 async fn try_move_high_throughput_table_to_dedicated_cg(&self, table_id: TableId) {
97 let parent_group_id = self
100 .on_current_version(|version| {
101 let group_id = version
102 .state_table_info
103 .info()
104 .get(&table_id)?
105 .compaction_group_id;
106 (version
107 .state_table_info
108 .compaction_group_member_table_ids(group_id)
109 .len()
110 > 1)
111 .then_some(group_id)
112 })
113 .await;
114 let Some(parent_group_id) = parent_group_id else {
115 return;
117 };
118
119 let ret = self
120 .move_state_tables_to_dedicated_compaction_group_impl(
121 parent_group_id,
122 &[table_id],
123 Some(self.env.opts.partition_vnode_count),
124 true,
125 )
126 .await;
127 match ret {
128 Ok(split_result) => {
129 tracing::info!(
130 "split state table [{}] from group-{} success table_vnode_partition_count {:?} split result {:?}",
131 table_id,
132 parent_group_id,
133 self.env.opts.partition_vnode_count,
134 split_result
135 );
136 }
137 Err(e) => {
138 tracing::info!(
139 error = %e.as_report(),
140 "failed to split state table [{}] from group-{}",
141 table_id,
142 parent_group_id,
143 )
144 }
145 }
146 }
147
148 pub async fn try_split_huge_compaction_group(&self, group: CompactionGroupStatistic) {
149 let group_max_size = (group.compaction_group_config.max_estimated_group_size() as f64
150 * self.env.opts.split_group_size_ratio) as u64;
151 let is_huge_hybrid_group =
152 group.group_size > group_max_size && group.table_statistic.len() > 1; if is_huge_hybrid_group {
154 let mut accumulated_size = 0;
155 let mut table_ids = Vec::default();
156 for (table_id, table_size) in &group.table_statistic {
157 accumulated_size += table_size;
158 table_ids.push(*table_id);
159 let remaining_size = group.group_size.saturating_sub(accumulated_size);
162 if accumulated_size > group_max_size / 2
163 && remaining_size > 0
164 && table_ids.len() < group.table_statistic.len()
165 {
166 let ret = self
167 .move_state_tables_to_dedicated_compaction_group_impl(
168 group.group_id,
169 &table_ids,
170 None,
171 true,
172 )
173 .await;
174 match ret {
175 Ok(split_result) => {
176 tracing::info!(
177 "split_huge_compaction_group success {:?}",
178 split_result
179 );
180 self.metrics
181 .split_compaction_group_count
182 .with_label_values(&[&group.group_id.to_string()])
183 .inc();
184 return;
185 }
186 Err(e) => {
187 tracing::error!(
188 error = %e.as_report(),
189 "failed to split_huge_compaction_group table {:?} from group-{}",
190 table_ids,
191 group.group_id
192 );
193
194 return;
195 }
196 }
197 }
198 }
199 }
200 }
201
202 pub async fn try_merge_compaction_group(
203 &self,
204 table_write_throughput_statistic_manager: &TableWriteThroughputStatisticManager,
205 group: &CompactionGroupStatistic,
206 next_group: &CompactionGroupStatistic,
207 created_tables: &HashSet<TableId>,
208 ) -> Result<CompactionGroupStatistic> {
209 merge_policy::validate_group_merge(
210 group,
211 next_group,
212 created_tables,
213 table_write_throughput_statistic_manager,
214 &self.env.opts,
215 )?;
216
217 let result = self
218 .merge_compaction_group_impl(
219 group.group_id,
220 next_group.group_id,
221 Some(created_tables),
222 true,
223 )
224 .await;
225
226 match &result {
227 Ok(survivor) => {
228 tracing::info!(
229 "merge groups {} and {} into group-{}",
230 group.group_id,
231 next_group.group_id,
232 survivor.group_id,
233 );
234 }
235 Err(e) => {
236 tracing::info!(
237 error = %e.as_report(),
238 "failed to merge group-{} group-{}",
239 next_group.group_id,
240 group.group_id,
241 );
242 }
243 }
244
245 result
246 }
247}