risingwave_frontend/handler/
alter_parallelism.rs1use pgwire::pg_response::StatementType;
16use risingwave_common::session_config::parallelism::ConfigParallelism;
17use risingwave_common::system_param::AdaptiveParallelismStrategy;
18use risingwave_pb::meta::table_parallelism::{
19 AdaptiveParallelism, FixedParallelism, PbParallelism,
20};
21use risingwave_pb::meta::{PbTableParallelism, TableParallelism};
22use risingwave_sqlparser::ast::{ObjectName, SetVariableValue, SetVariableValueSingle, Value};
23use risingwave_sqlparser::keywords::Keyword;
24use thiserror_ext::AsReport;
25
26use super::alter_utils::resolve_streaming_job_id_for_alter;
27use super::{HandlerArgs, RwPgResponse};
28use crate::catalog::FragmentId;
29use crate::error::{ErrorCode, Result};
30use crate::handler::util::{LongRunningNotificationAction, execute_with_long_running_notification};
31
32pub async fn handle_alter_parallelism(
33 handler_args: HandlerArgs,
34 obj_name: ObjectName,
35 parallelism: SetVariableValue,
36 stmt_type: StatementType,
37 deferred: bool,
38) -> Result<RwPgResponse> {
39 let session = handler_args.session;
40
41 let job_id = resolve_streaming_job_id_for_alter(&session, obj_name, stmt_type, "parallelism")?;
42
43 let (target_parallelism, adaptive_parallelism_strategy) =
44 extract_table_parallelism(parallelism)?;
45
46 let mut builder = RwPgResponse::builder(stmt_type);
47
48 let catalog_writer = session.catalog_writer()?;
49 execute_with_long_running_notification(
50 catalog_writer.alter_parallelism(
51 job_id,
52 target_parallelism,
53 adaptive_parallelism_strategy,
54 deferred,
55 ),
56 &session,
57 "ALTER PARALLELISM",
58 LongRunningNotificationAction::SuggestRecover,
59 )
60 .await?;
61
62 if deferred {
63 builder = builder.notice("DEFERRED is used, please ensure that automatic parallelism control is enabled on the meta, otherwise, the alter will not take effect.".to_owned());
64 }
65
66 Ok(builder.into())
67}
68
69pub async fn handle_alter_backfill_parallelism(
70 handler_args: HandlerArgs,
71 obj_name: ObjectName,
72 parallelism: SetVariableValue,
73 stmt_type: StatementType,
74 deferred: bool,
75) -> Result<RwPgResponse> {
76 let session = handler_args.session;
77
78 let job_id =
79 resolve_streaming_job_id_for_alter(&session, obj_name, stmt_type, "backfill_parallelism")?;
80
81 let (target_parallelism, adaptive_parallelism_strategy) =
82 extract_backfill_parallelism(parallelism)?;
83
84 let mut builder = RwPgResponse::builder(stmt_type);
85
86 let catalog_writer = session.catalog_writer()?;
87 execute_with_long_running_notification(
88 catalog_writer.alter_backfill_parallelism(
89 job_id,
90 target_parallelism,
91 adaptive_parallelism_strategy,
92 deferred,
93 ),
94 &session,
95 "ALTER BACKFILL PARALLELISM",
96 LongRunningNotificationAction::SuggestRecover,
97 )
98 .await?;
99
100 if deferred {
101 builder = builder.notice("DEFERRED is used, please ensure that automatic parallelism control is enabled on the meta, otherwise, the alter will not take effect.".to_owned());
102 }
103
104 Ok(builder.into())
105}
106
107pub async fn handle_alter_fragment_parallelism(
108 handler_args: HandlerArgs,
109 fragment_ids: Vec<FragmentId>,
110 parallelism: SetVariableValue,
111) -> Result<RwPgResponse> {
112 let session = handler_args.session;
113 let target_parallelism = extract_fragment_parallelism(parallelism)?;
114
115 session
116 .env()
117 .meta_client()
118 .alter_fragment_parallelism(fragment_ids, target_parallelism)
119 .await?;
120
121 Ok(RwPgResponse::builder(StatementType::ALTER_FRAGMENT).into())
122}
123
124fn extract_table_parallelism(
125 parallelism: SetVariableValue,
126) -> Result<(TableParallelism, Option<AdaptiveParallelismStrategy>)> {
127 extract_job_parallelism(parallelism)
128}
129
130fn extract_backfill_parallelism(
131 parallelism: SetVariableValue,
132) -> Result<(
133 Option<TableParallelism>,
134 Option<AdaptiveParallelismStrategy>,
135)> {
136 match parallelism {
137 SetVariableValue::Default => Ok((None, None)),
138 other => {
139 let (parallelism, strategy) = extract_job_parallelism(other)?;
140 Ok((Some(parallelism), strategy))
141 }
142 }
143}
144
145fn extract_job_parallelism(
146 parallelism: SetVariableValue,
147) -> Result<(TableParallelism, Option<AdaptiveParallelismStrategy>)> {
148 let adaptive_parallelism = PbTableParallelism {
149 parallelism: Some(PbParallelism::Adaptive(AdaptiveParallelism {})),
150 };
151
152 let value = parse_single_parallelism_value(parallelism)?;
153 let config_parallelism = value.parse::<ConfigParallelism>().map_err(|e| {
154 ErrorCode::InvalidInputSyntax(format!(
155 "target parallelism must be a valid number, adaptive, bounded(n), or ratio(r): {}",
156 e.as_report()
157 ))
158 })?;
159
160 let result = match config_parallelism {
161 ConfigParallelism::Default | ConfigParallelism::Adaptive => (
162 adaptive_parallelism,
163 Some(AdaptiveParallelismStrategy::Auto),
164 ),
165 ConfigParallelism::Fixed(fixed_parallelism) => (
166 PbTableParallelism {
167 parallelism: Some(PbParallelism::Fixed(FixedParallelism {
168 parallelism: fixed_parallelism.get() as _,
169 })),
170 },
171 None,
172 ),
173 ConfigParallelism::Bounded(_) | ConfigParallelism::Ratio(_) => {
174 (adaptive_parallelism, config_parallelism.adaptive_strategy())
175 }
176 };
177
178 Ok(result)
179}
180
181fn extract_fragment_parallelism(parallelism: SetVariableValue) -> Result<Option<TableParallelism>> {
182 match parallelism {
183 SetVariableValue::Default => Ok(None),
184 other => extract_simple_adaptive_or_fixed_parallelism(other).map(Some),
185 }
186}
187
188fn parse_single_parallelism_value(parallelism: SetVariableValue) -> Result<String> {
189 match parallelism {
190 SetVariableValue::Default => Ok("default".to_owned()),
191 SetVariableValue::Single(value) => Ok(value.to_string_unquoted()),
192 SetVariableValue::List(_) => Err(ErrorCode::InvalidInputSyntax(
193 "target parallelism must be a single value".to_owned(),
194 )
195 .into()),
196 }
197}
198
199fn extract_simple_adaptive_or_fixed_parallelism(
200 parallelism: SetVariableValue,
201) -> Result<TableParallelism> {
202 let adaptive_parallelism = PbTableParallelism {
203 parallelism: Some(PbParallelism::Adaptive(AdaptiveParallelism {})),
204 };
205
206 let target_parallelism = match parallelism {
207 SetVariableValue::Single(SetVariableValueSingle::Ident(ident))
208 if ident
209 .real_value()
210 .eq_ignore_ascii_case(&Keyword::ADAPTIVE.to_string()) =>
211 {
212 adaptive_parallelism
213 }
214
215 SetVariableValue::Default => adaptive_parallelism,
216 SetVariableValue::Single(SetVariableValueSingle::Literal(Value::Number(v))) => {
217 let fixed_parallelism = v.parse::<u32>().map_err(|e| {
218 ErrorCode::InvalidInputSyntax(format!(
219 "target parallelism must be a valid number or adaptive: {}",
220 e.as_report()
221 ))
222 })?;
223
224 if fixed_parallelism == 0 {
225 adaptive_parallelism
226 } else {
227 PbTableParallelism {
228 parallelism: Some(PbParallelism::Fixed(FixedParallelism {
229 parallelism: fixed_parallelism,
230 })),
231 }
232 }
233 }
234
235 _ => {
236 return Err(ErrorCode::InvalidInputSyntax(
237 "target parallelism must be a valid number or adaptive".to_owned(),
238 )
239 .into());
240 }
241 };
242
243 Ok(target_parallelism)
244}
245
246#[cfg(test)]
247mod tests {
248 use risingwave_common::system_param::adaptive_parallelism_strategy::AdaptiveParallelismStrategy;
249 use risingwave_pb::meta::table_parallelism::{FixedParallelism, PbParallelism};
250 use risingwave_pb::meta::{PbTableParallelism, TableParallelism};
251 use risingwave_sqlparser::ast::{Ident, SetVariableValueSingle};
252
253 use super::*;
254
255 fn fixed_parallelism(parallelism: u32) -> TableParallelism {
256 PbTableParallelism {
257 parallelism: Some(PbParallelism::Fixed(FixedParallelism { parallelism })),
258 }
259 }
260
261 fn adaptive_parallelism() -> TableParallelism {
262 PbTableParallelism {
263 parallelism: Some(PbParallelism::Adaptive(AdaptiveParallelism {})),
264 }
265 }
266
267 #[test]
268 fn test_extract_table_parallelism_fixed() {
269 let (parallelism, strategy) = extract_table_parallelism(SetVariableValue::Single(
270 SetVariableValueSingle::Literal(Value::Number("4".into())),
271 ))
272 .unwrap();
273
274 assert_eq!(parallelism, fixed_parallelism(4));
275 assert_eq!(strategy, None);
276 }
277
278 #[test]
279 fn test_extract_table_parallelism_adaptive_variants() {
280 let (parallelism, strategy) = extract_table_parallelism(SetVariableValue::Default).unwrap();
281 assert_eq!(parallelism, adaptive_parallelism());
282 assert_eq!(strategy, Some(AdaptiveParallelismStrategy::Auto));
283
284 let (parallelism, strategy) = extract_table_parallelism(SetVariableValue::Single(
285 SetVariableValueSingle::Ident(Ident::new_unchecked("adaptive")),
286 ))
287 .unwrap();
288 assert_eq!(parallelism, adaptive_parallelism());
289 assert_eq!(strategy, Some(AdaptiveParallelismStrategy::Auto));
290
291 let (parallelism, strategy) = extract_table_parallelism(SetVariableValue::Single(
292 SetVariableValueSingle::Raw("bounded(4)".to_owned()),
293 ))
294 .unwrap();
295 assert_eq!(parallelism, adaptive_parallelism());
296 assert_eq!(
297 strategy,
298 Some(AdaptiveParallelismStrategy::Bounded(4.try_into().unwrap()))
299 );
300
301 let (parallelism, strategy) = extract_table_parallelism(SetVariableValue::Single(
302 SetVariableValueSingle::Raw("ratio(0.5)".to_owned()),
303 ))
304 .unwrap();
305 assert_eq!(parallelism, adaptive_parallelism());
306 assert_eq!(strategy, Some(AdaptiveParallelismStrategy::Ratio(0.5)));
307 }
308
309 #[test]
310 fn test_extract_fragment_parallelism_does_not_support_bounded_ratio() {
311 assert_eq!(
312 extract_fragment_parallelism(SetVariableValue::Default).unwrap(),
313 None
314 );
315 assert_eq!(
316 extract_fragment_parallelism(SetVariableValue::Single(
317 SetVariableValueSingle::Literal(Value::Number("0".into())),
318 ))
319 .unwrap(),
320 Some(adaptive_parallelism())
321 );
322 assert!(
323 extract_fragment_parallelism(SetVariableValue::Single(SetVariableValueSingle::Raw(
324 "bounded(4)".to_owned()
325 ),))
326 .is_err()
327 );
328 assert!(
329 extract_fragment_parallelism(SetVariableValue::Single(SetVariableValueSingle::Raw(
330 "ratio(0.5)".to_owned()
331 ),))
332 .is_err()
333 );
334 }
335
336 #[test]
337 fn test_extract_backfill_parallelism_adaptive_variants() {
338 let (parallelism, strategy) = extract_backfill_parallelism(SetVariableValue::Single(
339 SetVariableValueSingle::Raw("bounded(4)".to_owned()),
340 ))
341 .unwrap();
342 assert_eq!(parallelism, Some(adaptive_parallelism()));
343 assert_eq!(
344 strategy,
345 Some(AdaptiveParallelismStrategy::Bounded(4.try_into().unwrap()))
346 );
347
348 let (parallelism, strategy) =
349 extract_backfill_parallelism(SetVariableValue::Default).unwrap();
350 assert_eq!(parallelism, None);
351 assert_eq!(strategy, None);
352 }
353
354 #[test]
355 fn test_extract_table_parallelism_rejects_list_values() {
356 assert!(
357 extract_table_parallelism(SetVariableValue::List(vec![
358 SetVariableValueSingle::Literal(Value::Number("1".into())),
359 SetVariableValueSingle::Literal(Value::Number("2".into())),
360 ]))
361 .is_err()
362 );
363 }
364}