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