Skip to main content

risingwave_meta/hummock/compaction/picker/
manual_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::collections::HashSet;
16use std::sync::Arc;
17
18use itertools::Itertools;
19use risingwave_hummock_sdk::HummockSstableId;
20use risingwave_hummock_sdk::level::{InputLevel, Level, Levels, OverlappingLevel};
21use risingwave_hummock_sdk::sstable_info::SstableInfo;
22use risingwave_pb::hummock::LevelType;
23
24use super::{CompactionInput, CompactionPicker, LocalPickerStatistic};
25use crate::hummock::compaction::overlap_strategy::{
26    OverlapInfo, OverlapStrategy, RangeOverlapInfo,
27};
28use crate::hummock::compaction::selector::ManualCompactionOption;
29use crate::hummock::level_handler::LevelHandler;
30
31pub struct ManualCompactionPicker {
32    overlap_strategy: Arc<dyn OverlapStrategy>,
33    option: ManualCompactionOption,
34    target_level: usize,
35}
36
37impl ManualCompactionPicker {
38    pub fn new(
39        overlap_strategy: Arc<dyn OverlapStrategy>,
40        option: ManualCompactionOption,
41        target_level: usize,
42    ) -> Self {
43        Self {
44            overlap_strategy,
45            option,
46            target_level,
47        }
48    }
49
50    fn pick_l0_to_sub_level(
51        &self,
52        l0: &OverlappingLevel,
53        level_handlers: &[LevelHandler],
54    ) -> Option<CompactionInput> {
55        assert_eq!(self.option.level, 0);
56        let mut input_levels = vec![];
57        let mut sub_level_id = 0;
58        let mut start_idx = None;
59        let mut end_idx = None;
60        // Decides the range of sub levels as input.
61        // We need pick consecutive sub_levels. See #5217.
62        for (idx, level) in l0.sub_levels.iter().enumerate() {
63            if !self.filter_level_by_option(level) {
64                continue;
65            }
66            if level_handlers[0].is_level_pending_compact(level) {
67                return None;
68            }
69            // Pick this sub_level.
70            if start_idx.is_none() {
71                sub_level_id = level.sub_level_id;
72                start_idx = Some(idx as u64);
73                end_idx = start_idx;
74            } else {
75                end_idx = Some(idx as u64);
76            }
77        }
78        let (start_idx, end_idx) = match (start_idx, end_idx) {
79            (Some(start_idx), Some(end_idx)) => (start_idx, end_idx),
80            _ => {
81                return None;
82            }
83        };
84        // Construct input.
85        // The interval may include sublevels that did not match the option filter.
86        for level in l0
87            .sub_levels
88            .iter()
89            .skip(start_idx as usize)
90            .take((end_idx - start_idx + 1) as usize)
91        {
92            if level_handlers[0].is_level_pending_compact(level) {
93                return None;
94            }
95            input_levels.push(InputLevel {
96                level_idx: 0,
97                level_type: level.level_type,
98                table_infos: level.table_infos.clone(),
99            });
100        }
101        if input_levels.is_empty() {
102            return None;
103        }
104        input_levels.reverse();
105        Some(CompactionInput {
106            input_levels,
107            target_level: 0,
108            target_sub_level_id: sub_level_id,
109            ..Default::default()
110        })
111    }
112
113    fn pick_l0_to_base_level(
114        &self,
115        levels: &Levels,
116        level_handlers: &[LevelHandler],
117    ) -> Option<CompactionInput> {
118        assert!(self.option.level == 0 && self.target_level > 0);
119        for l in 1..self.target_level {
120            assert!(levels.levels[l - 1].table_infos.is_empty());
121        }
122        let l0 = &levels.l0;
123        let mut input_levels = vec![];
124        let mut max_sub_level_idx = usize::MAX;
125        let mut info = self.overlap_strategy.create_overlap_info();
126        // Decides the range of sub levels as input.
127        for (idx, level) in l0.sub_levels.iter().enumerate() {
128            if !self.filter_level_by_option(level) {
129                continue;
130            }
131            if level_handlers[0].is_level_pending_compact(level) {
132                return None;
133            }
134
135            // Pick this sub_level.
136            max_sub_level_idx = idx;
137        }
138        if max_sub_level_idx == usize::MAX {
139            return None;
140        }
141        // Construct input.
142        for idx in 0..=max_sub_level_idx {
143            // The prefix may include sublevels that did not match the option filter.
144            if level_handlers[0].is_level_pending_compact(&l0.sub_levels[idx]) {
145                return None;
146            }
147            for table in &l0.sub_levels[idx].table_infos {
148                info.update(&table.key_range);
149            }
150            input_levels.push(InputLevel {
151                level_idx: 0,
152                level_type: l0.sub_levels[idx].level_type,
153                table_infos: l0.sub_levels[idx].table_infos.clone(),
154            })
155        }
156        let target_input_ssts_range =
157            info.check_multiple_overlap(&levels.levels[self.target_level - 1].table_infos);
158        let target_input_ssts = if target_input_ssts_range.is_empty() {
159            vec![]
160        } else {
161            levels.levels[self.target_level - 1].table_infos[target_input_ssts_range].to_vec()
162        };
163        if target_input_ssts
164            .iter()
165            .any(|table| level_handlers[self.target_level].is_pending_compact(&table.sst_id))
166        {
167            return None;
168        }
169        if input_levels.is_empty() {
170            return None;
171        }
172        input_levels.reverse();
173        input_levels.push(InputLevel {
174            level_idx: self.target_level as u32,
175            level_type: LevelType::Nonoverlapping,
176            table_infos: target_input_ssts,
177        });
178
179        Some(CompactionInput {
180            input_levels,
181            target_level: self.target_level,
182            target_sub_level_id: 0,
183            ..Default::default()
184        })
185    }
186
187    /// Returns false if the given `sst` is rejected by filter defined by `option`.
188    /// Otherwise returns true.
189    fn filter_level_by_option(&self, level: &Level) -> bool {
190        let mut hint_sst_ids: HashSet<HummockSstableId> = HashSet::new();
191        hint_sst_ids.extend(self.option.sst_ids.iter());
192        if self
193            .overlap_strategy
194            .check_overlap_with_range(&self.option.key_range, &level.table_infos)
195            .is_empty()
196        {
197            return false;
198        }
199        if !hint_sst_ids.is_empty()
200            && !level
201                .table_infos
202                .iter()
203                .any(|t| hint_sst_ids.contains(&t.sst_id))
204        {
205            return false;
206        }
207        if !self.option.internal_table_id.is_empty()
208            && !level.table_infos.iter().any(|sst_info| {
209                sst_info
210                    .table_ids
211                    .iter()
212                    .any(|t| self.option.internal_table_id.contains(t))
213            })
214        {
215            return false;
216        }
217        true
218    }
219}
220
221impl CompactionPicker for ManualCompactionPicker {
222    fn pick_compaction(
223        &mut self,
224        levels: &Levels,
225        level_handlers: &[LevelHandler],
226        _stats: &mut LocalPickerStatistic,
227    ) -> Option<CompactionInput> {
228        if self.option.exclusive
229            && level_handlers
230                .iter()
231                .any(|level_handler| level_handler.pending_file_count() > 0)
232        {
233            return None;
234        }
235        if self.option.level == 0 {
236            if !self.option.sst_ids.is_empty() {
237                return self.pick_l0_to_sub_level(&levels.l0, level_handlers);
238            } else if self.target_level > 0 {
239                return self.pick_l0_to_base_level(levels, level_handlers);
240            } else {
241                return None;
242            }
243        }
244        let mut hint_sst_ids: HashSet<HummockSstableId> = HashSet::new();
245        hint_sst_ids.extend(self.option.sst_ids.iter());
246        let mut range_overlap_info = RangeOverlapInfo::default();
247        range_overlap_info.update(&self.option.key_range);
248        let level = self.option.level;
249        let target_level = self.target_level;
250        assert!(
251            self.option.level == self.target_level || self.option.level + 1 == self.target_level
252        );
253        // We either include all `select_input_ssts` as input, or return None.
254        let mut select_input_ssts: Vec<SstableInfo> = levels
255            .get_level(self.option.level)
256            .table_infos
257            .iter()
258            .filter(|sst_info| hint_sst_ids.is_empty() || hint_sst_ids.contains(&sst_info.sst_id))
259            .filter(|sst_info| range_overlap_info.check_overlap(sst_info))
260            .filter(|sst_info| {
261                if self.option.internal_table_id.is_empty() {
262                    return true;
263                }
264
265                // to filter sst_file by table_id
266                for table_id in &sst_info.table_ids {
267                    if self.option.internal_table_id.contains(table_id) {
268                        return true;
269                    }
270                }
271                false
272            })
273            .cloned()
274            .collect();
275        if select_input_ssts.is_empty() {
276            return None;
277        }
278        let target_input_ssts = if target_level == level {
279            // For intra level compaction, input SSTs must be consecutive.
280            let (left, _) = levels
281                .get_level(level)
282                .table_infos
283                .iter()
284                .find_position(|p| p.sst_id == select_input_ssts.first().unwrap().sst_id)
285                .unwrap();
286            let (right, _) = levels
287                .get_level(level)
288                .table_infos
289                .iter()
290                .find_position(|p| p.sst_id == select_input_ssts.last().unwrap().sst_id)
291                .unwrap();
292            select_input_ssts = levels.get_level(level).table_infos[left..=right].to_vec();
293            vec![]
294        } else {
295            self.overlap_strategy.check_base_level_overlap(
296                &select_input_ssts,
297                &levels.get_level(target_level).table_infos,
298            )
299        };
300        if select_input_ssts
301            .iter()
302            .any(|table| level_handlers[level].is_pending_compact(&table.sst_id))
303        {
304            return None;
305        }
306        if target_input_ssts
307            .iter()
308            .any(|table| level_handlers[target_level].is_pending_compact(&table.sst_id))
309        {
310            return None;
311        }
312
313        Some(CompactionInput {
314            select_input_size: select_input_ssts.iter().map(|sst| sst.sst_size).sum(),
315            target_input_size: target_input_ssts.iter().map(|sst| sst.sst_size).sum(),
316            total_file_count: (select_input_ssts.len() + target_input_ssts.len()) as u64,
317            input_levels: vec![
318                InputLevel {
319                    level_idx: level as u32,
320                    level_type: levels.levels[level - 1].level_type,
321                    table_infos: select_input_ssts,
322                },
323                InputLevel {
324                    level_idx: target_level as u32,
325                    level_type: levels.levels[target_level - 1].level_type,
326                    table_infos: target_input_ssts,
327                },
328            ],
329            target_level,
330            ..Default::default()
331        })
332    }
333}
334
335#[cfg(test)]
336pub mod tests {
337    use std::collections::{BTreeSet, HashMap};
338
339    use bytes::Bytes;
340    use risingwave_hummock_sdk::key_range::KeyRange;
341    use risingwave_hummock_sdk::version::HummockVersionStateTableInfo;
342    use risingwave_pb::hummock::compact_task;
343
344    use super::*;
345    use crate::hummock::compaction::compaction_config::CompactionConfigBuilder;
346    use crate::hummock::compaction::overlap_strategy::RangeOverlapStrategy;
347    use crate::hummock::compaction::selector::tests::{
348        assert_compaction_task, generate_l0_nonoverlapping_sublevels,
349        generate_l0_overlapping_sublevels, generate_level, generate_table,
350    };
351    use crate::hummock::compaction::selector::{CompactionSelector, ManualCompactionSelector};
352    use crate::hummock::compaction::{CompactionDeveloperConfig, LocalSelectorStatistic};
353    use crate::hummock::model::CompactionGroup;
354    use crate::hummock::test_utils::{compaction_selector_context, iterator_test_key_of_epoch};
355
356    fn clean_task_state(level_handler: &mut LevelHandler) {
357        for pending_task_id in &level_handler.pending_tasks_ids() {
358            level_handler.remove_task(*pending_task_id);
359        }
360    }
361
362    fn is_l0_to_lbase(compaction_input: &CompactionInput) -> bool {
363        compaction_input
364            .input_levels
365            .iter()
366            .take(compaction_input.input_levels.len() - 1)
367            .all(|i| i.level_idx == 0)
368            && compaction_input
369                .input_levels
370                .iter()
371                .last()
372                .unwrap()
373                .level_idx as usize
374                == compaction_input.target_level
375            && compaction_input.target_level > 0
376    }
377
378    fn is_l0_to_l0(compaction_input: &CompactionInput) -> bool {
379        compaction_input
380            .input_levels
381            .iter()
382            .all(|i| i.level_idx == 0)
383            && compaction_input.target_level == 0
384    }
385
386    #[test]
387    fn test_manual_compaction_picker() {
388        let levels = vec![
389            Level {
390                level_idx: 1,
391                level_type: LevelType::Nonoverlapping,
392                table_infos: vec![
393                    generate_table(0, 1, 0, 100, 1),
394                    generate_table(1, 1, 101, 200, 1),
395                    generate_table(2, 1, 222, 300, 1),
396                ],
397                ..Default::default()
398            },
399            Level {
400                level_idx: 2,
401                level_type: LevelType::Nonoverlapping,
402                table_infos: vec![
403                    generate_table(4, 1, 0, 100, 1),
404                    generate_table(5, 1, 101, 150, 1),
405                    generate_table(6, 1, 151, 201, 1),
406                    generate_table(7, 1, 501, 800, 1),
407                    generate_table(8, 2, 301, 400, 1),
408                ],
409                ..Default::default()
410            },
411        ];
412        let mut levels = Levels {
413            levels,
414            l0: generate_l0_nonoverlapping_sublevels(vec![]),
415            ..Default::default()
416        };
417        let mut levels_handler = vec![
418            LevelHandler::new(0),
419            LevelHandler::new(1),
420            LevelHandler::new(2),
421        ];
422        let mut local_stats = LocalPickerStatistic::default();
423
424        {
425            // test key_range option
426            let option = ManualCompactionOption {
427                level: 1,
428                key_range: KeyRange {
429                    left: Bytes::from(iterator_test_key_of_epoch(1, 0, 1)),
430                    right: Bytes::from(iterator_test_key_of_epoch(1, 201, 1)),
431                    right_exclusive: false,
432                },
433                ..Default::default()
434            };
435
436            let target_level = option.level + 1;
437            let mut picker = ManualCompactionPicker::new(
438                Arc::new(RangeOverlapStrategy::default()),
439                option,
440                target_level,
441            );
442            let result = picker
443                .pick_compaction(&levels, &levels_handler, &mut local_stats)
444                .unwrap();
445            result.add_pending_task(0, &mut levels_handler);
446
447            assert_eq!(2, result.input_levels[0].table_infos.len());
448            assert_eq!(3, result.input_levels[1].table_infos.len());
449        }
450
451        {
452            clean_task_state(&mut levels_handler[1]);
453            clean_task_state(&mut levels_handler[2]);
454
455            // test all key range
456            let option = ManualCompactionOption::default();
457            let target_level = option.level + 1;
458            let mut picker = ManualCompactionPicker::new(
459                Arc::new(RangeOverlapStrategy::default()),
460                option,
461                target_level,
462            );
463            let result = picker
464                .pick_compaction(&levels, &levels_handler, &mut local_stats)
465                .unwrap();
466            result.add_pending_task(0, &mut levels_handler);
467
468            assert_eq!(3, result.input_levels[0].table_infos.len());
469            assert_eq!(3, result.input_levels[1].table_infos.len());
470        }
471
472        {
473            clean_task_state(&mut levels_handler[1]);
474            clean_task_state(&mut levels_handler[2]);
475
476            let level_table_info = &mut levels.levels[0].table_infos;
477            let table_info_1 = &mut level_table_info[1];
478            let mut t_inner = table_info_1.get_inner();
479            t_inner.table_ids.resize(2, 0.into());
480            t_inner.table_ids[0] = 1.into();
481            t_inner.table_ids[1] = 2.into();
482            *table_info_1 = t_inner.into();
483
484            // test internal_table_id
485            let option = ManualCompactionOption {
486                level: 1,
487                internal_table_id: HashSet::from([2.into()]),
488                ..Default::default()
489            };
490
491            let target_level = option.level + 1;
492            let mut picker = ManualCompactionPicker::new(
493                Arc::new(RangeOverlapStrategy::default()),
494                option,
495                target_level,
496            );
497
498            let result = picker
499                .pick_compaction(&levels, &levels_handler, &mut local_stats)
500                .unwrap();
501            result.add_pending_task(0, &mut levels_handler);
502
503            assert_eq!(1, result.input_levels[0].table_infos.len());
504            assert_eq!(2, result.input_levels[1].table_infos.len());
505        }
506
507        {
508            clean_task_state(&mut levels_handler[1]);
509            clean_task_state(&mut levels_handler[2]);
510
511            // include all table_info
512            let level_table_info = &mut levels.levels[0].table_infos;
513            for table_info in level_table_info {
514                let mut t_inner = table_info.get_inner();
515                t_inner.table_ids.resize(2, 0.into());
516                t_inner.table_ids[0] = 1.into();
517                t_inner.table_ids[1] = 2.into();
518                *table_info = t_inner.into();
519            }
520
521            // test key range filter first
522            let option = ManualCompactionOption {
523                sst_ids: vec![],
524                level: 1,
525                key_range: KeyRange {
526                    left: Bytes::from(iterator_test_key_of_epoch(1, 101, 1)),
527                    right: Bytes::from(iterator_test_key_of_epoch(1, 199, 1)),
528                    right_exclusive: false,
529                },
530                internal_table_id: HashSet::from([2.into()]),
531                target_level: None,
532                exclusive: false,
533            };
534
535            let target_level = option.level + 1;
536            let mut picker = ManualCompactionPicker::new(
537                Arc::new(RangeOverlapStrategy::default()),
538                option,
539                target_level,
540            );
541
542            let result = picker
543                .pick_compaction(&levels, &levels_handler, &mut local_stats)
544                .unwrap();
545
546            assert_eq!(1, result.input_levels[0].table_infos.len());
547            assert_eq!(2, result.input_levels[1].table_infos.len());
548        }
549    }
550
551    #[test]
552    fn test_manual_compaction_exclusive_blocked_by_pending() {
553        let (levels, mut levels_handler) = generate_test_levels();
554        let option = ManualCompactionOption {
555            exclusive: true,
556            ..Default::default()
557        };
558        let target_level = option.level + 1;
559        let mut picker = ManualCompactionPicker::new(
560            Arc::new(RangeOverlapStrategy::default()),
561            option,
562            target_level,
563        );
564
565        let pending_sst_id = levels.levels[0].table_infos[0].sst_id;
566        levels_handler[1].test_add_pending_sst(pending_sst_id, 1);
567
568        assert!(
569            picker
570                .pick_compaction(
571                    &levels,
572                    &levels_handler,
573                    &mut LocalPickerStatistic::default()
574                )
575                .is_none()
576        );
577    }
578
579    fn generate_test_levels() -> (Levels, Vec<LevelHandler>) {
580        let mut l0 = generate_l0_overlapping_sublevels(vec![
581            vec![
582                generate_table(5, 1, 0, 500, 2),
583                generate_table(6, 2, 600, 1000, 2),
584            ],
585            vec![
586                generate_table(7, 1, 0, 500, 3),
587                generate_table(8, 2, 600, 1000, 3),
588            ],
589            vec![
590                generate_table(9, 1, 300, 500, 4),
591                generate_table(10, 2, 600, 1000, 4),
592            ],
593        ]);
594        // Set a nonoverlapping sub_level.
595        l0.sub_levels[1].level_type = LevelType::Nonoverlapping as _;
596        assert_eq!(l0.sub_levels.len(), 3);
597        let mut levels = vec![
598            Level {
599                level_idx: 1,
600                level_type: LevelType::Nonoverlapping,
601                table_infos: vec![
602                    generate_table(3, 1, 0, 100, 1),
603                    generate_table(4, 2, 2000, 3000, 1),
604                ],
605                ..Default::default()
606            },
607            Level {
608                level_idx: 2,
609                level_type: LevelType::Nonoverlapping,
610                table_infos: vec![
611                    generate_table(1, 1, 0, 100, 1),
612                    generate_table(2, 2, 2000, 3000, 1),
613                ],
614                ..Default::default()
615            },
616        ];
617        // Set internal_table_ids.
618        assert_eq!(levels.len(), 2);
619        for iter in [l0.sub_levels.iter_mut(), levels.iter_mut()] {
620            for (idx, l) in iter.enumerate() {
621                for t in &mut l.table_infos {
622                    let mut t_inner = t.get_inner();
623                    t_inner.table_ids.clear();
624                    if idx == 0 {
625                        t_inner
626                            .table_ids
627                            .push((((t.sst_id.as_raw_id() % 2) + 1) as u32).into());
628                    } else {
629                        t_inner.table_ids.push(3.into());
630                    }
631                    *t = t_inner.into();
632                }
633            }
634        }
635        let levels = Levels {
636            levels,
637            l0,
638            ..Default::default()
639        };
640
641        let levels_handler = vec![
642            LevelHandler::new(0),
643            LevelHandler::new(1),
644            LevelHandler::new(2),
645        ];
646        (levels, levels_handler)
647    }
648
649    fn generate_intra_test_levels() -> (Levels, Vec<LevelHandler>) {
650        let l0 = generate_l0_overlapping_sublevels(vec![]);
651        let levels = vec![Level {
652            level_idx: 1,
653            level_type: LevelType::Nonoverlapping,
654            table_infos: vec![
655                generate_table(1, 1, 0, 100, 1),
656                generate_table(2, 2, 100, 200, 1),
657                generate_table(3, 2, 200, 300, 1),
658                generate_table(4, 2, 300, 400, 1),
659            ],
660            ..Default::default()
661        }];
662        let levels = Levels {
663            levels,
664            l0,
665            ..Default::default()
666        };
667
668        let levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
669        (levels, levels_handler)
670    }
671
672    #[test]
673    fn test_l0_empty() {
674        let l0 = generate_l0_nonoverlapping_sublevels(vec![]);
675        let levels = vec![Level {
676            level_idx: 1,
677            level_type: LevelType::Nonoverlapping,
678            table_infos: vec![],
679            total_file_size: 0,
680            sub_level_id: 0,
681            uncompressed_file_size: 0,
682            ..Default::default()
683        }];
684        let levels = Levels {
685            levels,
686            l0,
687            ..Default::default()
688        };
689        let levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)];
690        let option = ManualCompactionOption {
691            sst_ids: vec![1.into()],
692            level: 0,
693            key_range: KeyRange {
694                left: Bytes::default(),
695                right: Bytes::default(),
696                right_exclusive: false,
697            },
698            internal_table_id: HashSet::default(),
699            target_level: None,
700            exclusive: false,
701        };
702        let mut picker =
703            ManualCompactionPicker::new(Arc::new(RangeOverlapStrategy::default()), option, 0);
704        assert!(
705            picker
706                .pick_compaction(
707                    &levels,
708                    &levels_handler,
709                    &mut LocalPickerStatistic::default()
710                )
711                .is_none()
712        );
713    }
714
715    #[test]
716    fn test_l0_basic() {
717        let (levels, levels_handler) = generate_test_levels();
718
719        // target_level == 0, None
720        let option = ManualCompactionOption {
721            sst_ids: vec![],
722            level: 0,
723            key_range: KeyRange {
724                left: Bytes::default(),
725                right: Bytes::default(),
726                right_exclusive: false,
727            },
728            internal_table_id: HashSet::default(),
729            target_level: None,
730            exclusive: false,
731        };
732        let mut picker = ManualCompactionPicker::new(
733            Arc::new(RangeOverlapStrategy::default()),
734            option.clone(),
735            0,
736        );
737        let mut local_stats = LocalPickerStatistic::default();
738        assert!(
739            picker
740                .pick_compaction(&levels, &levels_handler, &mut local_stats)
741                .is_none()
742        );
743
744        // pick_l0_to_base_level
745        let mut picker =
746            ManualCompactionPicker::new(Arc::new(RangeOverlapStrategy::default()), option, 1);
747        let mut expected = [vec![5, 6], vec![7, 8], vec![9, 10]];
748        expected.reverse();
749        let result = picker
750            .pick_compaction(&levels, &levels_handler, &mut local_stats)
751            .unwrap();
752        assert_eq!(result.input_levels.len(), 4);
753        assert!(is_l0_to_lbase(&result));
754        assert_eq!(result.target_level, 1);
755        for (l, e) in expected.iter().enumerate().take(3) {
756            assert_eq!(
757                result.input_levels[l]
758                    .table_infos
759                    .iter()
760                    .map(|s| s.sst_id)
761                    .collect_vec(),
762                *e
763            );
764        }
765        assert_eq!(
766            result.input_levels[3].table_infos,
767            vec![levels.levels[0].table_infos[0].clone()]
768        );
769
770        // pick_l0_to_base_level, filtered by key_range
771        let option = ManualCompactionOption {
772            sst_ids: vec![],
773            level: 0,
774            key_range: KeyRange {
775                left: Bytes::from(iterator_test_key_of_epoch(1, 0, 2)),
776                right: Bytes::from(iterator_test_key_of_epoch(1, 200, 2)),
777                right_exclusive: false,
778            },
779            internal_table_id: HashSet::default(),
780            target_level: None,
781            exclusive: false,
782        };
783        let mut picker =
784            ManualCompactionPicker::new(Arc::new(RangeOverlapStrategy::default()), option, 1);
785        let mut expected = [vec![5, 6], vec![7, 8]];
786        expected.reverse();
787        let result = picker
788            .pick_compaction(&levels, &levels_handler, &mut local_stats)
789            .unwrap();
790        assert_eq!(result.input_levels.len(), 3);
791        assert!(is_l0_to_lbase(&result));
792        assert_eq!(result.target_level, 1);
793        for (l, e) in expected.iter().enumerate().take(2) {
794            assert_eq!(
795                result.input_levels[l]
796                    .table_infos
797                    .iter()
798                    .map(|s| s.sst_id)
799                    .collect_vec(),
800                *e
801            );
802        }
803        assert_eq!(
804            result.input_levels[2].table_infos,
805            vec![levels.levels[0].table_infos[0].clone()]
806        );
807    }
808
809    #[test]
810    fn test_l0_to_l0_option_sst_ids() {
811        let (levels, levels_handler) = generate_test_levels();
812        // (input_level, sst_id_filter, expected_result_input_level_ssts)
813        let sst_id_filters = vec![
814            (0, vec![6], vec![vec![5, 6]]),
815            (0, vec![7], vec![vec![7, 8]]),
816            (0, vec![9], vec![vec![9, 10]]),
817            (0, vec![6, 9], vec![vec![5, 6], vec![7, 8], vec![9, 10]]),
818            (0, vec![8, 9], vec![vec![7, 8], vec![9, 10]]),
819            (0, vec![6, 8, 9], vec![vec![5, 6], vec![7, 8], vec![9, 10]]),
820        ];
821        let mut local_stats = LocalPickerStatistic::default();
822        for (input_level, sst_id_filter, expected) in &sst_id_filters {
823            let expected = expected.iter().rev().cloned().collect_vec();
824            let option = ManualCompactionOption {
825                sst_ids: sst_id_filter.iter().cloned().map(Into::into).collect(),
826                level: *input_level as _,
827                key_range: KeyRange {
828                    left: Bytes::default(),
829                    right: Bytes::default(),
830                    right_exclusive: false,
831                },
832                internal_table_id: HashSet::default(),
833                target_level: None,
834                exclusive: false,
835            };
836            let mut picker = ManualCompactionPicker::new(
837                Arc::new(RangeOverlapStrategy::default()),
838                option.clone(),
839                // l0 to l0 will ignore target_level
840                input_level + 1,
841            );
842            let result = picker
843                .pick_compaction(&levels, &levels_handler, &mut local_stats)
844                .unwrap();
845            assert!(is_l0_to_l0(&result));
846            assert_eq!(result.input_levels.len(), expected.len());
847            for (i, e) in expected.iter().enumerate().take(result.input_levels.len()) {
848                assert_eq!(
849                    result.input_levels[i]
850                        .table_infos
851                        .iter()
852                        .map(|s| s.sst_id)
853                        .collect_vec(),
854                    *e
855                );
856            }
857        }
858    }
859
860    #[test]
861    fn test_l0_to_lbase_option_internal_table() {
862        let (levels, mut levels_handler) = generate_test_levels();
863        let input_level = 0;
864        let target_level = input_level + 1;
865        let mut local_stats = LocalPickerStatistic::default();
866        {
867            let option = ManualCompactionOption {
868                sst_ids: vec![],
869                level: input_level,
870                key_range: KeyRange {
871                    left: Bytes::default(),
872                    right: Bytes::default(),
873                    right_exclusive: false,
874                },
875                // No matching internal table id.
876                internal_table_id: HashSet::from([100.into()]),
877                target_level: None,
878                exclusive: false,
879            };
880            let mut picker = ManualCompactionPicker::new(
881                Arc::new(RangeOverlapStrategy::default()),
882                option,
883                target_level,
884            );
885            assert!(
886                picker
887                    .pick_compaction(&levels, &levels_handler, &mut local_stats)
888                    .is_none()
889            )
890        }
891
892        {
893            let option = ManualCompactionOption {
894                sst_ids: vec![],
895                level: input_level,
896                key_range: KeyRange {
897                    left: Bytes::default(),
898                    right: Bytes::default(),
899                    right_exclusive: false,
900                },
901                // Include all sub level's table ids
902                internal_table_id: HashSet::from([1.into(), 2.into(), 3.into()]),
903                target_level: None,
904                exclusive: false,
905            };
906            let mut picker = ManualCompactionPicker::new(
907                Arc::new(RangeOverlapStrategy::default()),
908                option,
909                target_level,
910            );
911            let result = picker
912                .pick_compaction(&levels, &levels_handler, &mut local_stats)
913                .unwrap();
914            assert_eq!(result.input_levels.len(), 4);
915            assert!(is_l0_to_lbase(&result));
916            assert_eq!(result.target_level, 1);
917            assert!(is_l0_to_lbase(&result));
918            assert_eq!(
919                result
920                    .input_levels
921                    .iter()
922                    .take(3)
923                    .flat_map(|s| s.table_infos.clone())
924                    .map(|s| s.sst_id)
925                    .collect_vec(),
926                vec![9, 10, 7, 8, 5, 6]
927            );
928            assert_eq!(
929                result.input_levels[3]
930                    .table_infos
931                    .iter()
932                    .map(|s| s.sst_id)
933                    .collect_vec(),
934                vec![3]
935            );
936        }
937
938        {
939            let option = ManualCompactionOption {
940                sst_ids: vec![],
941                level: input_level,
942                key_range: KeyRange {
943                    left: Bytes::default(),
944                    right: Bytes::default(),
945                    right_exclusive: false,
946                },
947                // Only include bottom sub level's table id
948                internal_table_id: HashSet::from([3.into()]),
949                target_level: None,
950                exclusive: false,
951            };
952            let mut picker = ManualCompactionPicker::new(
953                Arc::new(RangeOverlapStrategy::default()),
954                option,
955                target_level,
956            );
957            let result = picker
958                .pick_compaction(&levels, &levels_handler, &mut local_stats)
959                .unwrap();
960            assert_eq!(result.input_levels.len(), 4);
961            assert!(is_l0_to_lbase(&result));
962            assert_eq!(
963                result
964                    .input_levels
965                    .iter()
966                    .take(3)
967                    .flat_map(|s| s.table_infos.clone())
968                    .map(|s| s.sst_id)
969                    .collect_vec(),
970                vec![9, 10, 7, 8, 5, 6]
971            );
972            assert_eq!(
973                result.input_levels[3]
974                    .table_infos
975                    .iter()
976                    .map(|s| s.sst_id)
977                    .collect_vec(),
978                vec![3]
979            );
980            assert_eq!(result.target_level, 1);
981        }
982
983        {
984            let option = ManualCompactionOption {
985                sst_ids: vec![],
986                level: input_level,
987                key_range: KeyRange {
988                    left: Bytes::default(),
989                    right: Bytes::default(),
990                    right_exclusive: false,
991                },
992                // Only include partial top sub level's table id, but the whole top sub level is
993                // picked.
994                internal_table_id: HashSet::from([1.into()]),
995                target_level: None,
996                exclusive: false,
997            };
998            let mut picker = ManualCompactionPicker::new(
999                Arc::new(RangeOverlapStrategy::default()),
1000                option,
1001                target_level,
1002            );
1003            let result = picker
1004                .pick_compaction(&levels, &levels_handler, &mut local_stats)
1005                .unwrap();
1006            result.add_pending_task(0, &mut levels_handler);
1007            assert_eq!(result.input_levels.len(), 2);
1008            assert!(is_l0_to_lbase(&result));
1009            assert_eq!(result.target_level, 1);
1010            assert_eq!(
1011                result
1012                    .input_levels
1013                    .iter()
1014                    .take(1)
1015                    .flat_map(|s| s.table_infos.clone())
1016                    .map(|s| s.sst_id)
1017                    .collect_vec(),
1018                vec![5, 6]
1019            );
1020            assert_eq!(
1021                result.input_levels[1]
1022                    .table_infos
1023                    .iter()
1024                    .map(|s| s.sst_id)
1025                    .collect_vec(),
1026                vec![3]
1027            );
1028
1029            // Pick bottom sub level while top sub level is pending
1030            let option = ManualCompactionOption {
1031                sst_ids: vec![],
1032                level: input_level,
1033                key_range: KeyRange {
1034                    left: Bytes::default(),
1035                    right: Bytes::default(),
1036                    right_exclusive: false,
1037                },
1038                // Only include bottom sub level's table id
1039                internal_table_id: HashSet::from([3.into()]),
1040                target_level: None,
1041                exclusive: false,
1042            };
1043            let mut picker = ManualCompactionPicker::new(
1044                Arc::new(RangeOverlapStrategy::default()),
1045                option,
1046                target_level,
1047            );
1048            // Because top sub-level is pending.
1049            assert!(
1050                picker
1051                    .pick_compaction(&levels, &levels_handler, &mut local_stats)
1052                    .is_none()
1053            );
1054
1055            clean_task_state(&mut levels_handler[0]);
1056            clean_task_state(&mut levels_handler[1]);
1057        }
1058    }
1059
1060    #[test]
1061    fn test_ln_to_lnext_option_internal_table() {
1062        let (levels, levels_handler) = generate_test_levels();
1063        let input_level = 1;
1064        let target_level = input_level + 1;
1065        let mut local_stats = LocalPickerStatistic::default();
1066        {
1067            let option = ManualCompactionOption {
1068                sst_ids: vec![],
1069                level: input_level,
1070                key_range: KeyRange {
1071                    left: Bytes::default(),
1072                    right: Bytes::default(),
1073                    right_exclusive: false,
1074                },
1075                // No matching internal table id.
1076                internal_table_id: HashSet::from([100.into()]),
1077                target_level: None,
1078                exclusive: false,
1079            };
1080            let mut picker = ManualCompactionPicker::new(
1081                Arc::new(RangeOverlapStrategy::default()),
1082                option,
1083                target_level,
1084            );
1085            assert!(
1086                picker
1087                    .pick_compaction(&levels, &levels_handler, &mut local_stats)
1088                    .is_none()
1089            )
1090        }
1091
1092        {
1093            let expected_input_level_sst_ids = [vec![4], vec![2]];
1094            let option = ManualCompactionOption {
1095                sst_ids: vec![],
1096                level: input_level,
1097                key_range: KeyRange {
1098                    left: Bytes::default(),
1099                    right: Bytes::default(),
1100                    right_exclusive: false,
1101                },
1102                // Only include partial input level's table id
1103                internal_table_id: HashSet::from([1.into()]),
1104                target_level: None,
1105                exclusive: false,
1106            };
1107            let mut picker = ManualCompactionPicker::new(
1108                Arc::new(RangeOverlapStrategy::default()),
1109                option,
1110                target_level,
1111            );
1112            let result = picker
1113                .pick_compaction(&levels, &levels_handler, &mut local_stats)
1114                .unwrap();
1115            assert_eq!(
1116                result.input_levels.len(),
1117                expected_input_level_sst_ids.len()
1118            );
1119            assert_eq!(result.target_level, target_level);
1120            for (l, e) in expected_input_level_sst_ids
1121                .iter()
1122                .enumerate()
1123                .take(result.input_levels.len())
1124            {
1125                assert_eq!(
1126                    result.input_levels[l]
1127                        .table_infos
1128                        .iter()
1129                        .map(|s| s.sst_id)
1130                        .collect_vec(),
1131                    *e
1132                );
1133            }
1134        }
1135    }
1136
1137    #[test]
1138    fn test_ln_to_lnext_option_sst_ids() {
1139        let (levels, levels_handler) = generate_test_levels();
1140        // (input_level, sst_id_filter, expected_result_input_level_ssts)
1141        let sst_id_filters = vec![
1142            (1, vec![3], vec![vec![3], vec![1]]),
1143            (1, vec![4], vec![vec![4], vec![2]]),
1144            (1, vec![3, 4], vec![vec![3, 4], vec![1, 2]]),
1145        ];
1146        let mut local_stats = LocalPickerStatistic::default();
1147        for (input_level, sst_id_filter, expected) in &sst_id_filters {
1148            let option = ManualCompactionOption {
1149                sst_ids: sst_id_filter.iter().cloned().map(Into::into).collect(),
1150                level: *input_level as _,
1151                key_range: KeyRange {
1152                    left: Bytes::default(),
1153                    right: Bytes::default(),
1154                    right_exclusive: false,
1155                },
1156                internal_table_id: HashSet::default(),
1157                target_level: None,
1158                exclusive: false,
1159            };
1160            let mut picker = ManualCompactionPicker::new(
1161                Arc::new(RangeOverlapStrategy::default()),
1162                option.clone(),
1163                input_level + 1,
1164            );
1165            let result = picker
1166                .pick_compaction(&levels, &levels_handler, &mut local_stats)
1167                .unwrap();
1168            assert_eq!(result.input_levels.len(), expected.len());
1169            for (i, e) in expected.iter().enumerate().take(result.input_levels.len()) {
1170                assert_eq!(
1171                    result.input_levels[i]
1172                        .table_infos
1173                        .iter()
1174                        .map(|s| s.sst_id)
1175                        .collect_vec(),
1176                    *e
1177                );
1178            }
1179        }
1180    }
1181
1182    #[test]
1183    fn test_ln_to_ln() {
1184        let (levels, levels_handler) = generate_intra_test_levels();
1185        // (input_level, sst_id_filter, expected_result_input_level_ssts)
1186        let sst_id_filters = vec![
1187            (1, vec![1], vec![vec![1], vec![]]),
1188            (1, vec![3], vec![vec![3], vec![]]),
1189            (1, vec![4], vec![vec![4], vec![]]),
1190            (1, vec![3, 4], vec![vec![3, 4], vec![]]),
1191            (1, vec![1, 4], vec![vec![1, 2, 3, 4], vec![]]),
1192            (1, vec![2, 4], vec![vec![2, 3, 4], vec![]]),
1193            (1, vec![1, 3], vec![vec![1, 2, 3], vec![]]),
1194        ];
1195        for (input_level, sst_id_filter, expected) in &sst_id_filters {
1196            let option = ManualCompactionOption {
1197                sst_ids: sst_id_filter.iter().cloned().map(Into::into).collect(),
1198                level: *input_level as _,
1199                key_range: KeyRange {
1200                    left: Bytes::default(),
1201                    right: Bytes::default(),
1202                    right_exclusive: false,
1203                },
1204                internal_table_id: HashSet::default(),
1205                target_level: None,
1206                exclusive: false,
1207            };
1208            let mut picker = ManualCompactionPicker::new(
1209                Arc::new(RangeOverlapStrategy::default()),
1210                option.clone(),
1211                *input_level as _,
1212            );
1213            let result = picker
1214                .pick_compaction(
1215                    &levels,
1216                    &levels_handler,
1217                    &mut LocalPickerStatistic::default(),
1218                )
1219                .unwrap();
1220            assert_eq!(result.input_levels.len(), expected.len());
1221            for (i, e) in expected.iter().enumerate().take(result.input_levels.len()) {
1222                assert_eq!(
1223                    result.input_levels[i]
1224                        .table_infos
1225                        .iter()
1226                        .map(|s| s.sst_id)
1227                        .collect_vec(),
1228                    *e
1229                );
1230            }
1231        }
1232    }
1233
1234    #[test]
1235    fn test_manual_compaction_selector_l0() {
1236        let config = CompactionConfigBuilder::new().max_level(4).build();
1237        let group_config = CompactionGroup::new(1, config);
1238        let l0 = generate_l0_nonoverlapping_sublevels(vec![
1239            generate_table(0, 1, 0, 500, 1),
1240            generate_table(1, 1, 0, 500, 1),
1241        ]);
1242        assert_eq!(l0.sub_levels.len(), 2);
1243        let levels = vec![
1244            generate_level(1, vec![]),
1245            generate_level(2, vec![]),
1246            generate_level(3, vec![]),
1247            Level {
1248                level_idx: 4,
1249                level_type: LevelType::Nonoverlapping,
1250                table_infos: vec![
1251                    generate_table(2, 1, 0, 100, 1),
1252                    generate_table(3, 1, 101, 200, 1),
1253                    generate_table(4, 1, 222, 300, 1),
1254                ],
1255                ..Default::default()
1256            },
1257        ];
1258        assert_eq!(levels.len(), 4);
1259        let levels = Levels {
1260            levels,
1261            l0,
1262            ..Default::default()
1263        };
1264        let mut levels_handler = (0..5).map(LevelHandler::new).collect_vec();
1265        let mut local_stats = LocalSelectorStatistic::default();
1266
1267        // pick_l0_to_sub_level
1268        {
1269            let option = ManualCompactionOption {
1270                sst_ids: [0, 1].iter().cloned().map(Into::into).collect(),
1271                key_range: KeyRange {
1272                    left: Bytes::default(),
1273                    right: Bytes::default(),
1274                    right_exclusive: false,
1275                },
1276                internal_table_id: HashSet::default(),
1277                level: 0,
1278                target_level: None,
1279                exclusive: false,
1280            };
1281            let mut selector = ManualCompactionSelector::new(option);
1282            let task = selector
1283                .pick_compaction(
1284                    1,
1285                    compaction_selector_context(
1286                        &group_config,
1287                        &levels,
1288                        &BTreeSet::new(),
1289                        &mut levels_handler,
1290                        &mut local_stats,
1291                        &HashMap::default(),
1292                        Arc::new(CompactionDeveloperConfig::default()),
1293                        &Default::default(),
1294                        &HummockVersionStateTableInfo::empty(),
1295                    ),
1296                )
1297                .unwrap();
1298            assert_compaction_task(&task, &levels_handler);
1299            assert_eq!(task.input.input_levels.len(), 2);
1300            assert_eq!(task.input.input_levels[0].level_idx, 0);
1301            assert_eq!(task.input.input_levels[1].level_idx, 0);
1302            assert_eq!(task.input.target_level, 0);
1303        }
1304
1305        for level_handler in &mut levels_handler {
1306            for pending_task_id in &level_handler.pending_tasks_ids() {
1307                level_handler.remove_task(*pending_task_id);
1308            }
1309        }
1310
1311        // pick_l0_to_base_level
1312        {
1313            let option = ManualCompactionOption {
1314                sst_ids: vec![],
1315                key_range: KeyRange {
1316                    left: Bytes::default(),
1317                    right: Bytes::default(),
1318                    right_exclusive: false,
1319                },
1320                internal_table_id: HashSet::default(),
1321                level: 0,
1322                target_level: None,
1323                exclusive: false,
1324            };
1325            let mut selector = ManualCompactionSelector::new(option);
1326            let task = selector
1327                .pick_compaction(
1328                    2,
1329                    compaction_selector_context(
1330                        &group_config,
1331                        &levels,
1332                        &BTreeSet::new(),
1333                        &mut levels_handler,
1334                        &mut local_stats,
1335                        &HashMap::default(),
1336                        Arc::new(CompactionDeveloperConfig::default()),
1337                        &Default::default(),
1338                        &HummockVersionStateTableInfo::empty(),
1339                    ),
1340                )
1341                .unwrap();
1342            assert_compaction_task(&task, &levels_handler);
1343            assert_eq!(task.input.input_levels.len(), 3);
1344            assert_eq!(task.input.input_levels[0].level_idx, 0);
1345            assert_eq!(task.input.input_levels[1].level_idx, 0);
1346            assert_eq!(task.input.input_levels[2].level_idx, 4);
1347            assert_eq!(task.input.target_level, 4);
1348        }
1349    }
1350
1351    #[test]
1352    fn test_manual_compaction_selector_rejects_l0_non_base_target_level() {
1353        let config = CompactionConfigBuilder::new().max_level(4).build();
1354        let group_config = CompactionGroup::new(1, config);
1355        let l0 = generate_l0_nonoverlapping_sublevels(vec![
1356            generate_table(0, 1, 0, 500, 1),
1357            generate_table(1, 1, 0, 500, 1),
1358        ]);
1359        let levels = Levels {
1360            levels: vec![
1361                generate_level(1, vec![]),
1362                generate_level(2, vec![]),
1363                generate_level(3, vec![generate_table(2, 1, 0, 500, 1)]),
1364                generate_level(4, vec![]),
1365            ],
1366            l0,
1367            ..Default::default()
1368        };
1369        let mut levels_handler = (0..5).map(LevelHandler::new).collect_vec();
1370        let mut local_stats = LocalSelectorStatistic::default();
1371        let option = ManualCompactionOption {
1372            sst_ids: vec![],
1373            key_range: KeyRange {
1374                left: Bytes::default(),
1375                right: Bytes::default(),
1376                right_exclusive: false,
1377            },
1378            internal_table_id: HashSet::default(),
1379            level: 0,
1380            target_level: Some(4),
1381            exclusive: false,
1382        };
1383        let mut selector = ManualCompactionSelector::new(option);
1384
1385        assert!(
1386            selector
1387                .pick_compaction(
1388                    1,
1389                    compaction_selector_context(
1390                        &group_config,
1391                        &levels,
1392                        &BTreeSet::new(),
1393                        &mut levels_handler,
1394                        &mut local_stats,
1395                        &HashMap::default(),
1396                        Arc::new(CompactionDeveloperConfig::default()),
1397                        &Default::default(),
1398                        &HummockVersionStateTableInfo::empty(),
1399                    ),
1400                )
1401                .is_none()
1402        );
1403        assert_eq!(
1404            selector.validation_error(),
1405            Some("target_level for L0 must be 3, got 4")
1406        );
1407    }
1408
1409    /// tests `DynamicLevelSelector::manual_pick_compaction`
1410    #[test]
1411    fn test_manual_compaction_selector() {
1412        let config = CompactionConfigBuilder::new().max_level(4).build();
1413        let group_config = CompactionGroup::new(1, config);
1414        let l0 = generate_l0_nonoverlapping_sublevels(vec![]);
1415        assert_eq!(l0.sub_levels.len(), 0);
1416        let levels = vec![
1417            generate_level(1, vec![]),
1418            generate_level(2, vec![]),
1419            generate_level(
1420                3,
1421                vec![
1422                    generate_table(0, 1, 150, 151, 1),
1423                    generate_table(1, 1, 250, 251, 1),
1424                ],
1425            ),
1426            Level {
1427                level_idx: 4,
1428                level_type: LevelType::Nonoverlapping,
1429                table_infos: vec![
1430                    generate_table(2, 1, 0, 100, 1),
1431                    generate_table(3, 1, 101, 200, 1),
1432                    generate_table(4, 1, 222, 300, 1),
1433                    generate_table(5, 1, 333, 400, 1),
1434                    generate_table(6, 1, 444, 500, 1),
1435                    generate_table(7, 1, 555, 600, 1),
1436                ],
1437                ..Default::default()
1438            },
1439        ];
1440        assert_eq!(levels.len(), 4);
1441        let levels = Levels {
1442            levels,
1443            l0,
1444            ..Default::default()
1445        };
1446        let mut levels_handler = (0..5).map(LevelHandler::new).collect_vec();
1447        let mut local_stats = LocalSelectorStatistic::default();
1448
1449        // pick l3 -> l4
1450        {
1451            let option = ManualCompactionOption {
1452                sst_ids: [0, 1].iter().cloned().map(Into::into).collect(),
1453                key_range: KeyRange {
1454                    left: Bytes::default(),
1455                    right: Bytes::default(),
1456                    right_exclusive: false,
1457                },
1458                internal_table_id: HashSet::default(),
1459                level: 3,
1460                target_level: None,
1461                exclusive: false,
1462            };
1463            let mut selector = ManualCompactionSelector::new(option);
1464            let task = selector
1465                .pick_compaction(
1466                    1,
1467                    compaction_selector_context(
1468                        &group_config,
1469                        &levels,
1470                        &BTreeSet::new(),
1471                        &mut levels_handler,
1472                        &mut local_stats,
1473                        &HashMap::default(),
1474                        Arc::new(CompactionDeveloperConfig::default()),
1475                        &Default::default(),
1476                        &HummockVersionStateTableInfo::empty(),
1477                    ),
1478                )
1479                .unwrap();
1480            assert_compaction_task(&task, &levels_handler);
1481            assert_eq!(task.input.input_levels.len(), 2);
1482            assert_eq!(task.input.input_levels[0].level_idx, 3);
1483            assert_eq!(task.input.input_levels[0].table_infos.len(), 2);
1484            assert_eq!(task.input.input_levels[1].level_idx, 4);
1485            assert_eq!(task.input.input_levels[1].table_infos.len(), 2);
1486            assert_eq!(task.input.target_level, 4);
1487        }
1488
1489        for level_handler in &mut levels_handler {
1490            for pending_task_id in &level_handler.pending_tasks_ids() {
1491                level_handler.remove_task(*pending_task_id);
1492            }
1493        }
1494
1495        // pick l3 -> l3
1496        {
1497            let option = ManualCompactionOption {
1498                sst_ids: [0, 1].iter().cloned().map(Into::into).collect(),
1499                key_range: KeyRange {
1500                    left: Bytes::default(),
1501                    right: Bytes::default(),
1502                    right_exclusive: false,
1503                },
1504                internal_table_id: HashSet::default(),
1505                level: 3,
1506                target_level: Some(3),
1507                exclusive: false,
1508            };
1509            let mut selector = ManualCompactionSelector::new(option);
1510            let task = selector
1511                .pick_compaction(
1512                    2,
1513                    compaction_selector_context(
1514                        &group_config,
1515                        &levels,
1516                        &BTreeSet::new(),
1517                        &mut levels_handler,
1518                        &mut local_stats,
1519                        &HashMap::default(),
1520                        Arc::new(CompactionDeveloperConfig::default()),
1521                        &Default::default(),
1522                        &HummockVersionStateTableInfo::empty(),
1523                    ),
1524                )
1525                .unwrap();
1526            assert_compaction_task(&task, &levels_handler);
1527            assert_eq!(task.input.input_levels.len(), 2);
1528            assert_eq!(task.input.input_levels[0].level_idx, 3);
1529            assert_eq!(task.input.input_levels[0].table_infos.len(), 2);
1530            assert_eq!(task.input.input_levels[1].level_idx, 3);
1531            assert_eq!(task.input.input_levels[1].table_infos.len(), 0);
1532            assert_eq!(task.input.target_level, 3);
1533        }
1534
1535        for level_handler in &mut levels_handler {
1536            for pending_task_id in &level_handler.pending_tasks_ids() {
1537                level_handler.remove_task(*pending_task_id);
1538            }
1539        }
1540
1541        // pick l4 -> l4
1542        {
1543            let option = ManualCompactionOption {
1544                sst_ids: vec![],
1545                key_range: KeyRange {
1546                    left: Bytes::default(),
1547                    right: Bytes::default(),
1548                    right_exclusive: false,
1549                },
1550                internal_table_id: HashSet::default(),
1551                level: 4,
1552                target_level: None,
1553                exclusive: false,
1554            };
1555            let mut selector = ManualCompactionSelector::new(option);
1556            let task = selector
1557                .pick_compaction(
1558                    1,
1559                    compaction_selector_context(
1560                        &group_config,
1561                        &levels,
1562                        &BTreeSet::new(),
1563                        &mut levels_handler,
1564                        &mut local_stats,
1565                        &HashMap::default(),
1566                        Arc::new(CompactionDeveloperConfig::default()),
1567                        &Default::default(),
1568                        &HummockVersionStateTableInfo::empty(),
1569                    ),
1570                )
1571                .unwrap();
1572            assert_compaction_task(&task, &levels_handler);
1573            assert_eq!(task.input.input_levels.len(), 2);
1574            assert_eq!(task.input.input_levels[0].level_idx, 4);
1575            assert_eq!(task.input.input_levels[0].table_infos.len(), 6);
1576            assert_eq!(task.input.input_levels[1].level_idx, 4);
1577            assert_eq!(task.input.input_levels[1].table_infos.len(), 0);
1578            assert_eq!(task.input.target_level, 4);
1579            assert!(matches!(
1580                task.compaction_task_type,
1581                compact_task::TaskType::Manual
1582            ));
1583        }
1584    }
1585}