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