1use std::collections::{HashMap, HashSet};
18use std::sync::atomic::Ordering;
19use std::sync::{Arc, Mutex, Weak};
20
21use common_base::readable_size::ReadableSize;
22use common_telemetry::{debug, error, info};
23use store_api::logstore::LogStore;
24use store_api::region_request::{RegionFlushReason, RegionFlushRequest};
25use store_api::storage::RegionId;
26
27use crate::config::{IndexBuildMode, MitoConfig};
28use crate::error::{RegionBusySnafu, RegionNotFoundSnafu, Result};
29use crate::flush::{FlushReason, RegionFlushTask};
30use crate::region::version::VersionRef;
31use crate::region::{MitoRegion, MitoRegionRef};
32use crate::request::{BuildIndexRequest, FlushFailed, FlushFinished, OnFailure, OptionOutputTx};
33use crate::sst::index::IndexBuildType;
34use crate::worker::RegionWorkerLoop;
35
36#[derive(Debug, Default)]
37pub(crate) struct RegionWriteBufferPressure {
38 pub(crate) stalled_region_ids: HashSet<RegionId>,
39 pub(crate) rejected_region_ids: HashSet<RegionId>,
40}
41
42#[derive(Debug, Default)]
46pub(crate) struct StagingRegions {
47 regions: Mutex<HashMap<RegionId, Weak<MitoRegion>>>,
48}
49
50pub(crate) type StagingRegionsRef = Arc<StagingRegions>;
51
52impl StagingRegions {
53 fn record_and_memtable_usage(&self, region: &MitoRegionRef) -> usize {
55 let mut regions = self.regions.lock().unwrap();
56 regions.insert(region.region_id, Arc::downgrade(region));
57 let mut usage = 0;
58 regions.retain(|_, region| match region.upgrade() {
59 Some(region) if region.is_staging_or_staged_edit() => {
60 usage += region_memtable_usage(®ion.version());
61 true
62 }
63 _ => false,
64 });
65 usage
66 }
67}
68
69#[derive(Debug, Default, PartialEq, Eq)]
70struct RegionWriteBufferStatus {
71 should_flush: bool,
72 should_stall: bool,
73 should_reject: bool,
74}
75
76fn resolve_flush_reason(
77 request_reason: Option<RegionFlushReason>,
78 is_downgrading: bool,
79) -> FlushReason {
80 match request_reason {
81 Some(reason) => FlushReason::from(reason),
82 None if is_downgrading => FlushReason::Downgrading,
83 None => FlushReason::Manual,
84 }
85}
86
87impl<S: LogStore> RegionWorkerLoop<S> {
88 pub(crate) async fn handle_flush_failed(&mut self, region_id: RegionId, request: FlushFailed) {
90 let mut pending_ddls = self.flush_scheduler.on_flush_failed(region_id, request.err);
91 if !pending_ddls.is_empty() {
92 info!(
93 "Flush terminated for region {}, handling {} pending lifecycle DDL requests",
94 region_id,
95 pending_ddls.len()
96 );
97 self.handle_ddl_requests(&mut pending_ddls).await;
98 }
99 self.maybe_flush_worker();
101
102 self.handle_stalled_requests().await;
104 self.dispatch_pending_region_requests(region_id).await;
105 }
106
107 pub(crate) fn maybe_flush_worker(&mut self) {
110 if !self.write_buffer_manager.should_flush_engine() {
111 debug!("No need to flush worker");
112 return;
114 }
115
116 if let Err(e) = self.flush_regions_on_engine_full() {
119 error!(e; "Failed to flush worker");
120 }
121 }
122
123 fn flush_regions_on_engine_full(&mut self) -> Result<()> {
125 let regions = self.regions.list_regions();
126 let now = self.time_provider.current_time_millis();
127 let mut pending_regions = vec![];
128
129 for region in ®ions {
130 if self.flush_scheduler.is_flush_requested(region.region_id)
131 || !region.is_writable()
132 || region.is_staging()
133 {
134 continue;
136 }
137
138 let version = region.version();
139 let region_memtable_size = region_memtable_usage(&version);
140
141 let auto_flush_interval = version
142 .options
143 .auto_flush_interval_or(self.config.auto_flush_interval);
144 let min_last_flush_time = now.saturating_sub(
145 auto_flush_interval
146 .as_millis()
147 .try_into()
148 .unwrap_or(i64::MAX),
149 );
150
151 if region.last_flush_millis() < min_last_flush_time {
152 let task =
154 self.new_flush_task(region, FlushReason::EngineFull, None, self.config.clone());
155 self.flush_scheduler.schedule_flush(
156 region.region_id,
157 ®ion.version_control,
158 task,
159 )?;
160 } else if region_memtable_size > 0 {
161 pending_regions.push((region, region_memtable_size));
163 }
164 }
165 pending_regions.sort_unstable_by_key(|(_, size)| std::cmp::Reverse(*size));
166 let target_memory_usage = self.write_buffer_manager.flush_limit();
170 let mut memory_usage = self.write_buffer_manager.memory_usage();
171
172 #[cfg(test)]
173 {
174 debug!(
175 "Flushing regions on engine full, target memory usage: {}, memory usage: {}, pending regions: {:?}",
176 target_memory_usage,
177 memory_usage,
178 pending_regions
179 .iter()
180 .map(|(region, mem_size)| (region.region_id, mem_size))
181 .collect::<Vec<_>>()
182 );
183 }
184 for (region, region_mem_size) in pending_regions.into_iter() {
187 if memory_usage < target_memory_usage {
189 break;
191 }
192 let task =
193 self.new_flush_task(region, FlushReason::EngineFull, None, self.config.clone());
194 debug!("Scheduling flush task for region {}", region.region_id);
195 self.flush_scheduler
197 .schedule_flush(region.region_id, ®ion.version_control, task)?;
198 memory_usage = memory_usage.saturating_sub(region_mem_size);
200 }
201
202 Ok(())
203 }
204
205 pub(crate) fn maybe_flush_write_regions(
207 &mut self,
208 region_ids: HashSet<RegionId>,
209 ) -> RegionWriteBufferPressure {
210 let mut pressure = RegionWriteBufferPressure::default();
211
212 for region_id in region_ids {
213 let Some(region) = self.regions.get_region(region_id) else {
214 continue;
215 };
216 if !region.is_writable() {
217 continue;
218 }
219
220 let status = region_write_buffer_status(
221 ®ion.version(),
222 self.config.default_region_write_buffer_size,
223 self.stalled_requests.estimated_size(®ion_id),
224 );
225
226 if region.is_staging() {
227 let staging_usage = self.staging_regions.record_and_memtable_usage(®ion);
231 if status.should_reject
232 || staging_usage >= self.write_buffer_manager.flush_limit() / 2
233 {
234 pressure.rejected_region_ids.insert(region_id);
235 }
236 continue;
237 }
238
239 if status.should_flush && !self.flush_scheduler.is_flush_requested(region.region_id) {
240 let task = self.new_flush_task(
241 ®ion,
242 FlushReason::RegionFull,
243 None,
244 self.config.clone(),
245 );
246 if let Err(e) = self.flush_scheduler.schedule_flush(
247 region.region_id,
248 ®ion.version_control,
249 task,
250 ) {
251 error!(e; "Failed to schedule flush task for region {}", region.region_id);
252 }
253 }
254
255 if status.should_reject {
256 pressure.rejected_region_ids.insert(region_id);
257 } else if status.should_stall {
258 pressure.stalled_region_ids.insert(region_id);
259 }
260 }
261
262 pressure
263 }
264
265 pub(crate) fn new_flush_task(
267 &self,
268 region: &MitoRegionRef,
269 reason: FlushReason,
270 row_group_size: Option<usize>,
271 engine_config: Arc<MitoConfig>,
272 ) -> RegionFlushTask {
273 RegionFlushTask {
274 region_id: region.region_id,
275 reason,
276 senders: Vec::new(),
277 request_sender: self.sender.clone(),
278 access_layer: region.access_layer.clone(),
279 listener: self.listener.clone(),
280 engine_config,
281 row_group_size: row_group_size.or(region.version().options.max_row_group_row_count),
284 cache_manager: self.cache_manager.clone(),
285 manifest_ctx: region.manifest_ctx.clone(),
286 index_options: region.version().options.index_options.clone(),
287 flush_semaphore: self.flush_semaphore.clone(),
288 partition_expr: region.metadata().partition_expr.clone(),
289 durability_barrier: self
290 .wal
291 .durability_barrier(region.region_id, ®ion.provider),
292 }
293 }
294}
295
296pub(crate) fn region_memtable_usage(version: &VersionRef) -> usize {
297 version.memtables.mutable_usage() + version.memtables.immutables_usage()
298}
299
300pub(crate) fn region_write_buffer_size(
301 version: &VersionRef,
302 default_region_write_buffer_size: ReadableSize,
303) -> Option<usize> {
304 let size = version
305 .options
306 .write_buffer_size
307 .unwrap_or(default_region_write_buffer_size);
308 (size.as_bytes() > 0).then_some(size.as_bytes() as usize)
309}
310
311fn region_write_buffer_status(
313 version: &VersionRef,
314 default_region_write_buffer_size: ReadableSize,
315 stalled_request_size: usize,
316) -> RegionWriteBufferStatus {
317 let Some(write_buffer_size) =
318 region_write_buffer_size(version, default_region_write_buffer_size)
319 else {
320 return RegionWriteBufferStatus::default();
321 };
322
323 let mutable_usage = version.memtables.mutable_usage();
324 let memory_usage = region_memtable_usage(version);
325 region_write_buffer_status_from_usage(
326 write_buffer_size,
327 mutable_usage,
328 memory_usage,
329 stalled_request_size,
330 )
331}
332
333fn region_write_buffer_status_from_usage(
334 write_buffer_size: usize,
335 mutable_usage: usize,
336 memory_usage: usize,
337 stalled_request_size: usize,
338) -> RegionWriteBufferStatus {
339 let mutable_limit = std::cmp::max(1, write_buffer_size / 2);
340 let should_stall = memory_usage >= write_buffer_size;
341 let reject_limit = write_buffer_size.saturating_mul(2);
342 let should_reject = memory_usage.saturating_add(stalled_request_size) >= reject_limit;
343
344 RegionWriteBufferStatus {
345 should_flush: mutable_usage >= mutable_limit || should_stall,
346 should_stall,
347 should_reject,
348 }
349}
350
351impl<S: LogStore> RegionWorkerLoop<S> {
352 pub(crate) fn handle_flush_request(
354 &mut self,
355 region_id: RegionId,
356 request: RegionFlushRequest,
357 sender: OptionOutputTx,
358 ) {
359 let region = match self.regions.flushable_region(region_id) {
360 Ok(region) => region,
361 Err(e) => {
362 sender.send(Err(e));
363 return;
364 }
365 };
366
367 self.update_topic_latest_entry_id(®ion);
371
372 if region.is_staging() && !region.version().memtables.is_empty() {
374 sender.send(match request.reason {
375 Some(RegionFlushReason::RegionMigration | RegionFlushReason::RemoteWalPrune) => {
377 Ok(0)
378 }
379 _ => RegionBusySnafu { region_id }.fail(),
380 });
381 return;
382 }
383
384 let reason = resolve_flush_reason(request.reason, region.is_downgrading());
385 let mut task =
386 self.new_flush_task(®ion, reason, request.row_group_size, self.config.clone());
387 task.push_sender(sender);
388 if let Err(e) =
389 self.flush_scheduler
390 .schedule_flush(region.region_id, ®ion.version_control, task)
391 {
392 error!(e; "Failed to schedule flush task for region {}", region.region_id);
393 }
394 }
395
396 pub(crate) fn flush_periodically(&mut self) -> Result<()> {
398 let regions = self.regions.list_regions();
399 let now = self.time_provider.current_time_millis();
400
401 for region in ®ions {
402 if self.flush_scheduler.is_flush_requested(region.region_id) || !region.is_writable() {
403 continue;
405 }
406 self.update_topic_latest_entry_id(region);
407 if region.is_staging() {
411 continue;
412 }
413
414 let auto_flush_interval = region
415 .version()
416 .options
417 .auto_flush_interval_or(self.config.auto_flush_interval);
418 let min_last_flush_time = now.saturating_sub(
419 auto_flush_interval
420 .as_millis()
421 .try_into()
422 .unwrap_or(i64::MAX),
423 );
424
425 if region.last_flush_millis() < min_last_flush_time {
426 let task = self.new_flush_task(
428 region,
429 FlushReason::Periodically,
430 None,
431 self.config.clone(),
432 );
433 self.flush_scheduler.schedule_flush(
434 region.region_id,
435 ®ion.version_control,
436 task,
437 )?;
438 }
439 }
440
441 Ok(())
442 }
443
444 pub(crate) async fn handle_flush_finished(
446 &mut self,
447 region_id: RegionId,
448 mut request: FlushFinished,
449 ) {
450 self.notify_group();
453
454 let region = match self.regions.get_region(region_id) {
455 Some(region) => region,
456 None => {
457 request.on_failure(RegionNotFoundSnafu { region_id }.build());
458 return;
459 }
460 };
461
462 region.version_control.apply_edit(
463 Some(request.edit.clone()),
464 &request.memtables_to_remove,
465 region.file_purger.clone(),
466 );
467
468 let ingested_parquet_bytes = request
471 .edit
472 .files_to_add
473 .iter()
474 .map(|file| file.file_size)
475 .sum();
476 region
477 .region_stats
478 .ingested_parquet_bytes
479 .fetch_add(ingested_parquet_bytes, Ordering::Relaxed);
480
481 region.update_flush_millis();
482 self.update_topic_latest_entry_id(®ion);
485
486 info!(
488 "Region {} flush finished, tries to bump wal to {}",
489 region_id, request.flushed_entry_id
490 );
491 if let Err(e) = self
492 .wal
493 .obsolete(region_id, request.flushed_entry_id, ®ion.provider)
494 .await
495 {
496 error!(e; "Failed to write wal, region: {}", region_id);
497 request.on_failure(e);
498 return;
499 }
500
501 let flush_on_close = request.flush_reason == FlushReason::Closing;
502
503 let index_build_file_metas = std::mem::take(&mut request.edit.files_to_add);
504
505 if self.config.index.build_mode == IndexBuildMode::Async {
507 self.handle_rebuild_index(
508 BuildIndexRequest {
509 region_id,
510 build_type: IndexBuildType::Flush,
511 file_metas: index_build_file_metas,
512 result_senders: Vec::new(),
513 },
514 OptionOutputTx::new(None),
515 )
516 .await;
517 }
518
519 if flush_on_close {
520 self.remove_region(region_id).await;
521 info!("Region {} closed after flush", region_id);
522 request.on_success();
523 self.listener.on_flush_success(region_id);
524 return;
525 }
526
527 request.on_success();
529 if let Some((mut ddl_requests, mut write_requests, mut bulk_writes)) =
531 self.flush_scheduler.on_flush_success(region_id)
532 {
533 for ddl in ddl_requests.drain(..) {
536 if let crate::request::DdlRequest::Alter(request) = ddl.request {
537 self.execute_alter_request(ddl.region_id, request, ddl.sender)
538 .await;
539 } else {
540 self.handle_ddl_requests(&mut vec![ddl]).await;
541 }
542 }
543 if self.flush_scheduler.is_flush_requested(region_id) {
544 for write_request in write_requests {
549 self.flush_scheduler
550 .add_write_request_to_pending(write_request);
551 }
552 for bulk_write in bulk_writes {
553 self.flush_scheduler.add_bulk_request_to_pending(bulk_write);
554 }
555 self.listener.on_flush_success(region_id);
556 return;
557 }
558 if !self.regions.is_region_exists(region_id) {
561 self.handle_write_requests(&mut write_requests, &mut bulk_writes, false)
562 .await;
563 self.listener.on_flush_success(region_id);
564 return;
565 }
566 self.handle_write_requests(&mut write_requests, &mut bulk_writes, false)
568 .await;
569 }
570 self.maybe_flush_worker();
572 self.handle_stalled_requests().await;
574 self.schedule_compaction(®ion).await;
576
577 self.dispatch_pending_region_requests(region_id).await;
578 self.listener.on_flush_success(region_id);
579 }
580
581 pub(crate) fn update_topic_latest_entry_id(&mut self, region: &MitoRegionRef) {
584 if region.provider.is_remote_wal() && region.version().memtables.is_empty() {
585 let latest_offset = self
586 .wal
587 .store()
588 .latest_entry_id(®ion.provider)
589 .unwrap_or(0);
590 let topic_last_entry_id = region.topic_latest_entry_id.load(Ordering::Relaxed);
591
592 if latest_offset > topic_last_entry_id {
593 region
594 .topic_latest_entry_id
595 .store(latest_offset, Ordering::Relaxed);
596 debug!(
597 "Region {} latest entry id updated to {}",
598 region.region_id, latest_offset
599 );
600 }
601 }
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608
609 #[test]
610 fn test_resolve_flush_reason_uses_request_reason() {
611 assert_eq!(
612 resolve_flush_reason(Some(RegionFlushReason::RegionMigration), true),
613 FlushReason::RegionMigration
614 );
615 assert_eq!(
616 resolve_flush_reason(Some(RegionFlushReason::Repartition), false),
617 FlushReason::Repartition
618 );
619 assert_eq!(
620 resolve_flush_reason(Some(RegionFlushReason::RemoteWalPrune), false),
621 FlushReason::RemoteWalPrune
622 );
623 assert_eq!(
624 resolve_flush_reason(Some(RegionFlushReason::Closing), false),
625 FlushReason::Closing
626 );
627 assert_eq!(
628 resolve_flush_reason(Some(RegionFlushReason::Downgrading), false),
629 FlushReason::Downgrading
630 );
631 }
632
633 #[test]
634 fn test_resolve_flush_reason_fallback_unchanged() {
635 assert_eq!(resolve_flush_reason(None, true), FlushReason::Downgrading);
636 assert_eq!(resolve_flush_reason(None, false), FlushReason::Manual);
637 }
638
639 #[test]
640 fn test_region_write_buffer_status_boundaries() {
641 assert_eq!(
642 RegionWriteBufferStatus::default(),
643 region_write_buffer_status_from_usage(100, 49, 99, 0)
644 );
645 assert_eq!(
646 RegionWriteBufferStatus {
647 should_flush: true,
648 should_stall: false,
649 should_reject: false,
650 },
651 region_write_buffer_status_from_usage(100, 50, 99, 0)
652 );
653 assert_eq!(
654 RegionWriteBufferStatus {
655 should_flush: true,
656 should_stall: true,
657 should_reject: false,
658 },
659 region_write_buffer_status_from_usage(100, 50, 100, 99)
660 );
661 assert_eq!(
662 RegionWriteBufferStatus {
663 should_flush: true,
664 should_stall: true,
665 should_reject: true,
666 },
667 region_write_buffer_status_from_usage(100, 50, 100, 100)
668 );
669 }
670
671 #[test]
672 fn test_region_write_buffer_status_saturates_reject_limit() {
673 assert_eq!(
674 RegionWriteBufferStatus {
675 should_flush: true,
676 should_stall: true,
677 should_reject: true,
678 },
679 region_write_buffer_status_from_usage(usize::MAX, usize::MAX, usize::MAX, usize::MAX,)
680 );
681 }
682}