Skip to main content

mito2/
flush.rs

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