1use std::str::FromStr;
18use std::sync::Arc;
19
20use common_base::readable_size::ReadableSize;
21use common_telemetry::info;
22use common_telemetry::tracing::warn;
23use humantime_serde::re::humantime;
24use snafu::{ResultExt, ensure};
25use store_api::logstore::LogStore;
26use store_api::metadata::{
27 InvalidSetRegionOptionRequestSnafu, MetadataError, RegionMetadata, RegionMetadataBuilder,
28 RegionMetadataRef,
29};
30use store_api::mito_engine_options;
31use store_api::mito_engine_options::MAX_ROW_GROUP_ROW_COUNT_LIMIT;
32use store_api::region_request::{AlterKind, RegionAlterRequest, SetRegionOption};
33use store_api::storage::RegionId;
34
35use crate::error::{InvalidMetadataSnafu, InvalidRegionRequestSnafu, Result};
36use crate::flush::FlushReason;
37use crate::manifest::action::RegionChange;
38use crate::region::MitoRegionRef;
39use crate::region::options::CompactionOptions::Twcs;
40use crate::region::options::{RegionOptions, TwcsOptions};
41use crate::region::version::VersionRef;
42use crate::request::{DdlRequest, OptionOutputTx, SenderDdlRequest};
43use crate::sst::FormatType;
44use crate::worker::RegionWorkerLoop;
45
46impl<S: LogStore> RegionWorkerLoop<S> {
47 pub(crate) async fn handle_alter_request(
48 &mut self,
49 region_id: RegionId,
50 request: RegionAlterRequest,
51 sender: OptionOutputTx,
52 ) {
53 let skip_wal_only = only_enables_skip_wal(&request.kind);
54 let (region, is_follower) = match self.regions.writable_non_staging_region(region_id) {
55 Ok(region) => (region, false),
56 Err(_) if skip_wal_only => match self.regions.follower_region(region_id) {
57 Ok(region) => (region, true),
58 Err(e) => {
59 sender.send(Err(e));
60 return;
61 }
62 },
63 Err(e) => {
64 sender.send(Err(e));
65 return;
66 }
67 };
68
69 info!("Try to alter region: {}, request: {:?}", region_id, request);
70
71 if is_follower {
74 let mut options = region.version().options.clone();
75 if !options.skip_wal {
76 info!("Stop writing WAL for follower region: {}", region_id);
77 options.skip_wal = true;
78 region.version_control.alter_options(options);
79 }
80 sender.send(Ok(0));
81 return;
82 }
83
84 let version = region.version();
86
87 let set_options = match &request.kind {
89 AlterKind::SetRegionOptions { options } => options.clone(),
90 AlterKind::UnsetRegionOptions { keys } => {
91 keys.iter().map(Into::into).collect()
95 }
96 _ => Vec::new(),
97 };
98 let mut new_options = None;
99 if !set_options.is_empty() {
100 match self.handle_alter_region_options_fast(®ion, version.clone(), set_options) {
101 Ok(staged_options) => {
102 let Some(staged_options) = staged_options else {
103 sender.send(Ok(0));
105 return;
106 };
107 new_options = Some(staged_options);
108 }
109 Err(e) => {
110 sender.send(Err(e).context(InvalidMetadataSnafu));
111 return;
112 }
113 }
114 }
115
116 if let Err(e) = request.validate(&version.metadata) {
118 sender.send(Err(e).context(InvalidRegionRequestSnafu));
120 return;
121 }
122
123 if !request.need_alter(&version.metadata) {
125 warn!(
126 "Ignores alter request as it alters nothing, region_id: {}, request: {:?}",
127 region_id, request
128 );
129 sender.send(Ok(0));
130 return;
131 }
132
133 if !version.memtables.is_empty() {
135 info!("Flush region: {} before alteration", region_id);
138
139 let task = self.new_flush_task(®ion, FlushReason::Alter, None, self.config.clone());
141 if let Err(e) =
142 self.flush_scheduler
143 .schedule_flush(region.region_id, ®ion.version_control, task)
144 {
145 sender.send(Err(e));
147 return;
148 }
149
150 self.flush_scheduler
152 .add_ddl_request_to_pending(SenderDdlRequest {
153 region_id,
154 sender,
155 request: DdlRequest::Alter(request),
156 });
157
158 return;
159 }
160
161 info!(
162 "Try to alter region {}, version.metadata: {:?}, version.options: {:?}, request: {:?}",
163 region_id, version.metadata, version.options, request,
164 );
165 self.handle_alter_region_with_empty_memtable(region, version, request, new_options, sender);
166 }
167
168 fn handle_alter_region_with_empty_memtable(
171 &mut self,
172 region: MitoRegionRef,
173 version: VersionRef,
174 request: RegionAlterRequest,
175 new_options: Option<RegionOptions>,
176 sender: OptionOutputTx,
177 ) {
178 let need_index = need_change_index(&request.kind);
179 let new_meta = match metadata_after_alteration(&version.metadata, request) {
180 Ok(new_meta) => new_meta,
181 Err(e) => {
182 sender.send(Err(e));
183 return;
184 }
185 };
186 let options = new_options.as_ref().unwrap_or(&version.options);
188 let change = RegionChange {
189 metadata: new_meta,
190 sst_format: options.sst_format.unwrap_or_default(),
191 append_mode: Some(options.append_mode),
192 };
193 self.handle_manifest_region_change(region, change, need_index, new_options, sender);
194 }
195
196 fn handle_alter_region_options_fast(
203 &mut self,
204 region: &MitoRegionRef,
205 version: VersionRef,
206 options: Vec<SetRegionOption>,
207 ) -> std::result::Result<Option<RegionOptions>, MetadataError> {
208 assert!(!options.is_empty());
209
210 let mut all_options_altered = true;
211 let mut current_options = version.options.clone();
212 for option in options.iter().cloned() {
213 match option {
214 SetRegionOption::WriteBufferSize(new_write_buffer_size) => {
215 info!(
216 "Update region write_buffer_size: {}, previous: {:?} new: {:?}",
217 region.region_id, current_options.write_buffer_size, new_write_buffer_size
218 );
219 current_options.write_buffer_size = new_write_buffer_size;
220 current_options.validate().map_err(|e| {
221 store_api::metadata::InvalidRegionRequestSnafu {
222 region_id: region.region_id,
223 err: e.to_string(),
224 }
225 .build()
226 })?;
227 }
228 SetRegionOption::Ttl(new_ttl) => {
229 info!(
230 "Update region ttl: {}, previous: {:?} new: {:?}",
231 region.region_id, current_options.ttl, new_ttl
232 );
233 current_options.ttl = new_ttl;
234 }
235 SetRegionOption::Twsc(key, value) => {
236 let Twcs(options) = &mut current_options.compaction;
237 set_twcs_options(
238 options,
239 &TwcsOptions::default(),
240 &key,
241 &value,
242 region.region_id,
243 )?;
244 }
245 SetRegionOption::Format(format_str) => {
246 let new_format = format_str.parse::<FormatType>().map_err(|_| {
247 store_api::metadata::InvalidRegionRequestSnafu {
248 region_id: region.region_id,
249 err: format!("Invalid format type: {}", format_str),
250 }
251 .build()
252 })?;
253 if new_format != current_options.sst_format.unwrap_or_default() {
255 all_options_altered = false;
256 }
257 }
258 SetRegionOption::AppendMode(new_append_mode) => {
259 if new_append_mode != current_options.append_mode {
261 ensure!(
263 !current_options.append_mode && new_append_mode,
264 store_api::metadata::InvalidRegionRequestSnafu {
265 region_id: region.region_id,
266 err: "Only allow changing append_mode from false to true",
267 }
268 );
269 current_options.merge_mode = None;
271 all_options_altered = false;
272 }
273 }
274 SetRegionOption::AutoFlushInterval(new_interval) => {
275 if new_interval != current_options.auto_flush_interval {
279 info!(
280 "Update region auto_flush_interval: {}, previous: {:?} new: {:?}",
281 region.region_id, current_options.auto_flush_interval, new_interval
282 );
283 current_options.auto_flush_interval = new_interval;
284 }
285 }
286 SetRegionOption::MaxRowGroupRowCount(new_row_count) => {
287 if let Some(row_count) = new_row_count {
288 ensure!(
289 row_count > 0 && row_count <= MAX_ROW_GROUP_ROW_COUNT_LIMIT,
290 store_api::metadata::InvalidRegionRequestSnafu {
291 region_id: region.region_id,
292 err: format!(
293 "max_row_group_row_count must be in (0, \
294 {MAX_ROW_GROUP_ROW_COUNT_LIMIT}], got {row_count}"
295 ),
296 }
297 );
298 }
299 if new_row_count != current_options.max_row_group_row_count {
300 all_options_altered = false;
301 }
302 }
303 SetRegionOption::SkipWal => {
304 if !current_options.skip_wal {
305 info!("Stop writing WAL for region: {}", region.region_id);
306 current_options.skip_wal = true;
307 }
308 }
309 }
310 }
311 if all_options_altered {
312 region.version_control.alter_options(current_options);
313 Ok(None)
314 } else {
315 let kind = AlterKind::SetRegionOptions { options };
316 Ok(new_region_options_on_empty_memtable(
317 ¤t_options,
318 &kind,
319 ))
320 }
321 }
322}
323
324fn only_enables_skip_wal(kind: &AlterKind) -> bool {
325 matches!(
326 kind,
327 AlterKind::SetRegionOptions { options }
328 if matches!(options.as_slice(), [SetRegionOption::SkipWal])
329 )
330}
331
332fn new_region_options_on_empty_memtable(
334 current_options: &RegionOptions,
335 kind: &AlterKind,
336) -> Option<RegionOptions> {
337 let options = match kind {
338 AlterKind::SetRegionOptions { options } => options.clone(),
339 AlterKind::UnsetRegionOptions { keys } => keys.iter().map(Into::into).collect(),
340 _ => return None,
341 };
342
343 if options.is_empty() {
344 return None;
345 }
346
347 let mut current_options = current_options.clone();
348 for option in &options {
349 match option {
350 SetRegionOption::WriteBufferSize(_)
351 | SetRegionOption::Ttl(_)
352 | SetRegionOption::Twsc(_, _)
353 | SetRegionOption::AutoFlushInterval(_)
354 | SetRegionOption::SkipWal => (),
355 SetRegionOption::Format(format_str) => {
356 let new_format = format_str.parse::<FormatType>().unwrap();
358 current_options.sst_format = Some(new_format);
359 }
360 SetRegionOption::AppendMode(new_append_mode) => {
361 if *new_append_mode != current_options.append_mode {
362 current_options.append_mode = *new_append_mode;
365 current_options.merge_mode = None;
366 }
367 }
368 SetRegionOption::MaxRowGroupRowCount(new_row_count) => {
369 current_options.max_row_group_row_count = *new_row_count;
370 }
371 }
372 }
373 Some(current_options)
374}
375
376fn metadata_after_alteration(
380 metadata: &RegionMetadata,
381 request: RegionAlterRequest,
382) -> Result<RegionMetadataRef> {
383 let mut builder = RegionMetadataBuilder::from_existing(metadata.clone());
384 builder
385 .alter(request.kind)
386 .context(InvalidRegionRequestSnafu)?
387 .bump_version();
388 let new_meta = builder.build().context(InvalidMetadataSnafu)?;
389
390 Ok(Arc::new(new_meta))
391}
392
393fn set_twcs_options(
394 options: &mut TwcsOptions,
395 default_option: &TwcsOptions,
396 key: &str,
397 value: &str,
398 region_id: RegionId,
399) -> std::result::Result<(), MetadataError> {
400 match key {
401 mito_engine_options::TWCS_TRIGGER_FILE_NUM => {
402 let files = parse_usize_with_default(key, value, default_option.trigger_file_num)?;
403 log_option_update(region_id, key, options.trigger_file_num, files);
404 options.trigger_file_num = files;
405 }
406 mito_engine_options::TWCS_MAX_OUTPUT_FILE_SIZE => {
407 let size = if value.is_empty() {
408 default_option.max_output_file_size
409 } else {
410 Some(
411 ReadableSize::from_str(value)
412 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?,
413 )
414 };
415 log_option_update(region_id, key, options.max_output_file_size, size);
416 options.max_output_file_size = size;
417 }
418 mito_engine_options::TWCS_TIME_WINDOW => {
419 let window = if value.is_empty() {
420 default_option.time_window
421 } else {
422 Some(
423 humantime::parse_duration(value)
424 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())?,
425 )
426 };
427 log_option_update(region_id, key, options.time_window, window);
428 options.time_window = window;
429 }
430 _ => return InvalidSetRegionOptionRequestSnafu { key, value }.fail(),
431 }
432 Ok(())
433}
434
435fn parse_usize_with_default(
436 key: &str,
437 value: &str,
438 default: usize,
439) -> std::result::Result<usize, MetadataError> {
440 if value.is_empty() {
441 Ok(default)
442 } else {
443 value
444 .parse::<usize>()
445 .map_err(|_| InvalidSetRegionOptionRequestSnafu { key, value }.build())
446 }
447}
448
449fn log_option_update<T: std::fmt::Debug>(
450 region_id: RegionId,
451 option_name: &str,
452 prev_value: T,
453 cur_value: T,
454) {
455 info!(
456 "Update region {}: {}, previous: {:?}, new: {:?}",
457 option_name, region_id, prev_value, cur_value
458 );
459}
460
461fn need_change_index(kind: &AlterKind) -> bool {
463 match kind {
464 AlterKind::SetIndexes { options: _ } => true,
467 _ => false,
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn test_new_region_options_with_idempotent_append_mode() {
480 let current_options = RegionOptions::default();
481 let kind = AlterKind::SetRegionOptions {
482 options: vec![
483 SetRegionOption::AppendMode(false),
484 SetRegionOption::MaxRowGroupRowCount(Some(1024)),
485 ],
486 };
487
488 let new_options = new_region_options_on_empty_memtable(¤t_options, &kind).unwrap();
489 assert!(!new_options.append_mode);
490 assert_eq!(Some(1024), new_options.max_row_group_row_count);
491 }
492}