1use api::v1::region::compact_request;
16use common_telemetry::{debug, error, info};
17use store_api::logstore::LogStore;
18use store_api::region_request::RegionCompactRequest;
19use store_api::storage::RegionId;
20
21use crate::compaction::{CompactionPickFinished, CompactionTransition};
22use crate::config::IndexBuildMode;
23use crate::error::{RegionNotFoundSnafu, StaleCompactionExecutionSnafu};
24use crate::metrics::COMPACTION_REQUEST_COUNT;
25use crate::region::MitoRegionRef;
26use crate::request::{
27 BuildIndexRequest, CompactionFailed, CompactionFinished, CompactionUnitNotification, OnFailure,
28 OptionOutputTx,
29};
30use crate::sst::index::IndexBuildType;
31use crate::worker::RegionWorkerLoop;
32
33fn made_progress(files_to_add: usize, files_to_remove: usize) -> bool {
34 files_to_add > 0 || files_to_remove > files_to_add
35}
36
37impl<S> RegionWorkerLoop<S> {
38 pub(crate) async fn handle_compaction_unit(
40 &mut self,
41 region_id: RegionId,
42 notify: CompactionUnitNotification,
43 ) where
44 S: LogStore,
45 {
46 match notify {
47 CompactionUnitNotification::Finished {
48 plan_id,
49 result,
50 applied,
51 } => {
52 let Some(region) = self.regions.get_region(region_id) else {
53 return;
54 };
55 match result {
56 Ok(edit) => {
57 if !self.compaction_scheduler.can_apply_unit(region_id, plan_id) {
58 let _ = applied.send(());
59 return;
60 }
61 let progress =
62 made_progress(edit.files_to_add.len(), edit.files_to_remove.len());
63 region.version_control.apply_edit(
64 Some(edit.clone()),
65 &[],
66 region.file_purger.clone(),
67 );
68 self.listener.on_compaction_unit_applied(region_id, plan_id);
69 self.compaction_scheduler.on_unit_finished(
70 region_id,
71 plan_id,
72 Ok(progress),
73 );
74 if self.config.index.build_mode == IndexBuildMode::Async
75 && !edit.files_to_add.is_empty()
76 {
77 self.handle_rebuild_index(
78 BuildIndexRequest {
79 region_id,
80 build_type: IndexBuildType::Compact,
81 file_metas: edit.files_to_add,
82 },
83 OptionOutputTx::new(None),
84 )
85 .await;
86 }
87 }
88 Err(err) => {
89 self.compaction_scheduler
90 .on_unit_finished(region_id, plan_id, Err(err));
91 }
92 }
93 let _ = applied.send(());
94 }
95 CompactionUnitNotification::Released { plan_id } => {
96 let Some(region) = self.regions.get_region(region_id) else {
97 return;
98 };
99 let transition = self
100 .compaction_scheduler
101 .on_unit_released(
102 region_id,
103 plan_id,
104 ®ion.manifest_ctx,
105 self.schema_metadata_manager.clone(),
106 )
107 .await;
108 self.listener.on_compaction_result_notified(region_id).await;
109 match transition {
110 CompactionTransition::AutomaticFollowupScheduled => {
111 region.update_schedule_compaction_millis()
112 }
113 CompactionTransition::DdlReady(mut ddls) => {
114 self.handle_ddl_requests(&mut ddls).await
115 }
116 CompactionTransition::NoAction => {}
117 }
118 }
119 }
120 }
121
122 pub(crate) async fn handle_compaction_pick_finished(
123 &mut self,
124 region_id: RegionId,
125 request: CompactionPickFinished,
126 ) where
127 S: LogStore,
128 {
129 let Some(region) = self.regions.get_region(region_id) else {
130 return;
131 };
132 let transition = self
137 .compaction_scheduler
138 .handle_compaction_pick_finished(
139 request,
140 ®ion.manifest_ctx,
141 self.schema_metadata_manager.clone(),
142 )
143 .await;
144 match transition {
145 CompactionTransition::AutomaticFollowupScheduled => {
146 region.update_schedule_compaction_millis();
149 }
150 CompactionTransition::NoAction => {}
151 CompactionTransition::DdlReady(mut pending_ddls) => {
152 if !pending_ddls.is_empty() {
153 self.listener.on_compaction_result_notified(region_id).await;
156 self.handle_ddl_requests(&mut pending_ddls).await;
157 }
158 }
159 }
160 }
161
162 pub(crate) async fn handle_compaction_request(
164 &mut self,
165 region_id: RegionId,
166 req: RegionCompactRequest,
167 mut sender: OptionOutputTx,
168 ) {
169 let Some(region) = self.regions.writable_region_or(region_id, &mut sender) else {
170 return;
171 };
172 COMPACTION_REQUEST_COUNT.inc();
173 let parallelism = req.parallelism.unwrap_or(1) as usize;
174 match self.compaction_scheduler.schedule_manual_compaction(
175 req.options,
176 ®ion.version_control,
177 ®ion.access_layer,
178 sender,
179 ®ion.manifest_ctx,
180 self.schema_metadata_manager.clone(),
181 parallelism,
182 req.time_range,
183 ) {
184 Ok(true) => info!(
188 "Successfully scheduled compaction task for region: {}",
189 region_id
190 ),
191 Ok(false) => {}
192 Err(e) => {
193 error!(e; "Failed to schedule compaction task for region: {}", region_id);
194 }
195 }
196 }
197
198 pub(crate) async fn handle_compaction_finished(
200 &mut self,
201 region_id: RegionId,
202 mut request: CompactionFinished,
203 ) where
204 S: LogStore,
205 {
206 let region = match self.regions.get_region(region_id) {
207 Some(region) => region,
208 None => {
209 request.on_failure(RegionNotFoundSnafu { region_id }.build());
210 return;
211 }
212 };
213 if !self
215 .compaction_scheduler
216 .is_current_execution(region_id, &request.execution)
217 {
218 request.on_failure(StaleCompactionExecutionSnafu { region_id }.build());
219 return;
220 }
221 let execution = request.execution.clone();
222 let made_progress = made_progress(
223 request.edit.files_to_add.len(),
224 request.edit.files_to_remove.len(),
225 );
226
227 region.version_control.apply_edit(
228 Some(request.edit.clone()),
229 &[],
230 region.file_purger.clone(),
231 );
232
233 let index_build_file_metas = std::mem::take(&mut request.edit.files_to_add);
234
235 request.on_success();
237 self.listener.on_compaction_result_notified(region_id).await;
238
239 if self.config.index.build_mode == IndexBuildMode::Async
241 && !index_build_file_metas.is_empty()
242 {
243 self.handle_rebuild_index(
244 BuildIndexRequest {
245 region_id,
246 build_type: IndexBuildType::Compact,
247 file_metas: index_build_file_metas,
248 },
249 OptionOutputTx::new(None),
250 )
251 .await;
252 }
253
254 let transition = self
256 .compaction_scheduler
257 .on_execution_finished(
258 region_id,
259 &execution,
260 ®ion.manifest_ctx,
261 self.schema_metadata_manager.clone(),
262 made_progress,
263 )
264 .await;
265 match transition {
266 CompactionTransition::AutomaticFollowupScheduled => {
267 region.update_schedule_compaction_millis();
268 }
269 CompactionTransition::NoAction => {}
270 CompactionTransition::DdlReady(mut pending_ddls) => {
271 self.handle_ddl_requests(&mut pending_ddls).await;
272 }
273 }
274 }
275
276 pub(crate) async fn handle_compaction_failure(&mut self, req: CompactionFailed) {
278 if self.regions.get_region(req.region_id).is_none() {
279 return;
280 }
281 if !self
282 .compaction_scheduler
283 .is_current_execution(req.region_id, &req.execution)
284 {
285 debug!(
286 "Ignores stale compaction failure for region {}: {:?}",
287 req.region_id, req.err
288 );
289 return;
290 }
291
292 error!(req.err; "Failed to compact region: {}", req.region_id);
293 self.compaction_scheduler
294 .on_execution_failed(req.region_id, &req.execution, req.err);
295 }
296
297 pub(crate) async fn schedule_compaction(&mut self, region: &MitoRegionRef) {
299 if region.is_staging() || region.is_enter_staging() || region.is_truncating() {
300 info!(
301 "Region {} is staging, entering staging, or truncating, skip compaction",
302 region.region_id
303 );
304 return;
305 }
306 let now = self.time_provider.current_time_millis();
307 if now - region.last_schedule_compaction_millis()
308 >= self.config.min_compaction_interval.as_millis() as i64
309 {
310 debug!(
311 "minimal compaction interval time {:?} has passed, scheduling next compaction",
312 self.config.min_compaction_interval
313 );
314 match self.compaction_scheduler.schedule_automatic_compaction(
315 compact_request::Options::Regular(Default::default()),
316 ®ion.version_control,
317 ®ion.access_layer,
318 ®ion.manifest_ctx,
319 self.schema_metadata_manager.clone(),
320 ) {
321 Ok(true) => region.update_schedule_compaction_millis(),
322 Ok(false) => {}
323 Err(e) => {
324 error!(e; "Failed to schedule compaction for region: {}", region.region_id)
325 }
326 }
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::made_progress;
334
335 #[test]
336 fn test_nonempty_output_or_file_reduction_is_progress() {
337 for (files_to_add, files_to_remove, expected) in [
338 (3, 3, true), (1, 3, true), (0, 3, true), (0, 0, false), (3, 2, true), ] {
344 assert_eq!(
345 expected,
346 made_progress(files_to_add, files_to_remove),
347 "files_to_remove: {files_to_remove}, files_to_add: {files_to_add}"
348 );
349 }
350 }
351}