Skip to main content

risingwave_meta/hummock/compaction/picker/
base_level_compaction_picker.rs

1// Copyright 2023 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 std::cell::RefCell;
16use std::sync::Arc;
17
18use itertools::Itertools;
19use risingwave_common::config::meta::default::compaction_config;
20use risingwave_hummock_sdk::level::{InputLevel, Level, Levels, OverlappingLevel};
21use risingwave_pb::hummock::{CompactionConfig, LevelType};
22
23use super::non_overlap_sub_level_picker::NonOverlapSubLevelPicker;
24use super::{
25    CompactionInput, CompactionPicker, CompactionTaskValidator, LocalPickerStatistic,
26    ValidationRuleType,
27};
28use crate::hummock::compaction::picker::TrivialMovePicker;
29use crate::hummock::compaction::{CompactionDeveloperConfig, create_overlap_strategy};
30use crate::hummock::level_handler::LevelHandler;
31
32std::thread_local! {
33    static LOG_COUNTER: RefCell<usize> = const { RefCell::new(0) };
34}
35
36pub struct LevelCompactionPicker {
37    target_level: usize,
38    config: Arc<CompactionConfig>,
39    compaction_task_validator: Arc<CompactionTaskValidator>,
40    developer_config: Arc<CompactionDeveloperConfig>,
41}
42
43impl CompactionPicker for LevelCompactionPicker {
44    fn pick_compaction(
45        &mut self,
46        levels: &Levels,
47        level_handlers: &[LevelHandler],
48        stats: &mut LocalPickerStatistic,
49    ) -> Option<CompactionInput> {
50        let l0 = &levels.l0;
51        if l0.sub_levels.is_empty() {
52            return None;
53        }
54        if l0.sub_levels[0].level_type != LevelType::Nonoverlapping
55            && l0.sub_levels[0].table_infos.len() > 1
56        {
57            stats.skip_by_overlapping += 1;
58            return None;
59        }
60
61        let is_l0_pending_compact =
62            level_handlers[0].is_level_all_pending_compact(&l0.sub_levels[0]);
63
64        if is_l0_pending_compact {
65            stats.skip_by_pending_files += 1;
66            return None;
67        }
68
69        if let Some(mut ret) = self.pick_base_trivial_move(
70            l0,
71            levels.get_level(self.target_level),
72            level_handlers,
73            stats,
74        ) {
75            ret.vnode_partition_count = self.config.split_weight_by_vnode;
76            return Some(ret);
77        }
78
79        debug_assert!(self.target_level == levels.get_level(self.target_level).level_idx as usize);
80        if let Some(ret) = self.pick_multi_level_to_base(
81            l0,
82            levels.get_level(self.target_level),
83            self.config.split_weight_by_vnode,
84            level_handlers,
85            stats,
86        ) {
87            return Some(ret);
88        }
89
90        None
91    }
92}
93
94impl LevelCompactionPicker {
95    #[cfg(test)]
96    pub fn new(
97        target_level: usize,
98        config: Arc<CompactionConfig>,
99        developer_config: Arc<CompactionDeveloperConfig>,
100    ) -> LevelCompactionPicker {
101        LevelCompactionPicker {
102            target_level,
103            compaction_task_validator: Arc::new(CompactionTaskValidator::new(config.clone())),
104            config,
105            developer_config,
106        }
107    }
108
109    pub fn new_with_validator(
110        target_level: usize,
111        config: Arc<CompactionConfig>,
112        compaction_task_validator: Arc<CompactionTaskValidator>,
113        developer_config: Arc<CompactionDeveloperConfig>,
114    ) -> LevelCompactionPicker {
115        LevelCompactionPicker {
116            target_level,
117            config,
118            compaction_task_validator,
119            developer_config,
120        }
121    }
122
123    fn pick_base_trivial_move(
124        &self,
125        l0: &OverlappingLevel,
126        target_level: &Level,
127        level_handlers: &[LevelHandler],
128        stats: &mut LocalPickerStatistic,
129    ) -> Option<CompactionInput> {
130        if !self.developer_config.enable_trivial_move {
131            return None;
132        }
133
134        let overlap_strategy = create_overlap_strategy(self.config.compaction_mode());
135        let trivial_move_picker = TrivialMovePicker::new(
136            0,
137            self.target_level,
138            overlap_strategy.clone(),
139            if self.compaction_task_validator.is_enable() {
140                // tips: Older versions of the compaction group will be upgraded without this configuration, we leave it with its default behaviour and enable it manually when needed.
141                self.config.sst_allowed_trivial_move_min_size.unwrap_or(0)
142            } else {
143                0
144            },
145            self.config
146                .sst_allowed_trivial_move_max_count
147                .unwrap_or(compaction_config::sst_allowed_trivial_move_max_count())
148                as usize,
149        );
150
151        trivial_move_picker.pick_trivial_move_task(
152            &l0.sub_levels[0].table_infos,
153            &target_level.table_infos,
154            level_handlers,
155            stats,
156        )
157    }
158
159    fn pick_multi_level_to_base(
160        &self,
161        l0: &OverlappingLevel,
162        target_level: &Level,
163        vnode_partition_count: u32,
164        level_handlers: &[LevelHandler],
165        stats: &mut LocalPickerStatistic,
166    ) -> Option<CompactionInput> {
167        let overlap_strategy = create_overlap_strategy(self.config.compaction_mode());
168        let min_compaction_bytes = self.config.sub_level_max_compaction_bytes;
169        let non_overlap_sub_level_picker = NonOverlapSubLevelPicker::new(
170            min_compaction_bytes,
171            // divide by 2 because we need to select files of base level and it need use the other
172            // half quota.
173            std::cmp::max(
174                self.config.max_bytes_for_level_base,
175                self.config.max_compaction_bytes / 2,
176            ),
177            1,
178            // The maximum number of sub_level compact level per task
179            self.config.level0_max_compact_file_number,
180            overlap_strategy.clone(),
181            self.developer_config.enable_check_task_level_overlap,
182            self.config
183                .max_l0_compact_level_count
184                .unwrap_or(compaction_config::max_l0_compact_level_count()) as usize,
185            self.config
186                .enable_optimize_l0_interval_selection
187                .unwrap_or(compaction_config::enable_optimize_l0_interval_selection()),
188        );
189
190        let mut max_vnode_partition_idx = 0;
191        for (idx, level) in l0.sub_levels.iter().enumerate() {
192            if level.vnode_partition_count < vnode_partition_count {
193                break;
194            }
195            max_vnode_partition_idx = idx;
196        }
197
198        let candidate_l0_plans = non_overlap_sub_level_picker.pick_l0_multi_non_overlap_level(
199            &l0.sub_levels[..=max_vnode_partition_idx],
200            &level_handlers[0],
201        );
202        if candidate_l0_plans.is_empty() {
203            stats.skip_by_pending_files += 1;
204            return None;
205        }
206
207        let mut all_pending = true;
208        for input in candidate_l0_plans {
209            let mut overlap_info = overlap_strategy.create_overlap_info();
210            for (_, tables) in &input.sstable_infos {
211                for sst in tables {
212                    overlap_info.update(&sst.key_range);
213                }
214            }
215            let target_range = overlap_info.check_multiple_overlap(&target_level.table_infos);
216            let target_level_files = if target_range.is_empty() {
217                &[][..]
218            } else {
219                &target_level.table_infos[target_range]
220            };
221
222            let mut target_file_size = 0;
223            let mut pending_compact = false;
224            for sst in target_level_files {
225                if level_handlers[target_level.level_idx as usize].is_pending_compact(&sst.sst_id) {
226                    pending_compact = true;
227                    break;
228                }
229                target_file_size += sst.sst_size;
230            }
231            if pending_compact {
232                continue;
233            }
234            all_pending = false;
235
236            let mut select_level_inputs = input
237                .sstable_infos
238                .into_iter()
239                .map(|(_, table_infos)| InputLevel {
240                    level_idx: 0,
241                    level_type: LevelType::Nonoverlapping,
242                    table_infos,
243                })
244                .collect_vec();
245            select_level_inputs.reverse();
246            let target_file_count = target_level_files.len();
247            select_level_inputs.push(InputLevel {
248                level_idx: target_level.level_idx,
249                level_type: target_level.level_type,
250                table_infos: target_level_files.to_vec(),
251            });
252
253            let result = CompactionInput {
254                input_levels: select_level_inputs,
255                target_level: self.target_level,
256                select_input_size: input.total_file_size,
257                target_input_size: target_file_size,
258                total_file_count: (input.total_file_count + target_file_count) as u64,
259                vnode_partition_count,
260                ..Default::default()
261            };
262
263            if !self.compaction_task_validator.valid_compact_task(
264                &result,
265                ValidationRuleType::ToBase,
266                stats,
267            ) {
268                if l0.total_file_size > target_level.total_file_size * 8 {
269                    let log_counter = LOG_COUNTER.with_borrow_mut(|counter| {
270                        *counter += 1;
271                        *counter
272                    });
273
274                    // reduce log
275                    if log_counter.is_multiple_of(100) {
276                        tracing::warn!(
277                            "skip task with level count: {}, file count: {}, select size: {}, target size: {}, target level size: {}",
278                            result.input_levels.len(),
279                            result.total_file_count,
280                            result.select_input_size,
281                            result.target_input_size,
282                            target_level.total_file_size,
283                        );
284                    }
285                }
286                continue;
287            }
288
289            return Some(result);
290        }
291        // Preserve the pending statistic: validator rejection is a separate reason to skip.
292        if all_pending {
293            stats.skip_by_pending_files += 1;
294        }
295        None
296    }
297}
298
299#[cfg(test)]
300pub mod tests {
301
302    use super::*;
303    use crate::hummock::compaction::compaction_config::CompactionConfigBuilder;
304    use crate::hummock::compaction::selector::tests::*;
305    use crate::hummock::compaction::{CompactionMode, TierCompactionPicker};
306
307    fn candidate_fixture(count: usize, target_size: u64) -> (OverlappingLevel, Level) {
308        let sub_levels = (0..2)
309            .map(|level_idx| {
310                let table_infos = (0..count)
311                    .map(|idx| {
312                        generate_table(
313                            (level_idx * count + idx) as u64,
314                            1,
315                            idx * 10,
316                            idx * 10 + 9,
317                            1,
318                        )
319                    })
320                    .collect();
321                Level {
322                    sub_level_id: level_idx as u64,
323                    level_type: LevelType::Nonoverlapping,
324                    table_infos,
325                    total_file_size: count as u64 * 10,
326                    ..Default::default()
327                }
328            })
329            .collect();
330        let table_infos = (0..count)
331            .map(|idx| {
332                let mut sst =
333                    generate_table_impl((2 * count + idx) as u64, 1, idx * 10, idx * 10 + 9, 1);
334                sst.sst_size = target_size;
335                sst.into()
336            })
337            .collect();
338        (
339            OverlappingLevel {
340                sub_levels,
341                total_file_size: count as u64 * 20,
342                ..Default::default()
343            },
344            Level {
345                level_idx: 1,
346                level_type: LevelType::Nonoverlapping,
347                table_infos,
348                total_file_size: count as u64 * target_size,
349                ..Default::default()
350            },
351        )
352    }
353
354    #[test]
355    fn test_candidate_pending_and_validation_statistics() {
356        let picker = create_compaction_picker_for_test();
357        for (target_size, pending_count, succeeds, write_amp_skips, pending_skips) in [
358            (10, 0, true, 0, 0),
359            (10, 7, true, 0, 0),
360            (10, 8, false, 0, 1),
361            (100, 0, false, 8, 0),
362            (100, 7, false, 1, 0),
363            (100, 8, false, 0, 1),
364        ] {
365            let (l0, target) = candidate_fixture(8, target_size);
366            let mut handlers = vec![LevelHandler::new(0), LevelHandler::new(1)];
367            for sst in target.table_infos.iter().take(pending_count) {
368                handlers[1].test_add_pending_sst(sst.sst_id, 1);
369            }
370            let mut stats = LocalPickerStatistic::default();
371            let result = picker.pick_multi_level_to_base(&l0, &target, 0, &handlers, &mut stats);
372            assert_eq!(
373                result.is_some(),
374                succeeds,
375                "target_size={target_size}, pending={pending_count}"
376            );
377            assert_eq!(stats.skip_by_write_amp_limit, write_amp_skips);
378            assert_eq!(stats.skip_by_pending_files, pending_skips);
379            assert_eq!(stats.skip_by_count_limit, 0);
380            assert_eq!(stats.skip_by_overlapping, 0);
381            if let Some(result) = result {
382                assert_eq!(
383                    result.input_levels[0].table_infos[0].sst_id,
384                    (8 + pending_count) as u64
385                );
386                assert_eq!(
387                    result.input_levels[1].table_infos[0].sst_id,
388                    pending_count as u64
389                );
390                assert_eq!(
391                    result.input_levels[2].table_infos[0].sst_id,
392                    (16 + pending_count) as u64
393                );
394                assert_eq!(result.select_input_size, 20);
395                assert_eq!(result.target_input_size, 10);
396                assert_eq!(result.total_file_count, 3);
397            }
398        }
399    }
400
401    // Run explicitly with cargo test --release -p risingwave_meta bench_level_to_base -- --ignored --nocapture --test-threads=1.
402    #[test]
403    #[ignore = "microbenchmark"]
404    fn bench_level_to_base() {
405        use std::hint::black_box;
406        use std::time::Duration;
407
408        let mut criterion = criterion::Criterion::default()
409            .sample_size(30)
410            .warm_up_time(Duration::from_secs(1))
411            .measurement_time(Duration::from_secs(3));
412        let picker = create_compaction_picker_for_test();
413        for (name, count, target_size, pending_count) in [
414            ("small", 8, 10, 0),
415            ("first_valid", 256, 10, 0),
416            ("late_valid", 256, 10, 255),
417            ("all_pending", 256, 10, 256),
418            ("all_rejected", 256, 100, 0),
419        ] {
420            let (l0, target) = candidate_fixture(count, target_size);
421            let mut handlers = vec![LevelHandler::new(0), LevelHandler::new(1)];
422            for sst in target.table_infos.iter().take(pending_count) {
423                handlers[1].test_add_pending_sst(sst.sst_id, 1);
424            }
425            criterion.bench_function(&format!("level_to_base/{name}"), |b| {
426                b.iter(|| {
427                    let mut stats = LocalPickerStatistic::default();
428                    let result = picker.pick_multi_level_to_base(
429                        black_box(&l0),
430                        black_box(&target),
431                        0,
432                        black_box(&handlers),
433                        &mut stats,
434                    );
435                    black_box((result, stats))
436                })
437            });
438        }
439        criterion.final_summary();
440    }
441
442    fn create_compaction_picker_for_test() -> LevelCompactionPicker {
443        let config = Arc::new(
444            CompactionConfigBuilder::new()
445                .level0_tier_compact_file_number(2)
446                .level0_sub_level_compact_level_count(1)
447                .build(),
448        );
449        LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()))
450    }
451
452    #[test]
453    fn test_compact_l0_to_l1() {
454        let mut picker = create_compaction_picker_for_test();
455        let l0 = generate_level(
456            0,
457            vec![
458                generate_table(5, 1, 100, 200, 2),
459                generate_table(4, 1, 201, 300, 2),
460            ],
461        );
462        let mut levels = Levels {
463            l0: OverlappingLevel {
464                total_file_size: l0.total_file_size,
465                uncompressed_file_size: l0.total_file_size,
466                sub_levels: vec![l0],
467            },
468            levels: vec![generate_level(
469                1,
470                vec![
471                    generate_table(3, 1, 1, 100, 1),
472                    generate_table(2, 1, 101, 150, 1),
473                    generate_table(1, 1, 201, 210, 1),
474                ],
475            )],
476            ..Default::default()
477        };
478        let mut local_stats = LocalPickerStatistic::default();
479        let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
480
481        let ret = picker
482            .pick_compaction(&levels, &levels_handler, &mut local_stats)
483            .unwrap();
484        assert_eq!(ret.input_levels[0].table_infos.len(), 1);
485        assert_eq!(ret.input_levels[0].table_infos[0].sst_id, 4);
486        assert_eq!(ret.input_levels[1].table_infos[0].sst_id, 1);
487
488        ret.add_pending_task(0, &mut levels_handler);
489        {
490            push_table_level0_nonoverlapping(&mut levels, generate_table(6, 1, 100, 200, 2));
491            push_table_level0_nonoverlapping(&mut levels, generate_table(7, 1, 301, 333, 4));
492            let ret2 = picker
493                .pick_compaction(&levels, &levels_handler, &mut local_stats)
494                .unwrap();
495
496            assert_eq!(ret2.input_levels[0].table_infos.len(), 1);
497            assert_eq!(ret2.input_levels[0].table_infos[0].sst_id, 6);
498            assert_eq!(ret2.input_levels[1].table_infos[0].sst_id, 5);
499        }
500
501        levels.l0.sub_levels[0]
502            .table_infos
503            .retain(|table| table.sst_id != 4);
504        levels.l0.total_file_size -= ret.input_levels[0].table_infos[0].file_size;
505
506        levels_handler[0].remove_task(0);
507        levels_handler[1].remove_task(0);
508
509        let ret = picker
510            .pick_compaction(&levels, &levels_handler, &mut local_stats)
511            .unwrap();
512        assert_eq!(ret.input_levels.len(), 3);
513        assert_eq!(ret.input_levels[0].table_infos[0].sst_id, 6);
514        assert_eq!(ret.input_levels[1].table_infos[0].sst_id, 5);
515        assert_eq!(ret.input_levels[2].table_infos.len(), 2);
516        assert_eq!(ret.input_levels[2].table_infos[0].sst_id, 3);
517        assert_eq!(ret.input_levels[2].table_infos[1].sst_id, 2);
518        ret.add_pending_task(1, &mut levels_handler);
519
520        let mut local_stats = LocalPickerStatistic::default();
521        // Cannot pick because no idle table in sub-level[0]. (And sub-level[0] is pending
522        // actually).
523        push_table_level0_overlapping(&mut levels, generate_table(8, 1, 199, 233, 3));
524        let ret = picker.pick_compaction(&levels, &levels_handler, &mut local_stats);
525        assert!(ret.is_none());
526
527        // Don't pick overlapping sub-level 8
528        levels_handler[0].remove_task(1);
529        levels_handler[1].remove_task(1);
530        let ret = picker
531            .pick_compaction(&levels, &levels_handler, &mut local_stats)
532            .unwrap();
533        assert_eq!(ret.input_levels.len(), 3);
534        assert_eq!(ret.input_levels[0].table_infos[0].sst_id, 6);
535        assert_eq!(ret.input_levels[1].table_infos[0].sst_id, 5);
536        assert_eq!(ret.input_levels[2].table_infos.len(), 2);
537    }
538
539    #[test]
540    fn test_selecting_key_range_overlap() {
541        // When picking L0->L1, all L1 files overlapped with selecting_key_range should be picked.
542        let config = Arc::new(
543            CompactionConfigBuilder::new()
544                .level0_tier_compact_file_number(2)
545                .compaction_mode(CompactionMode::Range as i32)
546                .level0_sub_level_compact_level_count(1)
547                .enable_optimize_l0_interval_selection(Some(false))
548                .build(),
549        );
550
551        let config_enable_optimize_l0_interval_selection = Arc::new(
552            CompactionConfigBuilder::new()
553                .level0_tier_compact_file_number(2)
554                .compaction_mode(CompactionMode::Range as i32)
555                .level0_sub_level_compact_level_count(1)
556                .enable_optimize_l0_interval_selection(Some(true))
557                .build(),
558        );
559
560        let mut picker =
561            LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()));
562
563        let mut picker_enable_optimize_l0_interval_selection = LevelCompactionPicker::new(
564            1,
565            config_enable_optimize_l0_interval_selection,
566            Arc::new(CompactionDeveloperConfig::default()),
567        );
568
569        let levels = vec![Level {
570            level_idx: 1,
571            level_type: LevelType::Nonoverlapping,
572            table_infos: vec![
573                generate_table(3, 1, 0, 50, 1),
574                generate_table(4, 1, 150, 180, 1),
575                generate_table(5, 1, 250, 300, 1),
576            ],
577            ..Default::default()
578        }];
579        let mut levels = Levels {
580            levels,
581            l0: OverlappingLevel {
582                sub_levels: vec![],
583                total_file_size: 0,
584                uncompressed_file_size: 0,
585            },
586            ..Default::default()
587        };
588        push_tables_level0_nonoverlapping(&mut levels, vec![generate_table(1, 1, 50, 140, 2)]);
589        push_tables_level0_nonoverlapping(
590            &mut levels,
591            vec![
592                generate_table(7, 1, 200, 250, 2),
593                generate_table(8, 1, 400, 500, 2),
594            ],
595        );
596
597        {
598            let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
599
600            let mut local_stats = LocalPickerStatistic::default();
601            let ret = picker_enable_optimize_l0_interval_selection
602                .pick_compaction(&levels, &levels_handler, &mut local_stats)
603                .unwrap();
604
605            // pick
606            // l0 [sst_8]
607            assert_eq!(ret.input_levels.len(), 2);
608            assert_eq!(
609                ret.input_levels[0]
610                    .table_infos
611                    .iter()
612                    .map(|t| t.sst_id)
613                    .collect_vec(),
614                vec![8]
615            );
616            // trivial_move
617            assert!(ret.input_levels[1].table_infos.is_empty());
618
619            ret.add_pending_task(0, &mut levels_handler);
620
621            let ret = picker_enable_optimize_l0_interval_selection
622                .pick_compaction(&levels, &levels_handler, &mut local_stats)
623                .unwrap();
624
625            assert_eq!(ret.input_levels.len(), 2);
626            assert_eq!(
627                ret.input_levels[0]
628                    .table_infos
629                    .iter()
630                    .map(|t| t.sst_id)
631                    .collect_vec(),
632                vec![7]
633            );
634
635            assert_eq!(
636                ret.input_levels[1]
637                    .table_infos
638                    .iter()
639                    .map(|t| t.sst_id)
640                    .collect_vec(),
641                vec![5]
642            );
643        }
644
645        {
646            let levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
647
648            let mut local_stats = LocalPickerStatistic::default();
649            let ret = picker
650                .pick_compaction(&levels, &levels_handler, &mut local_stats)
651                .unwrap();
652
653            // pick
654            assert_eq!(ret.input_levels.len(), 2);
655            assert_eq!(
656                ret.input_levels[0]
657                    .table_infos
658                    .iter()
659                    .map(|t| t.sst_id.as_raw_id())
660                    .collect_vec(),
661                vec![1]
662            );
663
664            assert_eq!(
665                ret.input_levels[1]
666                    .table_infos
667                    .iter()
668                    .map(|t| t.sst_id.as_raw_id())
669                    .collect_vec(),
670                vec![3]
671            );
672        }
673    }
674
675    #[test]
676    fn test_l0_to_l1_compact_conflict() {
677        // When picking L0->L1, L0's selecting_key_range should not be overlapped with L0's
678        // compacting_key_range.
679        let mut picker = create_compaction_picker_for_test();
680        let levels = vec![Level {
681            level_idx: 1,
682            level_type: LevelType::Nonoverlapping,
683            table_infos: vec![],
684            total_file_size: 0,
685            sub_level_id: 0,
686            uncompressed_file_size: 0,
687            ..Default::default()
688        }];
689        let mut levels = Levels {
690            levels,
691            l0: OverlappingLevel {
692                sub_levels: vec![],
693                total_file_size: 0,
694                uncompressed_file_size: 0,
695            },
696            ..Default::default()
697        };
698        push_tables_level0_nonoverlapping(
699            &mut levels,
700            vec![
701                generate_table(1, 1, 100, 300, 2),
702                generate_table(2, 1, 350, 500, 2),
703            ],
704        );
705        let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
706
707        let mut local_stats = LocalPickerStatistic::default();
708        let ret = picker
709            .pick_compaction(&levels, &levels_handler, &mut local_stats)
710            .unwrap();
711        // trivial_move
712        ret.add_pending_task(0, &mut levels_handler); // pending only for test
713        push_tables_level0_nonoverlapping(&mut levels, vec![generate_table(3, 1, 250, 300, 3)]);
714        let config: CompactionConfig = CompactionConfigBuilder::new()
715            .level0_tier_compact_file_number(2)
716            .max_compaction_bytes(1000)
717            .sub_level_max_compaction_bytes(150)
718            .max_bytes_for_level_multiplier(1)
719            .level0_sub_level_compact_level_count(3)
720            .build();
721        let mut picker = TierCompactionPicker::new(Arc::new(config));
722
723        let ret: Option<CompactionInput> =
724            picker.pick_compaction(&levels, &levels_handler, &mut local_stats);
725        assert!(ret.is_none());
726    }
727
728    #[test]
729    fn test_skip_compact_write_amplification_limit() {
730        let config: CompactionConfig = CompactionConfigBuilder::new()
731            .level0_tier_compact_file_number(2)
732            .max_compaction_bytes(1000)
733            .sub_level_max_compaction_bytes(150)
734            .max_bytes_for_level_multiplier(1)
735            .level0_sub_level_compact_level_count(2)
736            .build();
737        let mut picker = LevelCompactionPicker::new(
738            1,
739            Arc::new(config),
740            Arc::new(CompactionDeveloperConfig::default()),
741        );
742
743        let mut levels = Levels {
744            levels: vec![Level {
745                level_idx: 1,
746                level_type: LevelType::Nonoverlapping,
747                table_infos: vec![
748                    generate_table(1, 1, 100, 399, 2),
749                    generate_table(2, 1, 400, 699, 2),
750                    generate_table(3, 1, 700, 999, 2),
751                ],
752                total_file_size: 900,
753                sub_level_id: 0,
754                uncompressed_file_size: 900,
755                ..Default::default()
756            }],
757            l0: generate_l0_nonoverlapping_sublevels(vec![]),
758            ..Default::default()
759        };
760        push_tables_level0_nonoverlapping(
761            &mut levels,
762            vec![
763                generate_table(4, 1, 100, 180, 2),
764                generate_table(5, 1, 400, 450, 2),
765                generate_table(6, 1, 600, 700, 2),
766            ],
767        );
768
769        let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
770        let mut local_stats = LocalPickerStatistic::default();
771        levels_handler[0].add_pending_task(1, 4, &levels.l0.sub_levels[0].table_infos);
772        let ret = picker.pick_compaction(&levels, &levels_handler, &mut local_stats);
773        // Skip this compaction because the write amplification is too large.
774        assert!(ret.is_none());
775    }
776
777    #[test]
778    fn test_l0_to_l1_break_on_exceed_compaction_size() {
779        let mut local_stats = LocalPickerStatistic::default();
780        let mut l0 = generate_l0_overlapping_sublevels(vec![
781            vec![
782                generate_table(4, 1, 10, 90, 1),
783                generate_table(5, 1, 210, 220, 1),
784            ],
785            vec![generate_table(6, 1, 0, 100000, 1)],
786            vec![generate_table(7, 1, 0, 100000, 1)],
787        ]);
788        // We can set level_type only because the input above is valid.
789        for s in &mut l0.sub_levels {
790            s.level_type = LevelType::Nonoverlapping;
791        }
792        let levels = Levels {
793            l0,
794            levels: vec![generate_level(1, vec![generate_table(3, 1, 0, 100000, 1)])],
795            ..Default::default()
796        };
797        let levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
798
799        // Pick with large max_compaction_bytes results all sub levels included in input.
800        let config = Arc::new(
801            CompactionConfigBuilder::new()
802                .max_compaction_bytes(500000)
803                .sub_level_max_compaction_bytes(50000)
804                .max_bytes_for_level_base(500000)
805                .level0_sub_level_compact_level_count(1)
806                .build(),
807        );
808        // Only include sub-level 0 results will violate MAX_WRITE_AMPLIFICATION.
809        // So all sub-levels are included to make write amplification < MAX_WRITE_AMPLIFICATION.
810        let mut picker =
811            LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()));
812        let ret = picker
813            .pick_compaction(&levels, &levels_handler, &mut local_stats)
814            .unwrap();
815        assert_eq!(ret.input_levels[0].table_infos[0].sst_id, 7);
816        assert_eq!(
817            3,
818            ret.input_levels.iter().filter(|l| l.level_idx == 0).count()
819        );
820        assert_eq!(
821            4,
822            ret.input_levels
823                .iter()
824                .filter(|l| l.level_idx == 0)
825                .map(|l| l.table_infos.len())
826                .sum::<usize>()
827        );
828
829        // Pick with small max_compaction_bytes results partial sub levels included in input.
830        let config = Arc::new(
831            CompactionConfigBuilder::new()
832                .max_compaction_bytes(100010)
833                .max_bytes_for_level_base(512)
834                .level0_sub_level_compact_level_count(1)
835                .build(),
836        );
837        let mut picker =
838            LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()));
839
840        let ret = picker
841            .pick_compaction(&levels, &levels_handler, &mut local_stats)
842            .unwrap();
843        assert_eq!(ret.input_levels[0].table_infos[0].sst_id, 6);
844        assert_eq!(
845            2,
846            ret.input_levels.iter().filter(|l| l.level_idx == 0).count()
847        );
848        assert_eq!(
849            3,
850            ret.input_levels
851                .iter()
852                .filter(|l| l.level_idx == 0)
853                .map(|l| l.table_infos.len())
854                .sum::<usize>()
855        );
856    }
857
858    #[test]
859    fn test_l0_to_l1_break_on_pending_sub_level() {
860        let l0 = generate_l0_nonoverlapping_multi_sublevels(vec![
861            vec![
862                generate_table(4, 1, 10, 90, 1),
863                generate_table(5, 1, 210, 220, 1),
864            ],
865            vec![generate_table(6, 1, 0, 100000, 1)],
866            vec![generate_table(7, 1, 0, 100000, 1)],
867        ]);
868
869        let levels = Levels {
870            l0,
871            levels: vec![generate_level(1, vec![generate_table(3, 1, 0, 100000, 1)])],
872            ..Default::default()
873        };
874        let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
875        let mut local_stats = LocalPickerStatistic::default();
876
877        // Create a pending sub-level.
878        let pending_level = levels.l0.sub_levels[1].clone();
879        assert_eq!(pending_level.sub_level_id, 1);
880        let tier_task_input = CompactionInput {
881            input_levels: vec![InputLevel {
882                level_idx: 0,
883                level_type: pending_level.level_type,
884                table_infos: pending_level.table_infos.clone(),
885            }],
886            target_level: 1,
887            target_sub_level_id: pending_level.sub_level_id,
888            ..Default::default()
889        };
890        assert!(!levels_handler[0].is_level_pending_compact(&pending_level));
891        tier_task_input.add_pending_task(1, &mut levels_handler);
892        assert!(levels_handler[0].is_level_pending_compact(&pending_level));
893
894        // Pick with large max_compaction_bytes results all sub levels included in input.
895        let config = Arc::new(
896            CompactionConfigBuilder::new()
897                .max_compaction_bytes(500000)
898                .level0_sub_level_compact_level_count(2)
899                .build(),
900        );
901
902        // Only include sub-level 0 results will violate MAX_WRITE_AMPLIFICATION.
903        // But stopped by pending sub-level when trying to include more sub-levels.
904        let mut picker = LevelCompactionPicker::new(
905            1,
906            config.clone(),
907            Arc::new(CompactionDeveloperConfig::default()),
908        );
909        let ret = picker.pick_compaction(&levels, &levels_handler, &mut local_stats);
910        assert!(ret.is_none());
911
912        // Free the pending sub-level.
913        for pending_task_id in &levels_handler[0].pending_tasks_ids() {
914            levels_handler[0].remove_task(*pending_task_id);
915        }
916
917        // No more pending sub-level so we can get a task now.
918        let mut picker =
919            LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()));
920        picker
921            .pick_compaction(&levels, &levels_handler, &mut local_stats)
922            .unwrap();
923    }
924
925    #[test]
926    fn test_l0_to_base_when_all_base_pending() {
927        let l0 = generate_l0_nonoverlapping_multi_sublevels(vec![
928            vec![
929                generate_table(4, 1, 10, 90, 1),
930                generate_table(5, 1, 1000, 2000, 1),
931            ],
932            vec![generate_table(6, 1, 10, 90, 1)],
933        ]);
934
935        let levels = Levels {
936            l0,
937            levels: vec![generate_level(1, vec![generate_table(3, 1, 1, 100, 1)])],
938            ..Default::default()
939        };
940        let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
941        let mut local_stats = LocalPickerStatistic::default();
942
943        let config = Arc::new(
944            CompactionConfigBuilder::new()
945                .max_compaction_bytes(500000)
946                .level0_sub_level_compact_level_count(2)
947                .sub_level_max_compaction_bytes(1000)
948                .build(),
949        );
950
951        let mut picker =
952            LevelCompactionPicker::new(1, config, Arc::new(CompactionDeveloperConfig::default()));
953        let ret = picker
954            .pick_compaction(&levels, &levels_handler, &mut local_stats)
955            .unwrap();
956        // 1. trivial_move
957        assert_eq!(2, ret.input_levels.len());
958        assert!(ret.input_levels[1].table_infos.is_empty());
959        assert_eq!(5, ret.input_levels[0].table_infos[0].sst_id);
960        ret.add_pending_task(0, &mut levels_handler);
961
962        let ret = picker
963            .pick_compaction(&levels, &levels_handler, &mut local_stats)
964            .unwrap();
965        assert_eq!(3, ret.input_levels.len());
966        assert_eq!(6, ret.input_levels[0].table_infos[0].sst_id);
967    }
968}