Skip to main content

mito2/
flush.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Flush related utilities and structs.
16
17use std::collections::HashMap;
18use std::num::NonZeroU64;
19use std::sync::atomic::{AtomicUsize, Ordering};
20use std::sync::{Arc, Mutex};
21use std::time::Instant;
22
23use bytes::Bytes;
24use common_base::cancellation::CancellableFuture;
25use common_telemetry::{debug, error, info};
26use datatypes::arrow::datatypes::SchemaRef;
27use partition::expr::PartitionExpr;
28use smallvec::{SmallVec, smallvec};
29use snafu::{ResultExt, ensure};
30use store_api::metadata::RegionMetadataRef;
31use store_api::region_request::RegionFlushReason;
32use store_api::storage::{RegionId, SequenceNumber};
33use strum::IntoStaticStr;
34use tokio::sync::{Semaphore, mpsc, watch};
35
36use crate::access_layer::{
37    AccessLayerRef, Metrics, OperationType, SstInfoArray, SstWriteRequest, WriteType,
38};
39use crate::cache::CacheManagerRef;
40use crate::compaction::{collect_json2_rewrite_plans, rewrite_json2_batch, rewrite_json2_schema};
41use crate::config::MitoConfig;
42use crate::engine::region_hook::SstFileInfo;
43use crate::error::{
44    Error, FlushCancelledSnafu, FlushRegionSnafu, JoinSnafu, RegionBusySnafu, RegionClosedSnafu,
45    RegionDroppedSnafu, RegionTruncatedSnafu, Result, UnexpectedSnafu,
46};
47use crate::manifest::action::{RegionEdit, RegionMetaAction, RegionMetaActionList};
48use crate::memtable::bulk::ENCODE_ROW_THRESHOLD;
49use crate::memtable::{BoxedRecordBatchIterator, EncodedRange, MemtableRanges, RangesOptions};
50use crate::metrics::{
51    FLUSH_BYTES_TOTAL, FLUSH_ELAPSED, FLUSH_FAILURE_TOTAL, FLUSH_FILE_TOTAL, FLUSH_REQUESTS_TOTAL,
52    INFLIGHT_FLUSH_COUNT,
53};
54use crate::read::FlatSource;
55use crate::read::flat_dedup::{FlatDedupIterator, FlatLastNonNull, FlatLastRow};
56use crate::read::flat_merge::FlatMergeIterator;
57use crate::region::options::{IndexOptions, MergeMode, RegionOptions};
58use crate::region::version::{VersionControlData, VersionControlRef, VersionRef};
59use crate::region::{ManifestContextRef, RegionLeaderState, RegionRoleState, parse_partition_expr};
60use crate::request::{
61    BackgroundNotify, DdlRequest, FlushFailed, FlushFinished, OnFailure, OptionOutputTx, OutputTx,
62    SenderBulkRequest, SenderDdlRequest, SenderWriteRequest, WorkerRequest, WorkerRequestWithTime,
63};
64use crate::schedule::CancellableTaskState;
65use crate::schedule::scheduler::{Job, SchedulerRef};
66use crate::sst::file::{FileMeta, UncommittedSsts};
67use crate::sst::parquet::metadata::extract_primary_key_range;
68use crate::sst::parquet::{
69    DEFAULT_READ_BATCH_SIZE, DEFAULT_ROW_GROUP_SIZE, SstInfo, WriteOptions, flat_format,
70};
71use crate::sst::{FlatSchemaOptions, FormatType, to_flat_sst_arrow_schema};
72use crate::worker::WorkerListener;
73
74/// Global write buffer (memtable) manager.
75///
76/// Tracks write buffer (memtable) usages and decide whether the engine needs to flush.
77pub trait WriteBufferManager: Send + Sync + std::fmt::Debug {
78    /// Returns whether to trigger the engine.
79    fn should_flush_engine(&self) -> bool;
80
81    /// Returns whether to stall write requests.
82    fn should_stall(&self) -> bool;
83
84    /// Reserves `mem` bytes.
85    fn reserve_mem(&self, mem: usize);
86
87    /// Tells the manager we are freeing `mem` bytes.
88    ///
89    /// We are in the process of freeing `mem` bytes, so it is not considered
90    /// when checking the soft limit.
91    fn schedule_free_mem(&self, mem: usize);
92
93    /// We have freed `mem` bytes.
94    fn free_mem(&self, mem: usize);
95
96    /// Returns the total memory used by memtables.
97    fn memory_usage(&self) -> usize;
98
99    /// Returns the mutable memtable memory limit.
100    ///
101    /// The write buffer manager should flush memtables when the mutable memory usage
102    /// exceeds this limit.
103    fn flush_limit(&self) -> usize;
104}
105
106pub type WriteBufferManagerRef = Arc<dyn WriteBufferManager>;
107
108/// Default [WriteBufferManager] implementation.
109///
110/// Inspired by RocksDB's WriteBufferManager.
111/// <https://github.com/facebook/rocksdb/blob/main/include/rocksdb/write_buffer_manager.h>
112#[derive(Debug)]
113pub struct WriteBufferManagerImpl {
114    /// Write buffer size for the engine.
115    global_write_buffer_size: usize,
116    /// Mutable memtable memory size limit.
117    mutable_limit: usize,
118    /// Memory in used (e.g. used by mutable and immutable memtables).
119    memory_used: AtomicUsize,
120    /// Memory that hasn't been scheduled to free (e.g. used by mutable memtables).
121    memory_active: AtomicUsize,
122    /// Optional notifier.
123    /// The manager can wake up the worker once we free the write buffer.
124    notifier: Option<watch::Sender<()>>,
125}
126
127impl WriteBufferManagerImpl {
128    /// Returns a new manager with specific `global_write_buffer_size`.
129    pub fn new(global_write_buffer_size: usize) -> Self {
130        Self {
131            global_write_buffer_size,
132            mutable_limit: Self::get_mutable_limit(global_write_buffer_size),
133            memory_used: AtomicUsize::new(0),
134            memory_active: AtomicUsize::new(0),
135            notifier: None,
136        }
137    }
138
139    /// Attaches a notifier to the manager.
140    pub fn with_notifier(mut self, notifier: watch::Sender<()>) -> Self {
141        self.notifier = Some(notifier);
142        self
143    }
144
145    /// Returns memory usage of mutable memtables.
146    pub fn mutable_usage(&self) -> usize {
147        self.memory_active.load(Ordering::Relaxed)
148    }
149
150    /// Returns the size limit for mutable memtables.
151    fn get_mutable_limit(global_write_buffer_size: usize) -> usize {
152        // Reserves half of the write buffer for mutable memtable.
153        global_write_buffer_size / 2
154    }
155}
156
157impl WriteBufferManager for WriteBufferManagerImpl {
158    fn should_flush_engine(&self) -> bool {
159        let mutable_memtable_memory_usage = self.memory_active.load(Ordering::Relaxed);
160        if mutable_memtable_memory_usage >= self.mutable_limit {
161            debug!(
162                "Engine should flush (over mutable limit), mutable_usage: {}, memory_usage: {}, mutable_limit: {}, global_limit: {}",
163                mutable_memtable_memory_usage,
164                self.memory_usage(),
165                self.mutable_limit,
166                self.global_write_buffer_size,
167            );
168            return true;
169        }
170
171        let memory_usage = self.memory_used.load(Ordering::Relaxed);
172        if memory_usage >= self.global_write_buffer_size {
173            return true;
174        }
175
176        false
177    }
178
179    fn should_stall(&self) -> bool {
180        self.memory_usage() >= self.global_write_buffer_size
181    }
182
183    fn reserve_mem(&self, mem: usize) {
184        self.memory_used.fetch_add(mem, Ordering::Relaxed);
185        self.memory_active.fetch_add(mem, Ordering::Relaxed);
186    }
187
188    fn schedule_free_mem(&self, mem: usize) {
189        self.memory_active.fetch_sub(mem, Ordering::Relaxed);
190    }
191
192    fn free_mem(&self, mem: usize) {
193        self.memory_used.fetch_sub(mem, Ordering::Relaxed);
194        if let Some(notifier) = &self.notifier {
195            // Notifies the worker after the memory usage is decreased. When we drop the memtable
196            // outside of the worker, the worker may still stall requests because the memory usage
197            // is not updated. So we need to notify the worker to handle stalled requests again.
198            let _ = notifier.send(());
199        }
200    }
201
202    fn memory_usage(&self) -> usize {
203        self.memory_used.load(Ordering::Relaxed)
204    }
205
206    fn flush_limit(&self) -> usize {
207        self.mutable_limit
208    }
209}
210
211/// Reason of a flush task.
212#[derive(Debug, IntoStaticStr, Clone, Copy, PartialEq, Eq)]
213pub enum FlushReason {
214    /// Engine reaches flush threshold.
215    EngineFull,
216    /// Region reaches its write buffer threshold.
217    RegionFull,
218    /// Manual flush.
219    Manual,
220    /// Flush to alter table.
221    Alter,
222    /// Flush periodically.
223    Periodically,
224    /// Flush memtable during downgrading state.
225    Downgrading,
226    /// Enter staging mode.
227    EnterStaging,
228    /// Flush triggered before region migration.
229    RegionMigration,
230    /// Flush triggered by repartition procedure.
231    Repartition,
232    /// Flush triggered by remote WAL pruning.
233    RemoteWalPrune,
234    /// Flush before closing a Noop WAL region.
235    Closing,
236}
237
238impl FlushReason {
239    /// Get flush reason as static str.
240    fn as_str(&self) -> &'static str {
241        self.into()
242    }
243}
244
245impl From<RegionFlushReason> for FlushReason {
246    fn from(reason: RegionFlushReason) -> Self {
247        match reason {
248            RegionFlushReason::RegionMigration => FlushReason::RegionMigration,
249            RegionFlushReason::Repartition => FlushReason::Repartition,
250            RegionFlushReason::RemoteWalPrune => FlushReason::RemoteWalPrune,
251            RegionFlushReason::Closing => FlushReason::Closing,
252            RegionFlushReason::Downgrading => FlushReason::Downgrading,
253        }
254    }
255}
256
257/// Task to flush a region.
258pub(crate) struct RegionFlushTask {
259    /// Region to flush.
260    pub(crate) region_id: RegionId,
261    /// Reason to flush.
262    pub(crate) reason: FlushReason,
263    /// Flush result senders.
264    pub(crate) senders: Vec<OutputTx>,
265    /// Request sender to notify the worker.
266    pub(crate) request_sender: mpsc::Sender<WorkerRequestWithTime>,
267
268    pub(crate) access_layer: AccessLayerRef,
269    pub(crate) listener: WorkerListener,
270    pub(crate) engine_config: Arc<MitoConfig>,
271    pub(crate) row_group_size: Option<usize>,
272    pub(crate) cache_manager: CacheManagerRef,
273    pub(crate) manifest_ctx: ManifestContextRef,
274
275    /// Index options for the region.
276    pub(crate) index_options: IndexOptions,
277    /// Semaphore to control flush concurrency.
278    pub(crate) flush_semaphore: Arc<Semaphore>,
279    /// Whether the region is in staging mode.
280    pub(crate) is_staging: bool,
281    /// Partition expression of the region.
282    ///
283    /// This is used to generate the file meta.
284    pub(crate) partition_expr: Option<String>,
285}
286
287struct FlushTaskWaiters {
288    region_id: RegionId,
289    senders: Mutex<Vec<OutputTx>>,
290}
291
292impl FlushTaskWaiters {
293    fn new(region_id: RegionId, senders: Vec<OutputTx>) -> Self {
294        Self {
295            region_id,
296            senders: Mutex::new(senders),
297        }
298    }
299
300    fn take(&self) -> Vec<OutputTx> {
301        std::mem::take(&mut *self.senders.lock().unwrap())
302    }
303
304    fn on_failure(&self, err: Arc<Error>) {
305        for sender in self.take() {
306            sender.send(Err(err.clone()).context(FlushRegionSnafu {
307                region_id: self.region_id,
308            }));
309        }
310    }
311}
312
313impl Drop for FlushTaskWaiters {
314    fn drop(&mut self) {
315        self.on_failure(Arc::new(
316            RegionBusySnafu {
317                region_id: self.region_id,
318            }
319            .build(),
320        ));
321    }
322}
323
324impl RegionFlushTask {
325    /// Push the sender if it is not none.
326    pub(crate) fn push_sender(&mut self, mut sender: OptionOutputTx) {
327        if let Some(sender) = sender.take_inner() {
328            self.senders.push(sender);
329        }
330    }
331
332    /// Consumes the task and notify the sender the job is success.
333    fn on_success(self) {
334        for sender in self.senders {
335            sender.send(Ok(0));
336        }
337    }
338
339    /// Send flush error to waiter.
340    fn on_failure(&mut self, err: Arc<Error>) {
341        for sender in self.senders.drain(..) {
342            sender.send(Err(err.clone()).context(FlushRegionSnafu {
343                region_id: self.region_id,
344            }));
345        }
346    }
347
348    /// Converts the flush task into a background job.
349    ///
350    /// We must call this in the region worker.
351    fn into_flush_job(
352        mut self,
353        version_control: &VersionControlRef,
354        state: CancellableTaskState,
355    ) -> (Job, Arc<FlushTaskWaiters>) {
356        // Get a version of this region before creating a job to get current
357        // wal entry id, sequence and immutable memtables.
358        let version_data = version_control.current();
359        let waiters = Arc::new(FlushTaskWaiters::new(
360            self.region_id,
361            std::mem::take(&mut self.senders),
362        ));
363        let job_waiters = waiters.clone();
364
365        let job = Box::pin(async move {
366            self.senders = job_waiters.take();
367            INFLIGHT_FLUSH_COUNT.inc();
368            self.do_flush(version_data, state).await;
369            INFLIGHT_FLUSH_COUNT.dec();
370        });
371        (job, waiters)
372    }
373
374    /// Runs the flush task.
375    async fn do_flush(&mut self, version_data: VersionControlData, state: CancellableTaskState) {
376        let timer = FLUSH_ELAPSED.with_label_values(&["total"]).start_timer();
377        let uncommitted = UncommittedSsts::new(
378            self.region_id,
379            self.access_layer.clone(),
380            Some(self.cache_manager.clone()),
381        );
382        self.listener.on_flush_begin(self.region_id).await;
383        let flush_result = if state.is_cancelled() {
384            FlushCancelledSnafu.fail()
385        } else {
386            self.flush_memtables(&version_data, &state, &uncommitted)
387                .await
388        };
389
390        let worker_request = match flush_result {
391            Ok(edit) => {
392                let memtables_to_remove = version_data
393                    .version
394                    .memtables
395                    .immutables()
396                    .iter()
397                    .map(|m| m.id())
398                    .collect();
399                let flush_finished = FlushFinished {
400                    region_id: self.region_id,
401                    flush_reason: self.reason,
402                    // The last entry has been flushed.
403                    flushed_entry_id: version_data.last_entry_id,
404                    senders: std::mem::take(&mut self.senders),
405                    _timer: timer,
406                    edit,
407                    memtables_to_remove,
408                    is_staging: self.is_staging,
409                };
410                WorkerRequest::Background {
411                    region_id: self.region_id,
412                    notify: BackgroundNotify::FlushFinished(flush_finished),
413                }
414            }
415            Err(e) => {
416                let err = Arc::new(e);
417                let failed = FlushFailed { err: err.clone() };
418                if failed.is_cancelled() {
419                    info!("Flush cancelled for region {}", self.region_id);
420                } else {
421                    error!(err; "Failed to flush region {}", self.region_id);
422                }
423                // Discard the timer.
424                timer.stop_and_discard();
425                uncommitted.cleanup().await;
426
427                self.on_failure(err.clone());
428                WorkerRequest::Background {
429                    region_id: self.region_id,
430                    notify: BackgroundNotify::FlushFailed(failed),
431                }
432            }
433        };
434        self.send_worker_request(worker_request).await;
435    }
436
437    /// Flushes memtables to level 0 SSTs and updates the manifest.
438    /// Returns the [RegionEdit] to apply.
439    async fn flush_memtables(
440        &self,
441        version_data: &VersionControlData,
442        state: &CancellableTaskState,
443        uncommitted: &UncommittedSsts,
444    ) -> Result<RegionEdit> {
445        // We must use the immutable memtables list and entry ids from the `version_data`
446        // for consistency as others might already modify the version in the `version_control`.
447        let version = &version_data.version;
448        let timer = FLUSH_ELAPSED
449            .with_label_values(&["flush_memtables"])
450            .start_timer();
451
452        let mut write_opts = WriteOptions {
453            write_buffer_size: self.engine_config.sst_write_buffer_size,
454            ..Default::default()
455        };
456        if let Some(row_group_size) = self.row_group_size {
457            write_opts.row_group_size = row_group_size;
458        }
459
460        let DoFlushMemtablesResult {
461            file_metas,
462            flushed_bytes,
463            series_count,
464            encoded_part_count,
465            flush_metrics,
466            sst_infos,
467        } = self
468            .do_flush_memtables(version, write_opts, state, uncommitted)
469            .await?;
470
471        if !file_metas.is_empty() {
472            FLUSH_BYTES_TOTAL.inc_by(flushed_bytes);
473        }
474
475        let mut file_ids = Vec::with_capacity(file_metas.len());
476        let mut total_rows = 0;
477        let mut total_bytes = 0;
478        for meta in &file_metas {
479            file_ids.push(meta.file_id);
480            total_rows += meta.num_rows;
481            total_bytes += meta.file_size;
482        }
483        info!(
484            "Successfully flush memtables, region: {}, reason: {}, files: {:?}, series count: {}, total_rows: {}, total_bytes: {}, cost: {:?}, encoded_part_count: {}, metrics: {:?}",
485            self.region_id,
486            self.reason.as_str(),
487            file_ids,
488            series_count,
489            total_rows,
490            total_bytes,
491            timer.stop_and_record(),
492            encoded_part_count,
493            flush_metrics,
494        );
495        flush_metrics.observe();
496
497        let hook = self.manifest_ctx.hook();
498        if let Some(hook) = &hook {
499            let files: Vec<SstFileInfo<'_>> = sst_infos
500                .iter()
501                .zip(file_metas.iter())
502                .map(|(sst_info, file_meta)| SstFileInfo {
503                    sst_info_ref: sst_info,
504                    file_meta,
505                })
506                .collect();
507            hook.on_sst_files_written(self.region_id, &version.metadata, &files)
508                .await;
509        }
510
511        let edit = RegionEdit {
512            files_to_add: file_metas,
513            files_to_remove: Vec::new(),
514            timestamp_ms: Some(chrono::Utc::now().timestamp_millis()),
515            compaction_time_window: None,
516            // The last entry has been flushed.
517            flushed_entry_id: Some(version_data.last_entry_id),
518            flushed_sequence: Some(version_data.committed_sequence),
519            committed_sequence: None,
520        };
521        info!(
522            "Applying {edit:?} to region {}, is_staging: {}",
523            self.region_id, self.is_staging
524        );
525
526        let action_list = RegionMetaActionList::with_action(RegionMetaAction::Edit(edit.clone()));
527
528        // Stop accepting cancellation once the flush is about to publish its manifest edit.
529        if !state.mark_commit_started() {
530            return FlushCancelledSnafu.fail();
531        }
532        self.listener.on_flush_commit_begin(self.region_id).await;
533
534        let expected_state = if matches!(self.reason, FlushReason::Downgrading) {
535            RegionLeaderState::Downgrading
536        } else {
537            // Check if region is in staging mode
538            let current_state = self.manifest_ctx.current_state();
539            if current_state == RegionRoleState::Leader(RegionLeaderState::Staging) {
540                RegionLeaderState::Staging
541            } else {
542                RegionLeaderState::Writable
543            }
544        };
545        let manifest_version = match self
546            .manifest_ctx
547            .update_manifest(expected_state, action_list, self.is_staging)
548            .await
549        {
550            Ok(manifest_version) => {
551                uncommitted.disarm_cleanup();
552                manifest_version
553            }
554            Err(e) => {
555                if e.may_have_persisted_manifest_update() {
556                    uncommitted.disarm_cleanup();
557                } else {
558                    info!(
559                        "Cleaning uncommitted SSTs because the manifest update was not persisted, region: {}, job: flush, error: {:?}",
560                        self.region_id, e
561                    );
562                    uncommitted.cleanup().await;
563                }
564                return Err(e);
565            }
566        };
567        info!(
568            "Successfully update manifest version to {manifest_version}, region: {}, is_staging: {}, reason: {}",
569            self.region_id,
570            self.is_staging,
571            self.reason.as_str()
572        );
573
574        Ok(edit)
575    }
576
577    async fn do_flush_memtables(
578        &self,
579        version: &VersionRef,
580        write_opts: WriteOptions,
581        state: &CancellableTaskState,
582        uncommitted: &UncommittedSsts,
583    ) -> Result<DoFlushMemtablesResult> {
584        let memtables = version.memtables.immutables();
585        let mut file_metas = Vec::with_capacity(memtables.len());
586        let mut flushed_bytes = 0;
587        let mut series_count = 0;
588        let mut encoded_part_count = 0;
589        let mut flush_metrics = Metrics::new(WriteType::Flush);
590        let partition_expr = parse_partition_expr(self.partition_expr.as_deref())?;
591        let hook = self.manifest_ctx.hook();
592        let mut all_sst_infos = Vec::new();
593        for mem in memtables {
594            if mem.is_empty() {
595                // Skip empty memtables.
596                continue;
597            }
598
599            // Compact the memtable first, this waits the background compaction to finish.
600            let compact_start = std::time::Instant::now();
601            if let Err(e) = mem.compact(true) {
602                common_telemetry::error!(e; "Failed to compact memtable before flush");
603            }
604            let compact_cost = compact_start.elapsed();
605            flush_metrics.compact_memtable += compact_cost;
606
607            let mem_stats = mem.stats();
608            let batch_size = crate::batch_size::estimate_batch_size([(
609                mem_stats.num_rows() as u64,
610                mem_stats.bytes_allocated() as u64,
611            )]);
612            // Sets `for_flush` flag and propagates the reader batch size.
613            let mem_ranges =
614                mem.ranges(None, RangesOptions::for_flush().with_batch_size(batch_size))?;
615            let num_mem_ranges = mem_ranges.ranges.len();
616
617            // Aggregate stats from all ranges
618            let num_mem_rows = mem_ranges.num_rows();
619            let memtable_series_count = mem_ranges.series_count();
620            let memtable_id = mem.id();
621            // Increases series count for each mem range. We consider each mem range has different series so
622            // the counter may have more series than the actual series count.
623            series_count += memtable_series_count;
624
625            let flush_start = Instant::now();
626            let FlushFlatMemResult {
627                num_encoded,
628                num_sources,
629                results,
630            } = self
631                .flush_flat_mem_ranges(version, &write_opts, mem_ranges, state, uncommitted)
632                .await?;
633            encoded_part_count += num_encoded;
634            for (source_idx, result) in results.into_iter().enumerate() {
635                let (max_sequence, ssts_written, metrics) = result?;
636                if ssts_written.is_empty() {
637                    // No data written.
638                    continue;
639                }
640
641                common_telemetry::debug!(
642                    "Region {} flush one memtable {} {}/{}, metrics: {:?}",
643                    self.region_id,
644                    memtable_id,
645                    source_idx,
646                    num_sources,
647                    metrics
648                );
649
650                flush_metrics = flush_metrics.merge(metrics);
651
652                for sst_info in &ssts_written {
653                    flushed_bytes += sst_info.file_size;
654                    let pk_range = sst_info
655                        .file_metadata
656                        .as_ref()
657                        .and_then(|meta| extract_primary_key_range(meta, &version.metadata));
658                    file_metas.push(Self::new_file_meta(
659                        self.region_id,
660                        max_sequence,
661                        sst_info,
662                        partition_expr.clone(),
663                        pk_range,
664                        version.options.preserve_row_sequence,
665                    ));
666                }
667                if hook.is_some() {
668                    all_sst_infos.extend(ssts_written);
669                }
670            }
671
672            common_telemetry::debug!(
673                "Region {} flush {} memtables for {}, num_mem_ranges: {}, num_encoded: {}, num_rows: {}, flush_cost: {:?}, compact_cost: {:?}",
674                self.region_id,
675                num_sources,
676                memtable_id,
677                num_mem_ranges,
678                num_encoded,
679                num_mem_rows,
680                flush_start.elapsed(),
681                compact_cost,
682            );
683        }
684
685        Ok(DoFlushMemtablesResult {
686            file_metas,
687            flushed_bytes,
688            series_count,
689            encoded_part_count,
690            flush_metrics,
691            sst_infos: all_sst_infos,
692        })
693    }
694
695    async fn flush_flat_mem_ranges(
696        &self,
697        version: &VersionRef,
698        write_opts: &WriteOptions,
699        mem_ranges: MemtableRanges,
700        state: &CancellableTaskState,
701        uncommitted: &UncommittedSsts,
702    ) -> Result<FlushFlatMemResult> {
703        let batch_schema = to_flat_sst_arrow_schema(
704            &version.metadata,
705            &FlatSchemaOptions::from_encoding(version.metadata.primary_key_encoding),
706        );
707        let field_column_start =
708            flat_format::field_column_start(&version.metadata, batch_schema.fields().len());
709        let flat_sources = memtable_flat_sources(
710            batch_schema,
711            mem_ranges,
712            &version.metadata,
713            &version.options,
714            field_column_start,
715        )?;
716        let mut tasks = Vec::with_capacity(flat_sources.encoded.len() + flat_sources.sources.len());
717        let num_encoded = flat_sources.encoded.len();
718        for (source, max_sequence) in flat_sources.sources {
719            let write_request = self.new_write_request(version, max_sequence, source);
720            let access_layer = self.access_layer.clone();
721            let write_opts = write_opts.clone();
722            let semaphore = self.flush_semaphore.clone();
723            let uncommitted = uncommitted.clone();
724            let task = common_runtime::spawn_global(async move {
725                let _permit = semaphore.acquire().await.unwrap();
726                let mut metrics = Metrics::new(WriteType::Flush);
727                let ssts = access_layer
728                    .write_sst(write_request, &write_opts, &mut metrics)
729                    .await?;
730                uncommitted.track(&ssts);
731                FLUSH_FILE_TOTAL.inc_by(ssts.len() as u64);
732                Ok((max_sequence, ssts, metrics))
733            });
734            tasks.push(task);
735        }
736        for (encoded, max_sequence) in flat_sources.encoded {
737            let access_layer = self.access_layer.clone();
738            let cache_manager = self.cache_manager.clone();
739            let region_id = version.metadata.region_id;
740            let semaphore = self.flush_semaphore.clone();
741            let uncommitted = uncommitted.clone();
742            let task = common_runtime::spawn_global(async move {
743                let _permit = semaphore.acquire().await.unwrap();
744                let metrics = access_layer
745                    .put_sst(&encoded.data, region_id, &encoded.sst_info, &cache_manager)
746                    .await?;
747                uncommitted.track(std::slice::from_ref(&encoded.sst_info));
748                FLUSH_FILE_TOTAL.inc();
749                Ok((max_sequence, smallvec![encoded.sst_info], metrics))
750            });
751            tasks.push(task);
752        }
753        let num_sources = tasks.len();
754        let abort_handles = tasks
755            .iter()
756            .map(|task| task.abort_handle())
757            .collect::<Vec<_>>();
758        let join_all = futures::future::join_all(tasks);
759        tokio::pin!(join_all);
760        let results = match CancellableFuture::new(join_all.as_mut(), state.cancel_handle()).await {
761            Ok(results) => results
762                .into_iter()
763                .map(|result| result.context(JoinSnafu))
764                .collect::<Result<Vec<_>>>()?,
765            Err(_) => {
766                for handle in abort_handles {
767                    handle.abort();
768                }
769                // Wait until every writer observes the abort so cleanup cannot race with a late
770                // finalized output.
771                let _ = join_all.await;
772                return FlushCancelledSnafu.fail();
773            }
774        };
775        Ok(FlushFlatMemResult {
776            num_encoded,
777            num_sources,
778            results,
779        })
780    }
781
782    fn new_file_meta(
783        region_id: RegionId,
784        max_sequence: u64,
785        sst_info: &SstInfo,
786        partition_expr: Option<PartitionExpr>,
787        primary_key_range: Option<(Bytes, Bytes)>,
788        preserve_row_sequence: bool,
789    ) -> FileMeta {
790        let (primary_key_min, primary_key_max) = match primary_key_range {
791            Some((min, max)) => (Some(min), Some(max)),
792            None => (None, None),
793        };
794        FileMeta {
795            region_id,
796            file_id: sst_info.file_id,
797            time_range: sst_info.time_range,
798            level: 0,
799            file_size: sst_info.file_size,
800            max_row_group_uncompressed_size: sst_info.max_row_group_uncompressed_size,
801            available_indexes: sst_info.index_metadata.build_available_indexes(),
802            indexes: sst_info.index_metadata.build_indexes(),
803            index_file_size: sst_info.index_metadata.file_size,
804            index_version: 0,
805            num_rows: sst_info.num_rows as u64,
806            num_row_groups: sst_info.num_row_groups,
807            sequence: NonZeroU64::new(max_sequence),
808            partition_expr,
809            num_series: sst_info.num_series,
810            primary_key_min,
811            primary_key_max,
812            preserve_row_sequence,
813        }
814    }
815
816    fn new_write_request(
817        &self,
818        version: &VersionRef,
819        max_sequence: u64,
820        source: FlatSource,
821    ) -> SstWriteRequest {
822        let flat_format = version
823            .options
824            .sst_format
825            .map(|f| f == FormatType::Flat)
826            .unwrap_or(self.engine_config.default_flat_format);
827        SstWriteRequest {
828            op_type: OperationType::Flush,
829            metadata: version.metadata.clone(),
830            source,
831            cache_manager: self.cache_manager.clone(),
832            storage: version.options.storage.clone(),
833            max_sequence: Some(max_sequence),
834            sst_write_format: if flat_format {
835                FormatType::Flat
836            } else {
837                FormatType::PrimaryKey
838            },
839            preserve_row_sequence: version.options.preserve_row_sequence,
840            index_options: self.index_options.clone(),
841            index_config: self.engine_config.index.clone(),
842            inverted_index_config: self.engine_config.inverted_index.clone(),
843            fulltext_index_config: self.engine_config.fulltext_index.clone(),
844            bloom_filter_index_config: self.engine_config.bloom_filter_index.clone(),
845            #[cfg(feature = "vector_index")]
846            vector_index_config: self.engine_config.vector_index.clone(),
847        }
848    }
849
850    /// Notify flush job status.
851    pub(crate) async fn send_worker_request(&self, request: WorkerRequest) {
852        if let Err(e) = self
853            .request_sender
854            .send(WorkerRequestWithTime::new(request))
855            .await
856        {
857            let request = e.0.request;
858            error!(
859                "Failed to notify flush job status for region {}, request: {:?}",
860                self.region_id, request
861            );
862            if let WorkerRequest::Background {
863                notify: BackgroundNotify::FlushFinished(mut finished),
864                ..
865            } = request
866            {
867                finished.on_failure(
868                    RegionClosedSnafu {
869                        region_id: self.region_id,
870                    }
871                    .build(),
872                );
873            }
874        }
875    }
876
877    /// Merge two flush tasks.
878    fn merge(&mut self, mut other: RegionFlushTask) {
879        assert_eq!(self.region_id, other.region_id);
880        // Now we only merge senders. They share the same flush reason.
881        self.senders.append(&mut other.senders);
882    }
883}
884
885struct FlushFlatMemResult {
886    num_encoded: usize,
887    num_sources: usize,
888    results: Vec<Result<(SequenceNumber, SstInfoArray, Metrics)>>,
889}
890
891struct DoFlushMemtablesResult {
892    file_metas: Vec<FileMeta>,
893    flushed_bytes: u64,
894    series_count: usize,
895    encoded_part_count: usize,
896    flush_metrics: Metrics,
897    sst_infos: Vec<SstInfo>,
898}
899
900struct FlatSources {
901    sources: SmallVec<[(FlatSource, SequenceNumber); 4]>,
902    encoded: SmallVec<[(EncodedRange, SequenceNumber); 4]>,
903}
904
905/// Returns the max sequence and [FlatSource] for the given memtable.
906fn memtable_flat_sources(
907    schema: SchemaRef,
908    mem_ranges: MemtableRanges,
909    metadata: &RegionMetadataRef,
910    options: &RegionOptions,
911    field_column_start: usize,
912) -> Result<FlatSources> {
913    let MemtableRanges { ranges } = mem_ranges;
914    let mut flat_sources = FlatSources {
915        sources: SmallVec::new(),
916        encoded: SmallVec::new(),
917    };
918
919    if ranges.len() == 1 {
920        debug!("Flushing single flat range");
921
922        let only_range = ranges.into_values().next().unwrap();
923        let max_sequence = only_range.stats().max_sequence();
924        if let Some(encoded) = only_range.encoded() {
925            flat_sources.encoded.push((encoded, max_sequence));
926        } else {
927            let schema = only_range.record_batch_schema_hint().unwrap_or(schema);
928            let iter = only_range.build_record_batch_iter(None, None)?;
929            // Dedup according to append mode and merge mode.
930            // Even single range may have duplicate rows.
931            let iter = maybe_dedup_one(
932                options.append_mode,
933                options.merge_mode(),
934                field_column_start,
935                iter,
936            );
937            flat_sources
938                .sources
939                .push((FlatSource::new_iter(schema, iter), max_sequence));
940        };
941    } else {
942        let min_flush_rows = *ENCODE_ROW_THRESHOLD;
943        // Calculate total rows from non-encoded ranges.
944        let total_rows: usize = ranges
945            .values()
946            .filter(|r| r.encoded().is_none())
947            .map(|r| r.num_rows())
948            .sum();
949        debug!(
950            "Flushing multiple flat ranges, total_rows: {}, min_flush_rows: {}, num_ranges: {}",
951            total_rows,
952            min_flush_rows,
953            ranges.len()
954        );
955        let mut rows_remaining = total_rows;
956        let mut last_iter_rows = 0;
957        let num_ranges = ranges.len();
958        let mut input_iters = Vec::with_capacity(num_ranges);
959        let mut current_ranges = Vec::new();
960
961        let schemas = ranges
962            .values()
963            .filter(|range| range.encoded().is_none())
964            .map(|range| {
965                (
966                    range
967                        .record_batch_schema_hint()
968                        .unwrap_or_else(|| schema.clone()),
969                    range.num_rows() as u64,
970                )
971            })
972            .collect::<Vec<_>>();
973        let plans = Arc::new(collect_json2_rewrite_plans(metadata, &schemas)?);
974        let schema = rewrite_json2_schema(
975            schemas.first().map(|(schema, _)| schema).unwrap_or(&schema),
976            &plans,
977        );
978
979        for (_range_id, range) in ranges {
980            if let Some(encoded) = range.encoded() {
981                let max_sequence = range.stats().max_sequence();
982                flat_sources.encoded.push((encoded, max_sequence));
983                continue;
984            }
985
986            if let Some(actual) = range.record_batch_schema_hint() {
987                let actual = rewrite_json2_schema(&actual, &plans);
988                ensure!(
989                    actual == schema,
990                    UnexpectedSnafu {
991                        reason: format!(
992                            "Different schemas found in a MemtableRanges, expected: {}, actual: {}",
993                            schema, actual,
994                        ),
995                    }
996                )
997            }
998
999            let iter = range.build_record_batch_iter(None, None)?;
1000            let iter: BoxedRecordBatchIterator = if plans.is_empty() {
1001                iter
1002            } else {
1003                let plans = plans.clone();
1004                Box::new(iter.map(move |batch| rewrite_json2_batch(batch?, &plans)))
1005            };
1006            input_iters.push(iter);
1007            let range_rows = range.num_rows();
1008            last_iter_rows += range_rows;
1009            rows_remaining -= range_rows;
1010            current_ranges.push(range);
1011
1012            // Flush if we have enough rows, but don't flush if the remaining rows
1013            // would be less than DEFAULT_ROW_GROUP_SIZE (to avoid small last files).
1014            if last_iter_rows >= min_flush_rows
1015                && (rows_remaining == 0 || rows_remaining >= DEFAULT_ROW_GROUP_SIZE)
1016            {
1017                debug!(
1018                    "Flush batch ready, rows: {}, min_rows: {}, num_iters: {}, remaining: {}",
1019                    last_iter_rows,
1020                    min_flush_rows,
1021                    input_iters.len(),
1022                    rows_remaining
1023                );
1024
1025                // Calculate max_sequence from all merged ranges
1026                let max_sequence = current_ranges
1027                    .iter()
1028                    .map(|r| r.stats().max_sequence())
1029                    .max()
1030                    .unwrap_or(0);
1031                let batch_size =
1032                    crate::batch_size::estimate_batch_size(current_ranges.iter().map(|range| {
1033                        let stats = range.stats();
1034                        (stats.num_rows() as u64, stats.bytes_allocated() as u64)
1035                    }));
1036
1037                let input_iters =
1038                    std::mem::replace(&mut input_iters, Vec::with_capacity(num_ranges));
1039
1040                let maybe_dedup = merge_and_dedup_with_batch_size(
1041                    &schema,
1042                    options.append_mode,
1043                    options.merge_mode(),
1044                    field_column_start,
1045                    input_iters,
1046                    batch_size,
1047                )?;
1048
1049                flat_sources.sources.push((
1050                    FlatSource::new_iter(schema.clone(), maybe_dedup),
1051                    max_sequence,
1052                ));
1053                last_iter_rows = 0;
1054                current_ranges.clear();
1055            }
1056        }
1057
1058        // Handle remaining iters.
1059        if !input_iters.is_empty() {
1060            debug!(
1061                "Flush remaining batch, rows: {}, min_rows: {}, num_iters: {}, remaining: {}",
1062                last_iter_rows,
1063                min_flush_rows,
1064                input_iters.len(),
1065                rows_remaining
1066            );
1067
1068            let max_sequence = current_ranges
1069                .iter()
1070                .map(|r| r.stats().max_sequence())
1071                .max()
1072                .unwrap_or(0);
1073            let batch_size =
1074                crate::batch_size::estimate_batch_size(current_ranges.iter().map(|range| {
1075                    let stats = range.stats();
1076                    (stats.num_rows() as u64, stats.bytes_allocated() as u64)
1077                }));
1078
1079            let maybe_dedup = merge_and_dedup_with_batch_size(
1080                &schema,
1081                options.append_mode,
1082                options.merge_mode(),
1083                field_column_start,
1084                input_iters,
1085                batch_size,
1086            )?;
1087
1088            flat_sources
1089                .sources
1090                .push((FlatSource::new_iter(schema, maybe_dedup), max_sequence));
1091        }
1092    }
1093
1094    Ok(flat_sources)
1095}
1096
1097/// Merges multiple record batch iterators and applies deduplication based on the specified mode.
1098///
1099/// This function is used during the flush process to combine data from multiple memtable ranges
1100/// into a single stream while handling duplicate records according to the configured merge strategy.
1101///
1102/// # Arguments
1103///
1104/// * `schema` - The Arrow schema reference that defines the structure of the record batches
1105/// * `append_mode` - When true, no deduplication is performed and all records are preserved.
1106///                  This is used for append-only workloads where duplicate handling is not required.
1107/// * `merge_mode` - The strategy used for deduplication when not in append mode:
1108///   - `MergeMode::LastRow`: Keeps the last record for each primary key
1109///   - `MergeMode::LastNonNull`: Keeps the last non-null values for each field
1110/// * `field_column_start` - The starting column index for fields in the record batch.
1111///                          Used when `MergeMode::LastNonNull` to identify which columns
1112///                          contain field values versus primary key columns.
1113/// * `input_iters` - A vector of record batch iterators to be merged and deduplicated
1114///
1115/// # Returns
1116///
1117/// Returns a boxed record batch iterator that yields the merged and potentially deduplicated
1118/// record batches.
1119///
1120/// # Behavior
1121///
1122/// 1. Creates a `FlatMergeIterator` to merge all input iterators in sorted order based on
1123///    primary key and timestamp
1124/// 2. If `append_mode` is true, returns the merge iterator directly without deduplication
1125/// 3. If `append_mode` is false, wraps the merge iterator with a `FlatDedupIterator` that
1126///    applies the specified merge mode:
1127///    - `LastRow`: Removes duplicate rows, keeping only the last one
1128///    - `LastNonNull`: Removes duplicates but preserves the last non-null value for each field
1129///
1130/// # Examples
1131///
1132/// ```ignore
1133/// let merged_iter = merge_and_dedup(
1134///     &schema,
1135///     false,  // not append mode, apply dedup
1136///     MergeMode::LastRow,
1137///     2,  // fields start at column 2 after primary key columns
1138///     vec![iter1, iter2, iter3],
1139/// )?;
1140/// ```
1141pub fn merge_and_dedup(
1142    schema: &SchemaRef,
1143    append_mode: bool,
1144    merge_mode: MergeMode,
1145    field_column_start: usize,
1146    input_iters: Vec<BoxedRecordBatchIterator>,
1147) -> Result<BoxedRecordBatchIterator> {
1148    merge_and_dedup_with_batch_size(
1149        schema,
1150        append_mode,
1151        merge_mode,
1152        field_column_start,
1153        input_iters,
1154        DEFAULT_READ_BATCH_SIZE,
1155    )
1156}
1157
1158/// Merges and optionally deduplicates record batch iterators with an explicit output batch size.
1159///
1160/// `batch_size` controls the target number of rows in batches assembled by the merge iterator and
1161/// is clamped to at least one. The other arguments have the same meaning as in
1162/// [`merge_and_dedup`].
1163pub fn merge_and_dedup_with_batch_size(
1164    schema: &SchemaRef,
1165    append_mode: bool,
1166    merge_mode: MergeMode,
1167    field_column_start: usize,
1168    input_iters: Vec<BoxedRecordBatchIterator>,
1169    batch_size: usize,
1170) -> Result<BoxedRecordBatchIterator> {
1171    let batch_size = batch_size.max(1);
1172    let merge_iter = FlatMergeIterator::new(schema.clone(), input_iters, batch_size)?;
1173    let maybe_dedup = if append_mode {
1174        // No dedup in append mode
1175        Box::new(merge_iter) as _
1176    } else {
1177        // Dedup according to merge mode.
1178        match merge_mode {
1179            MergeMode::LastRow => {
1180                Box::new(FlatDedupIterator::new(merge_iter, FlatLastRow::new(false))) as _
1181            }
1182            MergeMode::LastNonNull => Box::new(FlatDedupIterator::new(
1183                merge_iter,
1184                FlatLastNonNull::new(field_column_start, false),
1185            )) as _,
1186        }
1187    };
1188    Ok(maybe_dedup)
1189}
1190
1191pub fn maybe_dedup_one(
1192    append_mode: bool,
1193    merge_mode: MergeMode,
1194    field_column_start: usize,
1195    input_iter: BoxedRecordBatchIterator,
1196) -> BoxedRecordBatchIterator {
1197    if append_mode {
1198        // No dedup in append mode
1199        input_iter
1200    } else {
1201        // Dedup according to merge mode.
1202        match merge_mode {
1203            MergeMode::LastRow => {
1204                Box::new(FlatDedupIterator::new(input_iter, FlatLastRow::new(false)))
1205            }
1206            MergeMode::LastNonNull => Box::new(FlatDedupIterator::new(
1207                input_iter,
1208                FlatLastNonNull::new(field_column_start, false),
1209            )),
1210        }
1211    }
1212}
1213
1214/// Manages background flushes of a worker.
1215pub(crate) struct FlushScheduler {
1216    /// Tracks regions need to flush.
1217    region_status: HashMap<RegionId, FlushStatus>,
1218    /// Background job scheduler.
1219    scheduler: SchedulerRef,
1220}
1221
1222impl FlushScheduler {
1223    /// Creates a new flush scheduler.
1224    pub(crate) fn new(scheduler: SchedulerRef) -> FlushScheduler {
1225        FlushScheduler {
1226            region_status: HashMap::new(),
1227            scheduler,
1228        }
1229    }
1230
1231    /// Returns true if the region already requested flush.
1232    pub(crate) fn is_flush_requested(&self, region_id: RegionId) -> bool {
1233        self.region_status.contains_key(&region_id)
1234    }
1235
1236    fn schedule_flush_task(
1237        &mut self,
1238        version_control: &VersionControlRef,
1239        mut task: RegionFlushTask,
1240    ) -> Result<CancellableTaskState> {
1241        let region_id = task.region_id;
1242
1243        // If current region doesn't have flush status, we can flush the region directly.
1244        if let Err(e) = version_control.freeze_mutable() {
1245            error!(e; "Failed to freeze the mutable memtable for region {}", region_id);
1246            task.on_failure(Arc::new(RegionBusySnafu { region_id }.build()));
1247
1248            return Err(e);
1249        }
1250        // Submit a flush job.
1251        let state = CancellableTaskState::new();
1252        let (job, waiters) = task.into_flush_job(version_control, state.clone());
1253        if let Err(e) = self.scheduler.schedule(job) {
1254            error!(e; "Failed to schedule flush job for region {}", region_id);
1255            waiters.on_failure(Arc::new(RegionBusySnafu { region_id }.build()));
1256
1257            return Err(e);
1258        }
1259        Ok(state)
1260    }
1261
1262    /// Schedules a flush `task` for specific `region`.
1263    pub(crate) fn schedule_flush(
1264        &mut self,
1265        region_id: RegionId,
1266        version_control: &VersionControlRef,
1267        mut task: RegionFlushTask,
1268    ) -> Result<()> {
1269        debug_assert_eq!(region_id, task.region_id);
1270
1271        let version = version_control.current().version;
1272        if version.memtables.is_empty() {
1273            debug_assert!(!self.region_status.contains_key(&region_id));
1274            // The region has nothing to flush.
1275            task.on_success();
1276            return Ok(());
1277        }
1278
1279        // Don't increase the counter if a region has nothing to flush.
1280        FLUSH_REQUESTS_TOTAL
1281            .with_label_values(&[task.reason.as_str()])
1282            .inc();
1283
1284        // If current region has flush status, merge the task.
1285        if let Some(flush_status) = self.region_status.get_mut(&region_id) {
1286            if flush_status.has_pending_lifecycle_ddl() {
1287                task.on_failure(Arc::new(FlushCancelledSnafu.build()));
1288                return Ok(());
1289            }
1290            // Checks whether we can flush the region now.
1291            debug!("Merging flush task for region {}", region_id);
1292            flush_status.merge_task(task);
1293            return Ok(());
1294        }
1295
1296        let closing = task.reason == FlushReason::Closing;
1297        let state = self.schedule_flush_task(version_control, task)?;
1298
1299        // Add this region to status map.
1300        let _ = self.region_status.insert(
1301            region_id,
1302            FlushStatus::new(region_id, version_control.clone(), state, closing),
1303        );
1304
1305        Ok(())
1306    }
1307
1308    /// Notifies the scheduler that the flush job is finished.
1309    ///
1310    /// Returns all pending requests if the region doesn't need to flush again.
1311    pub(crate) fn on_flush_success(
1312        &mut self,
1313        region_id: RegionId,
1314    ) -> Option<(
1315        Vec<SenderDdlRequest>,
1316        Vec<SenderWriteRequest>,
1317        Vec<SenderBulkRequest>,
1318    )> {
1319        let flush_status = self.region_status.get_mut(&region_id)?;
1320        // If region doesn't have any pending flush task, we need to remove it from the status.
1321        if flush_status.pending_task.is_none() {
1322            // The region doesn't have any pending flush task.
1323            // Safety: The flush status must exist.
1324            debug!(
1325                "Region {} doesn't have any pending flush task, removing it from the status",
1326                region_id
1327            );
1328            let flush_status = self.region_status.remove(&region_id).unwrap();
1329            return Some((
1330                flush_status.pending_ddls,
1331                flush_status.pending_writes,
1332                flush_status.pending_bulk_writes,
1333            ));
1334        }
1335
1336        // If region has pending task, but has nothing to flush, we need to remove it from the status.
1337        let version_data = flush_status.version_control.current();
1338        if version_data.version.memtables.is_empty() {
1339            // The region has nothing to flush, we also need to remove it from the status.
1340            // Safety: The pending task is not None.
1341            let task = flush_status.pending_task.take().unwrap();
1342            // The region has nothing to flush. We can notify pending task.
1343            task.on_success();
1344            debug!(
1345                "Region {} has nothing to flush, removing it from the status",
1346                region_id
1347            );
1348            // Safety: The flush status must exist.
1349            let flush_status = self.region_status.remove(&region_id).unwrap();
1350            return Some((
1351                flush_status.pending_ddls,
1352                flush_status.pending_writes,
1353                flush_status.pending_bulk_writes,
1354            ));
1355        }
1356
1357        // If region has pending task and has something to flush, we need to schedule it.
1358        debug!("Scheduling pending flush task for region {}", region_id);
1359        // Safety: The flush status must exist.
1360        let task = flush_status.pending_task.take().unwrap();
1361        let version_control = flush_status.version_control.clone();
1362        match self.schedule_flush_task(&version_control, task) {
1363            Ok(state) => {
1364                self.region_status.get_mut(&region_id).unwrap().state = state;
1365            }
1366            Err(err) => {
1367                error!(
1368                    err;
1369                    "Flush succeeded for region {region_id}, but failed to schedule next flush for it."
1370                );
1371                let flush_status = self.region_status.remove(&region_id).unwrap();
1372                flush_status.fail_all(Arc::new(RegionBusySnafu { region_id }.build()));
1373                return None;
1374            }
1375        }
1376        // We can flush the region again, keep it in the region status.
1377        None
1378    }
1379
1380    /// Notifies the scheduler that the flush job failed.
1381    ///
1382    /// Returns pending drop and truncate requests in their original order. All other pending
1383    /// requests are failed with `err`.
1384    pub(crate) fn on_flush_failed(
1385        &mut self,
1386        region_id: RegionId,
1387        err: Arc<Error>,
1388    ) -> Vec<SenderDdlRequest> {
1389        if matches!(err.as_ref(), Error::FlushCancelled { .. }) {
1390            info!("Region {} flush was cancelled", region_id);
1391        } else {
1392            error!(err; "Region {} failed to flush, cancel all pending tasks", region_id);
1393            FLUSH_FAILURE_TOTAL.inc();
1394        }
1395
1396        // Remove this region.
1397        let Some(flush_status) = self.region_status.remove(&region_id) else {
1398            return Vec::new();
1399        };
1400
1401        flush_status.on_failure(err)
1402    }
1403
1404    /// Cancels the running flush and queues its dependent lifecycle DDL atomically.
1405    pub(crate) fn try_cancel_and_add_ddl<T>(
1406        &mut self,
1407        region_id: RegionId,
1408        sender: OptionOutputTx,
1409        request: T,
1410        into_ddl_request: impl FnOnce(T) -> DdlRequest,
1411    ) -> std::result::Result<(), (OptionOutputTx, T)> {
1412        let Some(status) = self.region_status.get_mut(&region_id) else {
1413            return Err((sender, request));
1414        };
1415
1416        let cancel_result = status.state.request_cancel();
1417        debug!(
1418            "Requested flush cancellation for region {}, result: {:?}",
1419            region_id, cancel_result
1420        );
1421        status.pending_ddls.push(SenderDdlRequest {
1422            region_id,
1423            sender,
1424            request: into_ddl_request(request),
1425        });
1426        Ok(())
1427    }
1428
1429    /// Notifies the scheduler that the region is dropped.
1430    pub(crate) fn on_region_dropped(&mut self, region_id: RegionId) {
1431        self.remove_region_on_failure(
1432            region_id,
1433            Arc::new(RegionDroppedSnafu { region_id }.build()),
1434        );
1435    }
1436
1437    /// Notifies the scheduler that the region is closed.
1438    pub(crate) fn on_region_closed(&mut self, region_id: RegionId) {
1439        let Some(flush_status) = self.region_status.remove(&region_id) else {
1440            return;
1441        };
1442
1443        flush_status.on_region_closed(Arc::new(RegionClosedSnafu { region_id }.build()));
1444    }
1445
1446    /// Notifies the scheduler that the region is truncated.
1447    pub(crate) fn on_region_truncated(&mut self, region_id: RegionId) {
1448        self.remove_region_on_failure(
1449            region_id,
1450            Arc::new(RegionTruncatedSnafu { region_id }.build()),
1451        );
1452    }
1453
1454    fn remove_region_on_failure(&mut self, region_id: RegionId, err: Arc<Error>) {
1455        // Remove this region.
1456        let Some(flush_status) = self.region_status.remove(&region_id) else {
1457            return;
1458        };
1459
1460        // Notifies all pending tasks.
1461        flush_status.fail_all(err);
1462    }
1463
1464    /// Add ddl request to pending queue.
1465    ///
1466    /// # Panics
1467    /// Panics if region didn't request flush.
1468    pub(crate) fn add_ddl_request_to_pending(&mut self, request: SenderDdlRequest) {
1469        let status = self.region_status.get_mut(&request.region_id).unwrap();
1470        status.pending_ddls.push(request);
1471    }
1472
1473    /// Add write request to pending queue.
1474    ///
1475    /// # Panics
1476    /// Panics if region didn't request flush.
1477    pub(crate) fn add_write_request_to_pending(&mut self, request: SenderWriteRequest) {
1478        let status = self
1479            .region_status
1480            .get_mut(&request.request.region_id)
1481            .unwrap();
1482        status.pending_writes.push(request);
1483    }
1484
1485    /// Add bulk write request to pending queue.
1486    ///
1487    /// # Panics
1488    /// Panics if region didn't request flush.
1489    pub(crate) fn add_bulk_request_to_pending(&mut self, request: SenderBulkRequest) {
1490        let status = self.region_status.get_mut(&request.region_id).unwrap();
1491        status.pending_bulk_writes.push(request);
1492    }
1493
1494    /// Returns true if the region has pending DDLs or a close-time flush.
1495    pub(crate) fn has_pending_ddls(&self, region_id: RegionId) -> bool {
1496        self.region_status
1497            .get(&region_id)
1498            .map(|status| !status.pending_ddls.is_empty() || status.closing)
1499            .unwrap_or(false)
1500    }
1501}
1502
1503impl Drop for FlushScheduler {
1504    fn drop(&mut self) {
1505        for (region_id, flush_status) in self.region_status.drain() {
1506            // We are shutting down so notify all pending tasks.
1507            flush_status.fail_all(Arc::new(RegionClosedSnafu { region_id }.build()));
1508        }
1509    }
1510}
1511
1512/// Flush status of a region scheduled by the [FlushScheduler].
1513///
1514/// Tracks running and pending flush tasks and all pending requests of a region.
1515struct FlushStatus {
1516    /// Current region.
1517    region_id: RegionId,
1518    /// Version control of the region.
1519    version_control: VersionControlRef,
1520    /// Cancellation state of the running flush.
1521    state: CancellableTaskState,
1522    /// Task waiting for next flush.
1523    pending_task: Option<RegionFlushTask>,
1524    /// Whether a close-time flush is in progress or pending.
1525    closing: bool,
1526    /// Pending ddl requests.
1527    pending_ddls: Vec<SenderDdlRequest>,
1528    /// Requests waiting to write after altering the region.
1529    pending_writes: Vec<SenderWriteRequest>,
1530    /// Bulk requests waiting to write after altering the region.
1531    pending_bulk_writes: Vec<SenderBulkRequest>,
1532}
1533
1534impl FlushStatus {
1535    fn new(
1536        region_id: RegionId,
1537        version_control: VersionControlRef,
1538        state: CancellableTaskState,
1539        closing: bool,
1540    ) -> FlushStatus {
1541        FlushStatus {
1542            region_id,
1543            version_control,
1544            state,
1545            pending_task: None,
1546            closing,
1547            pending_ddls: Vec::new(),
1548            pending_writes: Vec::new(),
1549            pending_bulk_writes: Vec::new(),
1550        }
1551    }
1552
1553    /// Merges the task to pending task.
1554    fn merge_task(&mut self, task: RegionFlushTask) {
1555        self.closing |= task.reason == FlushReason::Closing;
1556        if let Some(pending) = &mut self.pending_task {
1557            pending.merge(task);
1558        } else {
1559            self.pending_task = Some(task);
1560        }
1561    }
1562
1563    fn has_pending_lifecycle_ddl(&self) -> bool {
1564        self.pending_ddls
1565            .iter()
1566            .any(|ddl| matches!(ddl.request, DdlRequest::Drop(_) | DdlRequest::Truncate(_)))
1567    }
1568
1569    /// Fails pending requests except drop and truncate, which the worker must revalidate.
1570    fn on_failure(self, err: Arc<Error>) -> Vec<SenderDdlRequest> {
1571        if let Some(mut task) = self.pending_task {
1572            task.on_failure(err.clone());
1573        }
1574        let mut lifecycle_ddls = Vec::new();
1575        for ddl in self.pending_ddls {
1576            if matches!(ddl.request, DdlRequest::Drop(_) | DdlRequest::Truncate(_)) {
1577                lifecycle_ddls.push(ddl);
1578            } else {
1579                ddl.sender.send(Err(err.clone()).context(FlushRegionSnafu {
1580                    region_id: self.region_id,
1581                }));
1582            }
1583        }
1584        for write_req in self.pending_writes {
1585            write_req
1586                .sender
1587                .send(Err(err.clone()).context(FlushRegionSnafu {
1588                    region_id: self.region_id,
1589                }));
1590        }
1591        for bulk_req in self.pending_bulk_writes {
1592            bulk_req
1593                .sender
1594                .send(Err(err.clone()).context(FlushRegionSnafu {
1595                    region_id: self.region_id,
1596                }));
1597        }
1598        lifecycle_ddls
1599    }
1600
1601    fn fail_all(self, err: Arc<Error>) {
1602        let region_id = self.region_id;
1603        let ddls = self.on_failure(err.clone());
1604        for ddl in ddls {
1605            ddl.sender
1606                .send(Err(err.clone()).context(FlushRegionSnafu { region_id }));
1607        }
1608    }
1609
1610    fn on_region_closed(self, err: Arc<Error>) {
1611        if let Some(mut task) = self.pending_task {
1612            task.on_failure(err.clone());
1613        }
1614
1615        for ddl in self.pending_ddls {
1616            if matches!(ddl.request, DdlRequest::Close(_)) {
1617                ddl.sender.send(Ok(0));
1618            } else {
1619                ddl.sender.send(Err(err.clone()).context(FlushRegionSnafu {
1620                    region_id: self.region_id,
1621                }));
1622            }
1623        }
1624
1625        for write_req in self.pending_writes {
1626            write_req
1627                .sender
1628                .send(Err(err.clone()).context(FlushRegionSnafu {
1629                    region_id: self.region_id,
1630                }));
1631        }
1632        for bulk_req in self.pending_bulk_writes {
1633            bulk_req
1634                .sender
1635                .send(Err(err.clone()).context(FlushRegionSnafu {
1636                    region_id: self.region_id,
1637                }));
1638        }
1639    }
1640}
1641
1642#[cfg(test)]
1643mod tests {
1644    use api::v1::{OpType, Rows};
1645    use common_error::ext::ErrorExt;
1646    use common_error::status_code::StatusCode;
1647    use datatypes::arrow::datatypes::Schema;
1648    use datatypes::arrow::record_batch::RecordBatch;
1649    use mito_codec::row_converter::build_primary_key_codec;
1650    use tokio::sync::oneshot;
1651
1652    use super::*;
1653    use crate::cache::CacheManager;
1654    use crate::error::InvalidSchedulerStateSnafu;
1655    use crate::memtable::bulk::part::BulkPartConverter;
1656    use crate::memtable::time_series::TimeSeriesMemtableBuilder;
1657    use crate::memtable::{
1658        IterBuilder, Memtable, MemtableRange, MemtableRangeContext, MemtableStats, RangesOptions,
1659    };
1660    use crate::request::WriteRequest;
1661    use crate::schedule::scheduler::Scheduler;
1662    use crate::sst::{FlatSchemaOptions, to_flat_sst_arrow_schema};
1663    use crate::test_util::memtable_util::{build_key_values_with_ts_seq_values, metadata_for_test};
1664    use crate::test_util::scheduler_util::{SchedulerEnv, VecScheduler};
1665    use crate::test_util::version_util::{VersionControlBuilder, write_rows_to_version};
1666
1667    struct FailingScheduler;
1668
1669    #[async_trait::async_trait]
1670    impl Scheduler for FailingScheduler {
1671        fn schedule(&self, _job: Job) -> Result<()> {
1672            InvalidSchedulerStateSnafu.fail()
1673        }
1674
1675        async fn stop(&self, _await_termination: bool) -> Result<()> {
1676            Ok(())
1677        }
1678    }
1679
1680    fn new_test_flush_task(
1681        env: &SchedulerEnv,
1682        region_id: RegionId,
1683        reason: FlushReason,
1684        request_sender: mpsc::Sender<WorkerRequestWithTime>,
1685        manifest_ctx: ManifestContextRef,
1686    ) -> RegionFlushTask {
1687        RegionFlushTask {
1688            region_id,
1689            reason,
1690            senders: Vec::new(),
1691            request_sender,
1692            access_layer: env.access_layer.clone(),
1693            listener: WorkerListener::default(),
1694            engine_config: Arc::new(MitoConfig::default()),
1695            row_group_size: None,
1696            cache_manager: Arc::new(CacheManager::default()),
1697            manifest_ctx,
1698            index_options: IndexOptions::default(),
1699            flush_semaphore: Arc::new(Semaphore::new(2)),
1700            is_staging: false,
1701            partition_expr: None,
1702        }
1703    }
1704
1705    fn new_test_bulk_request(
1706        region_id: RegionId,
1707    ) -> (
1708        SenderBulkRequest,
1709        oneshot::Receiver<Result<store_api::region_request::AffectedRows>>,
1710    ) {
1711        let metadata = metadata_for_test();
1712        let schema = to_flat_sst_arrow_schema(
1713            &metadata,
1714            &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
1715        );
1716        let pk_codec = build_primary_key_codec(&metadata);
1717        let mut converter = BulkPartConverter::new(&metadata, schema, 16, pk_codec, true);
1718        let kvs = build_key_values_with_ts_seq_values(
1719            &metadata,
1720            "bulk_key".to_string(),
1721            1,
1722            std::iter::once(1000i64),
1723            std::iter::once(Some(1.0f64)),
1724            1,
1725        );
1726        converter.append_key_values(&kvs).unwrap();
1727        let (sender, receiver) = oneshot::channel();
1728
1729        (
1730            SenderBulkRequest {
1731                sender: OptionOutputTx::from(sender),
1732                region_id,
1733                request: converter.convert().unwrap(),
1734                region_metadata: Some(metadata),
1735                partition_expr_version: None,
1736            },
1737            receiver,
1738        )
1739    }
1740
1741    fn new_test_write_request(
1742        region_id: RegionId,
1743    ) -> (
1744        SenderWriteRequest,
1745        oneshot::Receiver<Result<store_api::region_request::AffectedRows>>,
1746    ) {
1747        let (sender, receiver) = oneshot::channel();
1748        let request = WriteRequest::new(region_id, OpType::Put, Rows::default(), None).unwrap();
1749        (
1750            SenderWriteRequest {
1751                sender: OptionOutputTx::from(sender),
1752                request,
1753            },
1754            receiver,
1755        )
1756    }
1757
1758    #[test]
1759    fn test_get_mutable_limit() {
1760        assert_eq!(4, WriteBufferManagerImpl::get_mutable_limit(8));
1761        assert_eq!(5, WriteBufferManagerImpl::get_mutable_limit(10));
1762        assert_eq!(32, WriteBufferManagerImpl::get_mutable_limit(64));
1763        assert_eq!(0, WriteBufferManagerImpl::get_mutable_limit(0));
1764    }
1765
1766    #[test]
1767    fn test_over_mutable_limit() {
1768        // Mutable limit is 500.
1769        let manager = WriteBufferManagerImpl::new(1000);
1770        manager.reserve_mem(400);
1771        assert!(!manager.should_flush_engine());
1772        assert!(!manager.should_stall());
1773
1774        // More than mutable limit.
1775        manager.reserve_mem(400);
1776        assert!(manager.should_flush_engine());
1777
1778        // Freezes mutable.
1779        manager.schedule_free_mem(400);
1780        assert!(!manager.should_flush_engine());
1781        assert_eq!(800, manager.memory_used.load(Ordering::Relaxed));
1782        assert_eq!(400, manager.memory_active.load(Ordering::Relaxed));
1783
1784        // Releases immutable.
1785        manager.free_mem(400);
1786        assert_eq!(400, manager.memory_used.load(Ordering::Relaxed));
1787        assert_eq!(400, manager.memory_active.load(Ordering::Relaxed));
1788    }
1789
1790    #[test]
1791    fn test_over_global() {
1792        // Mutable limit is 500.
1793        let manager = WriteBufferManagerImpl::new(1000);
1794        manager.reserve_mem(1100);
1795        assert!(manager.should_stall());
1796        // Global usage is still 1100.
1797        manager.schedule_free_mem(200);
1798        assert!(manager.should_flush_engine());
1799        assert!(manager.should_stall());
1800
1801        // More than global limit, mutable (1100-200-450=450) is less than mutable limit (< 500).
1802        manager.schedule_free_mem(450);
1803        assert!(manager.should_flush_engine());
1804        assert!(manager.should_stall());
1805
1806        // Now mutable is enough.
1807        manager.reserve_mem(50);
1808        assert!(manager.should_flush_engine());
1809        manager.reserve_mem(100);
1810        assert!(manager.should_flush_engine());
1811    }
1812
1813    #[test]
1814    fn test_manager_notify() {
1815        let (sender, receiver) = watch::channel(());
1816        let manager = WriteBufferManagerImpl::new(1000).with_notifier(sender);
1817        manager.reserve_mem(500);
1818        assert!(!receiver.has_changed().unwrap());
1819        manager.schedule_free_mem(500);
1820        assert!(!receiver.has_changed().unwrap());
1821        manager.free_mem(500);
1822        assert!(receiver.has_changed().unwrap());
1823    }
1824
1825    #[tokio::test]
1826    async fn test_schedule_empty() {
1827        let env = SchedulerEnv::new().await;
1828        let (tx, _rx) = mpsc::channel(4);
1829        let mut scheduler = env.mock_flush_scheduler();
1830        let builder = VersionControlBuilder::new();
1831
1832        let version_control = Arc::new(builder.build());
1833        let (output_tx, output_rx) = oneshot::channel();
1834        let mut task = RegionFlushTask {
1835            region_id: builder.region_id(),
1836            reason: FlushReason::Manual,
1837            senders: Vec::new(),
1838            request_sender: tx,
1839            access_layer: env.access_layer.clone(),
1840            listener: WorkerListener::default(),
1841            engine_config: Arc::new(MitoConfig::default()),
1842            row_group_size: None,
1843            cache_manager: Arc::new(CacheManager::default()),
1844            manifest_ctx: env
1845                .mock_manifest_context(version_control.current().version.metadata.clone())
1846                .await,
1847            index_options: IndexOptions::default(),
1848            flush_semaphore: Arc::new(Semaphore::new(2)),
1849            is_staging: false,
1850            partition_expr: None,
1851        };
1852        task.push_sender(OptionOutputTx::from(output_tx));
1853        scheduler
1854            .schedule_flush(builder.region_id(), &version_control, task)
1855            .unwrap();
1856        assert!(scheduler.region_status.is_empty());
1857        let output = output_rx.await.unwrap().unwrap();
1858        assert_eq!(output, 0);
1859        assert!(scheduler.region_status.is_empty());
1860    }
1861
1862    #[tokio::test]
1863    async fn test_schedule_flush_failure_notifies_waiter() {
1864        let env = SchedulerEnv::new()
1865            .await
1866            .scheduler(Arc::new(FailingScheduler));
1867        let (tx, _rx) = mpsc::channel(4);
1868        let mut scheduler = env.mock_flush_scheduler();
1869        let mut builder = VersionControlBuilder::new();
1870        builder.set_memtable_builder(Arc::new(TimeSeriesMemtableBuilder::default()));
1871        let version_control = Arc::new(builder.build());
1872        let version_data = version_control.current();
1873        write_rows_to_version(&version_data.version, "host0", 0, 10);
1874        let manifest_ctx = env
1875            .mock_manifest_context(version_data.version.metadata.clone())
1876            .await;
1877        let (output_tx, output_rx) = oneshot::channel();
1878        let mut task = new_test_flush_task(
1879            &env,
1880            builder.region_id(),
1881            FlushReason::Manual,
1882            tx,
1883            manifest_ctx,
1884        );
1885        task.push_sender(OptionOutputTx::from(output_tx));
1886
1887        scheduler
1888            .schedule_flush(builder.region_id(), &version_control, task)
1889            .unwrap_err();
1890
1891        let err = output_rx
1892            .await
1893            .expect("waiter must receive explicit error")
1894            .unwrap_err();
1895        assert_eq!(err.status_code(), StatusCode::RegionBusy);
1896    }
1897
1898    #[tokio::test]
1899    async fn test_send_worker_request_failure_notifies_flush_finished_waiter() {
1900        let env = SchedulerEnv::new().await;
1901        let (tx, rx) = mpsc::channel(1);
1902        drop(rx);
1903        let builder = VersionControlBuilder::new();
1904        let version_control = Arc::new(builder.build());
1905        let manifest_ctx = env
1906            .mock_manifest_context(version_control.current().version.metadata.clone())
1907            .await;
1908        let task = new_test_flush_task(
1909            &env,
1910            builder.region_id(),
1911            FlushReason::Manual,
1912            tx,
1913            manifest_ctx,
1914        );
1915        let (output_tx, output_rx) = oneshot::channel();
1916        let request = WorkerRequest::Background {
1917            region_id: builder.region_id(),
1918            notify: BackgroundNotify::FlushFinished(FlushFinished {
1919                region_id: builder.region_id(),
1920                flush_reason: FlushReason::Manual,
1921                flushed_entry_id: 0,
1922                senders: vec![OutputTx::new(output_tx)],
1923                _timer: FLUSH_ELAPSED.with_label_values(&["total"]).start_timer(),
1924                edit: RegionEdit {
1925                    files_to_add: Vec::new(),
1926                    files_to_remove: Vec::new(),
1927                    timestamp_ms: None,
1928                    compaction_time_window: None,
1929                    flushed_entry_id: None,
1930                    flushed_sequence: None,
1931                    committed_sequence: None,
1932                },
1933                memtables_to_remove: smallvec![],
1934                is_staging: false,
1935            }),
1936        };
1937
1938        task.send_worker_request(request).await;
1939
1940        let output = output_rx.await.expect("waiter must receive explicit error");
1941        assert!(output.is_err());
1942    }
1943
1944    #[tokio::test]
1945    async fn test_flush_waiters_drop_notifies_waiter() {
1946        let region_id = RegionId::new(1, 1);
1947        let (output_tx, output_rx) = oneshot::channel();
1948        let waiters = FlushTaskWaiters::new(region_id, vec![OutputTx::new(output_tx)]);
1949
1950        drop(waiters);
1951
1952        let err = output_rx
1953            .await
1954            .expect("waiter must receive explicit error")
1955            .unwrap_err();
1956        assert_eq!(err.status_code(), StatusCode::RegionBusy);
1957    }
1958
1959    #[tokio::test]
1960    async fn test_flush_failure_notifies_pending_bulk_writes() {
1961        let region_id = RegionId::new(1, 1);
1962        let version_control = Arc::new(VersionControlBuilder::new().build());
1963        let (bulk_req, output_rx) = new_test_bulk_request(region_id);
1964        let status = FlushStatus {
1965            region_id,
1966            version_control,
1967            state: CancellableTaskState::new(),
1968            pending_task: None,
1969            closing: false,
1970            pending_ddls: Vec::new(),
1971            pending_writes: Vec::new(),
1972            pending_bulk_writes: vec![bulk_req],
1973        };
1974
1975        let pending_ddls = status.on_failure(Arc::new(RegionClosedSnafu { region_id }.build()));
1976        assert!(pending_ddls.is_empty());
1977
1978        let err = output_rx
1979            .await
1980            .expect("pending bulk write must receive explicit error")
1981            .unwrap_err();
1982        assert_eq!(err.status_code(), StatusCode::Cancelled);
1983    }
1984
1985    #[tokio::test]
1986    async fn test_flush_failure_retains_lifecycle_ddls_and_fails_pending_writes() {
1987        let region_id = RegionId::new(1, 1);
1988        let version_control = Arc::new(VersionControlBuilder::new().build());
1989        let (write_req, write_rx) = new_test_write_request(region_id);
1990        let (bulk_req, bulk_rx) = new_test_bulk_request(region_id);
1991        let (truncate_tx, mut truncate_rx) = oneshot::channel();
1992        let (drop_tx, mut drop_rx) = oneshot::channel();
1993        let status = FlushStatus {
1994            region_id,
1995            version_control,
1996            state: CancellableTaskState::new(),
1997            pending_task: None,
1998            closing: false,
1999            pending_ddls: vec![
2000                SenderDdlRequest {
2001                    region_id,
2002                    sender: OptionOutputTx::from(truncate_tx),
2003                    request: DdlRequest::Truncate(
2004                        store_api::region_request::RegionTruncateRequest::All,
2005                    ),
2006                },
2007                SenderDdlRequest {
2008                    region_id,
2009                    sender: OptionOutputTx::from(drop_tx),
2010                    request: DdlRequest::Drop(store_api::region_request::RegionDropRequest {
2011                        fast_path: false,
2012                        force: false,
2013                        partial_drop: false,
2014                    }),
2015                },
2016            ],
2017            pending_writes: vec![write_req],
2018            pending_bulk_writes: vec![bulk_req],
2019        };
2020
2021        let ddls = status.on_failure(Arc::new(RegionBusySnafu { region_id }.build()));
2022        assert_eq!(2, ddls.len());
2023        assert!(matches!(ddls[0].request, DdlRequest::Truncate(_)));
2024        assert!(matches!(ddls[1].request, DdlRequest::Drop(_)));
2025
2026        let write_err = write_rx
2027            .await
2028            .expect("pending write must receive explicit error")
2029            .unwrap_err();
2030        assert_eq!(write_err.status_code(), StatusCode::RegionBusy);
2031        let bulk_err = bulk_rx
2032            .await
2033            .expect("pending bulk write must receive explicit error")
2034            .unwrap_err();
2035        assert_eq!(bulk_err.status_code(), StatusCode::RegionBusy);
2036
2037        assert!(truncate_rx.try_recv().is_err());
2038        assert!(drop_rx.try_recv().is_err());
2039        for ddl in ddls {
2040            ddl.sender.send(Ok(0));
2041        }
2042        assert_eq!(0, truncate_rx.await.unwrap().unwrap());
2043        assert_eq!(0, drop_rx.await.unwrap().unwrap());
2044    }
2045
2046    #[tokio::test]
2047    async fn test_uncommitted_ssts_cleanup_finalized_file() {
2048        let env = SchedulerEnv::new().await;
2049        let region_id = RegionId::new(1, 1);
2050        let file_id = store_api::storage::FileId::random();
2051        let path = crate::sst::location::sst_file_path(
2052            env.access_layer.table_dir(),
2053            crate::sst::file::RegionFileId::new(region_id, file_id),
2054            env.access_layer.path_type(),
2055        );
2056        env.access_layer
2057            .object_store()
2058            .write(&path, Bytes::from_static(b"sst"))
2059            .await
2060            .unwrap();
2061        assert!(env.access_layer.object_store().exists(&path).await.unwrap());
2062
2063        let uncommitted = UncommittedSsts::new(region_id, env.access_layer.clone(), None);
2064        uncommitted.track(&[SstInfo {
2065            file_id,
2066            ..Default::default()
2067        }]);
2068        assert_eq!(1, uncommitted.num_tracked_files());
2069        uncommitted.cleanup_for_test().await.unwrap();
2070        assert_eq!(0, uncommitted.num_tracked_files());
2071
2072        // The object-store wrapper caches successful stat results, so list the directory instead
2073        // of using `exists()` again to verify the deletion.
2074        let entries = env
2075            .access_layer
2076            .object_store()
2077            .list(&env.access_layer.build_region_dir(region_id))
2078            .await
2079            .unwrap();
2080        assert!(entries.iter().all(|entry| entry.path() != path));
2081    }
2082
2083    #[tokio::test]
2084    async fn test_region_closed_notifies_pending_bulk_writes() {
2085        let region_id = RegionId::new(1, 1);
2086        let version_control = Arc::new(VersionControlBuilder::new().build());
2087        let (bulk_req, output_rx) = new_test_bulk_request(region_id);
2088        let status = FlushStatus {
2089            region_id,
2090            version_control,
2091            state: CancellableTaskState::new(),
2092            pending_task: None,
2093            closing: false,
2094            pending_ddls: Vec::new(),
2095            pending_writes: Vec::new(),
2096            pending_bulk_writes: vec![bulk_req],
2097        };
2098
2099        status.on_region_closed(Arc::new(RegionClosedSnafu { region_id }.build()));
2100
2101        let err = output_rx
2102            .await
2103            .expect("pending bulk write must receive explicit error")
2104            .unwrap_err();
2105        assert_eq!(err.status_code(), StatusCode::Cancelled);
2106    }
2107
2108    #[tokio::test]
2109    async fn test_schedule_pending_request() {
2110        let job_scheduler = Arc::new(VecScheduler::default());
2111        let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
2112        let (tx, _rx) = mpsc::channel(4);
2113        let mut scheduler = env.mock_flush_scheduler();
2114        let mut builder = VersionControlBuilder::new();
2115        // Overwrites the empty memtable builder.
2116        builder.set_memtable_builder(Arc::new(TimeSeriesMemtableBuilder::default()));
2117        let version_control = Arc::new(builder.build());
2118        // Writes data to the memtable so it is not empty.
2119        let version_data = version_control.current();
2120        write_rows_to_version(&version_data.version, "host0", 0, 10);
2121        let manifest_ctx = env
2122            .mock_manifest_context(version_data.version.metadata.clone())
2123            .await;
2124        // Creates 3 tasks.
2125        let mut tasks: Vec<_> = (0..3)
2126            .map(|_| RegionFlushTask {
2127                region_id: builder.region_id(),
2128                reason: FlushReason::Manual,
2129                senders: Vec::new(),
2130                request_sender: tx.clone(),
2131                access_layer: env.access_layer.clone(),
2132                listener: WorkerListener::default(),
2133                engine_config: Arc::new(MitoConfig::default()),
2134                row_group_size: None,
2135                cache_manager: Arc::new(CacheManager::default()),
2136                manifest_ctx: manifest_ctx.clone(),
2137                index_options: IndexOptions::default(),
2138                flush_semaphore: Arc::new(Semaphore::new(2)),
2139                is_staging: false,
2140                partition_expr: None,
2141            })
2142            .collect();
2143        // Schedule first task.
2144        let task = tasks.pop().unwrap();
2145        scheduler
2146            .schedule_flush(builder.region_id(), &version_control, task)
2147            .unwrap();
2148        // Should schedule 1 flush.
2149        assert_eq!(1, scheduler.region_status.len());
2150        assert_eq!(1, job_scheduler.num_jobs());
2151        // Check the new version.
2152        let version_data = version_control.current();
2153        assert_eq!(0, version_data.version.memtables.immutables()[0].id());
2154        // Schedule remaining tasks.
2155        let output_rxs: Vec<_> = tasks
2156            .into_iter()
2157            .map(|mut task| {
2158                let (output_tx, output_rx) = oneshot::channel();
2159                task.push_sender(OptionOutputTx::from(output_tx));
2160                scheduler
2161                    .schedule_flush(builder.region_id(), &version_control, task)
2162                    .unwrap();
2163                output_rx
2164            })
2165            .collect();
2166        // Assumes the flush job is finished.
2167        version_control.apply_edit(
2168            Some(RegionEdit {
2169                files_to_add: Vec::new(),
2170                files_to_remove: Vec::new(),
2171                timestamp_ms: None,
2172                compaction_time_window: None,
2173                flushed_entry_id: None,
2174                flushed_sequence: None,
2175                committed_sequence: None,
2176            }),
2177            &[0],
2178            builder.file_purger(),
2179        );
2180        scheduler.on_flush_success(builder.region_id());
2181        // No new flush task.
2182        assert_eq!(1, job_scheduler.num_jobs());
2183        // The flush status is cleared.
2184        assert!(scheduler.region_status.is_empty());
2185        for output_rx in output_rxs {
2186            let output = output_rx.await.unwrap().unwrap();
2187            assert_eq!(output, 0);
2188        }
2189    }
2190
2191    // Verifies single-range flat flush path respects append_mode (no dedup) vs dedup when disabled.
2192    #[test]
2193    fn test_memtable_flat_sources_single_range_append_mode_behavior() {
2194        // Build test metadata and flat schema
2195        let metadata = metadata_for_test();
2196        let schema = to_flat_sst_arrow_schema(
2197            &metadata,
2198            &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2199        );
2200
2201        // Prepare a bulk part containing duplicate rows for the same PK and timestamp
2202        // Two rows with identical keys and timestamps (ts = 1000), different field values
2203        let capacity = 16;
2204        let pk_codec = build_primary_key_codec(&metadata);
2205        let mut converter =
2206            BulkPartConverter::new(&metadata, schema.clone(), capacity, pk_codec, true);
2207        let kvs = build_key_values_with_ts_seq_values(
2208            &metadata,
2209            "dup_key".to_string(),
2210            1,
2211            vec![1000i64, 1000i64].into_iter(),
2212            vec![Some(1.0f64), Some(2.0f64)].into_iter(),
2213            1,
2214        );
2215        converter.append_key_values(&kvs).unwrap();
2216        let part = converter.convert().unwrap();
2217
2218        // Helper to build MemtableRanges with a single range from one bulk part.
2219        // We use BulkMemtable directly because it produces record batch iterators.
2220        let build_ranges = |append_mode: bool| -> MemtableRanges {
2221            let memtable = crate::memtable::bulk::BulkMemtable::new(
2222                1,
2223                crate::memtable::bulk::BulkMemtableConfig::default(),
2224                metadata.clone(),
2225                None,
2226                None,
2227                append_mode,
2228                MergeMode::LastRow,
2229            );
2230            memtable.write_bulk(part.clone()).unwrap();
2231            memtable.ranges(None, RangesOptions::for_flush()).unwrap()
2232        };
2233
2234        // Case 1: append_mode = false => dedup happens, total rows should be 1
2235        {
2236            let mem_ranges = build_ranges(false);
2237            assert_eq!(1, mem_ranges.ranges.len());
2238
2239            let options = RegionOptions {
2240                append_mode: false,
2241                merge_mode: Some(MergeMode::LastRow),
2242                ..Default::default()
2243            };
2244
2245            let flat_sources = memtable_flat_sources(
2246                schema.clone(),
2247                mem_ranges,
2248                &metadata,
2249                &options,
2250                metadata.primary_key.len(),
2251            )
2252            .unwrap();
2253            assert!(flat_sources.encoded.is_empty());
2254            assert_eq!(1, flat_sources.sources.len());
2255
2256            // Consume the iterator and count rows
2257            let mut total_rows = 0usize;
2258            for (source, _sequence) in flat_sources.sources {
2259                total_rows += source
2260                    .take_iter()
2261                    .map(|x| x.unwrap().num_rows())
2262                    .sum::<usize>();
2263            }
2264            assert_eq!(1, total_rows, "dedup should keep a single row");
2265        }
2266
2267        // Case 2: append_mode = true => no dedup, total rows should be 2
2268        {
2269            let mem_ranges = build_ranges(true);
2270            assert_eq!(1, mem_ranges.ranges.len());
2271
2272            let options = RegionOptions {
2273                append_mode: true,
2274                ..Default::default()
2275            };
2276
2277            let flat_sources = memtable_flat_sources(
2278                schema,
2279                mem_ranges,
2280                &metadata,
2281                &options,
2282                metadata.primary_key.len(),
2283            )
2284            .unwrap();
2285            assert!(flat_sources.encoded.is_empty());
2286            assert_eq!(1, flat_sources.sources.len());
2287
2288            let mut total_rows = 0usize;
2289            for (source, _sequence) in flat_sources.sources {
2290                total_rows += source
2291                    .take_iter()
2292                    .map(|x| x.unwrap().num_rows())
2293                    .sum::<usize>();
2294            }
2295            assert_eq!(2, total_rows, "append_mode should preserve duplicates");
2296        }
2297    }
2298
2299    #[test]
2300    fn test_memtable_flat_sources_uses_non_encoded_schema() -> Result<()> {
2301        struct TestIterBuilder {
2302            schema: SchemaRef,
2303            batch: Option<RecordBatch>,
2304        }
2305
2306        impl IterBuilder for TestIterBuilder {
2307            fn build(
2308                &self,
2309                _metrics: Option<crate::memtable::MemScanMetrics>,
2310            ) -> Result<crate::memtable::BoxedBatchIterator> {
2311                unimplemented!()
2312            }
2313
2314            fn is_record_batch(&self) -> bool {
2315                true
2316            }
2317
2318            fn build_record_batch(
2319                &self,
2320                _time_range: Option<(common_time::Timestamp, common_time::Timestamp)>,
2321                _metrics: Option<crate::memtable::MemScanMetrics>,
2322            ) -> Result<BoxedRecordBatchIterator> {
2323                let Some(batch) = self.batch.clone() else {
2324                    unimplemented!()
2325                };
2326                Ok(Box::new(std::iter::once(Ok(batch))))
2327            }
2328
2329            fn record_batch_schema_hint(&self) -> Option<SchemaRef> {
2330                Some(self.schema.clone())
2331            }
2332
2333            fn encoded_range(&self) -> Option<EncodedRange> {
2334                self.batch.is_none().then(|| EncodedRange {
2335                    data: Bytes::new(),
2336                    sst_info: SstInfo::default(),
2337                })
2338            }
2339        }
2340
2341        let metadata = metadata_for_test();
2342        let schema = to_flat_sst_arrow_schema(
2343            &metadata,
2344            &FlatSchemaOptions::from_encoding(metadata.primary_key_encoding),
2345        );
2346        let pk_codec = build_primary_key_codec(&metadata);
2347        let mut converter = BulkPartConverter::new(&metadata, schema.clone(), 1, pk_codec, true);
2348        let kvs = build_key_values_with_ts_seq_values(
2349            &metadata,
2350            "key".to_string(),
2351            1,
2352            std::iter::once(1000),
2353            std::iter::once(Some(1.0)),
2354            1,
2355        );
2356        converter.append_key_values(&kvs)?;
2357        let batch = converter.convert()?.batch;
2358        let encoded_schema = Arc::new(Schema::empty());
2359
2360        let new_range = |id, builder| {
2361            MemtableRange::new(
2362                Arc::new(MemtableRangeContext::new(
2363                    id,
2364                    Box::new(builder),
2365                    Default::default(),
2366                )),
2367                MemtableStats {
2368                    num_rows: 1,
2369                    ..Default::default()
2370                },
2371            )
2372        };
2373        let mut ranges = std::collections::BTreeMap::new();
2374        ranges.insert(
2375            0,
2376            new_range(
2377                0,
2378                TestIterBuilder {
2379                    schema: encoded_schema.clone(),
2380                    batch: None,
2381                },
2382            ),
2383        );
2384        ranges.insert(
2385            1,
2386            new_range(
2387                0,
2388                TestIterBuilder {
2389                    schema: schema.clone(),
2390                    batch: Some(batch),
2391                },
2392            ),
2393        );
2394
2395        let sources = memtable_flat_sources(
2396            encoded_schema,
2397            MemtableRanges { ranges },
2398            &metadata,
2399            &RegionOptions {
2400                append_mode: true,
2401                ..Default::default()
2402            },
2403            metadata.primary_key.len(),
2404        )?;
2405        assert_eq!(1, sources.encoded.len());
2406        assert_eq!(1, sources.sources.len());
2407        assert_eq!(&schema, sources.sources[0].0.schema());
2408        Ok(())
2409    }
2410
2411    #[tokio::test]
2412    async fn test_schedule_pending_request_on_flush_success() {
2413        common_telemetry::init_default_ut_logging();
2414        let job_scheduler = Arc::new(VecScheduler::default());
2415        let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
2416        let (tx, _rx) = mpsc::channel(4);
2417        let mut scheduler = env.mock_flush_scheduler();
2418        let mut builder = VersionControlBuilder::new();
2419        // Overwrites the empty memtable builder.
2420        builder.set_memtable_builder(Arc::new(TimeSeriesMemtableBuilder::default()));
2421        let version_control = Arc::new(builder.build());
2422        // Writes data to the memtable so it is not empty.
2423        let version_data = version_control.current();
2424        write_rows_to_version(&version_data.version, "host0", 0, 10);
2425        let manifest_ctx = env
2426            .mock_manifest_context(version_data.version.metadata.clone())
2427            .await;
2428        // Creates 2 tasks.
2429        let mut tasks: Vec<_> = (0..2)
2430            .map(|_| RegionFlushTask {
2431                region_id: builder.region_id(),
2432                reason: FlushReason::Manual,
2433                senders: Vec::new(),
2434                request_sender: tx.clone(),
2435                access_layer: env.access_layer.clone(),
2436                listener: WorkerListener::default(),
2437                engine_config: Arc::new(MitoConfig::default()),
2438                row_group_size: None,
2439                cache_manager: Arc::new(CacheManager::default()),
2440                manifest_ctx: manifest_ctx.clone(),
2441                index_options: IndexOptions::default(),
2442                flush_semaphore: Arc::new(Semaphore::new(2)),
2443                is_staging: false,
2444                partition_expr: None,
2445            })
2446            .collect();
2447        // Schedule first task.
2448        let task = tasks.pop().unwrap();
2449        scheduler
2450            .schedule_flush(builder.region_id(), &version_control, task)
2451            .unwrap();
2452        // Should schedule 1 flush.
2453        assert_eq!(1, scheduler.region_status.len());
2454        assert_eq!(1, job_scheduler.num_jobs());
2455        // Schedule second task.
2456        let task = tasks.pop().unwrap();
2457        scheduler
2458            .schedule_flush(builder.region_id(), &version_control, task)
2459            .unwrap();
2460        assert!(
2461            scheduler
2462                .region_status
2463                .get(&builder.region_id())
2464                .unwrap()
2465                .pending_task
2466                .is_some()
2467        );
2468
2469        // Check the new version.
2470        let version_data = version_control.current();
2471        assert_eq!(0, version_data.version.memtables.immutables()[0].id());
2472        // Assumes the flush job is finished.
2473        version_control.apply_edit(
2474            Some(RegionEdit {
2475                files_to_add: Vec::new(),
2476                files_to_remove: Vec::new(),
2477                timestamp_ms: None,
2478                compaction_time_window: None,
2479                flushed_entry_id: None,
2480                flushed_sequence: None,
2481                committed_sequence: None,
2482            }),
2483            &[0],
2484            builder.file_purger(),
2485        );
2486        write_rows_to_version(&version_data.version, "host1", 0, 10);
2487        scheduler.on_flush_success(builder.region_id());
2488        assert_eq!(2, job_scheduler.num_jobs());
2489        // The pending task is cleared.
2490        assert!(
2491            scheduler
2492                .region_status
2493                .get(&builder.region_id())
2494                .unwrap()
2495                .pending_task
2496                .is_none()
2497        );
2498    }
2499
2500    #[tokio::test]
2501    async fn test_schedule_pending_request_failure_drains_pending_ddls() {
2502        common_telemetry::init_default_ut_logging();
2503        let job_scheduler = Arc::new(VecScheduler::default());
2504        let env = SchedulerEnv::new().await.scheduler(job_scheduler.clone());
2505        let (tx, _rx) = mpsc::channel(4);
2506        let mut scheduler = env.mock_flush_scheduler();
2507        let mut builder = VersionControlBuilder::new();
2508        builder.set_memtable_builder(Arc::new(TimeSeriesMemtableBuilder::default()));
2509        let version_control = Arc::new(builder.build());
2510
2511        let version_data = version_control.current();
2512        write_rows_to_version(&version_data.version, "host0", 0, 10);
2513        let manifest_ctx = env
2514            .mock_manifest_context(version_data.version.metadata.clone())
2515            .await;
2516
2517        let task = new_test_flush_task(
2518            &env,
2519            builder.region_id(),
2520            FlushReason::Manual,
2521            tx.clone(),
2522            manifest_ctx.clone(),
2523        );
2524        scheduler
2525            .schedule_flush(builder.region_id(), &version_control, task)
2526            .unwrap();
2527
2528        let task = new_test_flush_task(
2529            &env,
2530            builder.region_id(),
2531            FlushReason::Closing,
2532            tx,
2533            manifest_ctx,
2534        );
2535        scheduler
2536            .schedule_flush(builder.region_id(), &version_control, task)
2537            .unwrap();
2538
2539        let (sender, receiver) = oneshot::channel();
2540        scheduler.add_ddl_request_to_pending(SenderDdlRequest {
2541            sender: OptionOutputTx::from(sender),
2542            region_id: builder.region_id(),
2543            request: DdlRequest::Close(store_api::region_request::RegionCloseRequest::default()),
2544        });
2545
2546        let version_data = version_control.current();
2547        version_control.apply_edit(
2548            Some(RegionEdit {
2549                files_to_add: Vec::new(),
2550                files_to_remove: Vec::new(),
2551                timestamp_ms: None,
2552                compaction_time_window: None,
2553                flushed_entry_id: None,
2554                flushed_sequence: None,
2555                committed_sequence: None,
2556            }),
2557            &[0],
2558            builder.file_purger(),
2559        );
2560        write_rows_to_version(&version_data.version, "host1", 0, 10);
2561
2562        scheduler.scheduler = Arc::new(FailingScheduler);
2563        scheduler.on_flush_success(builder.region_id());
2564
2565        assert!(scheduler.region_status.is_empty());
2566        let err = receiver
2567            .await
2568            .expect("pending DDL must be notified")
2569            .unwrap_err();
2570        assert_eq!(err.status_code(), StatusCode::RegionBusy);
2571    }
2572}