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(
31 sync_config: SystemParameterSyncConfig,
32 adapter_client: Client,
33 tick_interval: Option<Duration>,
34) -> Result<(), anyhow::Error> {
35 let Some(tick_interval) = tick_interval else {
36 tracing::info!("skipping system parameter sync as tick_interval = None");
37 return Ok(());
38 };
39
40 let scoped_client = adapter_client.clone();
43
44 let mut frontend = Option::<Arc<SystemParameterFrontend>>::None; let mut backend = SystemParameterBackend::new(adapter_client).await?;
49
50 let mut interval = time::interval(tick_interval);
52 interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
53
54 tracing::info!(
56 "synchronizing system parameter values every {} seconds",
57 tick_interval.as_secs()
58 );
59
60 let mut params = SynchronizedParameters::default();
61 loop {
62 interval.tick().await;
64
65 backend.pull(&mut params).await;
67
68 if !params.enable_launchdarkly() && sync_config.backend_config.is_launch_darkly() {
69 if frontend.is_some() {
70 tracing::info!("stopping system parameter frontend");
71 frontend = None;
72 } else {
73 tracing::info!("system parameter sync is disabled; not syncing")
74 }
75
76 continue;
78 }
79
80 if frontend.is_none() {
81 tracing::info!("initializing system parameter frontend");
82 let new_frontend = Arc::new(SystemParameterFrontend::from(&sync_config).await?);
83 scoped_client.install_scoped_system_parameter_frontend(Arc::clone(&new_frontend));
87 frontend = Some(new_frontend);
88 }
89
90 let frontend = frontend.as_ref().expect("frontend exists");
92 if frontend.pull(&mut params) {
93 backend.push(&mut params).await;
94 }
95
96 sync_scoped_params(&scoped_client, frontend, ¶ms).await;
100 }
101}
102
103async fn sync_scoped_params(
107 client: &Client,
108 frontend: &SystemParameterFrontend,
109 params: &SynchronizedParameters,
110) {
111 let catalog = client.catalog_snapshot_expensive().await;
112
113 let prune_scope = ScopedParametersScope {
120 clusters: catalog.clusters().map(|cluster| cluster.id).collect(),
121 replicas: catalog
122 .clusters()
123 .flat_map(|cluster| cluster.replicas().map(|replica| replica.replica_id))
124 .collect(),
125 };
126 let scoped = evaluate_scoped_parameters(frontend, params, &catalog, None, None);
127 client
128 .update_scoped_system_parameters(scoped, prune_scope)
129 .await;
130}
131
132pub(crate) fn evaluate_scoped_parameters(
141 frontend: &SystemParameterFrontend,
142 params: &SynchronizedParameters,
143 catalog: &Catalog,
144 cluster_filter: Option<&BTreeSet<ClusterId>>,
145 replica_filter: Option<&BTreeSet<ReplicaId>>,
146) -> ScopedParameters {
147 let system_config = catalog.system_config();
148
149 let replica_param_names: Vec<&'static str> = system_config
153 .iter_synced()
154 .filter(|var| var.scope() == ParameterScope::Replica)
155 .map(|var| var.name())
156 .collect();
157 let cluster_param_names: Vec<&'static str> = system_config
158 .iter_synced()
159 .filter(|var| var.scope() == ParameterScope::Cluster)
160 .map(|var| var.name())
161 .collect();
162
163 let replica = if replica_param_names.is_empty() {
164 Default::default()
165 } else {
166 let replicas = build_replica_eval_contexts(catalog, replica_filter);
167 frontend.pull_replica_overrides(params, &replica_param_names, &replicas)
168 };
169 let cluster = if cluster_param_names.is_empty() {
170 Default::default()
171 } else {
172 let clusters = build_cluster_eval_contexts(catalog, cluster_filter);
173 frontend.pull_cluster_overrides(params, &cluster_param_names, &clusters)
174 };
175
176 ScopedParameters { cluster, replica }
177}
178
179fn build_cluster_eval_contexts(
182 catalog: &Catalog,
183 filter: Option<&BTreeSet<ClusterId>>,
184) -> Vec<ClusterEvalContext> {
185 catalog
186 .clusters()
187 .filter(|cluster| filter.is_none_or(|f| f.contains(&cluster.id)))
188 .map(|cluster| ClusterEvalContext {
189 cluster_id: cluster.id,
190 cluster: ClusterScopeContext {
191 id: cluster.id.to_string(),
192 name: cluster.name.clone(),
193 is_builtin: cluster.id.is_system(),
194 },
195 })
196 .collect()
197}
198
199fn build_replica_eval_contexts(
202 catalog: &Catalog,
203 filter: Option<&BTreeSet<ReplicaId>>,
204) -> Vec<ReplicaEvalContext> {
205 if filter.is_some_and(|f| f.is_empty()) {
209 return Vec::new();
210 }
211
212 let mut contexts = Vec::new();
213 for cluster in catalog.clusters() {
214 let is_builtin = cluster.id.is_system();
215 let cluster_ctx = ClusterScopeContext {
216 id: cluster.id.to_string(),
217 name: cluster.name.clone(),
218 is_builtin,
219 };
220 for replica in cluster.replicas() {
221 if filter.is_some_and(|f| !f.contains(&replica.replica_id)) {
222 continue;
223 }
224 let ReplicaLocation::Managed(location) = &replica.config.location else {
226 continue;
227 };
228 let replica_ctx = ReplicaScopeContext {
229 id: replica.replica_id.to_string(),
230 name: replica.name.clone(),
231 is_builtin,
232 size: location.size.clone(),
233 size_family: location.allocation.family().to_string(),
234 cluster_id: cluster.id.to_string(),
235 cluster_name: cluster.name.clone(),
236 };
237 contexts.push(ReplicaEvalContext {
238 cluster_id: cluster.id,
239 replica_id: replica.replica_id,
240 cluster: cluster_ctx.clone(),
241 replica: replica_ctx,
242 });
243 }
244 }
245 contexts
246}