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 result_senders: Vec::new(),
83 },
84 OptionOutputTx::new(None),
85 )
86 .await;
87 }
88 }
89 Err(err) => {
90 self.compaction_scheduler
91 .on_unit_finished(region_id, plan_id, Err(err));
92 }
93 }
94 let _ = applied.send(());
95 }
96 CompactionUnitNotification::Released { plan_id } => {
97 let Some(region) = self.regions.get_region(region_id) else {
98 return;
99 };
100 let transition = self
101 .compaction_scheduler
102 .on_unit_released(
103 region_id,
104 plan_id,
105 ®ion.manifest_ctx,
106 self.schema_metadata_manager.clone(),
107 )
108 .await;
109 self.listener.on_compaction_result_notified(region_id).await;
110 match transition {
111 CompactionTransition::AutomaticFollowupScheduled => {
112 region.update_schedule_compaction_millis()
113 }
114 CompactionTransition::DdlReady(mut ddls) => {
115 self.handle_ddl_requests(&mut ddls).await
116 }
117 CompactionTransition::NoAction => {}
118 }
119 }
120 }
121 }
122
123 pub(crate) async fn handle_compaction_pick_finished(
124 &mut self,
125 region_id: RegionId,
126 request: CompactionPickFinished,
127 ) where
128 S: LogStore,
129 {
130 let Some(region) = self.regions.get_region(region_id) else {
131 return;
132 };
133 let transition = self
138 .compaction_scheduler
139 .handle_compaction_pick_finished(
140 request,
141 ®ion.manifest_ctx,
142 self.schema_metadata_manager.clone(),
143 )
144 .await;
145 match transition {
146 CompactionTransition::AutomaticFollowupScheduled => {
147 region.update_schedule_compaction_millis();
150 }
151 CompactionTransition::NoAction => {}
152 CompactionTransition::DdlReady(mut pending_ddls) => {
153 if !pending_ddls.is_empty() {
154 self.listener.on_compaction_result_notified(region_id).await;
157 self.handle_ddl_requests(&mut pending_ddls).await;
158 }
159 }
160 }
161 }
162
163 pub(crate) async fn handle_compaction_request(
165 &mut self,
166 region_id: RegionId,
167 req: RegionCompactRequest,
168 mut sender: OptionOutputTx,
169 ) {
170 let Some(region) = self.regions.writable_region_or(region_id, &mut sender) else {
171 return;
172 };
173 COMPACTION_REQUEST_COUNT.inc();
174 let parallelism = req.parallelism.unwrap_or(1) as usize;
175 match self.compaction_scheduler.schedule_manual_compaction(
176 req.options,
177 ®ion.version_control,
178 ®ion.access_layer,
179 sender,
180 ®ion.manifest_ctx,
181 self.schema_metadata_manager.clone(),
182 parallelism,
183 req.time_range,
184 ) {
185 Ok(true) => info!(
189 "Successfully scheduled compaction task for region: {}",
190 region_id
191 ),
192 Ok(false) => {}
193 Err(e) => {
194 error!(e; "Failed to schedule compaction task for region: {}", region_id);
195 }
196 }
197 }
198
199 pub(crate) async fn handle_compaction_finished(
201 &mut self,
202 region_id: RegionId,
203 mut request: CompactionFinished,
204 ) where
205 S: LogStore,
206 {
207 let region = match self.regions.get_region(region_id) {
208 Some(region) => region,
209 None => {
210 request.on_failure(RegionNotFoundSnafu { region_id }.build());
211 return;
212 }
213 };
214 if !self
216 .compaction_scheduler
217 .is_current_execution(region_id, &request.execution)
218 {
219 request.on_failure(StaleCompactionExecutionSnafu { region_id }.build());
220 return;
221 }
222 let execution = request.execution.clone();
223 let made_progress = made_progress(
224 request.edit.files_to_add.len(),
225 request.edit.files_to_remove.len(),
226 );
227
228 region.version_control.apply_edit(
229 Some(request.edit.clone()),
230 &[],
231 region.file_purger.clone(),
232 );
233
234 let index_build_file_metas = std::mem::take(&mut request.edit.files_to_add);
235
236 request.on_success();
238 self.listener.on_compaction_result_notified(region_id).await;
239
240 if self.config.index.build_mode == IndexBuildMode::Async
242 && !index_build_file_metas.is_empty()
243 {
244 self.handle_rebuild_index(
245 BuildIndexRequest {
246 region_id,
247 build_type: IndexBuildType::Compact,
248 file_metas: index_build_file_metas,
249 result_senders: Vec::new(),
250 },
251 OptionOutputTx::new(None),
252 )
253 .await;
254 }
255
256 let transition = self
258 .compaction_scheduler
259 .on_execution_finished(
260 region_id,
261 &execution,
262 ®ion.manifest_ctx,
263 self.schema_metadata_manager.clone(),
264 made_progress,
265 )
266 .await;
267 match transition {
268 CompactionTransition::AutomaticFollowupScheduled => {
269 region.update_schedule_compaction_millis();
270 }
271 CompactionTransition::NoAction => {}
272 CompactionTransition::DdlReady(mut pending_ddls) => {
273 self.handle_ddl_requests(&mut pending_ddls).await;
274 }
275 }
276 }
277
278 pub(crate) async fn handle_compaction_failure(&mut self, req: CompactionFailed) {
280 if self.regions.get_region(req.region_id).is_none() {
281 return;
282 }
283 if !self
284 .compaction_scheduler
285 .is_current_execution(req.region_id, &req.execution)
286 {
287 debug!(
288 "Ignores stale compaction failure for region {}: {:?}",
289 req.region_id, req.err
290 );
291 return;
292 }
293
294 error!(req.err; "Failed to compact region: {}", req.region_id);
295 self.compaction_scheduler
296 .on_execution_failed(req.region_id, &req.execution, req.err);
297 }
298
299 pub(crate) async fn schedule_compaction(&mut self, region: &MitoRegionRef) {
301 if region.is_staging() || region.is_enter_staging() || region.is_truncating() {
302 info!(
303 "Region {} is staging, entering staging, or truncating, skip compaction",
304 region.region_id
305 );
306 return;
307 }
308 let now = self.time_provider.current_time_millis();
309 if now - region.last_schedule_compaction_millis()
310 >= self.config.min_compaction_interval.as_millis() as i64
311 {
312 debug!(
313 "minimal compaction interval time {:?} has passed, scheduling next compaction",
314 self.config.min_compaction_interval
315 );
316 match self.compaction_scheduler.schedule_automatic_compaction(
317 compact_request::Options::Regular(Default::default()),
318 ®ion.version_control,
319 ®ion.access_layer,
320 ®ion.manifest_ctx,
321 self.schema_metadata_manager.clone(),
322 ) {
323 Ok(true) => region.update_schedule_compaction_millis(),
324 Ok(false) => {}
325 Err(e) => {
326 error!(e; "Failed to schedule compaction for region: {}", region.region_id)
327 }
328 }
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::made_progress;
336
337 #[test]
338 fn test_nonempty_output_or_file_reduction_is_progress() {
339 for (files_to_add, files_to_remove, expected) in [
340 (3, 3, true), (1, 3, true), (0, 3, true), (0, 0, false), (3, 2, true), ] {
346 assert_eq!(
347 expected,
348 made_progress(files_to_add, files_to_remove),
349 "files_to_remove: {files_to_remove}, files_to_add: {files_to_add}"
350 );
351 }
352 }
353}