Skip to main content

mito2/
flush.rs

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