risingwave_meta/hummock/compaction/
compaction_config.rs1use risingwave_common::config::CompactionConfig as CompactionConfigOpt;
16use risingwave_common::config::meta::default::compaction_config;
17use risingwave_hummock_sdk::filter_utils::{
18 parse_sstable_filter_layout, parse_sstable_filter_type,
19};
20use risingwave_pb::hummock::CompactionConfig;
21use risingwave_pb::hummock::compaction_config::CompactionMode;
22
23pub struct CompactionConfigBuilder {
24 config: CompactionConfig,
25}
26
27impl CompactionConfigBuilder {
28 pub fn new() -> Self {
29 #[expect(deprecated)]
30 Self {
31 config: CompactionConfig {
32 max_bytes_for_level_base: compaction_config::max_bytes_for_level_base(),
33 max_bytes_for_level_multiplier: compaction_config::max_bytes_for_level_multiplier(),
34 max_level: compaction_config::max_level() as u64,
35 max_compaction_bytes: compaction_config::max_compaction_bytes(),
36 sub_level_max_compaction_bytes: compaction_config::sub_level_max_compaction_bytes(),
37 level0_tier_compact_file_number: compaction_config::level0_tier_compact_file_number(
38 ),
39 target_file_size_base: compaction_config::target_file_size_base(),
40 compaction_mode: CompactionMode::Range as i32,
41 compression_algorithm: compaction_config::compression_algorithm_vec(
42 compaction_config::max_level(),
43 ),
44 sstable_filter_type: compaction_config::sstable_filter_type(),
45 sstable_filter_layout: compaction_config::sstable_filter_layout(),
46 compaction_filter_mask: compaction_config::compaction_filter_mask(),
47 max_sub_compaction: compaction_config::max_sub_compaction(),
48 max_space_reclaim_bytes: compaction_config::max_space_reclaim_bytes(),
49 split_by_state_table: false,
50 split_weight_by_vnode: 0,
51 level0_stop_write_threshold_sub_level_number:
52 compaction_config::level0_stop_write_threshold_sub_level_number(),
53 level0_max_compact_file_number: compaction_config::level0_max_compact_file_number(),
58 level0_sub_level_compact_level_count:
59 compaction_config::level0_sub_level_compact_level_count(),
60 level0_overlapping_sub_level_compact_level_count:
61 compaction_config::level0_overlapping_sub_level_compact_level_count(),
62 tombstone_reclaim_ratio: compaction_config::tombstone_reclaim_ratio(),
63 enable_emergency_picker: compaction_config::enable_emergency_picker(),
64 max_l0_compact_level_count: Some(compaction_config::max_l0_compact_level_count()),
65 sst_allowed_trivial_move_min_size: Some(
66 compaction_config::sst_allowed_trivial_move_min_size(),
67 ),
68 disable_auto_group_scheduling: Some(
69 compaction_config::disable_auto_group_scheduling(),
70 ),
71 max_overlapping_level_size: Some(compaction_config::max_overlapping_level_size()),
72 sst_allowed_trivial_move_max_count: Some(
73 compaction_config::sst_allowed_trivial_move_max_count(),
74 ),
75 emergency_level0_sst_file_count: Some(
76 compaction_config::emergency_level0_sst_file_count(),
77 ),
78 emergency_level0_sub_level_partition: Some(
79 compaction_config::emergency_level0_sub_level_partition(),
80 ),
81 level0_stop_write_threshold_max_sst_count: Some(
82 compaction_config::level0_stop_write_threshold_max_sst_count(),
83 ),
84 level0_stop_write_threshold_max_size: Some(
85 compaction_config::level0_stop_write_threshold_max_size(),
86 ),
87 enable_optimize_l0_interval_selection: Some(
88 compaction_config::enable_optimize_l0_interval_selection(),
89 ),
90 vnode_aligned_level_size_threshold: None,
91 max_kv_count_for_xor16: compaction_config::blocked_xor_filter_kv_count_threshold(),
92 max_vnode_key_range_bytes: compaction_config::max_vnode_key_range_bytes(),
93 },
94 }
95 }
96
97 pub fn with_config(config: CompactionConfig) -> Self {
98 Self { config }
99 }
100
101 pub fn with_opt(opt: &CompactionConfigOpt) -> Self {
102 Self::new()
103 .max_bytes_for_level_base(opt.max_bytes_for_level_base)
104 .max_bytes_for_level_multiplier(opt.max_bytes_for_level_multiplier)
105 .max_compaction_bytes(opt.max_compaction_bytes)
106 .sub_level_max_compaction_bytes(opt.sub_level_max_compaction_bytes)
107 .level0_tier_compact_file_number(opt.level0_tier_compact_file_number)
108 .target_file_size_base(opt.target_file_size_base)
109 .compaction_filter_mask(opt.compaction_filter_mask)
110 .max_sub_compaction(opt.max_sub_compaction)
111 .level0_stop_write_threshold_sub_level_number(
112 opt.level0_stop_write_threshold_sub_level_number,
113 )
114 .level0_sub_level_compact_level_count(opt.level0_sub_level_compact_level_count)
115 .level0_overlapping_sub_level_compact_level_count(
116 opt.level0_overlapping_sub_level_compact_level_count,
117 )
118 .max_space_reclaim_bytes(opt.max_space_reclaim_bytes)
119 .level0_max_compact_file_number(opt.level0_max_compact_file_number)
120 .tombstone_reclaim_ratio(opt.tombstone_reclaim_ratio)
121 .enable_emergency_picker(opt.enable_emergency_picker)
122 .max_level(opt.max_level as u64)
123 .sst_allowed_trivial_move_min_size(Some(opt.sst_allowed_trivial_move_min_size))
124 .sst_allowed_trivial_move_max_count(Some(opt.sst_allowed_trivial_move_max_count))
125 .max_l0_compact_level_count(Some(opt.max_l0_compact_level_count))
126 .disable_auto_group_scheduling(Some(opt.disable_auto_group_scheduling))
127 .max_overlapping_level_size(Some(opt.max_overlapping_level_size))
128 .emergency_level0_sst_file_count(Some(opt.emergency_level0_sst_file_count))
129 .emergency_level0_sub_level_partition(Some(opt.emergency_level0_sub_level_partition))
130 .level0_stop_write_threshold_max_sst_count(Some(
131 opt.level0_stop_write_threshold_max_sst_count,
132 ))
133 .level0_stop_write_threshold_max_size(Some(opt.level0_stop_write_threshold_max_size))
134 .enable_optimize_l0_interval_selection(Some(opt.enable_optimize_l0_interval_selection))
135 .max_kv_count_for_xor16(opt.blocked_xor_filter_kv_count_threshold)
136 .max_vnode_key_range_bytes(opt.max_vnode_key_range_bytes)
137 .sstable_filter_type(opt.sstable_filter_type.clone())
138 .sstable_filter_layout(opt.sstable_filter_layout.clone())
139 }
140
141 pub fn build(self) -> CompactionConfig {
142 let mut config = self.config;
143 if let Err(reason) = validate_compaction_config(&config) {
144 tracing::warn!(
147 "Bad compaction config: {}. Falling back to default sstable filter type/layout.",
148 reason
149 );
150 config.sstable_filter_type.clear();
151 config.sstable_filter_layout.clear();
152 }
153 config
154 }
155}
156
157pub fn validate_compaction_config(config: &CompactionConfig) -> Result<(), String> {
160 let sub_level_number_threshold_min = 1;
161 if config.level0_stop_write_threshold_sub_level_number < sub_level_number_threshold_min {
162 return Err(format!(
163 "{} is too small for level0_stop_write_threshold_sub_level_number, expect >= {}",
164 config.level0_stop_write_threshold_sub_level_number, sub_level_number_threshold_min
165 ));
166 }
167 if !config.sstable_filter_type.is_empty() {
168 if config.sstable_filter_type.len() < config.max_level as usize + 1 {
169 return Err(format!(
170 "sstable_filter_type must provide at least {} entries for max_level {}",
171 config.max_level + 1,
172 config.max_level
173 ));
174 }
175 for filter_type in &config.sstable_filter_type {
176 parse_sstable_filter_type(filter_type)?;
177 }
178 }
179
180 if !config.sstable_filter_layout.is_empty() {
181 if config.sstable_filter_layout.len() < config.max_level as usize + 1 {
182 return Err(format!(
183 "sstable_filter_layout must provide at least {} entries for max_level {}",
184 config.max_level + 1,
185 config.max_level
186 ));
187 }
188 for layout in &config.sstable_filter_layout {
189 parse_sstable_filter_layout(layout)?;
190 }
191 }
192 Ok(())
193}
194
195impl Default for CompactionConfigBuilder {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201macro_rules! builder_field {
202 ($( $name:ident: $type:ty ),* ,) => {
203 impl CompactionConfigBuilder {
204 $(
205 pub fn $name(mut self, v:$type) -> Self {
206 self.config.$name = v;
207 self
208 }
209 )*
210 }
211 }
212}
213
214builder_field! {
215 max_bytes_for_level_base: u64,
216 max_bytes_for_level_multiplier: u64,
217 max_level: u64,
218 max_compaction_bytes: u64,
219 sub_level_max_compaction_bytes: u64,
220 level0_tier_compact_file_number: u64,
221 compaction_mode: i32,
222 compression_algorithm: Vec<String>,
223 sstable_filter_type: Vec<String>,
224 sstable_filter_layout: Vec<String>,
225 compaction_filter_mask: u32,
226 target_file_size_base: u64,
227 max_sub_compaction: u32,
228 max_space_reclaim_bytes: u64,
229 level0_stop_write_threshold_sub_level_number: u64,
230 level0_max_compact_file_number: u64,
231 level0_sub_level_compact_level_count: u32,
232 level0_overlapping_sub_level_compact_level_count: u32,
233 tombstone_reclaim_ratio: u32,
234 enable_emergency_picker: bool,
235 sst_allowed_trivial_move_min_size: Option<u64>,
236 sst_allowed_trivial_move_max_count: Option<u32>,
237 max_l0_compact_level_count: Option<u32>,
238 disable_auto_group_scheduling: Option<bool>,
239 max_overlapping_level_size: Option<u64>,
240 emergency_level0_sst_file_count: Option<u32>,
241 emergency_level0_sub_level_partition: Option<u32>,
242 level0_stop_write_threshold_max_sst_count: Option<u32>,
243 level0_stop_write_threshold_max_size: Option<u64>,
244 enable_optimize_l0_interval_selection: Option<bool>,
245 max_kv_count_for_xor16: Option<u64>,
246 max_vnode_key_range_bytes: Option<u64>,
247}
248
249#[cfg(test)]
250mod tests {
251 use risingwave_common::config::RwConfig;
252
253 use super::{CompactionConfigBuilder, validate_compaction_config};
254
255 #[test]
256 fn test_compaction_config_from_toml() {
257 let config: RwConfig = toml::from_str(
258 r#"
259 [meta.compaction_config]
260 level0_max_compact_file_number = 20
261 max_l0_compact_level_count = 10
262 max_compaction_bytes = 1073741824
263 "#,
264 )
265 .unwrap();
266 let config = CompactionConfigBuilder::with_opt(&config.meta.compaction_config).build();
267
268 assert_eq!(config.level0_max_compact_file_number, 20);
269 assert_eq!(config.max_l0_compact_level_count, Some(10));
270 assert_eq!(config.max_compaction_bytes, 1_073_741_824);
271 }
272
273 #[test]
274 fn test_compaction_config_flags_from_toml() {
275 let config: RwConfig = toml::from_str(
276 r#"
277 [meta.compaction_config]
278 enable_emergency_picker = false
279 disable_auto_group_scheduling = true
280 "#,
281 )
282 .unwrap();
283 let config = CompactionConfigBuilder::with_opt(&config.meta.compaction_config).build();
284
285 assert!(!config.enable_emergency_picker);
286 assert_eq!(config.disable_auto_group_scheduling, Some(true));
287 }
288
289 #[test]
290 fn test_compaction_config_defaults_from_toml() {
291 let config: RwConfig = toml::from_str("").unwrap();
292 let config = CompactionConfigBuilder::with_opt(&config.meta.compaction_config).build();
293
294 assert_eq!(config, CompactionConfigBuilder::new().build());
295 }
296
297 #[test]
298 fn test_validate_compaction_config_accepts_missing_filter_config() {
299 let mut config = CompactionConfigBuilder::new().build();
300 config.sstable_filter_type.clear();
301 config.sstable_filter_layout.clear();
302 assert!(validate_compaction_config(&config).is_ok());
303 }
304
305 #[test]
306 fn test_validate_compaction_config_rejects_invalid_filter_config() {
307 let mut config = CompactionConfigBuilder::new().build();
308 config.sstable_filter_type = vec!["xor16".to_owned()];
309 assert!(validate_compaction_config(&config).is_err());
310
311 let mut config = CompactionConfigBuilder::new().build();
312 config.sstable_filter_layout = vec!["auto".to_owned()];
313 assert!(validate_compaction_config(&config).is_err());
314
315 let mut config = CompactionConfigBuilder::new().build();
316 config.sstable_filter_layout = vec!["auto".to_owned(); config.max_level as usize + 1];
317 config.sstable_filter_layout[0] = "unknown".to_owned();
318 assert!(validate_compaction_config(&config).is_err());
319 }
320
321 #[test]
322 fn test_validate_compaction_config_accepts_none_filter_type() {
323 let mut config = CompactionConfigBuilder::new().build();
324 config.sstable_filter_type = vec!["none".to_owned(); config.max_level as usize + 1];
325 assert!(validate_compaction_config(&config).is_ok());
326 }
327}