1use std::fmt::Debug;
16use std::sync::Arc;
17
18use api::v1::region::compact_request;
19use common_time::range::TimestampRange;
20use common_time::{TimeToLive, Timestamp};
21use serde::{Deserialize, Serialize};
22
23use crate::compaction::compactor::CompactionRegion;
24use crate::compaction::last_non_null::LastNonNullPicker;
25use crate::compaction::twcs::TwcsPicker;
26use crate::compaction::window::WindowedCompactionPicker;
27use crate::compaction::{CompactionOutput, SerializedCompactionOutput};
28use crate::error::Result;
29use crate::region::options::{CompactionOptions, MergeMode, RegionOptions};
30use crate::sst::file::{FileHandle, FileMeta};
31use crate::sst::file_purger::FilePurger;
32use crate::sst::version::LevelMeta;
33
34#[async_trait::async_trait]
37pub trait Picker: Debug + Send + Sync + 'static {
38 async fn pick(&self, compaction_region: &CompactionRegion) -> Result<Option<PickerOutput>>;
40}
41
42#[derive(Default, Clone, Debug)]
45pub struct PickerOutput {
46 pub outputs: Vec<CompactionOutput>,
47 pub expired_ssts: Vec<FileHandle>,
48 pub time_window_size: i64,
49 pub max_file_size: Option<usize>,
51}
52
53#[derive(Default, Clone, Debug, Serialize, Deserialize)]
55pub struct SerializedPickerOutput {
56 pub outputs: Vec<SerializedCompactionOutput>,
57 pub expired_ssts: Vec<FileMeta>,
58 pub time_window_size: i64,
59 pub max_file_size: Option<usize>,
60}
61
62impl From<&PickerOutput> for SerializedPickerOutput {
63 fn from(input: &PickerOutput) -> Self {
64 let outputs = input
65 .outputs
66 .iter()
67 .map(|output| SerializedCompactionOutput {
68 output_level: output.output_level,
69 inputs: output.inputs.iter().map(|s| s.meta_ref().clone()).collect(),
70 filter_deleted: output.filter_deleted,
71 output_time_range: output.output_time_range,
72 })
73 .collect();
74 let expired_ssts = input
75 .expired_ssts
76 .iter()
77 .map(|s| s.meta_ref().clone())
78 .collect();
79 Self {
80 outputs,
81 expired_ssts,
82 time_window_size: input.time_window_size,
83 max_file_size: input.max_file_size,
84 }
85 }
86}
87
88impl PickerOutput {
89 pub fn from_serialized(
92 input: SerializedPickerOutput,
93 file_purger: Arc<dyn FilePurger>,
94 ) -> Self {
95 let outputs = input
96 .outputs
97 .into_iter()
98 .map(|output| CompactionOutput {
99 output_level: output.output_level,
100 inputs: output
101 .inputs
102 .into_iter()
103 .map(|file_meta| FileHandle::new(file_meta, file_purger.clone()))
104 .collect(),
105 filter_deleted: output.filter_deleted,
106 output_time_range: output.output_time_range,
107 })
108 .collect();
109
110 let expired_ssts = input
111 .expired_ssts
112 .into_iter()
113 .map(|file_meta| FileHandle::new(file_meta, file_purger.clone()))
114 .collect();
115
116 Self {
117 outputs,
118 expired_ssts,
119 time_window_size: input.time_window_size,
120 max_file_size: input.max_file_size,
121 }
122 }
123}
124
125pub fn new_picker(
127 compact_request_options: &compact_request::Options,
128 region_options: &RegionOptions,
129 max_background_tasks: Option<usize>,
130 time_range: Option<TimestampRange>,
131) -> Arc<dyn Picker> {
132 if let compact_request::Options::StrictWindow(window) = compact_request_options {
133 let window = if window.window_seconds == 0 {
134 None
135 } else {
136 Some(window.window_seconds)
137 };
138 Arc::new(WindowedCompactionPicker::new(window).with_time_range(time_range)) as Arc<_>
141 } else {
142 let picker = match ®ion_options.compaction {
143 CompactionOptions::Twcs(twcs_opts) => TwcsPicker {
144 trigger_file_num: twcs_opts.active_window_trigger_file_num,
145 active_window_l1_merge_trigger: twcs_opts.active_window_l1_merge_trigger,
146 inactive_window_trigger_file_num: twcs_opts.inactive_window_trigger_file_num,
147 inactive_window_l1_merge_trigger: twcs_opts.inactive_window_l1_merge_trigger,
148 time_window_seconds: twcs_opts.time_window_seconds(),
149 max_output_file_size: twcs_opts.max_output_file_size.map(|r| r.as_bytes()),
150 append_mode: region_options.append_mode,
151 max_background_tasks,
152 time_range,
153 },
154 };
155 if region_options.merge_mode() == MergeMode::LastNonNull {
156 Arc::new(LastNonNullPicker::new(picker))
159 } else {
160 Arc::new(picker)
161 }
162 }
163}
164
165pub(super) fn get_expired_ssts(
167 levels: &[LevelMeta],
168 ttl: Option<TimeToLive>,
169 now: Timestamp,
170) -> Vec<FileHandle> {
171 let Some(ttl) = ttl else {
172 return vec![];
173 };
174
175 levels
176 .iter()
177 .flat_map(|l| l.get_expired_files(&now, &ttl).into_iter())
178 .collect()
179}
180
181#[cfg(test)]
182mod tests {
183 use store_api::storage::FileId;
184
185 use super::*;
186 use crate::compaction::test_util::new_file_handle;
187 use crate::test_util::new_noop_file_purger;
188
189 #[test]
190 fn test_picker_output_serialization() {
191 let inputs_file_handle = vec![
192 new_file_handle(FileId::random(), 0, 999, 0),
193 new_file_handle(FileId::random(), 0, 999, 0),
194 new_file_handle(FileId::random(), 0, 999, 0),
195 ];
196 let expired_ssts_file_handle = vec![
197 new_file_handle(FileId::random(), 0, 999, 0),
198 new_file_handle(FileId::random(), 0, 999, 0),
199 ];
200
201 let picker_output = PickerOutput {
202 outputs: vec![
203 CompactionOutput {
204 output_level: 0,
205 inputs: inputs_file_handle.clone(),
206 filter_deleted: false,
207 output_time_range: None,
208 },
209 CompactionOutput {
210 output_level: 0,
211 inputs: inputs_file_handle.clone(),
212 filter_deleted: false,
213 output_time_range: None,
214 },
215 ],
216 expired_ssts: expired_ssts_file_handle.clone(),
217 time_window_size: 1000,
218 max_file_size: None,
219 };
220
221 let picker_output_str =
222 serde_json::to_string(&SerializedPickerOutput::from(&picker_output)).unwrap();
223 let serialized_picker_output: SerializedPickerOutput =
224 serde_json::from_str(&picker_output_str).unwrap();
225 let picker_output_from_serialized =
226 PickerOutput::from_serialized(serialized_picker_output, new_noop_file_purger());
227
228 picker_output
229 .expired_ssts
230 .iter()
231 .zip(picker_output_from_serialized.expired_ssts.iter())
232 .for_each(|(expected, actual)| {
233 assert_eq!(expected.meta_ref(), actual.meta_ref());
234 });
235
236 picker_output
237 .outputs
238 .iter()
239 .zip(picker_output_from_serialized.outputs.iter())
240 .for_each(|(expected, actual)| {
241 assert_eq!(expected.output_level, actual.output_level);
242 expected
243 .inputs
244 .iter()
245 .zip(actual.inputs.iter())
246 .for_each(|(expected, actual)| {
247 assert_eq!(expected.meta_ref(), actual.meta_ref());
248 });
249 assert_eq!(expected.filter_deleted, actual.filter_deleted);
250 assert_eq!(expected.output_time_range, actual.output_time_range);
251 });
252 }
253}