@@ -17,7 +17,7 @@ use anyhow::bail;
1717use mz_build_info:: BuildInfo ;
1818use mz_cluster_client:: client:: ClusterReplicaLocation ;
1919use mz_compute_types:: dyncfgs:: ENABLE_COMPUTE_REPLICA_EXPIRATION ;
20- use mz_dyncfg:: ConfigSet ;
20+ use mz_dyncfg:: { ConfigSet , ConfigUpdates } ;
2121use mz_ore:: channel:: InstrumentedUnboundedSender ;
2222use mz_ore:: retry:: { Retry , RetryState } ;
2323use mz_ore:: task:: AbortOnDropHandle ;
@@ -95,6 +95,7 @@ impl ReplicaClient {
9595 epoch,
9696 metrics : metrics. clone ( ) ,
9797 connected : Arc :: clone ( & connected) ,
98+ replica_dyncfg : seed_replica_dyncfg ( & dyncfg) ,
9899 dyncfg,
99100 }
100101 . run ( ) ,
@@ -131,6 +132,27 @@ impl ReplicaClient {
131132
132133type ComputeCtpClient = transport:: Client < ComputeCommand , ComputeResponse > ;
133134
135+ /// Creates a replica's effective configuration, seeded from the environment-wide one.
136+ ///
137+ /// The seed covers the window before the first configuration command arrives, and is replaced
138+ /// wholesale by the snapshot that `CreateInstance` carries.
139+ fn seed_replica_dyncfg ( dyncfg : & ConfigSet ) -> ConfigSet {
140+ let replica_dyncfg = mz_dyncfgs:: all_dyncfgs ( ) ;
141+ ConfigUpdates :: from ( dyncfg) . apply ( & replica_dyncfg) ;
142+ replica_dyncfg
143+ }
144+
145+ /// Applies the configuration a command carries, if any, to a replica's effective configuration.
146+ ///
147+ /// `CreateInstance` carries a full snapshot, `UpdateConfiguration` the subsequent deltas.
148+ fn apply_config_command ( command : & ComputeCommand , dyncfg : & ConfigSet ) {
149+ match command {
150+ ComputeCommand :: CreateInstance ( config) => config. initial_config . apply ( dyncfg) ,
151+ ComputeCommand :: UpdateConfiguration ( params) => params. dyncfg_updates . apply ( dyncfg) ,
152+ _ => ( ) ,
153+ }
154+ }
155+
134156/// Configuration for `replica_task`.
135157struct ReplicaTask {
136158 /// The ID of the replica.
@@ -150,8 +172,21 @@ struct ReplicaTask {
150172 metrics : ReplicaMetrics ,
151173 /// Flag to report successful replica connection.
152174 connected : Arc < AtomicBool > ,
153- /// Dynamic system configuration.
175+ /// The controller's environment-wide dynamic system configuration.
154176 dyncfg : Arc < ConfigSet > ,
177+ /// This replica's effective dynamic system configuration.
178+ ///
179+ /// Holds what the replica itself reads, including its scoped overrides, as opposed to
180+ /// [`Self::dyncfg`], which holds the environment-wide values. Seeded from the environment-wide
181+ /// configuration and then kept current from the configuration commands passing through this
182+ /// task, which `Instance::specialize_command_for_replica` has already specialized for this
183+ /// replica. Read it for any `ParameterScope::Replica` config the controller realizes on this
184+ /// replica's behalf, else the scope declaration is a silent no-op.
185+ ///
186+ /// A set of its own, rather than a clone of the controller's: a cloned `ConfigSet` shares its
187+ /// values with the original, so applying this replica's overrides to a clone would overwrite
188+ /// the environment-wide configuration for everyone.
189+ replica_dyncfg : ConfigSet ,
155190}
156191
157192impl ReplicaTask {
@@ -229,7 +264,7 @@ impl ReplicaTask {
229264 // The sequential hydration interceptor holds back `Schedule` commands and releases them as
230265 // hydration capacity frees up. It is recreated per incarnation, matching the lifetime of
231266 // the connection: any in-flight hydration state is reset when we reconnect.
232- let mut hydration = SequentialHydration :: new ( & self . dyncfg , self . metrics . clone ( ) ) ;
267+ let mut hydration = SequentialHydration :: new ( self . metrics . clone ( ) ) ;
233268
234269 loop {
235270 select ! {
@@ -242,7 +277,8 @@ impl ReplicaTask {
242277
243278 self . specialize_command( & mut command) ;
244279 self . observe_command( & command) ;
245- for command in hydration. absorb_command( command) {
280+ apply_config_command( & command, & self . replica_dyncfg) ;
281+ for command in hydration. absorb_command( command, & self . replica_dyncfg) {
246282 client. send( command) . await ?;
247283 }
248284 } ,
@@ -254,7 +290,7 @@ impl ReplicaTask {
254290
255291 self . observe_response( & response) ;
256292
257- for command in hydration. observe_response( & response) {
293+ for command in hydration. observe_response( & response, & self . replica_dyncfg ) {
258294 client. send( command) . await ?;
259295 }
260296
@@ -320,3 +356,52 @@ impl ReplicaTask {
320356 ) ;
321357 }
322358}
359+
360+ #[ cfg( test) ]
361+ mod tests {
362+ use mz_compute_types:: dyncfgs:: HYDRATION_CONCURRENCY ;
363+
364+ use crate :: protocol:: command:: { ComputeParameters , InstanceConfig } ;
365+
366+ use super :: * ;
367+
368+ /// A replica's effective configuration tracks the configuration commands passing through its
369+ /// task, which carry the replica's scoped overrides, and leaves the environment-wide
370+ /// configuration alone.
371+ #[ mz_ore:: test]
372+ fn replica_dyncfg_tracks_config_commands ( ) {
373+ let env_wide = mz_dyncfgs:: all_dyncfgs ( ) ;
374+ let mut updates = ConfigUpdates :: default ( ) ;
375+ updates. add ( & HYDRATION_CONCURRENCY , 1 ) ;
376+ updates. apply ( & env_wide) ;
377+
378+ let replica_dyncfg = seed_replica_dyncfg ( & env_wide) ;
379+ assert_eq ! ( HYDRATION_CONCURRENCY . get( & replica_dyncfg) , 1 ) ;
380+
381+ // A replica-scoped override arrives merged into the create-time snapshot.
382+ let mut initial_config = ConfigUpdates :: default ( ) ;
383+ initial_config. add ( & HYDRATION_CONCURRENCY , 2 ) ;
384+ let create = ComputeCommand :: CreateInstance ( Box :: new ( InstanceConfig {
385+ logging : Default :: default ( ) ,
386+ expiration_offset : None ,
387+ peek_stash_persist_location : mz_persist_client:: PersistLocation :: new_in_mem ( ) ,
388+ arrangement_dictionary_compression : false ,
389+ initial_config,
390+ } ) ) ;
391+ apply_config_command ( & create, & replica_dyncfg) ;
392+ assert_eq ! ( HYDRATION_CONCURRENCY . get( & replica_dyncfg) , 2 ) ;
393+
394+ // And into subsequent configuration updates.
395+ let mut dyncfg_updates = ConfigUpdates :: default ( ) ;
396+ dyncfg_updates. add ( & HYDRATION_CONCURRENCY , 3 ) ;
397+ let update = ComputeCommand :: UpdateConfiguration ( Box :: new ( ComputeParameters {
398+ dyncfg_updates,
399+ ..Default :: default ( )
400+ } ) ) ;
401+ apply_config_command ( & update, & replica_dyncfg) ;
402+ assert_eq ! ( HYDRATION_CONCURRENCY . get( & replica_dyncfg) , 3 ) ;
403+
404+ // The environment-wide configuration is untouched by the replica's overrides.
405+ assert_eq ! ( HYDRATION_CONCURRENCY . get( & env_wide) , 1 ) ;
406+ }
407+ }
0 commit comments