1mod buckets;
16pub mod compactor;
17pub mod memory_manager;
18pub mod picker;
19pub mod run;
20mod task;
21#[cfg(test)]
22mod test_util;
23mod twcs;
24mod window;
25
26use std::collections::HashMap;
27use std::sync::{Arc, Mutex};
28use std::time::Instant;
29
30use api::v1::region::compact_request;
31use api::v1::region::compact_request::Options;
32use arrow_schema::Schema;
33use common_base::Plugins;
34use common_base::cancellation::CancellationHandle;
35use common_memory_manager::OnExhaustedPolicy;
36use common_meta::key::SchemaMetadataManagerRef;
37use common_telemetry::{debug, error, info, warn};
38use common_time::range::TimestampRange;
39use common_time::timestamp::TimeUnit;
40use common_time::{TimeToLive, Timestamp};
41use datafusion_common::ScalarValue;
42use datafusion_expr::Expr;
43use datatypes::extension::json::is_json_extension_type;
44use datatypes::schema::ext::ArrowSchemaExt;
45use datatypes::types::json_type::JsonNativeType;
46use parquet::arrow::parquet_to_arrow_schema;
47use parquet::file::metadata::PageIndexPolicy;
48use serde::{Deserialize, Serialize};
49use snafu::{OptionExt, ResultExt};
50use store_api::metadata::RegionMetadataRef;
51use store_api::storage::RegionId;
52use task::MAX_PARALLEL_COMPACTION;
53use tokio::sync::mpsc::{self, Sender};
54
55use crate::access_layer::AccessLayerRef;
56use crate::cache::{CacheManagerRef, CacheStrategy};
57use crate::compaction::compactor::{CompactionRegion, CompactionVersion, DefaultCompactor};
58use crate::compaction::memory_manager::CompactionMemoryManager;
59use crate::compaction::picker::{CompactionTask, PickerOutput, new_picker};
60use crate::compaction::task::CompactionTaskImpl;
61use crate::config::MitoConfig;
62use crate::error::{
63 CompactRegionSnafu, CompactionCancelledSnafu, DataTypeMismatchSnafu, Error,
64 GetSchemaMetadataSnafu, ManualCompactionOverrideSnafu, ParquetToArrowSchemaSnafu,
65 RegionClosedSnafu, RegionDroppedSnafu, RegionTruncatedSnafu, RemoteCompactionSnafu, Result,
66 TimeRangePredicateOverflowSnafu, TimeoutSnafu,
67};
68use crate::metrics::{COMPACTION_STAGE_ELAPSED, INFLIGHT_COMPACTION_COUNT};
69use crate::read::FlatSource;
70use crate::read::flat_projection::FlatProjectionMapper;
71use crate::read::read_columns::ReadColumns;
72use crate::read::scan_region::{PredicateGroup, ScanInput};
73use crate::read::seq_scan::SeqScan;
74use crate::region::options::{MergeMode, RegionOptions};
75use crate::region::version::VersionControlRef;
76use crate::region::{ManifestContextRef, RegionLeaderState, RegionRoleState};
77use crate::request::{OptionOutputTx, OutputTx, SenderDdlRequest, WorkerRequestWithTime};
78use crate::schedule::remote_job_scheduler::{
79 CompactionJob, DefaultNotifier, RemoteJob, RemoteJobSchedulerRef,
80};
81use crate::schedule::scheduler::SchedulerRef;
82use crate::sst::file::{FileHandle, FileMeta, Level};
83use crate::sst::parquet::reader::MetadataCacheMetrics;
84use crate::sst::version::LevelMeta;
85use crate::worker::WorkerListener;
86
87pub struct CompactionRequest {
89 pub(crate) engine_config: Arc<MitoConfig>,
90 pub(crate) current_version: CompactionVersion,
91 pub(crate) access_layer: AccessLayerRef,
92 pub(crate) request_sender: mpsc::Sender<WorkerRequestWithTime>,
94 pub(crate) waiters: Vec<OutputTx>,
96 pub(crate) start_time: Instant,
98 pub(crate) cache_manager: CacheManagerRef,
99 pub(crate) manifest_ctx: ManifestContextRef,
100 pub(crate) listener: WorkerListener,
101 pub(crate) schema_metadata_manager: SchemaMetadataManagerRef,
102 pub(crate) max_parallelism: usize,
103}
104
105impl CompactionRequest {
106 pub(crate) fn region_id(&self) -> RegionId {
107 self.current_version.metadata.region_id
108 }
109}
110
111pub(crate) struct CompactionScheduler {
113 scheduler: SchedulerRef,
114 region_status: HashMap<RegionId, CompactionStatus>,
116 request_sender: Sender<WorkerRequestWithTime>,
118 cache_manager: CacheManagerRef,
119 engine_config: Arc<MitoConfig>,
120 memory_manager: Arc<CompactionMemoryManager>,
121 memory_policy: OnExhaustedPolicy,
122 listener: WorkerListener,
123 plugins: Plugins,
125}
126
127impl CompactionScheduler {
128 #[allow(clippy::too_many_arguments)]
129 pub(crate) fn new(
130 scheduler: SchedulerRef,
131 request_sender: Sender<WorkerRequestWithTime>,
132 cache_manager: CacheManagerRef,
133 engine_config: Arc<MitoConfig>,
134 listener: WorkerListener,
135 plugins: Plugins,
136 memory_manager: Arc<CompactionMemoryManager>,
137 memory_policy: OnExhaustedPolicy,
138 ) -> Self {
139 Self {
140 scheduler,
141 region_status: HashMap::new(),
142 request_sender,
143 cache_manager,
144 engine_config,
145 memory_manager,
146 memory_policy,
147 listener,
148 plugins,
149 }
150 }
151
152 #[allow(clippy::too_many_arguments)]
155 pub(crate) async fn schedule_compaction(
156 &mut self,
157 region_id: RegionId,
158 compact_options: compact_request::Options,
159 version_control: &VersionControlRef,
160 access_layer: &AccessLayerRef,
161 waiter: OptionOutputTx,
162 manifest_ctx: &ManifestContextRef,
163 schema_metadata_manager: SchemaMetadataManagerRef,
164 max_parallelism: usize,
165 ) -> Result<bool> {
166 let current_state = manifest_ctx.current_state();
168 if current_state == RegionRoleState::Leader(RegionLeaderState::Staging) {
169 info!(
170 "Skipping compaction for region {} in staging mode, options: {:?}",
171 region_id, compact_options
172 );
173 waiter.send(Ok(0));
174 return Ok(false);
175 }
176
177 if let Some(status) = self.region_status.get_mut(®ion_id) {
178 match compact_options {
179 Options::Regular(_) => {
180 status.merge_waiter(waiter);
182 }
183 options @ Options::StrictWindow(_) => {
184 status.set_pending_request(PendingCompaction {
186 options,
187 waiter,
188 max_parallelism,
189 });
190 info!(
191 "Region {} is compacting, manually compaction will be re-scheduled.",
192 region_id
193 );
194 }
195 }
196 return Ok(false);
197 }
198
199 let mut status =
201 CompactionStatus::new(region_id, version_control.clone(), access_layer.clone());
202 let request = status.new_compaction_request(
203 self.request_sender.clone(),
204 waiter,
205 self.engine_config.clone(),
206 self.cache_manager.clone(),
207 manifest_ctx,
208 self.listener.clone(),
209 schema_metadata_manager,
210 max_parallelism,
211 );
212
213 match self
214 .schedule_compaction_request(request, compact_options)
215 .await
216 {
217 Ok(Some(active_compaction)) => {
218 status.active_compaction = Some(active_compaction);
222 self.region_status.insert(region_id, status);
223
224 self.listener.on_compaction_scheduled(region_id);
225 Ok(true)
226 }
227 Ok(None) => Ok(false),
228 Err(e) => Err(e),
229 }
230 }
231
232 pub(crate) async fn handle_pending_compaction_request(
236 &mut self,
237 region_id: RegionId,
238 manifest_ctx: &ManifestContextRef,
239 schema_metadata_manager: SchemaMetadataManagerRef,
240 ) -> bool {
241 let Some(status) = self.region_status.get_mut(®ion_id) else {
242 return true;
243 };
244
245 let Some(pending_request) = std::mem::take(&mut status.pending_request) else {
248 return false;
249 };
250
251 let PendingCompaction {
252 options,
253 waiter,
254 max_parallelism,
255 } = pending_request;
256
257 let request = {
258 status.new_compaction_request(
259 self.request_sender.clone(),
260 waiter,
261 self.engine_config.clone(),
262 self.cache_manager.clone(),
263 manifest_ctx,
264 self.listener.clone(),
265 schema_metadata_manager,
266 max_parallelism,
267 )
268 };
269
270 match self.schedule_compaction_request(request, options).await {
271 Ok(Some(active_compaction)) => {
272 let status = self.region_status.get_mut(®ion_id).unwrap();
273 status.active_compaction = Some(active_compaction);
274 debug!(
275 "Successfully scheduled manual compaction for region id: {}",
276 region_id
277 );
278 true
279 }
280 Ok(None) => {
281 false
284 }
285 Err(e) => {
286 error!(e; "Failed to continue pending manual compaction for region id: {}", region_id);
287 self.remove_region_on_failure(region_id, Arc::new(e));
288 true
289 }
290 }
291 }
292
293 pub(crate) async fn on_compaction_finished(
295 &mut self,
296 region_id: RegionId,
297 manifest_ctx: &ManifestContextRef,
298 schema_metadata_manager: SchemaMetadataManagerRef,
299 ) -> Vec<SenderDdlRequest> {
300 let Some(status) = self.region_status.get_mut(®ion_id) else {
301 return Vec::new();
302 };
303 status.clear_running_task();
304
305 if self
308 .handle_pending_compaction_request(
309 region_id,
310 manifest_ctx,
311 schema_metadata_manager.clone(),
312 )
313 .await
314 {
315 return Vec::new();
316 }
317
318 let Some(status) = self.region_status.get_mut(®ion_id) else {
319 return Vec::new();
322 };
323
324 for waiter in std::mem::take(&mut status.waiters) {
325 waiter.send(Ok(0));
326 }
327
328 let pending_ddl_requests = std::mem::take(&mut status.pending_ddl_requests);
330 if !pending_ddl_requests.is_empty() {
331 self.region_status.remove(®ion_id);
332 return pending_ddl_requests;
335 }
336 Vec::new()
337 }
338
339 pub(crate) fn is_compacting(&self, region_id: RegionId) -> bool {
340 self.region_status
341 .get(®ion_id)
342 .map(|status| status.active_compaction.is_some())
343 .unwrap_or(false)
344 }
345
346 pub(crate) async fn schedule_next_compaction(
349 &mut self,
350 region_id: RegionId,
351 manifest_ctx: &ManifestContextRef,
352 schema_metadata_manager: SchemaMetadataManagerRef,
353 ) -> bool {
354 let Some(status) = self.region_status.get_mut(®ion_id) else {
355 return false;
356 };
357
358 let request = status.new_compaction_request(
360 self.request_sender.clone(),
361 OptionOutputTx::none(),
362 self.engine_config.clone(),
363 self.cache_manager.clone(),
364 manifest_ctx,
365 self.listener.clone(),
366 schema_metadata_manager,
367 MAX_PARALLEL_COMPACTION,
368 );
369
370 match self
372 .schedule_compaction_request(
373 request,
374 compact_request::Options::Regular(Default::default()),
375 )
376 .await
377 {
378 Ok(Some(active_compaction)) => {
379 self.region_status
380 .get_mut(®ion_id)
381 .unwrap()
382 .active_compaction = Some(active_compaction);
383 debug!(
384 "Successfully scheduled next compaction for region id: {}",
385 region_id
386 );
387 true
388 }
389 Ok(None) => {
390 self.region_status.remove(®ion_id);
394 false
395 }
396 Err(e) => {
397 error!(e; "Failed to schedule next compaction for region {}", region_id);
398 self.remove_region_on_failure(region_id, Arc::new(e));
399 false
400 }
401 }
402 }
403
404 pub(crate) async fn on_compaction_cancelled(
406 &mut self,
407 region_id: RegionId,
408 ) -> Vec<SenderDdlRequest> {
409 self.remove_region_on_cancel(region_id)
410 }
411
412 pub(crate) fn on_compaction_failed(&mut self, region_id: RegionId, err: Arc<Error>) {
414 error!(err; "Region {} failed to compact, cancel all pending tasks", region_id);
415 self.remove_region_on_failure(region_id, err);
416 }
417
418 pub(crate) fn on_region_dropped(&mut self, region_id: RegionId) {
420 self.remove_region_on_failure(
421 region_id,
422 Arc::new(RegionDroppedSnafu { region_id }.build()),
423 );
424 }
425
426 pub(crate) fn on_region_closed(&mut self, region_id: RegionId) {
428 self.remove_region_on_failure(region_id, Arc::new(RegionClosedSnafu { region_id }.build()));
429 }
430
431 pub(crate) fn on_region_truncated(&mut self, region_id: RegionId) {
433 self.remove_region_on_failure(
434 region_id,
435 Arc::new(RegionTruncatedSnafu { region_id }.build()),
436 );
437 }
438
439 pub(crate) fn add_ddl_request_to_pending(&mut self, request: SenderDdlRequest) {
444 debug!(
445 "Added pending DDL request for region: {}, ddl: {:?}",
446 request.region_id, request.request
447 );
448 let status = self.region_status.get_mut(&request.region_id).unwrap();
449 status.pending_ddl_requests.push(request);
450 }
451
452 #[cfg(test)]
453 pub(crate) fn has_pending_ddls(&self, region_id: RegionId) -> bool {
454 let has_pending = self
455 .region_status
456 .get(®ion_id)
457 .map(|status| !status.pending_ddl_requests.is_empty())
458 .unwrap_or(false);
459 debug!(
460 "Checked pending DDL requests for region: {}, has_pending: {}",
461 region_id, has_pending
462 );
463 has_pending
464 }
465
466 pub(crate) fn request_cancel(&mut self, region_id: RegionId) -> RequestCancelResult {
467 let Some(status) = self.region_status.get_mut(®ion_id) else {
468 return RequestCancelResult::NotRunning;
469 };
470
471 status.request_cancel()
472 }
473
474 async fn schedule_compaction_request(
479 &mut self,
480 request: CompactionRequest,
481 options: compact_request::Options,
482 ) -> Result<Option<ActiveCompaction>> {
483 let region_id = request.region_id();
484 let (dynamic_compaction_opts, ttl) = find_dynamic_options(
485 region_id,
486 &request.current_version.options,
487 &request.schema_metadata_manager,
488 )
489 .await
490 .unwrap_or_else(|e| {
491 warn!(e; "Failed to find dynamic options for region: {}", region_id);
492 (
493 request.current_version.options.compaction.clone(),
494 request.current_version.options.ttl.unwrap_or_default(),
495 )
496 });
497
498 let picker = new_picker(
499 &options,
500 &dynamic_compaction_opts,
501 request.current_version.options.append_mode,
502 Some(self.engine_config.max_background_compactions),
503 );
504 let region_id = request.region_id();
505 let CompactionRequest {
506 engine_config,
507 current_version,
508 access_layer,
509 request_sender,
510 waiters,
511 start_time,
512 cache_manager,
513 manifest_ctx,
514 listener,
515 schema_metadata_manager: _,
516 max_parallelism,
517 } = request;
518
519 debug!(
520 "Pick compaction strategy {:?} for region: {}, ttl: {:?}",
521 picker, region_id, ttl
522 );
523
524 let compaction_region = CompactionRegion {
525 region_id,
526 current_version: current_version.clone(),
527 region_options: RegionOptions {
528 compaction: dynamic_compaction_opts.clone(),
529 ..current_version.options.clone()
530 },
531 engine_config: engine_config.clone(),
532 region_metadata: current_version.metadata.clone(),
533 cache_manager: cache_manager.clone(),
534 access_layer: access_layer.clone(),
535 manifest_ctx: manifest_ctx.clone(),
536 file_purger: None,
537 ttl: Some(ttl),
538 max_parallelism,
539 plugins: self.plugins.clone(),
540 };
541
542 let picker_output = {
543 let _pick_timer = COMPACTION_STAGE_ELAPSED
544 .with_label_values(&["pick"])
545 .start_timer();
546 picker.pick(&compaction_region)
547 };
548
549 let picker_output = if let Some(picker_output) = picker_output {
550 picker_output
551 } else {
552 for waiter in waiters {
554 waiter.send(Ok(0));
555 }
556 return Ok(None);
557 };
558
559 let waiters = if dynamic_compaction_opts.remote_compaction() {
562 if let Some(remote_job_scheduler) = &self.plugins.get::<RemoteJobSchedulerRef>() {
563 let remote_compaction_job = CompactionJob {
564 compaction_region: compaction_region.clone(),
565 picker_output: picker_output.clone(),
566 start_time,
567 waiters,
568 ttl,
569 };
570
571 let result = remote_job_scheduler
572 .schedule(
573 RemoteJob::CompactionJob(remote_compaction_job),
574 Box::new(DefaultNotifier {
575 request_sender: request_sender.clone(),
576 }),
577 )
578 .await;
579
580 match result {
581 Ok(job_id) => {
582 info!(
583 "Scheduled remote compaction job {} for region {}",
584 job_id, region_id
585 );
586 INFLIGHT_COMPACTION_COUNT.inc();
587 return Ok(Some(ActiveCompaction::Remote));
588 }
589 Err(e) => {
590 if !dynamic_compaction_opts.fallback_to_local() {
591 error!(e; "Failed to schedule remote compaction job for region {}", region_id);
592 return RemoteCompactionSnafu {
593 region_id,
594 job_id: None,
595 reason: e.reason,
596 }
597 .fail();
598 }
599
600 error!(e; "Failed to schedule remote compaction job for region {}, fallback to local compaction", region_id);
601
602 e.waiters
604 }
605 }
606 } else {
607 debug!(
608 "Remote compaction is not enabled, fallback to local compaction for region {}",
609 region_id
610 );
611 waiters
612 }
613 } else {
614 waiters
615 };
616
617 let estimated_bytes = estimate_compaction_bytes(&picker_output);
619
620 let cancel_handle = Arc::new(CancellationHandle::default());
621 let state = LocalCompactionState::new(cancel_handle.clone());
622 let local_compaction_task = Box::new(CompactionTaskImpl {
623 state: state.clone(),
624 request_sender,
625 waiters,
626 start_time,
627 listener,
628 picker_output,
629 compaction_region,
630 compactor: Arc::new(DefaultCompactor::with_cancel_handle(cancel_handle.clone())),
631 memory_manager: self.memory_manager.clone(),
632 memory_policy: self.memory_policy,
633 estimated_memory_bytes: estimated_bytes,
634 });
635
636 self.submit_compaction_task(local_compaction_task, region_id)
637 .map(|_| Some(ActiveCompaction::Local { state }))
638 }
639
640 fn submit_compaction_task(
641 &mut self,
642 mut task: Box<CompactionTaskImpl>,
643 region_id: RegionId,
644 ) -> Result<()> {
645 self.scheduler
646 .schedule(Box::pin(async move {
647 INFLIGHT_COMPACTION_COUNT.inc();
648 task.run().await;
649 INFLIGHT_COMPACTION_COUNT.dec();
650 }))
651 .inspect_err(
652 |e| error!(e; "Failed to submit compaction request for region {}", region_id),
653 )
654 }
655
656 fn remove_region_on_failure(&mut self, region_id: RegionId, err: Arc<Error>) {
657 let Some(status) = self.region_status.remove(®ion_id) else {
659 return;
660 };
661
662 status.on_failure(err);
664 }
665
666 fn remove_region_on_cancel(&mut self, region_id: RegionId) -> Vec<SenderDdlRequest> {
667 let Some(status) = self.region_status.remove(®ion_id) else {
668 return Vec::new();
669 };
670
671 status.on_cancel()
672 }
673}
674
675#[derive(Debug, Clone)]
676pub(crate) struct LocalCompactionState {
677 cancel_handle: Arc<CancellationHandle>,
678 commit_started: Arc<Mutex<bool>>,
679}
680
681#[derive(Debug)]
682enum ActiveCompaction {
683 Local { state: LocalCompactionState },
684 Remote,
685}
686
687impl LocalCompactionState {
688 fn new(cancel_handle: Arc<CancellationHandle>) -> Self {
689 Self {
690 cancel_handle,
691 commit_started: Arc::new(Mutex::new(false)),
692 }
693 }
694
695 pub(crate) fn cancel_handle(&self) -> Arc<CancellationHandle> {
697 self.cancel_handle.clone()
698 }
699
700 pub(crate) fn mark_commit_started(&self) -> bool {
706 let mut commit_started = self.commit_started.lock().unwrap();
707 if self.cancel_handle.is_cancelled() {
708 return false;
709 }
710 *commit_started = true;
711 true
712 }
713
714 pub(crate) fn request_cancel(&self) -> RequestCancelResult {
716 let commit_started = self.commit_started.lock().unwrap();
718 if *commit_started {
719 return RequestCancelResult::TooLateToCancel;
720 }
721 if self.cancel_handle.is_cancelled() {
722 return RequestCancelResult::AlreadyCancelling;
723 }
724
725 self.cancel_handle.cancel();
726 RequestCancelResult::CancelIssued
727 }
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Eq)]
731pub(crate) enum RequestCancelResult {
732 CancelIssued,
733 AlreadyCancelling,
734 TooLateToCancel,
735 NotRunning,
736}
737
738impl Drop for CompactionScheduler {
739 fn drop(&mut self) {
740 for (region_id, status) in self.region_status.drain() {
741 status.on_failure(Arc::new(RegionClosedSnafu { region_id }.build()));
743 }
744 }
745}
746
747async fn find_dynamic_options(
749 region_id: RegionId,
750 region_options: &crate::region::options::RegionOptions,
751 schema_metadata_manager: &SchemaMetadataManagerRef,
752) -> Result<(crate::region::options::CompactionOptions, TimeToLive)> {
753 let table_id = region_id.table_id();
754 if let (true, Some(ttl)) = (region_options.compaction_override, region_options.ttl) {
755 debug!(
756 "Use region options directly for table {}: compaction={:?}, ttl={:?}",
757 table_id, region_options.compaction, region_options.ttl
758 );
759 return Ok((region_options.compaction.clone(), ttl));
760 }
761
762 let db_options = tokio::time::timeout(
763 crate::config::FETCH_OPTION_TIMEOUT,
764 schema_metadata_manager.get_schema_options_by_table_id(table_id),
765 )
766 .await
767 .context(TimeoutSnafu)?
768 .context(GetSchemaMetadataSnafu)?;
769
770 let ttl = if let Some(ttl) = region_options.ttl {
771 debug!(
772 "Use region TTL directly for table {}: ttl={:?}",
773 table_id, region_options.ttl
774 );
775 ttl
776 } else {
777 db_options
778 .as_ref()
779 .and_then(|options| options.ttl)
780 .unwrap_or_default()
781 .into()
782 };
783
784 let compaction = if !region_options.compaction_override {
785 if let Some(schema_opts) = db_options {
786 let map: HashMap<String, String> = schema_opts
787 .extra_options
788 .iter()
789 .filter_map(|(k, v)| {
790 if k.starts_with("compaction.") {
791 Some((k.clone(), v.clone()))
792 } else {
793 None
794 }
795 })
796 .collect();
797 if map.is_empty() {
798 region_options.compaction.clone()
799 } else {
800 crate::region::options::RegionOptions::try_from_options(region_id, &map)
801 .map(|o| o.compaction)
802 .unwrap_or_else(|e| {
803 error!(e; "Failed to create RegionOptions from map");
804 region_options.compaction.clone()
805 })
806 }
807 } else {
808 debug!(
809 "DB options is None for table {}, use region compaction: compaction={:?}",
810 table_id, region_options.compaction
811 );
812 region_options.compaction.clone()
813 }
814 } else {
815 debug!(
816 "No schema options for table {}, use region compaction: compaction={:?}",
817 table_id, region_options.compaction
818 );
819 region_options.compaction.clone()
820 };
821
822 debug!(
823 "Resolved dynamic options for table {}: compaction={:?}, ttl={:?}",
824 table_id, compaction, ttl
825 );
826 Ok((compaction, ttl))
827}
828
829struct CompactionStatus {
831 region_id: RegionId,
833 version_control: VersionControlRef,
835 access_layer: AccessLayerRef,
837 waiters: Vec<OutputTx>,
839 pending_request: Option<PendingCompaction>,
841 pending_ddl_requests: Vec<SenderDdlRequest>,
843 active_compaction: Option<ActiveCompaction>,
845}
846
847impl CompactionStatus {
848 fn new(
850 region_id: RegionId,
851 version_control: VersionControlRef,
852 access_layer: AccessLayerRef,
853 ) -> CompactionStatus {
854 CompactionStatus {
855 region_id,
856 version_control,
857 access_layer,
858 waiters: Vec::new(),
859 pending_request: None,
860 pending_ddl_requests: Vec::new(),
861 active_compaction: None,
862 }
863 }
864
865 #[cfg(test)]
866 fn start_local_task(&mut self) -> LocalCompactionState {
867 let state = LocalCompactionState::new(Arc::new(CancellationHandle::default()));
868 self.active_compaction = Some(ActiveCompaction::Local {
869 state: state.clone(),
870 });
871 state
872 }
873
874 #[cfg(test)]
875 fn start_remote_task(&mut self) {
876 self.active_compaction = Some(ActiveCompaction::Remote);
877 }
878
879 fn request_cancel(&mut self) -> RequestCancelResult {
880 let Some(active_compaction) = &self.active_compaction else {
881 return RequestCancelResult::NotRunning;
882 };
883
884 match active_compaction {
885 ActiveCompaction::Local { state, .. } => state.request_cancel(),
886 ActiveCompaction::Remote => RequestCancelResult::TooLateToCancel,
887 }
888 }
889
890 fn clear_running_task(&mut self) -> bool {
891 self.active_compaction.take().is_some()
892 }
893
894 fn merge_waiter(&mut self, mut waiter: OptionOutputTx) {
896 if let Some(waiter) = waiter.take_inner() {
897 self.waiters.push(waiter);
898 }
899 }
900
901 fn set_pending_request(&mut self, pending: PendingCompaction) {
903 if let Some(prev) = self.pending_request.replace(pending) {
904 debug!(
905 "Replace pending compaction options with new request {:?} for region: {}",
906 prev.options, self.region_id
907 );
908 prev.waiter.send(ManualCompactionOverrideSnafu.fail());
909 }
910 }
911
912 fn on_failure(mut self, err: Arc<Error>) {
913 for waiter in self.waiters.drain(..) {
914 waiter.send(Err(err.clone()).context(CompactRegionSnafu {
915 region_id: self.region_id,
916 }));
917 }
918
919 if let Some(pending_compaction) = self.pending_request {
920 pending_compaction
921 .waiter
922 .send(Err(err.clone()).context(CompactRegionSnafu {
923 region_id: self.region_id,
924 }));
925 }
926
927 for pending_ddl in self.pending_ddl_requests {
928 pending_ddl
929 .sender
930 .send(Err(err.clone()).context(CompactRegionSnafu {
931 region_id: self.region_id,
932 }));
933 }
934 }
935
936 #[must_use]
937 fn on_cancel(mut self) -> Vec<SenderDdlRequest> {
938 for waiter in self.waiters.drain(..) {
939 waiter.send(CompactionCancelledSnafu.fail());
940 }
941
942 if let Some(pending_compaction) = self.pending_request {
943 pending_compaction.waiter.send(
944 Err(Arc::new(CompactionCancelledSnafu.build())).context(CompactRegionSnafu {
945 region_id: self.region_id,
946 }),
947 );
948 }
949
950 std::mem::take(&mut self.pending_ddl_requests)
951 }
952
953 #[allow(clippy::too_many_arguments)]
957 fn new_compaction_request(
958 &mut self,
959 request_sender: Sender<WorkerRequestWithTime>,
960 mut waiter: OptionOutputTx,
961 engine_config: Arc<MitoConfig>,
962 cache_manager: CacheManagerRef,
963 manifest_ctx: &ManifestContextRef,
964 listener: WorkerListener,
965 schema_metadata_manager: SchemaMetadataManagerRef,
966 max_parallelism: usize,
967 ) -> CompactionRequest {
968 let current_version = CompactionVersion::from(self.version_control.current().version);
969 let start_time = Instant::now();
970 let mut waiters = Vec::with_capacity(self.waiters.len() + 1);
971 waiters.extend(std::mem::take(&mut self.waiters));
972
973 if let Some(waiter) = waiter.take_inner() {
974 waiters.push(waiter);
975 }
976
977 CompactionRequest {
978 engine_config,
979 current_version,
980 access_layer: self.access_layer.clone(),
981 request_sender: request_sender.clone(),
982 waiters,
983 start_time,
984 cache_manager,
985 manifest_ctx: manifest_ctx.clone(),
986 listener,
987 schema_metadata_manager,
988 max_parallelism,
989 }
990 }
991}
992
993#[derive(Debug, Clone)]
994pub struct CompactionOutput {
995 pub output_level: Level,
997 pub inputs: Vec<FileHandle>,
999 pub filter_deleted: bool,
1001 pub output_time_range: Option<TimestampRange>,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1007pub struct SerializedCompactionOutput {
1008 output_level: Level,
1009 inputs: Vec<FileMeta>,
1010 filter_deleted: bool,
1011 output_time_range: Option<TimestampRange>,
1012}
1013
1014struct CompactionSstReaderBuilder<'a> {
1016 metadata: RegionMetadataRef,
1017 sst_layer: AccessLayerRef,
1018 cache: CacheManagerRef,
1019 inputs: &'a [FileHandle],
1020 append_mode: bool,
1021 filter_deleted: bool,
1022 time_range: Option<TimestampRange>,
1023 merge_mode: MergeMode,
1024}
1025
1026impl CompactionSstReaderBuilder<'_> {
1027 async fn build_flat_sst_reader(self) -> Result<FlatSource> {
1030 let scan_input = self.build_scan_input().await?.with_compaction(true);
1031
1032 let schema = scan_input.mapper.output_schema();
1033 let schema = schema.arrow_schema();
1034
1035 let stream = SeqScan::new(scan_input)
1036 .build_flat_reader_for_compaction()
1037 .await?;
1038 Ok(FlatSource::new_stream(schema.clone(), stream))
1039 }
1040
1041 async fn build_scan_input(self) -> Result<ScanInput> {
1042 let schema = self.metadata.schema.arrow_schema();
1043 let json_type_hint = if schema.has_json_extension_field() {
1044 let mut json_type_hint = schema
1045 .fields()
1046 .iter()
1047 .filter(|&field| is_json_extension_type(field))
1048 .map(|field| (field.name().clone(), JsonNativeType::Null))
1049 .collect::<HashMap<_, _>>();
1050
1051 let schemas = self.collect_arrow_schemas_from_parquet().await?;
1052 for schema in schemas {
1053 for field in schema.fields() {
1054 let Some(merged) = json_type_hint.get_mut(field.name()) else {
1055 continue;
1056 };
1057
1058 let json_type = JsonNativeType::try_from(field.data_type())
1059 .context(DataTypeMismatchSnafu)?;
1060 merged.merge(&json_type);
1061 }
1062 }
1063
1064 Some(json_type_hint)
1065 } else {
1066 None
1067 };
1068
1069 let projection = (0..self.metadata.column_metadatas.len()).collect();
1070 let read_columns = ReadColumns::from_deduped_column_ids(
1071 self.metadata.column_metadatas.iter().map(|x| x.column_id),
1072 );
1073 let mapper = FlatProjectionMapper::new_with_read_columns(
1074 &self.metadata,
1075 projection,
1076 read_columns,
1077 json_type_hint.as_ref(),
1078 )?;
1079
1080 let mut scan_input = ScanInput::new(self.sst_layer, mapper)
1081 .with_files(self.inputs.to_vec())
1082 .with_append_mode(self.append_mode)
1083 .with_cache(CacheStrategy::Compaction(self.cache))
1085 .with_filter_deleted(self.filter_deleted)
1086 .with_ignore_file_not_found(true)
1088 .with_merge_mode(self.merge_mode);
1089
1090 if let Some(time_range) = self.time_range {
1093 scan_input =
1094 scan_input.with_predicate(time_range_to_predicate(time_range, &self.metadata)?);
1095 }
1096
1097 Ok(scan_input)
1098 }
1099
1100 async fn collect_arrow_schemas_from_parquet(&self) -> Result<Vec<Schema>> {
1101 let mut schemas = Vec::with_capacity(self.inputs.len());
1102
1103 for file_handle in self.inputs {
1104 let file_path =
1105 file_handle.file_path(self.sst_layer.table_dir(), self.sst_layer.path_type());
1106 let file_size = file_handle.meta_ref().file_size;
1107 let parquet_metadata = match self
1108 .sst_layer
1109 .read_sst(file_handle.clone())
1110 .cache(CacheStrategy::Compaction(self.cache.clone()))
1111 .read_parquet_metadata(
1112 &file_path,
1113 file_size,
1114 &mut MetadataCacheMetrics::default(),
1115 PageIndexPolicy::default(),
1116 )
1117 .await
1118 .map(|x| x.0.parquet_metadata())
1119 {
1120 Ok(x) => x,
1121 Err(e) if e.is_object_not_found() => continue,
1122 Err(e) => return Err(e),
1123 };
1124 let file_metadata = parquet_metadata.file_metadata();
1125
1126 let schema = parquet_to_arrow_schema(
1127 file_metadata.schema_descr(),
1128 file_metadata.key_value_metadata(),
1129 )
1130 .context(ParquetToArrowSchemaSnafu { file: file_path })?;
1131
1132 schemas.push(schema);
1133 }
1134 Ok(schemas)
1135 }
1136}
1137
1138fn time_range_to_predicate(
1140 range: TimestampRange,
1141 metadata: &RegionMetadataRef,
1142) -> Result<PredicateGroup> {
1143 let ts_col = metadata.time_index_column();
1144
1145 let ts_col_unit = ts_col
1147 .column_schema
1148 .data_type
1149 .as_timestamp()
1150 .unwrap()
1151 .unit();
1152
1153 let exprs = match (range.start(), range.end()) {
1154 (Some(start), Some(end)) => {
1155 vec![
1156 datafusion_expr::col(ts_col.column_schema.name.clone())
1157 .gt_eq(ts_to_lit(*start, ts_col_unit)?),
1158 datafusion_expr::col(ts_col.column_schema.name.clone())
1159 .lt(ts_to_lit(*end, ts_col_unit)?),
1160 ]
1161 }
1162 (Some(start), None) => {
1163 vec![
1164 datafusion_expr::col(ts_col.column_schema.name.clone())
1165 .gt_eq(ts_to_lit(*start, ts_col_unit)?),
1166 ]
1167 }
1168
1169 (None, Some(end)) => {
1170 vec![
1171 datafusion_expr::col(ts_col.column_schema.name.clone())
1172 .lt(ts_to_lit(*end, ts_col_unit)?),
1173 ]
1174 }
1175 (None, None) => {
1176 return Ok(PredicateGroup::default());
1177 }
1178 };
1179
1180 let predicate = PredicateGroup::new(metadata, &exprs)?;
1181 Ok(predicate)
1182}
1183
1184fn ts_to_lit(ts: Timestamp, ts_col_unit: TimeUnit) -> Result<Expr> {
1185 let ts = ts
1186 .convert_to(ts_col_unit)
1187 .context(TimeRangePredicateOverflowSnafu {
1188 timestamp: ts,
1189 unit: ts_col_unit,
1190 })?;
1191 let val = ts.value();
1192 let scalar_value = match ts_col_unit {
1193 TimeUnit::Second => ScalarValue::TimestampSecond(Some(val), None),
1194 TimeUnit::Millisecond => ScalarValue::TimestampMillisecond(Some(val), None),
1195 TimeUnit::Microsecond => ScalarValue::TimestampMicrosecond(Some(val), None),
1196 TimeUnit::Nanosecond => ScalarValue::TimestampNanosecond(Some(val), None),
1197 };
1198 Ok(datafusion_expr::lit(scalar_value))
1199}
1200
1201fn get_expired_ssts(
1203 levels: &[LevelMeta],
1204 ttl: Option<TimeToLive>,
1205 now: Timestamp,
1206) -> Vec<FileHandle> {
1207 let Some(ttl) = ttl else {
1208 return vec![];
1209 };
1210
1211 levels
1212 .iter()
1213 .flat_map(|l| l.get_expired_files(&now, &ttl).into_iter())
1214 .collect()
1215}
1216
1217fn estimate_compaction_bytes(picker_output: &PickerOutput) -> u64 {
1220 picker_output
1221 .outputs
1222 .iter()
1223 .flat_map(|output| output.inputs.iter())
1224 .map(|file: &FileHandle| {
1225 let meta = file.meta_ref();
1226 meta.max_row_group_uncompressed_size
1227 })
1228 .sum()
1229}
1230
1231struct PendingCompaction {
1234 pub(crate) options: compact_request::Options,
1236 pub(crate) waiter: OptionOutputTx,
1238 pub(crate) max_parallelism: usize,
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use std::assert_matches;
1245 use std::time::Duration;
1246
1247 use api::v1::region::StrictWindow;
1248 use common_datasource::compression::CompressionType;
1249 use common_meta::key::schema_name::SchemaNameValue;
1250 use common_time::DatabaseTimeToLive;
1251 use tokio::sync::{Barrier, oneshot};
1252
1253 use super::*;
1254 use crate::compaction::memory_manager::{CompactionMemoryGuard, new_compaction_memory_manager};
1255 use crate::error::InvalidSchedulerStateSnafu;
1256 use crate::manifest::manager::{RegionManifestManager, RegionManifestOptions};
1257 use crate::region::ManifestContext;
1258 use crate::schedule::scheduler::{Job, Scheduler};
1259 use crate::sst::FormatType;
1260 use crate::test_util::mock_schema_metadata_manager;
1261 use crate::test_util::scheduler_util::{SchedulerEnv, VecScheduler};
1262 use crate::test_util::version_util::{VersionControlBuilder, apply_edit};
1263
1264 struct FailingScheduler;
1265
1266 #[async_trait::async_trait]
1267 impl Scheduler for FailingScheduler {
1268 fn schedule(&self, _job: Job) -> Result<()> {
1269 InvalidSchedulerStateSnafu.fail()
1270 }
1271
1272 async fn stop(&self, _await_termination: bool) -> Result<()> {
1273 Ok(())
1274 }
1275 }
1276
1277 #[tokio::test]
1278 async fn test_find_compaction_options_db_level() {
1279 let env = SchedulerEnv::new().await;
1280 let builder = VersionControlBuilder::new();
1281 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1282 let region_id = builder.region_id();
1283 let table_id = region_id.table_id();
1284 let mut schema_value = SchemaNameValue {
1286 ttl: Some(DatabaseTimeToLive::default()),
1287 ..Default::default()
1288 };
1289 schema_value
1290 .extra_options
1291 .insert("compaction.type".to_string(), "twcs".to_string());
1292 schema_value
1293 .extra_options
1294 .insert("compaction.twcs.time_window".to_string(), "2h".to_string());
1295 schema_metadata_manager
1296 .register_region_table_info(
1297 table_id,
1298 "t",
1299 "c",
1300 "s",
1301 Some(schema_value),
1302 kv_backend.clone(),
1303 )
1304 .await;
1305
1306 let version_control = Arc::new(builder.build());
1307 let region_opts = version_control.current().version.options.clone();
1308 let (opts, _) = find_dynamic_options(region_id, ®ion_opts, &schema_metadata_manager)
1309 .await
1310 .unwrap();
1311 match opts {
1312 crate::region::options::CompactionOptions::Twcs(t) => {
1313 assert_eq!(t.time_window_seconds(), Some(2 * 3600));
1314 }
1315 }
1316 let manifest_ctx = env
1317 .mock_manifest_context(version_control.current().version.metadata.clone())
1318 .await;
1319 let (tx, _rx) = mpsc::channel(4);
1320 let mut scheduler = env.mock_compaction_scheduler(tx);
1321 let (otx, _orx) = oneshot::channel();
1322 let request = scheduler
1323 .region_status
1324 .entry(region_id)
1325 .or_insert_with(|| {
1326 crate::compaction::CompactionStatus::new(
1327 region_id,
1328 version_control.clone(),
1329 env.access_layer.clone(),
1330 )
1331 })
1332 .new_compaction_request(
1333 scheduler.request_sender.clone(),
1334 OptionOutputTx::new(Some(OutputTx::new(otx))),
1335 scheduler.engine_config.clone(),
1336 scheduler.cache_manager.clone(),
1337 &manifest_ctx,
1338 scheduler.listener.clone(),
1339 schema_metadata_manager.clone(),
1340 1,
1341 );
1342 scheduler
1343 .schedule_compaction_request(
1344 request,
1345 compact_request::Options::Regular(Default::default()),
1346 )
1347 .await
1348 .unwrap();
1349 }
1350
1351 #[tokio::test]
1352 async fn test_find_compaction_options_priority() {
1353 fn schema_value_with_twcs(time_window: &str) -> SchemaNameValue {
1354 let mut schema_value = SchemaNameValue {
1355 ttl: Some(DatabaseTimeToLive::default()),
1356 ..Default::default()
1357 };
1358 schema_value
1359 .extra_options
1360 .insert("compaction.type".to_string(), "twcs".to_string());
1361 schema_value.extra_options.insert(
1362 "compaction.twcs.time_window".to_string(),
1363 time_window.to_string(),
1364 );
1365 schema_value
1366 }
1367
1368 let cases = [
1369 (
1370 "db options set and table override set",
1371 Some(schema_value_with_twcs("2h")),
1372 true,
1373 Some(Duration::from_secs(5 * 3600)),
1374 Some(5 * 3600),
1375 ),
1376 (
1377 "db options set and table override not set",
1378 Some(schema_value_with_twcs("2h")),
1379 false,
1380 None,
1381 Some(2 * 3600),
1382 ),
1383 (
1384 "db options not set and table override set",
1385 None,
1386 true,
1387 Some(Duration::from_secs(4 * 3600)),
1388 Some(4 * 3600),
1389 ),
1390 (
1391 "db options not set and table override not set",
1392 None,
1393 false,
1394 None,
1395 None,
1396 ),
1397 ];
1398
1399 for (case_name, schema_value, override_set, table_window, expected_window) in cases {
1400 let builder = VersionControlBuilder::new();
1401 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1402 let region_id = builder.region_id();
1403 let table_id = region_id.table_id();
1404 schema_metadata_manager
1405 .register_region_table_info(
1406 table_id,
1407 "t",
1408 "c",
1409 "s",
1410 schema_value,
1411 kv_backend.clone(),
1412 )
1413 .await;
1414
1415 let version_control = Arc::new(builder.build());
1416 let mut region_opts = version_control.current().version.options.clone();
1417 region_opts.compaction_override = override_set;
1418 if let Some(window) = table_window {
1419 let crate::region::options::CompactionOptions::Twcs(twcs) =
1420 &mut region_opts.compaction;
1421 twcs.time_window = Some(window);
1422 }
1423
1424 let (opts, _) = find_dynamic_options(region_id, ®ion_opts, &schema_metadata_manager)
1425 .await
1426 .unwrap();
1427 match opts {
1428 crate::region::options::CompactionOptions::Twcs(t) => {
1429 assert_eq!(t.time_window_seconds(), expected_window, "{case_name}");
1430 }
1431 }
1432 }
1433 }
1434
1435 #[tokio::test]
1436 async fn test_schedule_empty() {
1437 let env = SchedulerEnv::new().await;
1438 let (tx, _rx) = mpsc::channel(4);
1439 let mut scheduler = env.mock_compaction_scheduler(tx);
1440 let mut builder = VersionControlBuilder::new();
1441 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1442 schema_metadata_manager
1443 .register_region_table_info(
1444 builder.region_id().table_id(),
1445 "test_table",
1446 "test_catalog",
1447 "test_schema",
1448 None,
1449 kv_backend,
1450 )
1451 .await;
1452 let version_control = Arc::new(builder.build());
1454 let (output_tx, output_rx) = oneshot::channel();
1455 let waiter = OptionOutputTx::from(output_tx);
1456 let manifest_ctx = env
1457 .mock_manifest_context(version_control.current().version.metadata.clone())
1458 .await;
1459 let scheduled = scheduler
1460 .schedule_compaction(
1461 builder.region_id(),
1462 compact_request::Options::Regular(Default::default()),
1463 &version_control,
1464 &env.access_layer,
1465 waiter,
1466 &manifest_ctx,
1467 schema_metadata_manager.clone(),
1468 1,
1469 )
1470 .await
1471 .unwrap();
1472 assert!(!scheduled);
1473 let output = output_rx.await.unwrap().unwrap();
1474 assert_eq!(output, 0);
1475 assert!(scheduler.region_status.is_empty());
1476
1477 let version_control = Arc::new(builder.push_l0_file(0, 1000).build());
1479 let (output_tx, output_rx) = oneshot::channel();
1480 let waiter = OptionOutputTx::from(output_tx);
1481 let scheduled = scheduler
1482 .schedule_compaction(
1483 builder.region_id(),
1484 compact_request::Options::Regular(Default::default()),
1485 &version_control,
1486 &env.access_layer,
1487 waiter,
1488 &manifest_ctx,
1489 schema_metadata_manager,
1490 1,
1491 )
1492 .await
1493 .unwrap();
1494 assert!(!scheduled);
1495 let output = output_rx.await.unwrap().unwrap();
1496 assert_eq!(output, 0);
1497 assert!(scheduler.region_status.is_empty());
1498 }
1499
1500 #[tokio::test]
1501 async fn test_schedule_compaction_returns_true_when_task_scheduled() {
1502 let job_scheduler = Arc::new(VecScheduler::default());
1503 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
1504 let (tx, _rx) = mpsc::channel(4);
1505 let mut scheduler = env.mock_compaction_scheduler(tx);
1506 let mut builder = VersionControlBuilder::new();
1507 let region_id = builder.region_id();
1508 let end = 1000 * 1000;
1509 let version_control = Arc::new(
1511 builder
1512 .push_l0_file(0, end)
1513 .push_l0_file(10, end)
1514 .push_l0_file(50, end)
1515 .push_l0_file(80, end)
1516 .push_l0_file(90, end)
1517 .build(),
1518 );
1519 let manifest_ctx = env
1520 .mock_manifest_context(version_control.current().version.metadata.clone())
1521 .await;
1522 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1523 schema_metadata_manager
1524 .register_region_table_info(
1525 region_id.table_id(),
1526 "test_table",
1527 "test_catalog",
1528 "test_schema",
1529 None,
1530 kv_backend,
1531 )
1532 .await;
1533
1534 let scheduled = scheduler
1535 .schedule_compaction(
1536 region_id,
1537 Options::Regular(Default::default()),
1538 &version_control,
1539 &env.access_layer,
1540 OptionOutputTx::none(),
1541 &manifest_ctx,
1542 schema_metadata_manager,
1543 1,
1544 )
1545 .await
1546 .unwrap();
1547
1548 assert!(scheduled);
1551 assert_eq!(1, job_scheduler.num_jobs());
1552 assert!(scheduler.region_status.contains_key(®ion_id));
1553 }
1554
1555 #[tokio::test]
1556 async fn test_schedule_on_finished() {
1557 common_telemetry::init_default_ut_logging();
1558 let job_scheduler = Arc::new(VecScheduler::default());
1559 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
1560 let (tx, _rx) = mpsc::channel(4);
1561 let mut scheduler = env.mock_compaction_scheduler(tx);
1562 let mut builder = VersionControlBuilder::new();
1563 let purger = builder.file_purger();
1564 let region_id = builder.region_id();
1565
1566 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1567 schema_metadata_manager
1568 .register_region_table_info(
1569 builder.region_id().table_id(),
1570 "test_table",
1571 "test_catalog",
1572 "test_schema",
1573 None,
1574 kv_backend,
1575 )
1576 .await;
1577
1578 let end = 1000 * 1000;
1580 let version_control = Arc::new(
1581 builder
1582 .push_l0_file(0, end)
1583 .push_l0_file(10, end)
1584 .push_l0_file(50, end)
1585 .push_l0_file(80, end)
1586 .push_l0_file(90, end)
1587 .build(),
1588 );
1589 let manifest_ctx = env
1590 .mock_manifest_context(version_control.current().version.metadata.clone())
1591 .await;
1592 let scheduled = scheduler
1593 .schedule_compaction(
1594 region_id,
1595 compact_request::Options::Regular(Default::default()),
1596 &version_control,
1597 &env.access_layer,
1598 OptionOutputTx::none(),
1599 &manifest_ctx,
1600 schema_metadata_manager.clone(),
1601 1,
1602 )
1603 .await
1604 .unwrap();
1605 assert!(scheduled);
1607 assert_eq!(1, scheduler.region_status.len());
1608 assert_eq!(1, job_scheduler.num_jobs());
1609 let data = version_control.current();
1610 let file_metas: Vec<_> = data.version.ssts.levels()[0]
1611 .files
1612 .values()
1613 .map(|file| file.meta_ref().clone())
1614 .collect();
1615
1616 apply_edit(
1618 &version_control,
1619 &[(0, end), (20, end), (40, end), (60, end), (80, end)],
1620 &file_metas,
1621 purger.clone(),
1622 );
1623 let (tx, _rx) = oneshot::channel();
1625 let scheduled = scheduler
1626 .schedule_compaction(
1627 region_id,
1628 compact_request::Options::Regular(Default::default()),
1629 &version_control,
1630 &env.access_layer,
1631 OptionOutputTx::new(Some(OutputTx::new(tx))),
1632 &manifest_ctx,
1633 schema_metadata_manager.clone(),
1634 1,
1635 )
1636 .await
1637 .unwrap();
1638 assert!(!scheduled);
1639 assert_eq!(1, scheduler.region_status.len());
1640 assert_eq!(1, job_scheduler.num_jobs());
1641 assert!(
1642 !scheduler
1643 .region_status
1644 .get(&builder.region_id())
1645 .unwrap()
1646 .waiters
1647 .is_empty()
1648 );
1649
1650 scheduler
1652 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager.clone())
1653 .await;
1654 let scheduled = scheduler
1655 .schedule_next_compaction(region_id, &manifest_ctx, schema_metadata_manager.clone())
1656 .await;
1657 assert!(scheduled);
1658 assert_eq!(1, scheduler.region_status.len());
1659 assert_eq!(2, job_scheduler.num_jobs());
1660
1661 apply_edit(
1663 &version_control,
1664 &[(0, end), (20, end), (40, end), (60, end), (80, end)],
1665 &[],
1666 purger.clone(),
1667 );
1668 let (tx, _rx) = oneshot::channel();
1669 let scheduled = scheduler
1671 .schedule_compaction(
1672 region_id,
1673 compact_request::Options::Regular(Default::default()),
1674 &version_control,
1675 &env.access_layer,
1676 OptionOutputTx::new(Some(OutputTx::new(tx))),
1677 &manifest_ctx,
1678 schema_metadata_manager,
1679 1,
1680 )
1681 .await
1682 .unwrap();
1683 assert!(!scheduled);
1684 assert_eq!(2, job_scheduler.num_jobs());
1685 assert!(
1686 !scheduler
1687 .region_status
1688 .get(&builder.region_id())
1689 .unwrap()
1690 .waiters
1691 .is_empty()
1692 );
1693 }
1694
1695 #[tokio::test]
1696 async fn test_schedule_compaction_does_not_publish_status_when_schedule_fails() {
1697 common_telemetry::init_default_ut_logging();
1698 let env = SchedulerEnv::new()
1699 .await
1700 .scheduler(Arc::new(FailingScheduler));
1701 let (tx, _rx) = mpsc::channel(4);
1702 let mut scheduler = env.mock_compaction_scheduler(tx);
1703 let mut builder = VersionControlBuilder::new();
1704 let end = 1000 * 1000;
1705 let version_control = Arc::new(
1706 builder
1707 .push_l0_file(0, end)
1708 .push_l0_file(10, end)
1709 .push_l0_file(50, end)
1710 .push_l0_file(80, end)
1711 .push_l0_file(90, end)
1712 .build(),
1713 );
1714 let region_id = builder.region_id();
1715 let manifest_ctx = env
1716 .mock_manifest_context(version_control.current().version.metadata.clone())
1717 .await;
1718 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1719 schema_metadata_manager
1720 .register_region_table_info(
1721 builder.region_id().table_id(),
1722 "test_table",
1723 "test_catalog",
1724 "test_schema",
1725 None,
1726 kv_backend,
1727 )
1728 .await;
1729
1730 let result = scheduler
1731 .schedule_compaction(
1732 region_id,
1733 compact_request::Options::Regular(Default::default()),
1734 &version_control,
1735 &env.access_layer,
1736 OptionOutputTx::none(),
1737 &manifest_ctx,
1738 schema_metadata_manager,
1739 1,
1740 )
1741 .await;
1742
1743 assert!(result.is_err());
1744 assert!(!scheduler.region_status.contains_key(®ion_id));
1745 }
1746
1747 #[tokio::test]
1748 async fn test_manual_compaction_when_compaction_in_progress() {
1749 common_telemetry::init_default_ut_logging();
1750 let job_scheduler = Arc::new(VecScheduler::default());
1751 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
1752 let (tx, _rx) = mpsc::channel(4);
1753 let mut scheduler = env.mock_compaction_scheduler(tx);
1754 let mut builder = VersionControlBuilder::new();
1755 let purger = builder.file_purger();
1756 let region_id = builder.region_id();
1757
1758 let (schema_metadata_manager, kv_backend) = mock_schema_metadata_manager();
1759 schema_metadata_manager
1760 .register_region_table_info(
1761 builder.region_id().table_id(),
1762 "test_table",
1763 "test_catalog",
1764 "test_schema",
1765 None,
1766 kv_backend,
1767 )
1768 .await;
1769
1770 let end = 1000 * 1000;
1772 let version_control = Arc::new(
1773 builder
1774 .push_l0_file(0, end)
1775 .push_l0_file(10, end)
1776 .push_l0_file(50, end)
1777 .push_l0_file(80, end)
1778 .push_l0_file(90, end)
1779 .build(),
1780 );
1781 let manifest_ctx = env
1782 .mock_manifest_context(version_control.current().version.metadata.clone())
1783 .await;
1784
1785 let file_metas: Vec<_> = version_control.current().version.ssts.levels()[0]
1786 .files
1787 .values()
1788 .map(|file| file.meta_ref().clone())
1789 .collect();
1790
1791 apply_edit(
1793 &version_control,
1794 &[(0, end), (20, end), (40, end), (60, end), (80, end)],
1795 &file_metas,
1796 purger.clone(),
1797 );
1798
1799 scheduler
1800 .schedule_compaction(
1801 region_id,
1802 compact_request::Options::Regular(Default::default()),
1803 &version_control,
1804 &env.access_layer,
1805 OptionOutputTx::none(),
1806 &manifest_ctx,
1807 schema_metadata_manager.clone(),
1808 1,
1809 )
1810 .await
1811 .unwrap();
1812 assert_eq!(1, scheduler.region_status.len());
1814 assert_eq!(1, job_scheduler.num_jobs());
1815 assert!(
1816 scheduler
1817 .region_status
1818 .get(®ion_id)
1819 .unwrap()
1820 .pending_request
1821 .is_none()
1822 );
1823
1824 let (tx, _rx) = oneshot::channel();
1826 scheduler
1827 .schedule_compaction(
1828 region_id,
1829 compact_request::Options::StrictWindow(StrictWindow { window_seconds: 60 }),
1830 &version_control,
1831 &env.access_layer,
1832 OptionOutputTx::new(Some(OutputTx::new(tx))),
1833 &manifest_ctx,
1834 schema_metadata_manager.clone(),
1835 1,
1836 )
1837 .await
1838 .unwrap();
1839 assert_eq!(1, scheduler.region_status.len());
1840 assert_eq!(1, job_scheduler.num_jobs());
1842 let status = scheduler.region_status.get(&builder.region_id()).unwrap();
1843 assert!(status.pending_request.is_some());
1844
1845 scheduler
1847 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager.clone())
1848 .await;
1849 assert_eq!(1, scheduler.region_status.len());
1850 assert_eq!(2, job_scheduler.num_jobs());
1851
1852 let status = scheduler.region_status.get(&builder.region_id()).unwrap();
1853 assert!(status.pending_request.is_none());
1854 }
1855
1856 #[tokio::test]
1857 async fn test_compaction_bypass_in_staging_mode() {
1858 let env = SchedulerEnv::new().await;
1859 let (tx, _rx) = mpsc::channel(4);
1860 let mut scheduler = env.mock_compaction_scheduler(tx);
1861
1862 let builder = VersionControlBuilder::new();
1864 let version_control = Arc::new(builder.build());
1865 let region_id = version_control.current().version.metadata.region_id;
1866
1867 let staging_manifest_ctx = {
1869 let manager = RegionManifestManager::new(
1870 version_control.current().version.metadata.clone(),
1871 0,
1872 RegionManifestOptions {
1873 manifest_dir: "".to_string(),
1874 object_store: env.access_layer.object_store().clone(),
1875 compress_type: CompressionType::Uncompressed,
1876 checkpoint_distance: 10,
1877 remove_file_options: Default::default(),
1878 manifest_cache: None,
1879 },
1880 FormatType::PrimaryKey,
1881 &Default::default(),
1882 )
1883 .await
1884 .unwrap();
1885 Arc::new(ManifestContext::new(
1886 manager,
1887 RegionRoleState::Leader(RegionLeaderState::Staging),
1888 None,
1889 ))
1890 };
1891
1892 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
1893
1894 let (tx, rx) = oneshot::channel();
1896 scheduler
1897 .schedule_compaction(
1898 region_id,
1899 compact_request::Options::Regular(Default::default()),
1900 &version_control,
1901 &env.access_layer,
1902 OptionOutputTx::new(Some(OutputTx::new(tx))),
1903 &staging_manifest_ctx,
1904 schema_metadata_manager,
1905 1,
1906 )
1907 .await
1908 .unwrap();
1909
1910 let result = rx.await.unwrap();
1911 assert_eq!(result.unwrap(), 0); assert_eq!(0, scheduler.region_status.len());
1913 }
1914
1915 #[tokio::test]
1916 async fn test_add_ddl_request_to_pending() {
1917 let env = SchedulerEnv::new().await;
1918 let (tx, _rx) = mpsc::channel(4);
1919 let mut scheduler = env.mock_compaction_scheduler(tx);
1920 let builder = VersionControlBuilder::new();
1921 let version_control = Arc::new(builder.build());
1922 let region_id = builder.region_id();
1923
1924 scheduler.region_status.insert(
1925 region_id,
1926 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
1927 );
1928 scheduler
1929 .region_status
1930 .get_mut(®ion_id)
1931 .unwrap()
1932 .start_local_task();
1933
1934 let (output_tx, _output_rx) = oneshot::channel();
1935 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
1936 region_id,
1937 sender: OptionOutputTx::from(output_tx),
1938 request: crate::request::DdlRequest::EnterStaging(
1939 store_api::region_request::EnterStagingRequest {
1940 partition_directive:
1941 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
1942 },
1943 ),
1944 });
1945
1946 assert!(scheduler.has_pending_ddls(region_id));
1947 }
1948
1949 #[tokio::test]
1950 async fn test_request_cancel_state_transitions() {
1951 let env = SchedulerEnv::new().await;
1952 let builder = VersionControlBuilder::new();
1953 let region_id = builder.region_id();
1954 let version_control = Arc::new(builder.build());
1955 let mut status =
1956 CompactionStatus::new(region_id, version_control, env.access_layer.clone());
1957 let state = status.start_local_task();
1958
1959 assert_eq!(status.request_cancel(), RequestCancelResult::CancelIssued);
1960 assert!(state.cancel_handle().is_cancelled());
1961 assert_eq!(
1962 status.request_cancel(),
1963 RequestCancelResult::AlreadyCancelling
1964 );
1965
1966 assert!(!state.mark_commit_started());
1967 assert_eq!(
1968 status.request_cancel(),
1969 RequestCancelResult::AlreadyCancelling
1970 );
1971
1972 assert!(status.clear_running_task());
1973 assert_eq!(status.request_cancel(), RequestCancelResult::NotRunning);
1974 }
1975
1976 #[tokio::test]
1977 async fn test_request_cancel_remote_compaction_is_too_late() {
1978 let env = SchedulerEnv::new().await;
1979 let builder = VersionControlBuilder::new();
1980 let region_id = builder.region_id();
1981 let version_control = Arc::new(builder.build());
1982 let mut status =
1983 CompactionStatus::new(region_id, version_control, env.access_layer.clone());
1984
1985 status.start_remote_task();
1986
1987 assert_eq!(
1988 status.request_cancel(),
1989 RequestCancelResult::TooLateToCancel
1990 );
1991 assert!(status.active_compaction.is_some());
1992 }
1993
1994 #[tokio::test]
1995 async fn test_on_compaction_cancelled_returns_pending_ddl_requests() {
1996 let job_scheduler = Arc::new(VecScheduler::default());
1997 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
1998 let (tx, _rx) = mpsc::channel(4);
1999 let mut scheduler = env.mock_compaction_scheduler(tx);
2000 let builder = VersionControlBuilder::new();
2001 let version_control = Arc::new(builder.build());
2002 let region_id = builder.region_id();
2003 let _manifest_ctx = env
2004 .mock_manifest_context(version_control.current().version.metadata.clone())
2005 .await;
2006 let (_schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2007
2008 scheduler.region_status.insert(
2009 region_id,
2010 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2011 );
2012 scheduler
2013 .region_status
2014 .get_mut(®ion_id)
2015 .unwrap()
2016 .start_local_task();
2017
2018 let (output_tx, _output_rx) = oneshot::channel();
2019 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2020 region_id,
2021 sender: OptionOutputTx::from(output_tx),
2022 request: crate::request::DdlRequest::EnterStaging(
2023 store_api::region_request::EnterStagingRequest {
2024 partition_directive:
2025 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2026 },
2027 ),
2028 });
2029
2030 let pending_ddls = scheduler.on_compaction_cancelled(region_id).await;
2031
2032 assert_eq!(pending_ddls.len(), 1);
2033 assert!(!scheduler.has_pending_ddls(region_id));
2034 assert!(!scheduler.region_status.contains_key(®ion_id));
2035 assert_eq!(job_scheduler.num_jobs(), 0);
2036 }
2037
2038 #[tokio::test]
2039 async fn test_on_compaction_cancelled_prioritizes_pending_ddls_over_pending_compaction() {
2040 let job_scheduler = Arc::new(VecScheduler::default());
2041 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
2042 let (tx, _rx) = mpsc::channel(4);
2043 let mut scheduler = env.mock_compaction_scheduler(tx);
2044 let builder = VersionControlBuilder::new();
2045 let version_control = Arc::new(builder.build());
2046 let region_id = builder.region_id();
2047 let _manifest_ctx = env
2048 .mock_manifest_context(version_control.current().version.metadata.clone())
2049 .await;
2050 let (_schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2051
2052 scheduler.region_status.insert(
2053 region_id,
2054 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2055 );
2056 let status = scheduler.region_status.get_mut(®ion_id).unwrap();
2057 status.start_local_task();
2058 let (manual_tx, manual_rx) = oneshot::channel();
2059 status.set_pending_request(PendingCompaction {
2060 options: compact_request::Options::StrictWindow(StrictWindow { window_seconds: 60 }),
2061 waiter: OptionOutputTx::from(manual_tx),
2062 max_parallelism: 1,
2063 });
2064
2065 let (output_tx, _output_rx) = oneshot::channel();
2066 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2067 region_id,
2068 sender: OptionOutputTx::from(output_tx),
2069 request: crate::request::DdlRequest::EnterStaging(
2070 store_api::region_request::EnterStagingRequest {
2071 partition_directive:
2072 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2073 },
2074 ),
2075 });
2076
2077 let pending_ddls = scheduler.on_compaction_cancelled(region_id).await;
2078
2079 assert_eq!(pending_ddls.len(), 1);
2080 assert!(!scheduler.region_status.contains_key(®ion_id));
2081 assert_eq!(job_scheduler.num_jobs(), 0);
2082 assert_matches!(manual_rx.await.unwrap(), Err(_));
2083 }
2084
2085 #[tokio::test]
2086 async fn test_pending_ddl_request_failed_on_compaction_failed() {
2087 let env = SchedulerEnv::new().await;
2088 let (tx, _rx) = mpsc::channel(4);
2089 let mut scheduler = env.mock_compaction_scheduler(tx);
2090 let builder = VersionControlBuilder::new();
2091 let version_control = Arc::new(builder.build());
2092 let region_id = builder.region_id();
2093
2094 scheduler.region_status.insert(
2095 region_id,
2096 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2097 );
2098
2099 let (output_tx, output_rx) = oneshot::channel();
2100 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2101 region_id,
2102 sender: OptionOutputTx::from(output_tx),
2103 request: crate::request::DdlRequest::EnterStaging(
2104 store_api::region_request::EnterStagingRequest {
2105 partition_directive:
2106 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2107 },
2108 ),
2109 });
2110
2111 assert!(scheduler.has_pending_ddls(region_id));
2112 scheduler
2113 .on_compaction_failed(region_id, Arc::new(RegionClosedSnafu { region_id }.build()));
2114
2115 assert!(!scheduler.has_pending_ddls(region_id));
2116 let result = output_rx.await.unwrap();
2117 assert_matches!(result, Err(_));
2118 }
2119
2120 #[tokio::test]
2121 async fn test_pending_ddl_request_failed_on_region_closed() {
2122 let env = SchedulerEnv::new().await;
2123 let (tx, _rx) = mpsc::channel(4);
2124 let mut scheduler = env.mock_compaction_scheduler(tx);
2125 let builder = VersionControlBuilder::new();
2126 let version_control = Arc::new(builder.build());
2127 let region_id = builder.region_id();
2128
2129 scheduler.region_status.insert(
2130 region_id,
2131 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2132 );
2133
2134 let (output_tx, output_rx) = oneshot::channel();
2135 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2136 region_id,
2137 sender: OptionOutputTx::from(output_tx),
2138 request: crate::request::DdlRequest::EnterStaging(
2139 store_api::region_request::EnterStagingRequest {
2140 partition_directive:
2141 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2142 },
2143 ),
2144 });
2145
2146 assert!(scheduler.has_pending_ddls(region_id));
2147 scheduler.on_region_closed(region_id);
2148
2149 assert!(!scheduler.has_pending_ddls(region_id));
2150 let result = output_rx.await.unwrap();
2151 assert_matches!(result, Err(_));
2152 }
2153
2154 #[tokio::test]
2155 async fn test_pending_ddl_request_failed_on_region_dropped() {
2156 let env = SchedulerEnv::new().await;
2157 let (tx, _rx) = mpsc::channel(4);
2158 let mut scheduler = env.mock_compaction_scheduler(tx);
2159 let builder = VersionControlBuilder::new();
2160 let version_control = Arc::new(builder.build());
2161 let region_id = builder.region_id();
2162
2163 scheduler.region_status.insert(
2164 region_id,
2165 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2166 );
2167
2168 let (output_tx, output_rx) = oneshot::channel();
2169 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2170 region_id,
2171 sender: OptionOutputTx::from(output_tx),
2172 request: crate::request::DdlRequest::EnterStaging(
2173 store_api::region_request::EnterStagingRequest {
2174 partition_directive:
2175 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2176 },
2177 ),
2178 });
2179
2180 assert!(scheduler.has_pending_ddls(region_id));
2181 scheduler.on_region_dropped(region_id);
2182
2183 assert!(!scheduler.has_pending_ddls(region_id));
2184 let result = output_rx.await.unwrap();
2185 assert_matches!(result, Err(_));
2186 }
2187
2188 #[tokio::test]
2189 async fn test_pending_ddl_request_failed_on_region_truncated() {
2190 let env = SchedulerEnv::new().await;
2191 let (tx, _rx) = mpsc::channel(4);
2192 let mut scheduler = env.mock_compaction_scheduler(tx);
2193 let builder = VersionControlBuilder::new();
2194 let version_control = Arc::new(builder.build());
2195 let region_id = builder.region_id();
2196
2197 scheduler.region_status.insert(
2198 region_id,
2199 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2200 );
2201
2202 let (output_tx, output_rx) = oneshot::channel();
2203 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2204 region_id,
2205 sender: OptionOutputTx::from(output_tx),
2206 request: crate::request::DdlRequest::EnterStaging(
2207 store_api::region_request::EnterStagingRequest {
2208 partition_directive:
2209 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2210 },
2211 ),
2212 });
2213
2214 assert!(scheduler.has_pending_ddls(region_id));
2215 scheduler.on_region_truncated(region_id);
2216
2217 assert!(!scheduler.has_pending_ddls(region_id));
2218 let result = output_rx.await.unwrap();
2219 assert_matches!(result, Err(_));
2220 }
2221
2222 #[tokio::test]
2223 async fn test_on_compaction_finished_returns_pending_ddl_requests() {
2224 let job_scheduler = Arc::new(VecScheduler::default());
2225 let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
2226 let (tx, _rx) = mpsc::channel(4);
2227 let mut scheduler = env.mock_compaction_scheduler(tx);
2228 let builder = VersionControlBuilder::new();
2229 let version_control = Arc::new(builder.build());
2230 let region_id = builder.region_id();
2231 let manifest_ctx = env
2232 .mock_manifest_context(version_control.current().version.metadata.clone())
2233 .await;
2234 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2235
2236 scheduler.region_status.insert(
2237 region_id,
2238 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2239 );
2240 scheduler
2241 .region_status
2242 .get_mut(®ion_id)
2243 .unwrap()
2244 .start_local_task();
2245
2246 let (output_tx, _output_rx) = oneshot::channel();
2247 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2248 region_id,
2249 sender: OptionOutputTx::from(output_tx),
2250 request: crate::request::DdlRequest::EnterStaging(
2251 store_api::region_request::EnterStagingRequest {
2252 partition_directive:
2253 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2254 },
2255 ),
2256 });
2257
2258 let pending_ddls = scheduler
2259 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2260 .await;
2261
2262 assert_eq!(pending_ddls.len(), 1);
2263 assert!(!scheduler.has_pending_ddls(region_id));
2264 assert!(!scheduler.region_status.contains_key(®ion_id));
2265 assert_eq!(job_scheduler.num_jobs(), 0);
2266 }
2267
2268 #[tokio::test]
2269 async fn test_on_compaction_finished_replays_pending_ddl_after_manual_noop() {
2270 let env = SchedulerEnv::new().await;
2271 let (tx, _rx) = mpsc::channel(4);
2272 let mut scheduler = env.mock_compaction_scheduler(tx);
2273 let builder = VersionControlBuilder::new();
2274 let version_control = Arc::new(builder.build());
2275 let region_id = builder.region_id();
2276 let manifest_ctx = env
2277 .mock_manifest_context(version_control.current().version.metadata.clone())
2278 .await;
2279 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2280
2281 let (manual_tx, manual_rx) = oneshot::channel();
2282 let mut status =
2283 CompactionStatus::new(region_id, version_control.clone(), env.access_layer.clone());
2284 status.start_local_task();
2285 status.set_pending_request(PendingCompaction {
2286 options: compact_request::Options::Regular(Default::default()),
2287 waiter: OptionOutputTx::from(manual_tx),
2288 max_parallelism: 1,
2289 });
2290 scheduler.region_status.insert(region_id, status);
2291
2292 let (ddl_tx, _ddl_rx) = oneshot::channel();
2293 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2294 region_id,
2295 sender: OptionOutputTx::from(ddl_tx),
2296 request: crate::request::DdlRequest::EnterStaging(
2297 store_api::region_request::EnterStagingRequest {
2298 partition_directive:
2299 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2300 },
2301 ),
2302 });
2303
2304 let pending_ddls = scheduler
2305 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2306 .await;
2307
2308 assert_eq!(pending_ddls.len(), 1);
2309 assert!(!scheduler.region_status.contains_key(®ion_id));
2310 assert_eq!(manual_rx.await.unwrap().unwrap(), 0);
2311 }
2312
2313 #[tokio::test]
2314 async fn test_on_compaction_finished_returns_empty_when_region_absent() {
2315 let env = SchedulerEnv::new().await;
2316 let (tx, _rx) = mpsc::channel(4);
2317 let mut scheduler = env.mock_compaction_scheduler(tx);
2318 let builder = VersionControlBuilder::new();
2319 let region_id = builder.region_id();
2320 let version_control = Arc::new(builder.build());
2321 let manifest_ctx = env
2322 .mock_manifest_context(version_control.current().version.metadata.clone())
2323 .await;
2324 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2325
2326 let pending_ddls = scheduler
2327 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2328 .await;
2329
2330 assert!(pending_ddls.is_empty());
2331 }
2332
2333 #[tokio::test]
2334 async fn test_on_compaction_finished_manual_schedule_error_cleans_status() {
2335 let env = SchedulerEnv::new()
2336 .await
2337 .scheduler(Arc::new(FailingScheduler));
2338 let (tx, _rx) = mpsc::channel(4);
2339 let mut scheduler = env.mock_compaction_scheduler(tx);
2340 let mut builder = VersionControlBuilder::new();
2341 let end = 1000 * 1000;
2342 let version_control = Arc::new(
2343 builder
2344 .push_l0_file(0, end)
2345 .push_l0_file(10, end)
2346 .push_l0_file(50, end)
2347 .push_l0_file(80, end)
2348 .push_l0_file(90, end)
2349 .build(),
2350 );
2351 let region_id = builder.region_id();
2352 let manifest_ctx = env
2353 .mock_manifest_context(version_control.current().version.metadata.clone())
2354 .await;
2355 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2356
2357 let (manual_tx, manual_rx) = oneshot::channel();
2358 let mut status =
2359 CompactionStatus::new(region_id, version_control.clone(), env.access_layer.clone());
2360 status.start_local_task();
2361 status.set_pending_request(PendingCompaction {
2362 options: compact_request::Options::Regular(Default::default()),
2363 waiter: OptionOutputTx::from(manual_tx),
2364 max_parallelism: 1,
2365 });
2366 scheduler.region_status.insert(region_id, status);
2367
2368 let (ddl_tx, ddl_rx) = oneshot::channel();
2369 scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2370 region_id,
2371 sender: OptionOutputTx::from(ddl_tx),
2372 request: crate::request::DdlRequest::EnterStaging(
2373 store_api::region_request::EnterStagingRequest {
2374 partition_directive:
2375 store_api::region_request::StagingPartitionDirective::RejectAllWrites,
2376 },
2377 ),
2378 });
2379
2380 let pending_ddls = scheduler
2381 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2382 .await;
2383
2384 assert!(pending_ddls.is_empty());
2385 assert!(!scheduler.region_status.contains_key(®ion_id));
2386 assert!(manual_rx.await.is_err());
2387 assert_matches!(ddl_rx.await.unwrap(), Err(_));
2388 }
2389
2390 #[tokio::test]
2391 async fn test_on_compaction_finished_next_schedule_noop_removes_status() {
2392 let env = SchedulerEnv::new().await;
2393 let (tx, _rx) = mpsc::channel(4);
2394 let mut scheduler = env.mock_compaction_scheduler(tx);
2395 let builder = VersionControlBuilder::new();
2396 let version_control = Arc::new(builder.build());
2397 let region_id = builder.region_id();
2398 let manifest_ctx = env
2399 .mock_manifest_context(version_control.current().version.metadata.clone())
2400 .await;
2401 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2402
2403 scheduler.region_status.insert(
2404 region_id,
2405 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2406 );
2407 scheduler
2408 .region_status
2409 .get_mut(®ion_id)
2410 .unwrap()
2411 .start_local_task();
2412
2413 let pending_ddls = scheduler
2414 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2415 .await;
2416
2417 assert!(pending_ddls.is_empty());
2418 assert!(scheduler.region_status.contains_key(®ion_id));
2419
2420 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2421 let scheduled = scheduler
2424 .schedule_next_compaction(region_id, &manifest_ctx, schema_metadata_manager)
2425 .await;
2426 assert!(!scheduled);
2427 assert!(!scheduler.region_status.contains_key(®ion_id));
2428 }
2429
2430 #[tokio::test]
2431 async fn test_on_compaction_finished_next_schedule_error_cleans_status() {
2432 let env = SchedulerEnv::new()
2433 .await
2434 .scheduler(Arc::new(FailingScheduler));
2435 let (tx, _rx) = mpsc::channel(4);
2436 let mut scheduler = env.mock_compaction_scheduler(tx);
2437 let mut builder = VersionControlBuilder::new();
2438 let end = 1000 * 1000;
2439 let version_control = Arc::new(
2440 builder
2441 .push_l0_file(0, end)
2442 .push_l0_file(10, end)
2443 .push_l0_file(50, end)
2444 .push_l0_file(80, end)
2445 .push_l0_file(90, end)
2446 .build(),
2447 );
2448 let region_id = builder.region_id();
2449 let manifest_ctx = env
2450 .mock_manifest_context(version_control.current().version.metadata.clone())
2451 .await;
2452 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2453
2454 scheduler.region_status.insert(
2455 region_id,
2456 CompactionStatus::new(region_id, version_control, env.access_layer.clone()),
2457 );
2458 scheduler
2459 .region_status
2460 .get_mut(®ion_id)
2461 .unwrap()
2462 .start_local_task();
2463
2464 let pending_ddls = scheduler
2465 .on_compaction_finished(region_id, &manifest_ctx, schema_metadata_manager)
2466 .await;
2467
2468 assert!(pending_ddls.is_empty());
2469 assert!(scheduler.region_status.contains_key(®ion_id));
2470
2471 let (schema_metadata_manager, _kv_backend) = mock_schema_metadata_manager();
2472 let scheduled = scheduler
2474 .schedule_next_compaction(region_id, &manifest_ctx, schema_metadata_manager)
2475 .await;
2476 assert!(!scheduled);
2477 assert!(!scheduler.region_status.contains_key(®ion_id));
2478 }
2479
2480 #[tokio::test]
2481 async fn test_concurrent_memory_competition() {
2482 let manager = Arc::new(new_compaction_memory_manager(3 * 1024 * 1024)); let barrier = Arc::new(Barrier::new(3));
2484 let mut handles = vec![];
2485
2486 for _i in 0..3 {
2488 let mgr = manager.clone();
2489 let bar = barrier.clone();
2490 let handle = tokio::spawn(async move {
2491 bar.wait().await; mgr.try_acquire(2 * 1024 * 1024)
2493 });
2494 handles.push(handle);
2495 }
2496
2497 let results: Vec<Option<CompactionMemoryGuard>> = futures::future::join_all(handles)
2498 .await
2499 .into_iter()
2500 .map(|r| r.unwrap())
2501 .collect();
2502
2503 let succeeded = results.iter().filter(|r| r.is_some()).count();
2505 let failed = results.iter().filter(|r| r.is_none()).count();
2506
2507 assert_eq!(succeeded, 1, "Expected exactly 1 task to acquire memory");
2508 assert_eq!(failed, 2, "Expected 2 tasks to fail");
2509
2510 drop(results);
2512 assert_eq!(manager.used_bytes(), 0);
2513 }
2514}