mito2/compaction/scheduler/
state.rs1use std::collections::HashSet;
16use std::sync::Arc;
17use std::time::Instant;
18
19use api::v1::region::compact_request;
20use common_meta::key::SchemaMetadataManagerRef;
21use common_telemetry::debug;
22use common_time::range::TimestampRange;
23use snafu::ResultExt;
24use store_api::storage::RegionId;
25use tokio::sync::mpsc::Sender;
26
27use crate::access_layer::AccessLayerRef;
28use crate::cache::CacheManagerRef;
29use crate::compaction::compactor::CompactionVersion;
30use crate::compaction::picker::PickerOutput;
31use crate::compaction::scheduler::planning::CompactionRequest;
32use crate::config::MitoConfig;
33use crate::error::{
34 CompactRegionSnafu, CompactionCancelledSnafu, Error, ManualCompactionOverrideSnafu,
35};
36use crate::region::ManifestContextRef;
37use crate::region::version::VersionControlRef;
38use crate::request::{OptionOutputTx, OutputTx, SenderDdlRequest, WorkerRequestWithTime};
39use crate::schedule::{CancellableTaskState, RequestCancelResult};
40use crate::sst::file::FileHandle;
41use crate::worker::WorkerListener;
42
43#[derive(Debug, Clone)]
46pub(crate) struct CompactionExecution {
47 plan_id: u64,
48 _files: CompactingFiles,
49}
50
51impl CompactionExecution {
52 pub(super) fn new(plan_id: u64, files: CompactingFiles) -> Self {
53 Self {
54 plan_id,
55 _files: files,
56 }
57 }
58
59 pub(crate) fn matches(&self, other: &Self) -> bool {
60 self.plan_id == other.plan_id
61 }
62
63 #[cfg(test)]
64 pub(crate) fn for_test(plan_id: u64) -> Self {
65 Self::new(plan_id, CompactingFiles::empty())
66 }
67}
68
69#[derive(Debug)]
70pub(super) enum CompactionPhase {
71 Picking {
72 plan_id: u64,
73 cancelled: bool,
74 },
75 Local {
76 state: CancellableTaskState,
77 execution: CompactionExecution,
78 },
79 Remote {
80 execution: CompactionExecution,
81 },
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub(super) enum CompactionTrigger {
87 Automatic,
88 Manual,
89}
90
91#[derive(Debug)]
92pub(super) struct ActiveCompaction {
93 pub(super) phase: CompactionPhase,
94 trigger: CompactionTrigger,
95 pub(super) waiters: Vec<OutputTx>,
98 pub(super) automatic_followup_required: bool,
102}
103
104impl ActiveCompaction {
105 pub(super) fn picking(
106 plan_id: u64,
107 waiters: Vec<OutputTx>,
108 trigger: CompactionTrigger,
109 ) -> Self {
110 Self {
111 phase: CompactionPhase::Picking {
112 plan_id,
113 cancelled: false,
114 },
115 trigger,
116 waiters,
117 automatic_followup_required: false,
118 }
119 }
120
121 pub(super) fn start_picking(&mut self, plan_id: u64, trigger: CompactionTrigger) {
122 self.phase = CompactionPhase::Picking {
123 plan_id,
124 cancelled: false,
125 };
126 self.trigger = trigger;
127 }
128
129 pub(super) fn start_regular_picking(&mut self, plan_id: u64) {
130 self.start_picking(plan_id, CompactionTrigger::Automatic);
131 }
132
133 pub(super) fn mark_automatic_trigger(&mut self) {
136 self.automatic_followup_required = true;
137 }
138
139 pub(super) fn reset_automatic_followup(&mut self) -> bool {
142 std::mem::take(&mut self.automatic_followup_required)
143 }
144
145 pub(super) fn is_manual(&self) -> bool {
146 self.trigger == CompactionTrigger::Manual
147 }
148
149 pub(super) fn is_picking(&self, expected_plan_id: u64) -> bool {
150 matches!(
151 self.phase,
152 CompactionPhase::Picking { plan_id, .. } if plan_id == expected_plan_id
153 )
154 }
155
156 pub(super) fn accept_plan(&self, expected_plan_id: u64) -> bool {
157 matches!(
158 self.phase,
159 CompactionPhase::Picking {
160 plan_id,
161 cancelled: false,
162 } if plan_id == expected_plan_id
163 )
164 }
165
166 pub(super) fn matches_execution(&self, execution: &CompactionExecution) -> bool {
167 match &self.phase {
168 CompactionPhase::Picking { .. } => None,
169 CompactionPhase::Local { execution, .. } | CompactionPhase::Remote { execution } => {
170 Some(execution)
171 }
172 }
173 .is_some_and(|current| current.matches(execution))
174 }
175
176 pub(super) fn request_cancel(&mut self) -> RequestCancelResult {
177 match &mut self.phase {
178 CompactionPhase::Picking { cancelled, .. } => {
179 if *cancelled {
180 RequestCancelResult::AlreadyCancelling
181 } else {
182 *cancelled = true;
183 RequestCancelResult::CancelIssued
184 }
185 }
186 CompactionPhase::Local { state, .. } => state.request_cancel(),
187 CompactionPhase::Remote { .. } => RequestCancelResult::TooLateToCancel,
188 }
189 }
190
191 pub(super) fn merge_waiter(&mut self, mut waiter: OptionOutputTx) {
192 if let Some(waiter) = waiter.take_inner() {
193 self.waiters.push(waiter);
194 }
195 }
196}
197
198#[derive(Debug, Clone)]
200pub(super) struct CompactingFiles {
201 _inner: Arc<CompactingFilesInner>,
202}
203
204#[derive(Debug)]
205struct CompactingFilesInner {
206 files: Vec<FileHandle>,
207}
208
209impl CompactingFiles {
210 pub(super) fn try_new(output: &PickerOutput) -> Option<Self> {
211 let mut seen = HashSet::new();
212 let mut files: Vec<FileHandle> = Vec::new();
213 let selected_files = output
214 .outputs
215 .iter()
216 .flat_map(|output| output.inputs.iter())
217 .chain(output.expired_ssts.iter());
218
219 for file in selected_files {
220 if !seen.insert(file.file_id()) {
221 continue;
222 }
223 if !file.try_set_compacting() {
224 for reserved in &files {
225 reserved.set_compacting(false);
226 }
227 return None;
228 }
229 files.push(file.clone());
230 }
231
232 Some(Self {
233 _inner: Arc::new(CompactingFilesInner { files }),
234 })
235 }
236
237 #[cfg(test)]
238 pub(super) fn empty() -> Self {
239 Self {
240 _inner: Arc::new(CompactingFilesInner { files: Vec::new() }),
241 }
242 }
243}
244
245impl Drop for CompactingFilesInner {
246 fn drop(&mut self) {
247 for file in &self.files {
248 file.set_compacting(false);
249 }
250 }
251}
252
253pub(super) struct CompactionStatus {
255 pub(super) region_id: RegionId,
257 pub(super) version_control: VersionControlRef,
259 pub(super) access_layer: AccessLayerRef,
261 pub(super) active: ActiveCompaction,
263 pub(super) pending_request: Option<PendingCompaction>,
268 pub(super) pending_ddl_requests: Vec<SenderDdlRequest>,
274}
275
276impl CompactionStatus {
277 pub(super) fn new(
279 region_id: RegionId,
280 version_control: VersionControlRef,
281 access_layer: AccessLayerRef,
282 plan_id: u64,
283 trigger: CompactionTrigger,
284 ) -> CompactionStatus {
285 CompactionStatus {
286 region_id,
287 version_control,
288 access_layer,
289 active: ActiveCompaction::picking(plan_id, Vec::new(), trigger),
290 pending_request: None,
291 pending_ddl_requests: Vec::new(),
292 }
293 }
294
295 #[cfg(test)]
296 pub(super) fn for_test(
297 region_id: RegionId,
298 version_control: VersionControlRef,
299 access_layer: AccessLayerRef,
300 ) -> Self {
301 Self::new(
302 region_id,
303 version_control,
304 access_layer,
305 0,
306 CompactionTrigger::Automatic,
307 )
308 }
309
310 #[cfg(test)]
311 pub(super) fn start_picking(&mut self, plan_id: u64) {
312 self.start_picking_with_trigger(plan_id, CompactionTrigger::Automatic);
313 }
314
315 pub(super) fn start_picking_with_trigger(&mut self, plan_id: u64, trigger: CompactionTrigger) {
316 self.active.start_picking(plan_id, trigger);
317 }
318
319 pub(super) fn start_regular_picking(&mut self, plan_id: u64) {
320 self.active.start_regular_picking(plan_id);
321 }
322
323 pub(super) fn is_picking(&self, expected_plan_id: u64) -> bool {
324 self.active.is_picking(expected_plan_id)
325 }
326
327 pub(super) fn accept_plan(&self, expected_plan_id: u64) -> bool {
328 self.active.accept_plan(expected_plan_id)
329 }
330
331 pub(super) fn is_manual_compaction(&self) -> bool {
332 self.active.is_manual()
333 }
334
335 pub(super) fn matches_execution(&self, execution: &CompactionExecution) -> bool {
336 self.active.matches_execution(execution)
337 }
338
339 #[cfg(test)]
340 pub(super) fn start_local_task(&mut self) -> CancellableTaskState {
341 let state = CancellableTaskState::new();
342 let execution = CompactionExecution::new(0, CompactingFiles::empty());
343 let phase = CompactionPhase::Local {
344 state: state.clone(),
345 execution,
346 };
347 self.active.phase = phase;
348 state
349 }
350
351 #[cfg(test)]
352 pub(super) fn start_remote_task(&mut self) {
353 let execution = CompactionExecution::new(0, CompactingFiles::empty());
354 let phase = CompactionPhase::Remote { execution };
355 self.active.phase = phase;
356 }
357
358 pub(super) fn request_cancel(&mut self) -> RequestCancelResult {
359 self.active.request_cancel()
360 }
361
362 pub(super) fn mark_automatic_trigger(&mut self) {
363 self.active.mark_automatic_trigger();
364 }
365
366 pub(super) fn merge_waiter(&mut self, waiter: OptionOutputTx) {
368 self.active.merge_waiter(waiter);
369 }
370
371 pub(super) fn take_waiters(&mut self) -> Vec<OutputTx> {
372 std::mem::take(&mut self.active.waiters)
373 }
374
375 pub(super) fn extend_waiters(&mut self, waiters: Vec<OutputTx>) {
376 self.active.waiters.extend(waiters);
377 }
378
379 pub(super) fn append_waiters(&mut self, waiters: &mut Vec<OutputTx>) {
380 self.active.waiters.append(waiters);
381 }
382
383 pub(super) fn set_phase(&mut self, phase: CompactionPhase) {
384 self.active.phase = phase;
385 }
386
387 pub(super) fn set_pending_request(&mut self, pending: PendingCompaction) {
389 if let Some(prev) = self.pending_request.replace(pending) {
390 debug!(
391 "Replace pending compaction options with new request {:?} for region: {}",
392 prev.options, self.region_id
393 );
394 prev.waiter.send(ManualCompactionOverrideSnafu.fail());
395 }
396 }
397
398 pub(super) fn on_failure(mut self, err: Arc<Error>) {
399 for waiter in self.active.waiters.drain(..) {
400 waiter.send(Err(err.clone()).context(CompactRegionSnafu {
401 region_id: self.region_id,
402 }));
403 }
404
405 if let Some(pending_compaction) = self.pending_request {
406 pending_compaction
407 .waiter
408 .send(Err(err.clone()).context(CompactRegionSnafu {
409 region_id: self.region_id,
410 }));
411 }
412
413 for pending_ddl in self.pending_ddl_requests {
414 pending_ddl
415 .sender
416 .send(Err(err.clone()).context(CompactRegionSnafu {
417 region_id: self.region_id,
418 }));
419 }
420 }
421
422 #[must_use]
423 pub(super) fn on_cancel(mut self) -> Vec<SenderDdlRequest> {
424 for waiter in self.active.waiters.drain(..) {
425 waiter.send(CompactionCancelledSnafu.fail());
426 }
427
428 if let Some(pending_compaction) = self.pending_request {
429 pending_compaction.waiter.send(
430 Err(Arc::new(CompactionCancelledSnafu.build())).context(CompactRegionSnafu {
431 region_id: self.region_id,
432 }),
433 );
434 }
435
436 std::mem::take(&mut self.pending_ddl_requests)
437 }
438
439 #[allow(clippy::too_many_arguments)]
441 pub(super) fn new_compaction_request(
442 &self,
443 request_sender: Sender<WorkerRequestWithTime>,
444 engine_config: Arc<MitoConfig>,
445 cache_manager: CacheManagerRef,
446 manifest_ctx: &ManifestContextRef,
447 listener: WorkerListener,
448 schema_metadata_manager: SchemaMetadataManagerRef,
449 max_parallelism: usize,
450 ) -> CompactionRequest {
451 let current_version = CompactionVersion::from(self.version_control.current().version);
452 let start_time = Instant::now();
453
454 CompactionRequest {
455 engine_config,
456 current_version,
457 access_layer: self.access_layer.clone(),
458 request_sender: request_sender.clone(),
459 start_time,
460 cache_manager,
461 manifest_ctx: manifest_ctx.clone(),
462 listener,
463 schema_metadata_manager,
464 max_parallelism,
465 }
466 }
467}
468
469pub(super) struct PendingCompaction {
471 pub(crate) options: compact_request::Options,
473 pub(crate) waiter: OptionOutputTx,
475 pub(crate) max_parallelism: usize,
477 pub(crate) time_range: Option<TimestampRange>,
479}