1mod iceberg_query_storage_mode;
16mod locality_backfill_mode;
17mod non_zero64;
18mod opt;
19pub mod parallelism;
20mod query_mode;
21mod search_path;
22pub mod sink_decouple;
23mod statement_timeout;
24mod transaction_isolation_level;
25mod visibility_mode;
26
27use chrono_tz::Tz;
28pub use iceberg_query_storage_mode::IcebergQueryStorageMode;
29use itertools::Itertools;
30pub use locality_backfill_mode::LocalityBackfillMode;
31pub use opt::OptionConfig;
32pub use query_mode::QueryMode;
33use risingwave_common_proc_macro::{ConfigDoc, SessionConfig};
34pub use search_path::{SearchPath, USER_NAME_WILD_CARD};
35use serde::{Deserialize, Serialize};
36pub use statement_timeout::StatementTimeout;
37use thiserror::Error;
38
39use self::non_zero64::ConfigNonZeroU64;
40use crate::config::mutate::TomlTableMutateExt;
41use crate::config::streaming::{CacheRefillPolicy, JoinEncodingType, OverWindowCachePolicy};
42use crate::config::{ConfigMergeError, StreamingConfig, merge_streaming_config_section};
43use crate::hash::VirtualNode;
44use crate::session_config::parallelism::{ConfigBackfillParallelism, ConfigParallelism};
45use crate::session_config::sink_decouple::SinkDecouple;
46use crate::session_config::transaction_isolation_level::IsolationLevel;
47pub use crate::session_config::visibility_mode::VisibilityMode;
48use crate::{PG_VERSION, SERVER_ENCODING, SERVER_VERSION_NUM, STANDARD_CONFORMING_STRINGS};
49
50pub const SESSION_CONFIG_LIST_SEP: &str = ", ";
51
52#[derive(Error, Debug)]
53pub enum SessionConfigError {
54 #[error("Invalid value `{value}` for `{entry}`")]
55 InvalidValue {
56 entry: &'static str,
57 value: String,
58 source: anyhow::Error,
59 },
60
61 #[error("Unrecognized config entry `{0}`")]
62 UnrecognizedEntry(String),
63}
64
65type SessionConfigResult<T> = std::result::Result<T, SessionConfigError>;
66
67const AUTO_LOCALITY_BACKFILL_MIN_SIZE: u64 = 10 * 1024 * 1024 * 1024;
68
69fn default_auto_locality_backfill_min_size() -> u64 {
70 AUTO_LOCALITY_BACKFILL_MIN_SIZE
71}
72
73fn default_legacy_locality_backfill_mode() -> LocalityBackfillMode {
74 LocalityBackfillMode::Always
75}
76
77const DISABLE_BACKFILL_RATE_LIMIT: i32 = -1;
80const DISABLE_SOURCE_RATE_LIMIT: i32 = -1;
81const DISABLE_DML_RATE_LIMIT: i32 = -1;
82const DISABLE_SINK_RATE_LIMIT: i32 = -1;
83
84const BYPASS_CLUSTER_LIMITS: bool = cfg!(debug_assertions);
86
87#[serde_with::apply(_ => #[serde_as(as = "serde_with::DisplayFromStr")] )]
99#[serde_with::serde_as]
100#[derive(Clone, Debug, Deserialize, Serialize, SessionConfig, ConfigDoc, PartialEq)]
101pub struct SessionConfig {
102 #[parameter(default = false, alias = "rw_implicit_flush")]
106 implicit_flush: bool,
107
108 #[parameter(default = false)]
111 dml_wait_persistence: bool,
112
113 #[parameter(default = false)]
116 create_compaction_group_for_mv: bool,
117
118 #[parameter(default = QueryMode::default())]
122 query_mode: QueryMode,
123
124 #[parameter(default = IcebergQueryStorageMode::default())]
127 iceberg_query_storage_mode: IcebergQueryStorageMode,
128
129 #[parameter(default = 1)]
132 extra_float_digits: i32,
133
134 #[parameter(default = "", flags = "REPORT")]
137 application_name: String,
138
139 #[parameter(default = "", rename = "datestyle")]
142 date_style: String,
143
144 #[parameter(default = true, alias = "rw_batch_enable_lookup_join")]
146 batch_enable_lookup_join: bool,
147
148 #[parameter(default = true, alias = "rw_batch_enable_sort_agg")]
151 batch_enable_sort_agg: bool,
152
153 #[parameter(default = false, rename = "batch_enable_distributed_dml")]
156 batch_enable_distributed_dml: bool,
157
158 #[parameter(default = true)]
162 batch_expr_strict_mode: bool,
163
164 #[parameter(default = 8)]
166 max_split_range_gap: i32,
167
168 #[parameter(default = SearchPath::default())]
172 search_path: SearchPath,
173
174 #[parameter(default = VisibilityMode::default())]
176 visibility_mode: VisibilityMode,
177
178 #[parameter(default = IsolationLevel::default())]
180 transaction_isolation: IsolationLevel,
181
182 #[parameter(default = ConfigNonZeroU64::default())]
185 query_epoch: ConfigNonZeroU64,
186
187 #[parameter(default = "UTC", check_hook = check_timezone)]
189 timezone: String,
190
191 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
195 streaming_parallelism: ConfigParallelism,
196
197 #[parameter(
200 default = ConfigBackfillParallelism::Default,
201 check_hook = check_streaming_parallelism_for_backfill,
202 flags = "SESSION_INIT"
203 )]
204 streaming_parallelism_for_backfill: ConfigBackfillParallelism,
205
206 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
210 streaming_parallelism_for_table: ConfigParallelism,
211
212 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
214 streaming_parallelism_for_sink: ConfigParallelism,
215
216 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
218 streaming_parallelism_for_index: ConfigParallelism,
219
220 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
224 streaming_parallelism_for_source: ConfigParallelism,
225
226 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
228 streaming_parallelism_for_materialized_view: ConfigParallelism,
229
230 #[parameter(default = false, alias = "rw_streaming_enable_delta_join")]
232 streaming_enable_delta_join: bool,
233
234 #[parameter(default = true, alias = "rw_streaming_enable_bushy_join")]
236 streaming_enable_bushy_join: bool,
237
238 #[parameter(default = false, alias = "rw_streaming_force_filter_inside_join")]
241 streaming_force_filter_inside_join: bool,
242
243 #[parameter(
246 default = true,
247 deprecated = "The session variable STREAMING_USE_ARRANGEMENT_BACKFILL has been deprecated and is ignored. Arrangement backfill is always used as the fallback backfill type for new streaming jobs."
248 )]
249 streaming_use_arrangement_backfill: bool,
250
251 #[parameter(default = true)]
252 streaming_use_snapshot_backfill: bool,
253
254 #[parameter(default = false)]
256 enable_serverless_backfill: bool,
257
258 #[parameter(default = false, alias = "rw_streaming_allow_jsonb_in_stream_key")]
260 streaming_allow_jsonb_in_stream_key: bool,
261
262 #[parameter(default = false)]
266 streaming_unsafe_allow_unmaterialized_impure_expr: bool,
267
268 #[parameter(default = false)]
274 streaming_unsafe_allow_upsert_sink_pk_mismatch: bool,
275
276 #[parameter(default = false)]
278 streaming_separate_consecutive_join: bool,
279
280 #[parameter(default = false)]
282 streaming_separate_sink: bool,
283
284 #[parameter(default = None)]
289 streaming_join_encoding: OptionConfig<JoinEncodingType>,
290
291 #[parameter(default = true, alias = "rw_enable_join_ordering")]
293 enable_join_ordering: bool,
294
295 #[parameter(default = true, flags = "SETTER", alias = "rw_enable_two_phase_agg")]
298 enable_two_phase_agg: bool,
299
300 #[parameter(default = false, flags = "SETTER", alias = "rw_force_two_phase_agg")]
304 force_two_phase_agg: bool,
305
306 #[parameter(default = true, alias = "rw_enable_share_plan")]
309 enable_share_plan: bool,
311
312 #[parameter(default = false, alias = "rw_force_split_distinct_agg")]
314 force_split_distinct_agg: bool,
315
316 #[parameter(default = "", rename = "intervalstyle")]
318 interval_style: String,
319
320 #[parameter(default = ConfigNonZeroU64::default())]
322 batch_parallelism: ConfigNonZeroU64,
323
324 #[parameter(default = PG_VERSION)]
326 server_version: String,
327
328 #[parameter(default = SERVER_VERSION_NUM)]
330 server_version_num: i32,
331
332 #[parameter(default = "notice")]
334 client_min_messages: String,
335
336 #[parameter(default = SERVER_ENCODING, check_hook = check_client_encoding)]
338 client_encoding: String,
339
340 #[parameter(default = SinkDecouple::default())]
342 sink_decouple: SinkDecouple,
343
344 #[parameter(default = false)]
347 synchronize_seqscans: bool,
348
349 #[parameter(default = StatementTimeout::default())]
354 statement_timeout: StatementTimeout,
355
356 #[parameter(default = 60000u32)]
358 idle_in_transaction_session_timeout: u32,
359
360 #[parameter(default = 0)]
363 lock_timeout: i32,
364
365 #[parameter(default = 60)]
367 cdc_source_wait_streaming_start_timeout: i32,
368
369 #[parameter(default = true)]
372 row_security: bool,
373
374 #[parameter(default = STANDARD_CONFORMING_STRINGS)]
376 standard_conforming_strings: String,
377
378 #[parameter(default = DISABLE_BACKFILL_RATE_LIMIT)]
382 backfill_rate_limit: i32,
383
384 #[parameter(default = DISABLE_SOURCE_RATE_LIMIT)]
388 source_rate_limit: i32,
389
390 #[parameter(default = DISABLE_DML_RATE_LIMIT)]
394 dml_rate_limit: i32,
395
396 #[parameter(default = DISABLE_SINK_RATE_LIMIT)]
400 sink_rate_limit: i32,
401
402 #[parameter(default = None, alias = "rw_streaming_over_window_cache_policy")]
408 streaming_over_window_cache_policy: OptionConfig<OverWindowCachePolicy>,
409
410 #[parameter(default = None)]
416 streaming_cache_refill_policy: OptionConfig<CacheRefillPolicy>,
417
418 #[parameter(default = false)]
420 background_ddl: bool,
421
422 #[parameter(default = true)]
427 streaming_use_shared_source: bool,
428
429 #[parameter(default = true)]
436 streaming_asof_join_use_cache: bool,
437
438 #[parameter(default = SERVER_ENCODING)]
440 server_encoding: String,
441
442 #[parameter(default = "hex", check_hook = check_bytea_output)]
443 bytea_output: String,
444
445 #[parameter(default = BYPASS_CLUSTER_LIMITS)]
449 bypass_cluster_limits: bool,
450
451 #[parameter(default = VirtualNode::COUNT_FOR_COMPAT, check_hook = check_streaming_max_parallelism)]
461 streaming_max_parallelism: usize,
462
463 #[parameter(default = "", check_hook = check_iceberg_engine_connection)]
466 iceberg_engine_connection: String,
467
468 #[parameter(default = false)]
470 streaming_enable_unaligned_join: bool,
471
472 #[parameter(default = None)]
479 streaming_sync_log_store_pause_duration_ms: OptionConfig<usize>,
480
481 #[parameter(default = None)]
486 streaming_sync_log_store_buffer_size: OptionConfig<usize>,
487
488 #[parameter(default = false, flags = "NO_ALTER_SYS")]
492 disable_purify_definition: bool,
493
494 #[parameter(default = 40_usize)] batch_hnsw_ef_search: usize,
497
498 #[parameter(default = true)]
500 enable_index_selection: bool,
501
502 #[parameter(default = false)]
504 enable_mv_selection: bool,
505
506 #[parameter(default = true)]
509 enable_locality_backfill: bool,
510
511 #[serde(default = "default_legacy_locality_backfill_mode")]
515 #[parameter(default = LocalityBackfillMode::Auto)]
516 locality_backfill_mode: LocalityBackfillMode,
517
518 #[serde(default = "default_auto_locality_backfill_min_size")]
521 #[parameter(default = AUTO_LOCALITY_BACKFILL_MIN_SIZE)]
522 auto_locality_backfill_min_size: u64,
523
524 #[parameter(default = true)]
530 enable_locality_sort_buffer: bool,
531
532 #[parameter(default = 30u32)]
535 slow_ddl_notification_secs: u32,
536
537 #[parameter(default = false)]
541 unsafe_enable_storage_retention_for_non_append_only_tables: bool,
542
543 #[parameter(default = true)]
546 enable_datafusion_engine: bool,
547
548 #[parameter(default = true)]
552 datafusion_prefer_hash_join: bool,
553
554 #[parameter(default = false)]
561 upsert_dml: bool,
562}
563
564fn check_iceberg_engine_connection(val: &str) -> Result<(), String> {
565 if val.is_empty() {
566 return Ok(());
567 }
568
569 let parts: Vec<&str> = val.split('.').collect();
570 if parts.len() != 2 {
571 return Err("Invalid iceberg engine connection format, Should be set to this format: schema_name.connection_name.".to_owned());
572 }
573
574 Ok(())
575}
576
577fn check_timezone(val: &str) -> Result<(), String> {
578 Tz::from_str_insensitive(val).map_err(|_e| "Not a valid timezone")?;
580 Ok(())
581}
582
583fn check_client_encoding(val: &str) -> Result<(), String> {
584 let clean = val.replace(|c: char| !c.is_ascii_alphanumeric(), "");
586 if !clean.eq_ignore_ascii_case("UTF8") {
587 Err("Only support 'UTF8' for CLIENT_ENCODING".to_owned())
588 } else {
589 Ok(())
590 }
591}
592
593fn check_bytea_output(val: &str) -> Result<(), String> {
594 if val == "hex" {
595 Ok(())
596 } else {
597 Err("Only support 'hex' for BYTEA_OUTPUT".to_owned())
598 }
599}
600
601fn check_streaming_max_parallelism(val: &usize) -> Result<(), String> {
603 match val {
604 0 | 1 => Err("STREAMING_MAX_PARALLELISM must be greater than 1".to_owned()),
607 2..=VirtualNode::MAX_COUNT => Ok(()),
608 _ => Err(format!(
609 "STREAMING_MAX_PARALLELISM must be less than or equal to {}",
610 VirtualNode::MAX_COUNT
611 )),
612 }
613}
614
615fn check_streaming_parallelism_for_backfill(val: &ConfigBackfillParallelism) -> Result<(), String> {
616 match val {
617 ConfigBackfillParallelism::Default | ConfigBackfillParallelism::Fixed(_) => Ok(()),
618 ConfigBackfillParallelism::Adaptive
619 | ConfigBackfillParallelism::Bounded(_)
620 | ConfigBackfillParallelism::Ratio(_) => Err(
621 "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.".to_owned(),
622 ),
623 }
624}
625
626impl SessionConfig {
627 pub fn set_force_two_phase_agg(
628 &mut self,
629 val: bool,
630 reporter: &mut impl ConfigReporter,
631 ) -> SessionConfigResult<bool> {
632 let set_val = self.set_force_two_phase_agg_inner(val, reporter)?;
633 if self.force_two_phase_agg {
634 self.set_enable_two_phase_agg(true, reporter)
635 } else {
636 Ok(set_val)
637 }
638 }
639
640 pub fn set_enable_two_phase_agg(
641 &mut self,
642 val: bool,
643 reporter: &mut impl ConfigReporter,
644 ) -> SessionConfigResult<bool> {
645 let set_val = self.set_enable_two_phase_agg_inner(val, reporter)?;
646 if !self.force_two_phase_agg {
647 self.set_force_two_phase_agg(false, reporter)
648 } else {
649 Ok(set_val)
650 }
651 }
652}
653
654pub struct VariableInfo {
655 pub name: String,
656 pub setting: String,
657 pub description: String,
658}
659
660pub trait ConfigReporter {
662 fn report_status(&mut self, key: &str, new_val: String);
663}
664
665impl ConfigReporter for () {
667 fn report_status(&mut self, _key: &str, _new_val: String) {}
668}
669
670def_anyhow_newtype! {
671 pub SessionConfigToOverrideError,
672 toml::ser::Error => "failed to serialize session config",
673 ConfigMergeError => transparent,
674}
675
676impl SessionConfig {
677 pub fn to_initial_streaming_config_override(
679 &self,
680 ) -> Result<String, SessionConfigToOverrideError> {
681 let mut table = toml::Table::new();
682
683 if let Some(v) = self.streaming_join_encoding.as_ref() {
686 table
687 .upsert("streaming.developer.join_encoding_type", v)
688 .unwrap();
689 }
690 if let Some(v) = self.streaming_sync_log_store_pause_duration_ms.as_ref() {
691 table
692 .upsert("streaming.developer.sync_log_store_pause_duration_ms", v)
693 .unwrap();
694 }
695 if let Some(v) = self.streaming_sync_log_store_buffer_size.as_ref() {
696 table
697 .upsert("streaming.developer.sync_log_store_buffer_size", v)
698 .unwrap();
699 }
700 if let Some(v) = self.streaming_over_window_cache_policy.as_ref() {
701 table
702 .upsert("streaming.developer.over_window_cache_policy", v)
703 .unwrap();
704 }
705 if let Some(v) = self.streaming_cache_refill_policy.as_ref() {
706 table
707 .upsert("streaming.developer.cache_refill_policy", v)
708 .unwrap();
709 }
710
711 let res = toml::to_string(&table)?;
712
713 if !res.is_empty() {
715 let merged =
716 merge_streaming_config_section(&StreamingConfig::default(), res.as_str())?.unwrap();
717
718 let unrecognized_keys = merged.unrecognized_keys().collect_vec();
719 if !unrecognized_keys.is_empty() {
720 bail!("unrecognized configs: {:?}", unrecognized_keys);
721 }
722 }
723
724 Ok(res)
725 }
726}
727
728#[cfg(test)]
729mod test {
730 use expect_test::expect;
731
732 use super::*;
733
734 #[derive(SessionConfig)]
735 struct TestConfig {
736 #[parameter(default = 1, flags = "NO_ALTER_SYS", alias = "test_param_alias" | "alias_param_test")]
737 test_param: i32,
738 #[parameter(default = false, deprecated = "deprecated test notice")]
739 deprecated_test_param: bool,
740 }
741
742 #[test]
743 fn test_session_config_alias() {
744 let mut config = TestConfig::default();
745 config.set("test_param", "2".to_owned(), &mut ()).unwrap();
746 assert_eq!(config.get("test_param_alias").unwrap(), "2");
747 config
748 .set("alias_param_test", "3".to_owned(), &mut ())
749 .unwrap();
750 assert_eq!(config.get("test_param_alias").unwrap(), "3");
751 assert!(TestConfig::check_no_alter_sys("test_param").unwrap());
752 assert_eq!(
753 TestConfig::deprecated_notice("deprecated_test_param").unwrap(),
754 Some("deprecated test notice")
755 );
756 assert_eq!(TestConfig::deprecated_notice("test_param").unwrap(), None);
757 }
758
759 #[test]
760 fn test_initial_streaming_config_override() {
761 let mut config = SessionConfig::default();
762 config
763 .set_streaming_join_encoding(Some(JoinEncodingType::Cpu).into(), &mut ())
764 .unwrap();
765 config
766 .set_streaming_over_window_cache_policy(
767 Some(OverWindowCachePolicy::RecentFirstN).into(),
768 &mut (),
769 )
770 .unwrap();
771 config
772 .set_streaming_cache_refill_policy(Some(CacheRefillPolicy::Both).into(), &mut ())
773 .unwrap();
774
775 let override_str = config.to_initial_streaming_config_override().unwrap();
777 expect![[r#"
778 [streaming.developer]
779 cache_refill_policy = "both"
780 join_encoding_type = "cpu_optimized"
781 over_window_cache_policy = "recent_first_n"
782 "#]]
783 .assert_eq(&override_str);
784
785 let merged = merge_streaming_config_section(&StreamingConfig::default(), &override_str)
787 .unwrap()
788 .unwrap();
789 assert_eq!(merged.developer.join_encoding_type, JoinEncodingType::Cpu);
790 assert_eq!(
791 merged.developer.over_window_cache_policy,
792 OverWindowCachePolicy::RecentFirstN
793 );
794 assert_eq!(
795 merged.developer.cache_refill_policy,
796 CacheRefillPolicy::Both
797 );
798 }
799
800 #[test]
801 fn test_streaming_parallelism_defaults() {
802 let config = SessionConfig::default();
803
804 assert_eq!(config.streaming_parallelism(), ConfigParallelism::Default);
805 assert_eq!(
806 config.streaming_parallelism_for_table(),
807 ConfigParallelism::Default
808 );
809 assert_eq!(
810 config.streaming_parallelism_for_source(),
811 ConfigParallelism::Default
812 );
813 assert_eq!(
814 config.streaming_parallelism_for_sink(),
815 ConfigParallelism::Default
816 );
817 assert_eq!(
818 config.streaming_parallelism_for_index(),
819 ConfigParallelism::Default
820 );
821 assert_eq!(
822 config.streaming_parallelism_for_materialized_view(),
823 ConfigParallelism::Default
824 );
825 assert!(!config.streaming_unsafe_allow_upsert_sink_pk_mismatch());
826 }
827
828 #[test]
829 fn test_streaming_parallelism_default_round_trip() {
830 let mut config = SessionConfig::default();
831
832 assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
833 assert_eq!(
834 config.get("streaming_parallelism_for_table").unwrap(),
835 "default"
836 );
837 assert_eq!(
838 config.get("streaming_parallelism_for_source").unwrap(),
839 "default"
840 );
841
842 config
843 .set("streaming_parallelism", "default".to_owned(), &mut ())
844 .unwrap();
845 assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
846
847 config
848 .set("streaming_parallelism", "bounded(16)".to_owned(), &mut ())
849 .unwrap();
850 config
851 .set(
852 "streaming_parallelism_for_table",
853 "bounded(8)".to_owned(),
854 &mut (),
855 )
856 .unwrap();
857 config
858 .set(
859 "streaming_parallelism_for_source",
860 "bounded(8)".to_owned(),
861 &mut (),
862 )
863 .unwrap();
864
865 assert_eq!(
866 config.reset("streaming_parallelism", &mut ()).unwrap(),
867 "default"
868 );
869 assert_eq!(
870 config
871 .reset("streaming_parallelism_for_table", &mut ())
872 .unwrap(),
873 "default"
874 );
875 assert_eq!(
876 config
877 .reset("streaming_parallelism_for_source", &mut ())
878 .unwrap(),
879 "default"
880 );
881 }
882 #[test]
883 fn test_streaming_parallelism_for_backfill_accepts_default_and_fixed() {
884 let mut config = SessionConfig::default();
885
886 config
887 .set(
888 "streaming_parallelism_for_backfill",
889 "default".to_owned(),
890 &mut (),
891 )
892 .unwrap();
893 assert_eq!(
894 config.get("streaming_parallelism_for_backfill").unwrap(),
895 "default"
896 );
897
898 config
899 .set(
900 "streaming_parallelism_for_backfill",
901 "2".to_owned(),
902 &mut (),
903 )
904 .unwrap();
905 assert_eq!(config.streaming_parallelism_for_backfill().to_string(), "2");
906 }
907
908 #[test]
909 fn test_streaming_parallelism_for_backfill_rejects_adaptive_modes() {
910 let mut config = SessionConfig::default();
911 let expected = "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.";
912
913 for value in ["adaptive", "bounded(2)", "ratio(0.5)"] {
914 let err = config
915 .set(
916 "streaming_parallelism_for_backfill",
917 value.to_owned(),
918 &mut (),
919 )
920 .unwrap_err();
921
922 match err {
923 SessionConfigError::InvalidValue {
924 entry,
925 value: actual_value,
926 source,
927 } => {
928 assert_eq!(entry, "streaming_parallelism_for_backfill");
929 assert_eq!(actual_value, value);
930 assert_eq!(source.to_string(), expected);
931 }
932 other => panic!("unexpected error: {other:?}"),
933 }
934 }
935 }
936}