Skip to main content

mito2/
flush.rs

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