Skip to main content

risingwave_frontend/handler/
alter_parallelism.rs

1// Copyright 2023 RisingWave Labs
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
15use 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}