1use std::collections::{BTreeMap, BTreeSet};
36use std::sync::{Arc, Mutex};
37use std::time::Duration;
38
39use mz_build_info::BuildInfo;
40use mz_cluster_client::client::ClusterReplicaLocation;
41use mz_cluster_client::metrics::ControllerMetrics;
42use mz_cluster_client::{ReplicaId, WallclockLagFn};
43use mz_compute_types::ComputeInstanceId;
44use mz_compute_types::config::ComputeReplicaConfig;
45use mz_compute_types::dataflows::DataflowDescription;
46use mz_compute_types::dyncfgs::{
47 COMPUTE_REPLICA_EXPIRATION_OFFSET, ENABLE_ARRANGEMENT_DICTIONARY_COMPRESSION_ALPHA,
48};
49use mz_dyncfg::{ConfigSet, ConfigUpdates};
50use mz_expr::RowSetFinishing;
51use mz_expr::row::RowCollection;
52use mz_ore::cast::CastFrom;
53use mz_ore::metrics::MetricsRegistry;
54use mz_ore::now::NowFn;
55use mz_ore::tracing::OpenTelemetryContext;
56use mz_persist_types::PersistLocation;
57use mz_repr::{GlobalId, RelationDesc, Row, Timestamp};
58use mz_storage_client::controller::StorageController;
59use mz_storage_types::dyncfgs::ORE_OVERFLOWING_BEHAVIOR;
60use mz_storage_types::read_holds::ReadHold;
61use mz_storage_types::read_policy::ReadPolicy;
62use mz_storage_types::time_dependence::{TimeDependence, TimeDependenceError};
63use prometheus::proto::LabelPair;
64use serde::{Deserialize, Serialize};
65use timely::PartialOrder;
66use timely::progress::Antichain;
67use tokio::sync::{mpsc, oneshot};
68use tokio::time::{self, MissedTickBehavior};
69use uuid::Uuid;
70
71use crate::controller::error::{
72 CollectionLookupError, CollectionMissing, CollectionUpdateError, DataflowCreationError,
73 HydrationCheckBadTarget, InstanceExists, InstanceMissing, PeekError, ReadPolicyError,
74 ReplicaCreationError, ReplicaDropError,
75};
76use crate::controller::instance::{Instance, SharedCollectionState};
77use crate::controller::introspection::{IntrospectionUpdates, spawn_introspection_sink};
78use crate::controller::replica::ReplicaConfig;
79use crate::logging::{LogVariant, LoggingConfig};
80use crate::metrics::ComputeControllerMetrics;
81use crate::protocol::command::{ComputeParameters, PeekTarget};
82use crate::protocol::response::{PeekResponse, SubscribeBatch};
83
84mod instance;
85mod introspection;
86mod replica;
87mod sequential_hydration;
88
89pub mod error;
90pub mod instance_client;
91pub use instance_client::InstanceClient;
92
93pub(crate) type StorageCollections =
94 Arc<dyn mz_storage_client::storage_collections::StorageCollections + Send + Sync>;
95
96#[derive(Debug)]
98pub enum ComputeControllerResponse {
99 PeekNotification(Uuid, PeekNotification, OpenTelemetryContext),
101 SubscribeResponse(GlobalId, SubscribeBatch),
103 CopyToResponse(GlobalId, Result<u64, anyhow::Error>),
114 FrontierUpper {
119 id: GlobalId,
121 upper: Antichain<Timestamp>,
123 },
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
128pub enum PeekNotification {
129 Success {
131 rows: u64,
133 result_size: u64,
135 },
136 Error(String),
138 Canceled,
140}
141
142impl PeekNotification {
143 fn new(peek_response: &PeekResponse, offset: usize, limit: Option<usize>) -> Self {
146 match peek_response {
147 PeekResponse::Rows(rows) => {
148 let num_rows = u64::cast_from(RowCollection::offset_limit(
149 rows.iter().map(|r| r.count()).sum(),
150 offset,
151 limit,
152 ));
153 let result_size = u64::cast_from(rows.iter().map(|r| r.byte_len()).sum::<usize>());
154
155 tracing::trace!(?num_rows, ?result_size, "inline peek result");
156
157 Self::Success {
158 rows: num_rows,
159 result_size,
160 }
161 }
162 PeekResponse::Stashed(stashed_response) => {
163 let rows = stashed_response.num_rows(offset, limit);
164 let result_size = stashed_response.size_bytes();
165
166 tracing::trace!(?rows, ?result_size, "stashed peek result");
167
168 Self::Success {
169 rows: u64::cast_from(rows),
170 result_size: u64::cast_from(result_size),
171 }
172 }
173 PeekResponse::Error(err) => Self::Error(err.clone()),
174 PeekResponse::Canceled => Self::Canceled,
175 }
176 }
177}
178
179pub struct ComputeController {
181 instances: BTreeMap<ComputeInstanceId, InstanceState>,
182 instance_workload_classes: Arc<Mutex<BTreeMap<ComputeInstanceId, Option<String>>>>,
186 build_info: &'static BuildInfo,
187 storage_collections: StorageCollections,
189 initialized: bool,
191 read_only: bool,
197 config: ComputeParameters,
199 peek_stash_persist_location: PersistLocation,
201 stashed_response: Option<ComputeControllerResponse>,
203 metrics: ComputeControllerMetrics,
205 now: NowFn,
207 wallclock_lag: WallclockLagFn<Timestamp>,
209 dyncfg: Arc<ConfigSet>,
214
215 response_rx: mpsc::UnboundedReceiver<ComputeControllerResponse>,
217 response_tx: mpsc::UnboundedSender<ComputeControllerResponse>,
219 introspection_rx: Option<mpsc::UnboundedReceiver<IntrospectionUpdates>>,
224 introspection_tx: mpsc::UnboundedSender<IntrospectionUpdates>,
226
227 maintenance_ticker: tokio::time::Interval,
229 maintenance_scheduled: bool,
231}
232
233impl ComputeController {
234 pub fn new(
236 build_info: &'static BuildInfo,
237 storage_collections: StorageCollections,
238 read_only: bool,
239 metrics_registry: &MetricsRegistry,
240 peek_stash_persist_location: PersistLocation,
241 controller_metrics: ControllerMetrics,
242 now: NowFn,
243 wallclock_lag: WallclockLagFn<Timestamp>,
244 ) -> Self {
245 let (response_tx, response_rx) = mpsc::unbounded_channel();
246 let (introspection_tx, introspection_rx) = mpsc::unbounded_channel();
247
248 let mut maintenance_ticker = time::interval(Duration::from_secs(1));
249 maintenance_ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
250
251 let instance_workload_classes = Arc::new(Mutex::new(BTreeMap::<
252 ComputeInstanceId,
253 Option<String>,
254 >::new()));
255
256 metrics_registry.register_postprocessor({
260 let instance_workload_classes = Arc::clone(&instance_workload_classes);
261 move |metrics| {
262 let instance_workload_classes = instance_workload_classes
263 .lock()
264 .expect("lock poisoned")
265 .iter()
266 .map(|(id, workload_class)| (id.to_string(), workload_class.clone()))
267 .collect::<BTreeMap<String, Option<String>>>();
268 for metric in metrics {
269 'metric: for metric in metric.mut_metric() {
270 for label in metric.get_label() {
271 if label.name() == "instance_id" {
272 if let Some(workload_class) = instance_workload_classes
273 .get(label.value())
274 .cloned()
275 .flatten()
276 {
277 let mut label = LabelPair::default();
278 label.set_name("workload_class".into());
279 label.set_value(workload_class.clone());
280
281 let mut labels = metric.take_label();
282 labels.push(label);
283 metric.set_label(labels);
284 }
285 continue 'metric;
286 }
287 }
288 }
289 }
290 }
291 });
292
293 let metrics = ComputeControllerMetrics::new(metrics_registry, controller_metrics);
294
295 Self {
296 instances: BTreeMap::new(),
297 instance_workload_classes,
298 build_info,
299 storage_collections,
300 initialized: false,
301 read_only,
302 config: Default::default(),
303 peek_stash_persist_location,
304 stashed_response: None,
305 metrics,
306 now,
307 wallclock_lag,
308 dyncfg: Arc::new(mz_dyncfgs::all_dyncfgs()),
309 response_rx,
310 response_tx,
311 introspection_rx: Some(introspection_rx),
312 introspection_tx,
313 maintenance_ticker,
314 maintenance_scheduled: false,
315 }
316 }
317
318 pub fn start_introspection_sink(&mut self, storage_controller: &dyn StorageController) {
323 if let Some(rx) = self.introspection_rx.take() {
324 spawn_introspection_sink(rx, storage_controller);
325 }
326 }
327
328 pub fn instance_exists(&self, id: ComputeInstanceId) -> bool {
330 self.instances.contains_key(&id)
331 }
332
333 fn instance(&self, id: ComputeInstanceId) -> Result<&InstanceState, InstanceMissing> {
335 self.instances.get(&id).ok_or(InstanceMissing(id))
336 }
337
338 pub fn instance_client(
340 &self,
341 id: ComputeInstanceId,
342 ) -> Result<InstanceClient, InstanceMissing> {
343 self.instance(id).map(|instance| instance.client.clone())
344 }
345
346 fn instance_mut(
348 &mut self,
349 id: ComputeInstanceId,
350 ) -> Result<&mut InstanceState, InstanceMissing> {
351 self.instances.get_mut(&id).ok_or(InstanceMissing(id))
352 }
353
354 pub fn collection_ids(
356 &self,
357 instance_id: ComputeInstanceId,
358 ) -> Result<impl Iterator<Item = GlobalId> + '_, InstanceMissing> {
359 let instance = self.instance(instance_id)?;
360 let ids = instance.collections.keys().copied();
361 Ok(ids)
362 }
363
364 pub fn collection_frontiers(
369 &self,
370 collection_id: GlobalId,
371 instance_id: Option<ComputeInstanceId>,
372 ) -> Result<CollectionFrontiers, CollectionLookupError> {
373 let collection = match instance_id {
374 Some(id) => self.instance(id)?.collection(collection_id)?,
375 None => self
376 .instances
377 .values()
378 .find_map(|i| i.collections.get(&collection_id))
379 .ok_or(CollectionMissing(collection_id))?,
380 };
381
382 Ok(collection.frontiers())
383 }
384
385 pub fn collection_reverse_dependencies(
387 &self,
388 instance_id: ComputeInstanceId,
389 id: GlobalId,
390 ) -> Result<impl Iterator<Item = GlobalId> + '_, InstanceMissing> {
391 let instance = self.instance(instance_id)?;
392 let collections = instance.collections.iter();
393 let ids = collections
394 .filter_map(move |(cid, c)| c.compute_dependencies.contains(&id).then_some(*cid));
395 Ok(ids)
396 }
397
398 pub async fn collection_hydrated(
404 &self,
405 instance_id: ComputeInstanceId,
406 collection_id: GlobalId,
407 ) -> Result<bool, anyhow::Error> {
408 let instance = self.instance(instance_id)?;
409
410 let res = instance
411 .call_sync(move |i| i.collection_hydrated(collection_id))
412 .await?;
413
414 Ok(res)
415 }
416
417 pub fn collections_hydrated_for_replicas(
424 &self,
425 instance_id: ComputeInstanceId,
426 replicas: Vec<ReplicaId>,
427 exclude_collections: BTreeSet<GlobalId>,
428 ) -> Result<oneshot::Receiver<bool>, anyhow::Error> {
429 let instance = self.instance(instance_id)?;
430
431 if !instance.replicas.is_empty()
433 && !replicas.iter().any(|id| instance.replicas.contains(id))
434 {
435 return Err(HydrationCheckBadTarget(replicas).into());
436 }
437
438 let (tx, rx) = oneshot::channel();
439 instance.call(move |i| {
440 let result = i
441 .collections_hydrated_on_replicas(Some(replicas), &exclude_collections)
442 .expect("validated");
443 let _ = tx.send(result);
444 });
445
446 Ok(rx)
447 }
448
449 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
453 let Self {
460 instances,
461 instance_workload_classes,
462 build_info: _,
463 storage_collections: _,
464 initialized,
465 read_only,
466 config: _,
467 peek_stash_persist_location: _,
468 stashed_response,
469 metrics: _,
470 now: _,
471 wallclock_lag: _,
472 dyncfg: _,
473 response_rx: _,
474 response_tx: _,
475 introspection_rx: _,
476 introspection_tx: _,
477 maintenance_ticker: _,
478 maintenance_scheduled,
479 } = self;
480
481 let mut instances_dump = BTreeMap::new();
482 for (id, instance) in instances {
483 let dump = instance.dump().await?;
484 instances_dump.insert(id.to_string(), dump);
485 }
486
487 let instance_workload_classes: BTreeMap<_, _> = instance_workload_classes
488 .lock()
489 .expect("lock poisoned")
490 .iter()
491 .map(|(id, wc)| (id.to_string(), format!("{wc:?}")))
492 .collect();
493
494 Ok(serde_json::json!({
495 "instances": instances_dump,
496 "instance_workload_classes": instance_workload_classes,
497 "initialized": initialized,
498 "read_only": read_only,
499 "stashed_response": format!("{stashed_response:?}"),
500 "maintenance_scheduled": maintenance_scheduled,
501 }))
502 }
503}
504
505impl ComputeController {
506 pub fn create_instance(
508 &mut self,
509 id: ComputeInstanceId,
510 arranged_logs: BTreeMap<LogVariant, GlobalId>,
511 workload_class: Option<String>,
512 ) -> Result<(), InstanceExists> {
513 if self.instances.contains_key(&id) {
514 return Err(InstanceExists(id));
515 }
516
517 let mut collections = BTreeMap::new();
518 let mut logs = Vec::with_capacity(arranged_logs.len());
519 for (&log, &id) in &arranged_logs {
520 let collection = Collection::new_log();
521 let shared = collection.shared.clone();
522 collections.insert(id, collection);
523 logs.push((log, id, shared));
524 }
525
526 let client = InstanceClient::spawn(
527 id,
528 self.build_info,
529 Arc::clone(&self.storage_collections),
530 self.peek_stash_persist_location.clone(),
531 logs,
532 self.metrics.for_instance(id),
533 self.now.clone(),
534 self.wallclock_lag.clone(),
535 Arc::clone(&self.dyncfg),
536 self.response_tx.clone(),
537 self.introspection_tx.clone(),
538 self.read_only,
539 );
540
541 let instance = InstanceState::new(client, collections);
542 self.instances.insert(id, instance);
543
544 self.instance_workload_classes
545 .lock()
546 .expect("lock poisoned")
547 .insert(id, workload_class.clone());
548
549 let instance = self.instances.get_mut(&id).expect("instance just added");
550 if self.initialized {
551 instance.call(Instance::initialization_complete);
552 }
553
554 let mut config_params = self.config.clone();
560 config_params.workload_class = Some(workload_class);
561 instance.call(|i| i.update_configuration(config_params));
562
563 Ok(())
564 }
565
566 pub fn update_instance_workload_class(
568 &mut self,
569 id: ComputeInstanceId,
570 workload_class: Option<String>,
571 ) -> Result<(), InstanceMissing> {
572 let _ = self.instance(id)?;
574
575 self.instance_workload_classes
576 .lock()
577 .expect("lock poisoned")
578 .insert(id, workload_class);
579
580 self.update_configuration(Default::default());
582
583 Ok(())
584 }
585
586 pub fn drop_instance(&mut self, id: ComputeInstanceId) {
592 if let Some(instance) = self.instances.remove(&id) {
593 instance.call(|i| i.shutdown());
594 }
595
596 self.instance_workload_classes
597 .lock()
598 .expect("lock poisoned")
599 .remove(&id);
600 }
601
602 pub fn dyncfg(&self) -> &Arc<ConfigSet> {
604 &self.dyncfg
605 }
606
607 pub fn update_configuration(&mut self, config_params: ComputeParameters) {
609 config_params.dyncfg_updates.apply(&self.dyncfg);
611
612 let instance_workload_classes = self
613 .instance_workload_classes
614 .lock()
615 .expect("lock poisoned");
616
617 for (id, instance) in self.instances.iter_mut() {
620 let mut params = config_params.clone();
621 params.workload_class = Some(instance_workload_classes[id].clone());
622 instance.call(|i| i.update_configuration(params));
623 }
624
625 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(&self.dyncfg);
626 match overflowing_behavior.parse() {
627 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
628 Err(err) => {
629 tracing::error!(
630 err,
631 overflowing_behavior,
632 "Invalid value for ore_overflowing_behavior"
633 );
634 }
635 }
636
637 self.config.update(config_params);
639 }
640
641 pub fn update_replica_dyncfg_overrides(
650 &mut self,
651 mut overrides: BTreeMap<ComputeInstanceId, BTreeMap<ReplicaId, ConfigUpdates>>,
652 ) {
653 for (id, instance) in self.instances.iter_mut() {
654 let instance_overrides = overrides.remove(id).unwrap_or_default();
655 instance.call(move |i| i.update_replica_dyncfg_overrides(instance_overrides));
656 }
657 }
658
659 pub fn initialization_complete(&mut self) {
665 self.initialized = true;
666 for instance in self.instances.values_mut() {
667 instance.call(Instance::initialization_complete);
668 }
669 }
670
671 pub async fn ready(&mut self) {
679 if self.stashed_response.is_some() {
680 return;
682 }
683 if self.maintenance_scheduled {
684 return;
686 }
687
688 tokio::select! {
689 resp = self.response_rx.recv() => {
690 let resp = resp.expect("`self.response_tx` not dropped");
691 self.stashed_response = Some(resp);
692 }
693 _ = self.maintenance_ticker.tick() => {
694 self.maintenance_scheduled = true;
695 },
696 }
697 }
698
699 pub fn add_replica_to_instance(
701 &mut self,
702 instance_id: ComputeInstanceId,
703 replica_id: ReplicaId,
704 location: ClusterReplicaLocation,
705 config: ComputeReplicaConfig,
706 ) -> Result<(), ReplicaCreationError> {
707 use ReplicaCreationError::*;
708
709 let instance = self.instance(instance_id)?;
710
711 if instance.replicas.contains(&replica_id) {
713 return Err(ReplicaExists(replica_id));
714 }
715
716 let (enable_logging, interval) = match config.logging.interval {
717 Some(interval) => (true, interval),
718 None => (false, Duration::from_secs(1)),
719 };
720
721 let expiration_offset = COMPUTE_REPLICA_EXPIRATION_OFFSET.get(&self.dyncfg);
722
723 let arrangement_dictionary_compression = ENABLE_ARRANGEMENT_DICTIONARY_COMPRESSION_ALPHA
730 .get(&self.dyncfg)
731 && config.arrangement_compression;
732
733 let replica_config = ReplicaConfig {
734 location,
735 logging: LoggingConfig {
736 interval,
737 enable_logging,
738 log_logging: config.logging.log_logging,
739 index_logs: Default::default(),
740 },
741 grpc_client: self.config.grpc_client.clone(),
742 expiration_offset: (!expiration_offset.is_zero()).then_some(expiration_offset),
743 arrangement_dictionary_compression,
744 };
745
746 let instance = self.instance_mut(instance_id).expect("validated");
747 instance.replicas.insert(replica_id);
748
749 instance.call(move |i| {
750 i.add_replica(replica_id, replica_config, None)
751 .expect("validated")
752 });
753
754 Ok(())
755 }
756
757 pub fn drop_replica(
759 &mut self,
760 instance_id: ComputeInstanceId,
761 replica_id: ReplicaId,
762 ) -> Result<(), ReplicaDropError> {
763 use ReplicaDropError::*;
764
765 let instance = self.instance_mut(instance_id)?;
766
767 if !instance.replicas.contains(&replica_id) {
769 return Err(ReplicaMissing(replica_id));
770 }
771
772 instance.replicas.remove(&replica_id);
773
774 instance.call(move |i| i.remove_replica(replica_id).expect("validated"));
775
776 Ok(())
777 }
778
779 pub fn create_dataflow(
786 &mut self,
787 instance_id: ComputeInstanceId,
788 mut dataflow: DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
789 target_replica: Option<ReplicaId>,
790 ) -> Result<(), DataflowCreationError> {
791 use DataflowCreationError::*;
792
793 let instance = self.instance(instance_id)?;
794
795 if let Some(replica_id) = target_replica {
797 if !instance.replicas.contains(&replica_id) {
798 return Err(ReplicaMissing(replica_id));
799 }
800 assert!(
801 dataflow.exported_index_ids().next().is_none(),
802 "Replica-targeted indexes are not supported"
803 );
804 }
805
806 let as_of = dataflow.as_of.as_ref().ok_or(MissingAsOf)?;
808 if as_of.is_empty() && dataflow.subscribe_ids().next().is_some() {
809 return Err(EmptyAsOfForSubscribe);
810 }
811 if as_of.is_empty() && dataflow.copy_to_ids().next().is_some() {
812 return Err(EmptyAsOfForCopyTo);
813 }
814
815 let storage_ids = dataflow.imported_source_ids().collect();
817 let mut import_read_holds = self.storage_collections.acquire_read_holds(storage_ids)?;
818 for id in dataflow.imported_index_ids() {
819 let read_hold = instance.acquire_read_hold(id)?;
820 import_read_holds.push(read_hold);
821 }
822 for hold in &import_read_holds {
823 if PartialOrder::less_than(as_of, hold.since()) {
824 return Err(SinceViolation(hold.id()));
825 }
826 }
827
828 for id in dataflow.persist_sink_ids() {
830 if self.storage_collections.check_exists(id).is_err() {
831 return Err(CollectionMissing(id));
832 }
833 }
834 let time_dependence = self
835 .determine_time_dependence(instance_id, &dataflow)
836 .expect("must exist");
837
838 let instance = self.instance_mut(instance_id).expect("validated");
839
840 let mut shared_collection_state = BTreeMap::new();
841 for id in dataflow.export_ids() {
842 let shared = SharedCollectionState::new(as_of.clone());
843 let collection = Collection {
844 write_only: dataflow.sink_exports.contains_key(&id),
845 compute_dependencies: dataflow.imported_index_ids().collect(),
846 shared: shared.clone(),
847 time_dependence: time_dependence.clone(),
848 };
849 instance.collections.insert(id, collection);
850 shared_collection_state.insert(id, shared);
851 }
852
853 dataflow.time_dependence = time_dependence;
854
855 instance.call(move |i| {
856 i.create_dataflow(
857 dataflow,
858 import_read_holds,
859 shared_collection_state,
860 target_replica,
861 )
862 .expect("validated")
863 });
864
865 Ok(())
866 }
867
868 pub fn drop_collections(
871 &mut self,
872 instance_id: ComputeInstanceId,
873 collection_ids: Vec<GlobalId>,
874 ) -> Result<(), CollectionUpdateError> {
875 let instance = self.instance_mut(instance_id)?;
876
877 for id in &collection_ids {
879 instance.collection(*id)?;
880 }
881
882 for id in &collection_ids {
883 instance.collections.remove(id);
884 }
885
886 instance.call(|i| i.drop_collections(collection_ids).expect("validated"));
887
888 Ok(())
889 }
890
891 pub fn peek(
898 &self,
899 instance_id: ComputeInstanceId,
900 peek_target: PeekTarget,
901 literal_constraints: Option<Vec<Row>>,
902 uuid: Uuid,
903 timestamp: Timestamp,
904 result_desc: RelationDesc,
905 finishing: RowSetFinishing,
906 map_filter_project: mz_expr::SafeMfpPlan,
907 read_hold: ReadHold,
908 target_replica: Option<ReplicaId>,
909 peek_response_tx: oneshot::Sender<PeekResponse>,
910 ) -> Result<(), PeekError> {
911 use PeekError::*;
912
913 let instance = self.instance(instance_id)?;
914
915 if let Some(replica_id) = target_replica {
917 if !instance.replicas.contains(&replica_id) {
918 return Err(ReplicaMissing(replica_id));
919 }
920 }
921
922 if read_hold.id() != peek_target.id() {
925 return Err(ReadHoldIdMismatch(read_hold.id()));
926 }
927 if !read_hold.since().less_equal(×tamp) {
928 return Err(SinceViolation(peek_target.id()));
929 }
930
931 instance.call(move |i| {
932 i.peek(
933 peek_target,
934 literal_constraints,
935 uuid,
936 timestamp,
937 result_desc,
938 finishing,
939 map_filter_project,
940 read_hold,
941 target_replica,
942 peek_response_tx,
943 )
944 .expect("validated")
945 });
946
947 Ok(())
948 }
949
950 pub fn cancel_peek(
960 &self,
961 instance_id: ComputeInstanceId,
962 uuid: Uuid,
963 reason: PeekResponse,
964 ) -> Result<(), InstanceMissing> {
965 self.instance(instance_id)?
966 .call(move |i| i.cancel_peek(uuid, reason));
967 Ok(())
968 }
969
970 pub fn set_read_policy(
982 &self,
983 instance_id: ComputeInstanceId,
984 policies: Vec<(GlobalId, ReadPolicy)>,
985 ) -> Result<(), ReadPolicyError> {
986 use ReadPolicyError::*;
987
988 let instance = self.instance(instance_id)?;
989
990 for (id, _) in &policies {
992 let collection = instance.collection(*id)?;
993 if collection.write_only {
994 return Err(WriteOnlyCollection(*id));
995 }
996 }
997
998 self.instance(instance_id)?
999 .call(|i| i.set_read_policy(policies).expect("validated"));
1000
1001 Ok(())
1002 }
1003
1004 pub fn acquire_read_hold(
1006 &self,
1007 instance_id: ComputeInstanceId,
1008 collection_id: GlobalId,
1009 ) -> Result<ReadHold, CollectionUpdateError> {
1010 let read_hold = self
1011 .instance(instance_id)?
1012 .acquire_read_hold(collection_id)?;
1013 Ok(read_hold)
1014 }
1015
1016 fn determine_time_dependence(
1018 &self,
1019 instance_id: ComputeInstanceId,
1020 dataflow: &DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
1021 ) -> Result<Option<TimeDependence>, TimeDependenceError> {
1022 let instance = self
1023 .instance(instance_id)
1024 .map_err(|err| TimeDependenceError::InstanceMissing(err.0))?;
1025 let mut time_dependencies = Vec::new();
1026
1027 for id in dataflow.imported_index_ids() {
1028 let dependence = instance
1029 .get_time_dependence(id)
1030 .map_err(|err| TimeDependenceError::CollectionMissing(err.0))?;
1031 time_dependencies.push(dependence);
1032 }
1033
1034 'source: for id in dataflow.imported_source_ids() {
1035 for instance in self.instances.values() {
1038 if let Ok(dependence) = instance.get_time_dependence(id) {
1039 time_dependencies.push(dependence);
1040 continue 'source;
1041 }
1042 }
1043
1044 time_dependencies.push(self.storage_collections.determine_time_dependence(id)?);
1046 }
1047
1048 Ok(TimeDependence::merge(
1049 time_dependencies,
1050 dataflow.refresh_schedule.as_ref(),
1051 ))
1052 }
1053
1054 #[mz_ore::instrument(level = "debug")]
1056 pub fn process(&mut self) -> Option<ComputeControllerResponse> {
1057 if self.maintenance_scheduled {
1059 self.maintain();
1060 self.maintenance_scheduled = false;
1061 }
1062
1063 self.stashed_response.take()
1065 }
1066
1067 #[mz_ore::instrument(level = "debug")]
1068 fn maintain(&mut self) {
1069 for instance in self.instances.values_mut() {
1071 instance.call(Instance::maintain);
1072 }
1073 }
1074
1075 pub fn allow_writes(
1079 &mut self,
1080 instance_id: ComputeInstanceId,
1081 collection_id: GlobalId,
1082 ) -> Result<(), CollectionUpdateError> {
1083 if self.read_only {
1084 tracing::debug!("Skipping allow_writes in read-only mode");
1085 return Ok(());
1086 }
1087
1088 let instance = self.instance_mut(instance_id)?;
1089
1090 instance.collection(collection_id)?;
1092
1093 instance.call(move |i| i.allow_writes(collection_id).expect("validated"));
1094
1095 Ok(())
1096 }
1097}
1098
1099#[derive(Debug)]
1100struct InstanceState {
1101 client: InstanceClient,
1102 replicas: BTreeSet<ReplicaId>,
1103 collections: BTreeMap<GlobalId, Collection>,
1104}
1105
1106impl InstanceState {
1107 fn new(client: InstanceClient, collections: BTreeMap<GlobalId, Collection>) -> Self {
1108 Self {
1109 client,
1110 replicas: Default::default(),
1111 collections,
1112 }
1113 }
1114
1115 fn collection(&self, id: GlobalId) -> Result<&Collection, CollectionMissing> {
1116 self.collections.get(&id).ok_or(CollectionMissing(id))
1117 }
1118
1119 fn call<F>(&self, f: F)
1125 where
1126 F: FnOnce(&mut Instance) + Send + 'static,
1127 {
1128 self.client.call(f).expect("instance not dropped")
1129 }
1130
1131 async fn call_sync<F, R>(&self, f: F) -> R
1137 where
1138 F: FnOnce(&mut Instance) -> R + Send + 'static,
1139 R: Send + 'static,
1140 {
1141 self.client
1142 .call_sync(f)
1143 .await
1144 .expect("instance not dropped")
1145 }
1146
1147 pub fn acquire_read_hold(&self, id: GlobalId) -> Result<ReadHold, CollectionMissing> {
1149 let collection = self.collection(id)?;
1159 let since = collection.shared.lock_read_capabilities(|caps| {
1160 let since = caps.frontier().to_owned();
1161 caps.update_iter(since.iter().map(|t| (t.clone(), 1)));
1162 since
1163 });
1164
1165 let hold = ReadHold::new(id, since, self.client.read_hold_tx());
1166 Ok(hold)
1167 }
1168
1169 fn get_time_dependence(
1171 &self,
1172 id: GlobalId,
1173 ) -> Result<Option<TimeDependence>, CollectionMissing> {
1174 Ok(self.collection(id)?.time_dependence.clone())
1175 }
1176
1177 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
1179 let Self {
1181 client: _,
1182 replicas,
1183 collections,
1184 } = self;
1185
1186 let instance = self.call_sync(|i| i.dump()).await?;
1187 let replicas: Vec<_> = replicas.iter().map(|id| id.to_string()).collect();
1188 let collections: BTreeMap<_, _> = collections
1189 .iter()
1190 .map(|(id, c)| (id.to_string(), format!("{c:?}")))
1191 .collect();
1192
1193 Ok(serde_json::json!({
1194 "instance": instance,
1195 "replicas": replicas,
1196 "collections": collections,
1197 }))
1198 }
1199}
1200
1201#[derive(Debug)]
1202struct Collection {
1203 write_only: bool,
1205 compute_dependencies: BTreeSet<GlobalId>,
1206 shared: SharedCollectionState,
1207 time_dependence: Option<TimeDependence>,
1210}
1211
1212impl Collection {
1213 fn new_log() -> Self {
1214 let as_of = Antichain::from_elem(Timestamp::MIN);
1215 Self {
1216 write_only: false,
1217 compute_dependencies: Default::default(),
1218 shared: SharedCollectionState::new(as_of),
1219 time_dependence: Some(TimeDependence::default()),
1220 }
1221 }
1222
1223 fn frontiers(&self) -> CollectionFrontiers {
1224 let read_frontier = self
1225 .shared
1226 .lock_read_capabilities(|c| c.frontier().to_owned());
1227 let write_frontier = self.shared.lock_write_frontier(|f| f.clone());
1228 CollectionFrontiers {
1229 read_frontier,
1230 write_frontier,
1231 }
1232 }
1233}
1234
1235#[derive(Clone, Debug)]
1237pub struct CollectionFrontiers {
1238 pub read_frontier: Antichain<Timestamp>,
1240 pub write_frontier: Antichain<Timestamp>,
1242}
1243
1244impl Default for CollectionFrontiers {
1245 fn default() -> Self {
1246 Self {
1247 read_frontier: Antichain::from_elem(Timestamp::MIN),
1248 write_frontier: Antichain::from_elem(Timestamp::MIN),
1249 }
1250 }
1251}