mz_controller/lib.rs
1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A representative of STORAGE and COMPUTE that maintains summaries of the involved objects.
11//!
12//! The `Controller` provides the ability to create and manipulate storage and compute instances.
13//! Each of Storage and Compute provide their own controllers, accessed through the `storage()`
14//! and `compute(instance_id)` methods. It is an error to access a compute instance before it has
15//! been created.
16//!
17//! The controller also provides a `recv()` method that returns responses from the storage and
18//! compute layers, which may remain of value to the interested user. With time, these responses
19//! may be thinned down in an effort to make the controller more self contained.
20//!
21//! Consult the `StorageController` and `ComputeController` documentation for more information
22//! about each of these interfaces.
23
24use std::collections::btree_map::Entry;
25use std::collections::{BTreeMap, BTreeSet};
26use std::mem;
27use std::num::NonZeroI64;
28use std::sync::Arc;
29use std::time::Duration;
30
31use futures::future::BoxFuture;
32use mz_build_info::BuildInfo;
33use mz_cluster_client::metrics::ControllerMetrics;
34use mz_cluster_client::{ReplicaId, WallclockLagFn};
35use mz_compute_client::controller::error::{CollectionLookupError, CollectionMissing};
36use mz_compute_client::controller::{
37 ComputeController, ComputeControllerResponse, PeekNotification,
38};
39use mz_compute_client::protocol::response::SubscribeBatch;
40use mz_controller_types::{ClusterId, WatchSetId};
41use mz_dyncfg::{ConfigSet, ConfigUpdates};
42use mz_orchestrator::{NamespacedOrchestrator, Orchestrator, ServiceProcessMetrics};
43use mz_ore::cast::CastFrom;
44use mz_ore::id_gen::Gen;
45use mz_ore::instrument;
46use mz_ore::metrics::MetricsRegistry;
47use mz_ore::now::NowFn;
48use mz_ore::task::AbortOnDropHandle;
49use mz_ore::tracing::OpenTelemetryContext;
50use mz_persist_client::PersistLocation;
51use mz_persist_client::cache::PersistClientCache;
52use mz_repr::{Datum, GlobalId, Row, Timestamp};
53use mz_service::secrets::SecretsReaderCliArgs;
54use mz_storage_client::controller::{
55 IntrospectionType, StorageController, StorageMetadata, StorageTxn,
56};
57use mz_storage_client::storage_collections::{self, StorageCollections};
58use mz_storage_types::configuration::StorageConfiguration;
59use mz_storage_types::connections::ConnectionContext;
60use mz_storage_types::controller::StorageError;
61use mz_txn_wal::metrics::Metrics as TxnMetrics;
62use timely::progress::Antichain;
63use tokio::sync::mpsc;
64use uuid::Uuid;
65
66pub mod clusters;
67pub mod replica_http_locator;
68
69// Export this on behalf of the storage controller to provide a unified
70// interface, allowing other crates to depend on this crate alone.
71pub use mz_storage_controller::prepare_initialization;
72pub use replica_http_locator::ReplicaHttpLocator;
73
74/// Configures a controller.
75#[derive(Debug, Clone)]
76pub struct ControllerConfig {
77 /// The build information for this process.
78 pub build_info: &'static BuildInfo,
79 /// The orchestrator implementation to use.
80 pub orchestrator: Arc<dyn Orchestrator>,
81 /// The persist location where all storage collections will be written to.
82 pub persist_location: PersistLocation,
83 /// A process-global cache of (blob_uri, consensus_uri) ->
84 /// PersistClient.
85 /// This is intentionally shared between workers.
86 pub persist_clients: Arc<PersistClientCache>,
87 /// The clusterd image to use when starting new cluster processes.
88 pub clusterd_image: String,
89 /// The init container image to use for clusterd.
90 pub init_container_image: Option<String>,
91 /// A number representing the environment's generation.
92 ///
93 /// This is incremented to request that the new process perform a graceful
94 /// transition of power from the prior generation.
95 pub deploy_generation: u64,
96 /// The now function to advance the controller's introspection collections.
97 pub now: NowFn,
98 /// The metrics registry.
99 pub metrics_registry: MetricsRegistry,
100 /// The URL for Persist PubSub.
101 pub persist_pubsub_url: String,
102 /// Arguments for secrets readers.
103 pub secrets_args: SecretsReaderCliArgs,
104 /// The connection context, to thread through to clusterd, with cli flags.
105 pub connection_context: ConnectionContext,
106 /// Locator for HTTP addresses of cluster replicas, used to proxy HTTP
107 /// requests from environmentd to clusterd.
108 pub replica_http_locator: Arc<ReplicaHttpLocator>,
109}
110
111/// Responses that [`Controller`] can produce.
112#[derive(Debug)]
113pub enum ControllerResponse {
114 /// Notification of a worker's response to a specified (by connection id) peek.
115 ///
116 /// Additionally, an `OpenTelemetryContext` to forward trace information
117 /// back into coord. This allows coord traces to be children of work
118 /// done in compute!
119 PeekNotification(Uuid, PeekNotification, OpenTelemetryContext),
120 /// The worker's next response to a specified subscribe.
121 SubscribeResponse(GlobalId, SubscribeBatch),
122 /// The worker's next response to a specified copy to.
123 CopyToResponse(GlobalId, Result<u64, anyhow::Error>),
124 /// Notification that a watch set has finished. See
125 /// [`Controller::install_compute_watch_set`] and
126 /// [`Controller::install_storage_watch_set`] for details.
127 WatchSetFinished(Vec<WatchSetId>),
128}
129
130/// Whether one of the underlying controllers is ready for their `process`
131/// method to be called.
132#[derive(Debug, Default)]
133pub enum Readiness {
134 /// No underlying controllers are ready.
135 #[default]
136 NotReady,
137 /// The storage controller is ready.
138 Storage,
139 /// The compute controller is ready.
140 Compute,
141 /// A batch of metric data is ready.
142 Metrics((ReplicaId, Vec<ServiceProcessMetrics>)),
143 /// An internally-generated message is ready to be returned.
144 Internal(ControllerResponse),
145}
146
147/// A client that maintains soft state and validates commands, in addition to forwarding them.
148pub struct Controller {
149 pub storage: Box<dyn StorageController>,
150 pub storage_collections: Arc<dyn StorageCollections + Send + Sync>,
151 pub compute: ComputeController,
152 /// The clusterd image to use when starting new cluster processes.
153 clusterd_image: String,
154 /// The init container image to use for clusterd.
155 init_container_image: Option<String>,
156 /// A number representing the environment's generation.
157 deploy_generation: u64,
158 /// Whether or not this controller is in read-only mode.
159 ///
160 /// When in read-only mode, neither this controller nor the instances
161 /// controlled by it are allowed to affect changes to external systems
162 /// (largely persist).
163 read_only: bool,
164 /// The cluster orchestrator.
165 orchestrator: Arc<dyn NamespacedOrchestrator>,
166 /// Tracks the readiness of the underlying controllers.
167 readiness: Readiness,
168 /// Tasks for collecting replica metrics.
169 metrics_tasks: BTreeMap<ReplicaId, AbortOnDropHandle<()>>,
170 /// Sender for the channel over which replica metrics are sent.
171 metrics_tx: mpsc::UnboundedSender<(ReplicaId, Vec<ServiceProcessMetrics>)>,
172 /// Receiver for the channel over which replica metrics are sent.
173 metrics_rx: mpsc::UnboundedReceiver<(ReplicaId, Vec<ServiceProcessMetrics>)>,
174 /// A function providing the current wallclock time.
175 now: NowFn,
176
177 /// The URL for Persist PubSub.
178 persist_pubsub_url: String,
179
180 /// Arguments for secrets readers.
181 secrets_args: SecretsReaderCliArgs,
182
183 /// A map associating a global ID to the set of all the unfulfilled watch
184 /// set ids that include it.
185 ///
186 /// See [`Controller::install_compute_watch_set`]/[`Controller::install_storage_watch_set`] for a description of watch sets.
187 // When a watch set is fulfilled for a given object (that is, when
188 // the object's frontier advances to at least the watch set's
189 // timestamp), the corresponding entry will be removed from the set.
190 unfulfilled_watch_sets_by_object: BTreeMap<GlobalId, BTreeSet<WatchSetId>>,
191 /// A map of installed watch sets indexed by id.
192 unfulfilled_watch_sets: BTreeMap<WatchSetId, (BTreeSet<GlobalId>, Timestamp)>,
193 /// A sequence of numbers used to mint unique WatchSetIds.
194 watch_set_id_gen: Gen<WatchSetId>,
195
196 /// A list of watch sets that were already fulfilled as soon as
197 /// they were installed, and thus that must be returned to the
198 /// client on the next call to [`Controller::process_compute_response`]/[`Controller::process_storage_response`].
199 ///
200 /// See [`Controller::install_compute_watch_set`]/[`Controller::install_storage_watch_set`] for a description of watch sets.
201 immediate_watch_sets: Vec<WatchSetId>,
202
203 /// Dynamic system configuration.
204 dyncfg: ConfigSet,
205
206 /// The replica-local scoped overrides of [`Self::dyncfg`], by replica.
207 ///
208 /// Sparse: only replicas LaunchDarkly targets to a replica-specific value
209 /// have an entry. See [`Self::update_replica_dyncfg_overrides`].
210 replica_dyncfg_overrides: BTreeMap<ReplicaId, ConfigUpdates>,
211
212 /// Locator for HTTP addresses of cluster replicas.
213 replica_http_locator: Arc<ReplicaHttpLocator>,
214}
215
216impl Controller {
217 /// Update the controller configuration.
218 pub fn update_configuration(&mut self, updates: ConfigUpdates) {
219 updates.apply(&self.dyncfg);
220 }
221
222 /// Replaces the per-replica dyncfg overrides of the replica-local scoped
223 /// system parameters, in this controller and in the compute and storage
224 /// controllers beneath it.
225 ///
226 /// Replicas absent from `overrides` have their overrides cleared, so a
227 /// replica that no longer has one reverts to the environment-wide
228 /// configuration. Callers should follow with a configuration push so
229 /// running replicas observe the new values.
230 ///
231 /// The three layers realize a [`ParameterScope::Replica`] config in
232 /// different places, which is why all three are fed from one call. The
233 /// compute and storage controllers specialize the configuration they push
234 /// to a running replica. This controller resolves the overrides that are
235 /// baked into a replica's process configuration when it is provisioned.
236 ///
237 /// [`ParameterScope::Replica`]: mz_dyncfg::ParameterScope::Replica
238 pub fn update_replica_dyncfg_overrides(
239 &mut self,
240 overrides: BTreeMap<ClusterId, BTreeMap<ReplicaId, ConfigUpdates>>,
241 ) {
242 self.replica_dyncfg_overrides = overrides
243 .values()
244 .flat_map(|replicas| replicas.iter())
245 .map(|(replica_id, updates)| (*replica_id, updates.clone()))
246 .collect();
247 self.compute
248 .update_replica_dyncfg_overrides(overrides.clone());
249 self.storage.update_replica_dyncfg_overrides(overrides);
250 }
251
252 /// Start sinking the compute controller's introspection data into storage.
253 ///
254 /// This method should be called once the introspection collections have been registered with
255 /// the storage controller. It will panic if invoked earlier than that.
256 pub fn start_compute_introspection_sink(&mut self) {
257 self.compute.start_introspection_sink(&*self.storage);
258 }
259
260 /// Returns the connection context installed in the controller.
261 ///
262 /// This is purely a helper, and can be obtained from `self.storage`.
263 pub fn connection_context(&self) -> &ConnectionContext {
264 &self.storage.config().connection_context
265 }
266
267 /// Returns the storage configuration installed in the storage controller.
268 ///
269 /// This is purely a helper, and can be obtained from `self.storage`.
270 pub fn storage_configuration(&self) -> &StorageConfiguration {
271 self.storage.config()
272 }
273
274 /// Returns the state of the [`Controller`] formatted as JSON.
275 ///
276 /// The returned value is not guaranteed to be stable and may change at any point in time.
277 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
278 // Note: We purposefully use the `Debug` formatting for the value of all fields in the
279 // returned object as a tradeoff between usability and stability. `serde_json` will fail
280 // to serialize an object if the keys aren't strings, so `Debug` formatting the values
281 // prevents a future unrelated change from silently breaking this method.
282
283 // Destructure `self` here so we don't forget to consider dumping newly added fields.
284 let Self {
285 storage_collections,
286 storage,
287 compute,
288 clusterd_image: _,
289 init_container_image: _,
290 deploy_generation,
291 read_only,
292 orchestrator: _,
293 readiness,
294 metrics_tasks: _,
295 metrics_tx: _,
296 metrics_rx: _,
297 now: _,
298 persist_pubsub_url: _,
299 secrets_args: _,
300 unfulfilled_watch_sets_by_object: _,
301 unfulfilled_watch_sets,
302 watch_set_id_gen: _,
303 immediate_watch_sets,
304 dyncfg: _,
305 replica_dyncfg_overrides: _,
306 replica_http_locator: _,
307 } = self;
308
309 let storage_collections = storage_collections.dump()?;
310 let storage = storage.dump()?;
311 let compute = compute.dump().await?;
312
313 let unfulfilled_watch_sets: BTreeMap<_, _> = unfulfilled_watch_sets
314 .iter()
315 .map(|(ws_id, watches)| (format!("{ws_id:?}"), format!("{watches:?}")))
316 .collect();
317 let immediate_watch_sets: Vec<_> = immediate_watch_sets
318 .iter()
319 .map(|watch| format!("{watch:?}"))
320 .collect();
321
322 Ok(serde_json::json!({
323 "storage_collections": storage_collections,
324 "storage": storage,
325 "compute": compute,
326 "deploy_generation": deploy_generation,
327 "read_only": read_only,
328 "readiness": format!("{readiness:?}"),
329 "unfulfilled_watch_sets": unfulfilled_watch_sets,
330 "immediate_watch_sets": immediate_watch_sets,
331 }))
332 }
333}
334
335impl Controller {
336 pub fn update_orchestrator_scheduling_config(
337 &self,
338 config: mz_orchestrator::scheduling_config::ServiceSchedulingConfig,
339 ) {
340 self.orchestrator.update_scheduling_config(config);
341 }
342 /// Marks the end of any initialization commands.
343 ///
344 /// The implementor may wait for this method to be called before implementing prior commands,
345 /// and so it is important for a user to invoke this method as soon as it is comfortable.
346 /// This method can be invoked immediately, at the potential expense of performance.
347 pub fn initialization_complete(&mut self) {
348 self.storage.initialization_complete();
349 self.compute.initialization_complete();
350 }
351
352 /// Reports whether the controller is in read only mode.
353 pub fn read_only(&self) -> bool {
354 self.read_only
355 }
356
357 /// Returns `Some` if there is an immediately available
358 /// internally-generated response that we need to return to the
359 /// client (as opposed to waiting for a response from compute or storage).
360 fn take_internal_response(&mut self) -> Option<ControllerResponse> {
361 let ws = std::mem::take(&mut self.immediate_watch_sets);
362 (!ws.is_empty()).then_some(ControllerResponse::WatchSetFinished(ws))
363 }
364
365 /// Waits until the controller is ready to process a response.
366 ///
367 /// This method may block for an arbitrarily long time.
368 ///
369 /// When the method returns, the owner should call [`Controller::ready`] to
370 /// process the ready message.
371 ///
372 /// This method is cancellation safe.
373 pub async fn ready(&mut self) {
374 if let Readiness::NotReady = self.readiness {
375 // the coordinator wants to be able to make a simple
376 // sequence of ready, process, ready, process, .... calls,
377 // but the controller sometimes has responses immediately
378 // ready to be processed and should do so before calling
379 // into either of the lower-level controllers. This `if`
380 // statement handles that case.
381 if let Some(response) = self.take_internal_response() {
382 self.readiness = Readiness::Internal(response);
383 } else {
384 // The underlying `ready` methods are cancellation safe, so it is
385 // safe to construct this `select!`.
386 tokio::select! {
387 () = self.storage.ready() => {
388 self.readiness = Readiness::Storage;
389 }
390 () = self.compute.ready() => {
391 self.readiness = Readiness::Compute;
392 }
393 Some(metrics) = self.metrics_rx.recv() => {
394 self.readiness = Readiness::Metrics(metrics);
395 }
396 }
397 }
398 }
399 }
400
401 /// Returns the [Readiness] status of this controller.
402 pub fn get_readiness(&self) -> &Readiness {
403 &self.readiness
404 }
405
406 /// Install a _watch set_ in the controller.
407 ///
408 /// A _watch set_ is a request to be informed by the controller when
409 /// all of the frontiers of a particular set of objects have advanced at
410 /// least to a particular timestamp.
411 ///
412 /// When all the objects in `objects` have advanced to `t`, the watchset id
413 /// is returned to the client on the next call to [`Self::process`].
414 pub fn install_compute_watch_set(
415 &mut self,
416 mut objects: BTreeSet<GlobalId>,
417 t: Timestamp,
418 ) -> Result<WatchSetId, CollectionLookupError> {
419 let ws_id = self.watch_set_id_gen.allocate_id();
420
421 // Collect all frontiers first, returning any errors
422 let frontiers: BTreeMap<GlobalId, _> = objects
423 .iter()
424 .map(|id| {
425 self.compute
426 .collection_frontiers(*id, None)
427 .map(|f| (*id, f.write_frontier))
428 })
429 .collect::<Result<_, _>>()?;
430 objects.retain(|id| {
431 let frontier = frontiers.get(id).expect("just collected");
432 frontier.less_equal(&t)
433 });
434 if objects.is_empty() {
435 self.immediate_watch_sets.push(ws_id);
436 } else {
437 for id in objects.iter() {
438 self.unfulfilled_watch_sets_by_object
439 .entry(*id)
440 .or_default()
441 .insert(ws_id);
442 }
443 self.unfulfilled_watch_sets.insert(ws_id, (objects, t));
444 }
445
446 Ok(ws_id)
447 }
448
449 /// Install a _watch set_ in the controller.
450 ///
451 /// A _watch set_ is a request to be informed by the controller when
452 /// all of the frontiers of a particular set of objects have advanced at
453 /// least to a particular timestamp.
454 ///
455 /// When all the objects in `objects` have advanced to `t`, the watchset id
456 /// is returned to the client on the next call to [`Self::process`].
457 pub fn install_storage_watch_set(
458 &mut self,
459 mut objects: BTreeSet<GlobalId>,
460 t: Timestamp,
461 ) -> Result<WatchSetId, CollectionMissing> {
462 let ws_id = self.watch_set_id_gen.allocate_id();
463
464 let uppers = self
465 .storage
466 .collections_frontiers(objects.iter().cloned().collect())?
467 .into_iter()
468 .map(|(id, _since, upper)| (id, upper))
469 .collect::<BTreeMap<_, _>>();
470
471 objects.retain(|id| {
472 let upper = uppers.get(id).expect("missing collection");
473 upper.less_equal(&t)
474 });
475 if objects.is_empty() {
476 self.immediate_watch_sets.push(ws_id);
477 } else {
478 for id in objects.iter() {
479 self.unfulfilled_watch_sets_by_object
480 .entry(*id)
481 .or_default()
482 .insert(ws_id);
483 }
484 self.unfulfilled_watch_sets.insert(ws_id, (objects, t));
485 }
486 Ok(ws_id)
487 }
488
489 /// Uninstalls a previously installed WatchSetId. The method is a no-op if the watch set has
490 /// already finished and therefore it's safe to call this function unconditionally.
491 ///
492 /// # Panics
493 /// This method panics if called with a WatchSetId that was never returned by the function.
494 pub fn uninstall_watch_set(&mut self, ws_id: &WatchSetId) {
495 if let Some((obj_ids, _)) = self.unfulfilled_watch_sets.remove(ws_id) {
496 for obj_id in obj_ids {
497 let mut entry = match self.unfulfilled_watch_sets_by_object.entry(obj_id) {
498 Entry::Occupied(entry) => entry,
499 Entry::Vacant(_) => panic!("corrupted watchset state"),
500 };
501 entry.get_mut().remove(ws_id);
502 if entry.get().is_empty() {
503 entry.remove();
504 }
505 }
506 }
507 }
508
509 /// Process a pending response from the storage controller. If necessary,
510 /// return a higher-level response to our client.
511 fn process_storage_response(
512 &mut self,
513 storage_metadata: &StorageMetadata,
514 ) -> Result<Option<ControllerResponse>, anyhow::Error> {
515 let maybe_response = self.storage.process(storage_metadata)?;
516 Ok(maybe_response.and_then(
517 |mz_storage_client::controller::Response::FrontierUpdates(r)| {
518 self.handle_frontier_updates(&r)
519 },
520 ))
521 }
522
523 /// Process a pending response from the compute controller. If necessary,
524 /// return a higher-level response to our client.
525 fn process_compute_response(&mut self) -> Result<Option<ControllerResponse>, anyhow::Error> {
526 let response = self.compute.process();
527
528 let response = response.and_then(|r| match r {
529 ComputeControllerResponse::PeekNotification(uuid, peek, otel_ctx) => {
530 Some(ControllerResponse::PeekNotification(uuid, peek, otel_ctx))
531 }
532 ComputeControllerResponse::SubscribeResponse(id, tail) => {
533 Some(ControllerResponse::SubscribeResponse(id, tail))
534 }
535 ComputeControllerResponse::CopyToResponse(id, tail) => {
536 Some(ControllerResponse::CopyToResponse(id, tail))
537 }
538 ComputeControllerResponse::FrontierUpper { id, upper } => {
539 self.handle_frontier_updates(&[(id, upper)])
540 }
541 });
542 Ok(response)
543 }
544
545 /// Processes the work queued by [`Controller::ready`].
546 ///
547 /// This method is guaranteed to return "quickly" unless doing so would
548 /// compromise the correctness of the system.
549 ///
550 /// This method is **not** guaranteed to be cancellation safe. It **must**
551 /// be awaited to completion.
552 #[mz_ore::instrument(level = "debug")]
553 pub fn process(
554 &mut self,
555 storage_metadata: &StorageMetadata,
556 ) -> Result<Option<ControllerResponse>, anyhow::Error> {
557 match mem::take(&mut self.readiness) {
558 Readiness::NotReady => Ok(None),
559 Readiness::Storage => self.process_storage_response(storage_metadata),
560 Readiness::Compute => self.process_compute_response(),
561 Readiness::Metrics((id, metrics)) => self.process_replica_metrics(id, metrics),
562 Readiness::Internal(message) => Ok(Some(message)),
563 }
564 }
565
566 /// Record updates to frontiers, and propagate any necessary responses.
567 /// As of this writing (2/29/2024), the only response that can be generated
568 /// from a frontier update is `WatchSetCompleted`.
569 fn handle_frontier_updates(
570 &mut self,
571 updates: &[(GlobalId, Antichain<Timestamp>)],
572 ) -> Option<ControllerResponse> {
573 let mut finished = vec![];
574 for (obj_id, antichain) in updates {
575 let ws_ids = self.unfulfilled_watch_sets_by_object.entry(*obj_id);
576 if let Entry::Occupied(mut ws_ids) = ws_ids {
577 ws_ids.get_mut().retain(|ws_id| {
578 let mut entry = match self.unfulfilled_watch_sets.entry(*ws_id) {
579 Entry::Occupied(entry) => entry,
580 Entry::Vacant(_) => panic!("corrupted watchset state"),
581 };
582 // If this object has made more progress than required by this watchset we:
583 if !antichain.less_equal(&entry.get().1) {
584 // 1. Remove the object from the set of pending objects for the watchset
585 entry.get_mut().0.remove(obj_id);
586 // 2. Mark the watchset as finished if this was the last watched object
587 if entry.get().0.is_empty() {
588 entry.remove();
589 finished.push(*ws_id);
590 }
591 // 3. Remove the watchset from the set of pending watchsets for the object
592 false
593 } else {
594 // Otherwise we keep the watchset around to re-check in the future
595 true
596 }
597 });
598 // Clear the entry if this was the last watchset that was interested in obj_id
599 if ws_ids.get().is_empty() {
600 ws_ids.remove();
601 }
602 }
603 }
604 (!(finished.is_empty())).then(|| ControllerResponse::WatchSetFinished(finished))
605 }
606
607 fn process_replica_metrics(
608 &mut self,
609 id: ReplicaId,
610 metrics: Vec<ServiceProcessMetrics>,
611 ) -> Result<Option<ControllerResponse>, anyhow::Error> {
612 self.record_replica_metrics(id, &metrics);
613 Ok(None)
614 }
615
616 fn record_replica_metrics(&mut self, replica_id: ReplicaId, metrics: &[ServiceProcessMetrics]) {
617 if self.read_only() {
618 return;
619 }
620
621 let now = mz_ore::now::to_datetime((self.now)());
622 let now_tz = now.try_into().expect("must fit");
623
624 let replica_id = replica_id.to_string();
625 let mut row = Row::default();
626 let updates = metrics
627 .iter()
628 .enumerate()
629 .map(|(process_id, m)| {
630 row.packer().extend(&[
631 Datum::String(&replica_id),
632 Datum::UInt64(u64::cast_from(process_id)),
633 m.cpu_nano_cores.into(),
634 m.memory_bytes.into(),
635 m.disk_bytes.into(),
636 Datum::TimestampTz(now_tz),
637 m.heap_bytes.into(),
638 m.heap_limit.into(),
639 ]);
640 (row.clone(), mz_repr::Diff::ONE)
641 })
642 .collect();
643
644 self.storage
645 .append_introspection_updates(IntrospectionType::ReplicaMetricsHistory, updates);
646 }
647
648 /// Determine the "real-time recency" timestamp for all `ids`.
649 ///
650 /// Real-time recency is defined as the minimum value of `T` that all
651 /// objects can be queried at to return all data visible in the upstream
652 /// system the query was issued. In this case, "the upstream systems" are
653 /// any user sources that connect to objects outside of Materialize, such as
654 /// Kafka sources.
655 ///
656 /// If no items in `ids` connect to external systems, this function will
657 /// return `Ok(T::minimum)`.
658 pub async fn determine_real_time_recent_timestamp(
659 &self,
660 ids: BTreeSet<GlobalId>,
661 timeout: Duration,
662 ) -> Result<BoxFuture<'static, Result<Timestamp, StorageError>>, StorageError> {
663 self.storage.real_time_recent_timestamp(ids, timeout).await
664 }
665}
666
667impl Controller {
668 /// Creates a new controller.
669 ///
670 /// For correctness, this function expects to have access to the mutations
671 /// to the `storage_txn` that occurred in [`prepare_initialization`].
672 ///
673 /// # Panics
674 /// If this function is called before [`prepare_initialization`].
675 #[instrument(name = "controller::new")]
676 pub async fn new(
677 config: ControllerConfig,
678 envd_epoch: NonZeroI64,
679 read_only: bool,
680 storage_txn: &dyn StorageTxn,
681 ) -> Self {
682 if read_only {
683 tracing::info!("starting controllers in read-only mode!");
684 }
685
686 let now_fn = config.now.clone();
687 let wallclock_lag_fn = WallclockLagFn::new(now_fn);
688
689 let controller_metrics = ControllerMetrics::new(&config.metrics_registry);
690
691 let txns_metrics = Arc::new(TxnMetrics::new(&config.metrics_registry));
692 let collections_ctl = storage_collections::StorageCollectionsImpl::new(
693 config.persist_location.clone(),
694 Arc::clone(&config.persist_clients),
695 &config.metrics_registry,
696 config.now.clone(),
697 Arc::clone(&txns_metrics),
698 envd_epoch,
699 read_only,
700 config.connection_context.clone(),
701 storage_txn,
702 )
703 .await;
704
705 let collections_ctl: Arc<dyn StorageCollections + Send + Sync> = Arc::new(collections_ctl);
706
707 let storage_controller = mz_storage_controller::Controller::new(
708 config.build_info,
709 config.persist_location.clone(),
710 config.persist_clients,
711 config.now.clone(),
712 wallclock_lag_fn.clone(),
713 Arc::clone(&txns_metrics),
714 read_only,
715 &config.metrics_registry,
716 controller_metrics.clone(),
717 config.connection_context,
718 storage_txn,
719 Arc::clone(&collections_ctl),
720 )
721 .await;
722
723 let storage_collections = Arc::clone(&collections_ctl);
724 let compute_controller = ComputeController::new(
725 config.build_info,
726 storage_collections,
727 read_only,
728 &config.metrics_registry,
729 config.persist_location,
730 controller_metrics,
731 config.now.clone(),
732 wallclock_lag_fn,
733 );
734 let (metrics_tx, metrics_rx) = mpsc::unbounded_channel();
735
736 let this = Self {
737 storage: Box::new(storage_controller),
738 storage_collections: collections_ctl,
739 compute: compute_controller,
740 clusterd_image: config.clusterd_image,
741 init_container_image: config.init_container_image,
742 deploy_generation: config.deploy_generation,
743 read_only,
744 orchestrator: config.orchestrator.namespace("cluster"),
745 readiness: Readiness::NotReady,
746 metrics_tasks: BTreeMap::new(),
747 metrics_tx,
748 metrics_rx,
749 now: config.now,
750 persist_pubsub_url: config.persist_pubsub_url,
751 secrets_args: config.secrets_args,
752 unfulfilled_watch_sets_by_object: BTreeMap::new(),
753 unfulfilled_watch_sets: BTreeMap::new(),
754 watch_set_id_gen: Gen::default(),
755 immediate_watch_sets: Vec::new(),
756 dyncfg: mz_dyncfgs::all_dyncfgs(),
757 replica_dyncfg_overrides: BTreeMap::new(),
758 replica_http_locator: config.replica_http_locator,
759 };
760
761 if !this.read_only {
762 this.remove_past_generation_replicas_in_background();
763 }
764
765 this
766 }
767}