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