mz_adapter/config/
sync.rs1use std::collections::BTreeSet;
11use std::sync::Arc;
12use std::time::Duration;
13
14use mz_controller::clusters::ReplicaLocation;
15use mz_controller_types::{ClusterId, ReplicaId};
16use mz_dyncfg::ParameterScope;
17use tokio::time;
18
19use crate::Client;
20use crate::catalog::Catalog;
21use crate::config::{
22 ClusterEvalContext, ClusterScopeContext, ReplicaEvalContext, ReplicaScopeContext,
23 ScopedParameters, ScopedParametersScope, SynchronizedParameters, SystemParameterBackend,
24 SystemParameterFrontend, SystemParameterSyncConfig,
25};
26
27pub async fn system_parameter_sync(
34 sync_config: SystemParameterSyncConfig,
35 mut boot_frontend: Option<Arc<SystemParameterFrontend>>,
36 adapter_client: Client,
37 tick_interval: Option<Duration>,
38) -> Result<(), anyhow::Error> {
39 let Some(tick_interval) = tick_interval else {
40 tracing::info!("skipping system parameter sync as tick_interval = None");
41 return Ok(());
42 };
43
44 let scoped_client = adapter_client.clone();
47
48 let mut frontend = Option::<Arc<SystemParameterFrontend>>::None; let mut backend = SystemParameterBackend::new(adapter_client).await?;
53
54 let mut interval = time::interval(tick_interval);
56 interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
57
58 tracing::info!(
60 "synchronizing system parameter values every {} seconds",
61 tick_interval.as_secs()
62 );
63
64 let mut params = SynchronizedParameters::default();
65 loop {
66 interval.tick().await;
68
69 backend.pull(&mut params).await;
71
72 if !params.enable_launchdarkly() && sync_config.backend_config.is_launch_darkly() {
73 if frontend.is_some() || boot_frontend.is_some() {
76 tracing::info!("stopping system parameter frontend");
77 frontend = None;
78 boot_frontend = None;
79 } else {
80 tracing::info!("system parameter sync is disabled; not syncing")
81 }
82
83 continue;
85 }
86
87 if frontend.is_none() {
88 let new_frontend = match boot_frontend.take() {
89 Some(boot_frontend) => boot_frontend,
90 None => {
91 tracing::info!("initializing system parameter frontend");
92 Arc::new(SystemParameterFrontend::from(&sync_config).await?)
93 }
94 };
95 scoped_client.install_scoped_system_parameter_frontend(Arc::clone(&new_frontend));
99 frontend = Some(new_frontend);
100 }
101
102 let frontend = frontend.as_ref().expect("frontend exists");
104 if frontend.pull(&mut params) {
105 backend.push(&mut params).await;
106 }
107
108 sync_scoped_params(&scoped_client, frontend, ¶ms).await;
112 }
113}
114
115async fn sync_scoped_params(
119 client: &Client,
120 frontend: &SystemParameterFrontend,
121 params: &SynchronizedParameters,
122) {
123 if !frontend.has_scoped_desired_state() {
127 return;
128 }
129
130 let catalog = client.catalog_snapshot_expensive().await;
131
132 let prune_scope = ScopedParametersScope {
139 clusters: catalog.clusters().map(|cluster| cluster.id).collect(),
140 replicas: catalog
141 .clusters()
142 .flat_map(|cluster| cluster.replicas().map(|replica| replica.replica_id))
143 .collect(),
144 };
145 let scoped = evaluate_scoped_parameters(frontend, params, &catalog, None, None);
146 client
147 .update_scoped_system_parameters(scoped, prune_scope)
148 .await;
149}
150
151pub(crate) fn evaluate_scoped_parameters(
160 frontend: &SystemParameterFrontend,
161 params: &SynchronizedParameters,
162 catalog: &Catalog,
163 cluster_filter: Option<&BTreeSet<ClusterId>>,
164 replica_filter: Option<&BTreeSet<ReplicaId>>,
165) -> ScopedParameters {
166 let system_config = catalog.system_config();
167
168 let replica_param_names: Vec<&'static str> = system_config
172 .iter_synced()
173 .filter(|var| var.scope() == ParameterScope::Replica)
174 .map(|var| var.name())
175 .collect();
176 let cluster_param_names: Vec<&'static str> = system_config
177 .iter_synced()
178 .filter(|var| var.scope() == ParameterScope::Cluster)
179 .map(|var| var.name())
180 .collect();
181
182 let replica = if replica_param_names.is_empty() {
183 Default::default()
184 } else {
185 let replicas = build_replica_eval_contexts(catalog, replica_filter);
186 frontend.pull_replica_overrides(params, &replica_param_names, &replicas)
187 };
188 let cluster = if cluster_param_names.is_empty() {
189 Default::default()
190 } else {
191 let clusters = build_cluster_eval_contexts(catalog, cluster_filter);
192 frontend.pull_cluster_overrides(params, &cluster_param_names, &clusters)
193 };
194
195 ScopedParameters { cluster, replica }
196}
197
198fn build_cluster_eval_contexts(
201 catalog: &Catalog,
202 filter: Option<&BTreeSet<ClusterId>>,
203) -> Vec<ClusterEvalContext> {
204 catalog
205 .clusters()
206 .filter(|cluster| filter.is_none_or(|f| f.contains(&cluster.id)))
207 .map(|cluster| ClusterEvalContext {
208 cluster_id: cluster.id,
209 cluster: ClusterScopeContext {
210 id: cluster.id.to_string(),
211 name: cluster.name.clone(),
212 is_builtin: cluster.id.is_system(),
213 },
214 })
215 .collect()
216}
217
218fn build_replica_eval_contexts(
221 catalog: &Catalog,
222 filter: Option<&BTreeSet<ReplicaId>>,
223) -> Vec<ReplicaEvalContext> {
224 if filter.is_some_and(|f| f.is_empty()) {
228 return Vec::new();
229 }
230
231 let mut contexts = Vec::new();
232 for cluster in catalog.clusters() {
233 let is_builtin = cluster.id.is_system();
234 let cluster_ctx = ClusterScopeContext {
235 id: cluster.id.to_string(),
236 name: cluster.name.clone(),
237 is_builtin,
238 };
239 for replica in cluster.replicas() {
240 if filter.is_some_and(|f| !f.contains(&replica.replica_id)) {
241 continue;
242 }
243 let ReplicaLocation::Managed(location) = &replica.config.location else {
245 continue;
246 };
247 let replica_ctx = ReplicaScopeContext {
248 id: replica.replica_id.to_string(),
249 name: replica.name.clone(),
250 is_builtin,
251 size: location.size.clone(),
252 size_family: location.allocation.family().to_string(),
253 cluster_id: cluster.id.to_string(),
254 cluster_name: cluster.name.clone(),
255 };
256 contexts.push(ReplicaEvalContext {
257 cluster_id: cluster.id,
258 replica_id: replica.replica_id,
259 cluster: cluster_ctx.clone(),
260 replica: replica_ctx,
261 });
262 }
263 }
264 contexts
265}