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 =
727 ENABLE_ARRANGEMENT_DICTIONARY_COMPRESSION_ALPHA.get(&self.dyncfg);
728
729 let replica_config = ReplicaConfig {
730 location,
731 logging: LoggingConfig {
732 interval,
733 enable_logging,
734 log_logging: config.logging.log_logging,
735 index_logs: Default::default(),
736 },
737 grpc_client: self.config.grpc_client.clone(),
738 expiration_offset: (!expiration_offset.is_zero()).then_some(expiration_offset),
739 arrangement_dictionary_compression,
740 };
741
742 let instance = self.instance_mut(instance_id).expect("validated");
743 instance.replicas.insert(replica_id);
744
745 instance.call(move |i| {
746 i.add_replica(replica_id, replica_config, None)
747 .expect("validated")
748 });
749
750 Ok(())
751 }
752
753 pub fn drop_replica(
755 &mut self,
756 instance_id: ComputeInstanceId,
757 replica_id: ReplicaId,
758 ) -> Result<(), ReplicaDropError> {
759 use ReplicaDropError::*;
760
761 let instance = self.instance_mut(instance_id)?;
762
763 if !instance.replicas.contains(&replica_id) {
765 return Err(ReplicaMissing(replica_id));
766 }
767
768 instance.replicas.remove(&replica_id);
769
770 instance.call(move |i| i.remove_replica(replica_id).expect("validated"));
771
772 Ok(())
773 }
774
775 pub fn create_dataflow(
782 &mut self,
783 instance_id: ComputeInstanceId,
784 mut dataflow: DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
785 target_replica: Option<ReplicaId>,
786 ) -> Result<(), DataflowCreationError> {
787 use DataflowCreationError::*;
788
789 let instance = self.instance(instance_id)?;
790
791 if let Some(replica_id) = target_replica {
793 if !instance.replicas.contains(&replica_id) {
794 return Err(ReplicaMissing(replica_id));
795 }
796 assert!(
797 dataflow.exported_index_ids().next().is_none(),
798 "Replica-targeted indexes are not supported"
799 );
800 }
801
802 let as_of = dataflow.as_of.as_ref().ok_or(MissingAsOf)?;
804 if as_of.is_empty() && dataflow.subscribe_ids().next().is_some() {
805 return Err(EmptyAsOfForSubscribe);
806 }
807 if as_of.is_empty() && dataflow.copy_to_ids().next().is_some() {
808 return Err(EmptyAsOfForCopyTo);
809 }
810
811 let storage_ids = dataflow.imported_source_ids().collect();
813 let mut import_read_holds = self.storage_collections.acquire_read_holds(storage_ids)?;
814 for id in dataflow.imported_index_ids() {
815 let read_hold = instance.acquire_read_hold(id)?;
816 import_read_holds.push(read_hold);
817 }
818 for hold in &import_read_holds {
819 if PartialOrder::less_than(as_of, hold.since()) {
820 return Err(SinceViolation(hold.id()));
821 }
822 }
823
824 for id in dataflow.persist_sink_ids() {
826 if self.storage_collections.check_exists(id).is_err() {
827 return Err(CollectionMissing(id));
828 }
829 }
830 let time_dependence = self
831 .determine_time_dependence(instance_id, &dataflow)
832 .expect("must exist");
833
834 let instance = self.instance_mut(instance_id).expect("validated");
835
836 let mut shared_collection_state = BTreeMap::new();
837 for id in dataflow.export_ids() {
838 let shared = SharedCollectionState::new(as_of.clone());
839 let collection = Collection {
840 write_only: dataflow.sink_exports.contains_key(&id),
841 compute_dependencies: dataflow.imported_index_ids().collect(),
842 shared: shared.clone(),
843 time_dependence: time_dependence.clone(),
844 };
845 instance.collections.insert(id, collection);
846 shared_collection_state.insert(id, shared);
847 }
848
849 dataflow.time_dependence = time_dependence;
850
851 instance.call(move |i| {
852 i.create_dataflow(
853 dataflow,
854 import_read_holds,
855 shared_collection_state,
856 target_replica,
857 )
858 .expect("validated")
859 });
860
861 Ok(())
862 }
863
864 pub fn drop_collections(
867 &mut self,
868 instance_id: ComputeInstanceId,
869 collection_ids: Vec<GlobalId>,
870 ) -> Result<(), CollectionUpdateError> {
871 let instance = self.instance_mut(instance_id)?;
872
873 for id in &collection_ids {
875 instance.collection(*id)?;
876 }
877
878 for id in &collection_ids {
879 instance.collections.remove(id);
880 }
881
882 instance.call(|i| i.drop_collections(collection_ids).expect("validated"));
883
884 Ok(())
885 }
886
887 pub fn peek(
894 &self,
895 instance_id: ComputeInstanceId,
896 peek_target: PeekTarget,
897 literal_constraints: Option<Vec<Row>>,
898 uuid: Uuid,
899 timestamp: Timestamp,
900 result_desc: RelationDesc,
901 finishing: RowSetFinishing,
902 map_filter_project: mz_expr::SafeMfpPlan,
903 read_hold: ReadHold,
904 target_replica: Option<ReplicaId>,
905 peek_response_tx: oneshot::Sender<PeekResponse>,
906 ) -> Result<(), PeekError> {
907 use PeekError::*;
908
909 let instance = self.instance(instance_id)?;
910
911 if let Some(replica_id) = target_replica {
913 if !instance.replicas.contains(&replica_id) {
914 return Err(ReplicaMissing(replica_id));
915 }
916 }
917
918 if read_hold.id() != peek_target.id() {
921 return Err(ReadHoldIdMismatch(read_hold.id()));
922 }
923 if !read_hold.since().less_equal(×tamp) {
924 return Err(SinceViolation(peek_target.id()));
925 }
926
927 instance.call(move |i| {
928 i.peek(
929 peek_target,
930 literal_constraints,
931 uuid,
932 timestamp,
933 result_desc,
934 finishing,
935 map_filter_project,
936 read_hold,
937 target_replica,
938 peek_response_tx,
939 )
940 .expect("validated")
941 });
942
943 Ok(())
944 }
945
946 pub fn cancel_peek(
956 &self,
957 instance_id: ComputeInstanceId,
958 uuid: Uuid,
959 reason: PeekResponse,
960 ) -> Result<(), InstanceMissing> {
961 self.instance(instance_id)?
962 .call(move |i| i.cancel_peek(uuid, reason));
963 Ok(())
964 }
965
966 pub fn set_read_policy(
978 &self,
979 instance_id: ComputeInstanceId,
980 policies: Vec<(GlobalId, ReadPolicy)>,
981 ) -> Result<(), ReadPolicyError> {
982 use ReadPolicyError::*;
983
984 let instance = self.instance(instance_id)?;
985
986 for (id, _) in &policies {
988 let collection = instance.collection(*id)?;
989 if collection.write_only {
990 return Err(WriteOnlyCollection(*id));
991 }
992 }
993
994 self.instance(instance_id)?
995 .call(|i| i.set_read_policy(policies).expect("validated"));
996
997 Ok(())
998 }
999
1000 pub fn acquire_read_hold(
1002 &self,
1003 instance_id: ComputeInstanceId,
1004 collection_id: GlobalId,
1005 ) -> Result<ReadHold, CollectionUpdateError> {
1006 let read_hold = self
1007 .instance(instance_id)?
1008 .acquire_read_hold(collection_id)?;
1009 Ok(read_hold)
1010 }
1011
1012 fn determine_time_dependence(
1014 &self,
1015 instance_id: ComputeInstanceId,
1016 dataflow: &DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
1017 ) -> Result<Option<TimeDependence>, TimeDependenceError> {
1018 let instance = self
1019 .instance(instance_id)
1020 .map_err(|err| TimeDependenceError::InstanceMissing(err.0))?;
1021 let mut time_dependencies = Vec::new();
1022
1023 for id in dataflow.imported_index_ids() {
1024 let dependence = instance
1025 .get_time_dependence(id)
1026 .map_err(|err| TimeDependenceError::CollectionMissing(err.0))?;
1027 time_dependencies.push(dependence);
1028 }
1029
1030 'source: for id in dataflow.imported_source_ids() {
1031 for instance in self.instances.values() {
1034 if let Ok(dependence) = instance.get_time_dependence(id) {
1035 time_dependencies.push(dependence);
1036 continue 'source;
1037 }
1038 }
1039
1040 time_dependencies.push(self.storage_collections.determine_time_dependence(id)?);
1042 }
1043
1044 Ok(TimeDependence::merge(
1045 time_dependencies,
1046 dataflow.refresh_schedule.as_ref(),
1047 ))
1048 }
1049
1050 #[mz_ore::instrument(level = "debug")]
1052 pub fn process(&mut self) -> Option<ComputeControllerResponse> {
1053 if self.maintenance_scheduled {
1055 self.maintain();
1056 self.maintenance_scheduled = false;
1057 }
1058
1059 self.stashed_response.take()
1061 }
1062
1063 #[mz_ore::instrument(level = "debug")]
1064 fn maintain(&mut self) {
1065 for instance in self.instances.values_mut() {
1067 instance.call(Instance::maintain);
1068 }
1069 }
1070
1071 pub fn allow_writes(
1075 &mut self,
1076 instance_id: ComputeInstanceId,
1077 collection_id: GlobalId,
1078 ) -> Result<(), CollectionUpdateError> {
1079 if self.read_only {
1080 tracing::debug!("Skipping allow_writes in read-only mode");
1081 return Ok(());
1082 }
1083
1084 let instance = self.instance_mut(instance_id)?;
1085
1086 instance.collection(collection_id)?;
1088
1089 instance.call(move |i| i.allow_writes(collection_id).expect("validated"));
1090
1091 Ok(())
1092 }
1093}
1094
1095#[derive(Debug)]
1096struct InstanceState {
1097 client: InstanceClient,
1098 replicas: BTreeSet<ReplicaId>,
1099 collections: BTreeMap<GlobalId, Collection>,
1100}
1101
1102impl InstanceState {
1103 fn new(client: InstanceClient, collections: BTreeMap<GlobalId, Collection>) -> Self {
1104 Self {
1105 client,
1106 replicas: Default::default(),
1107 collections,
1108 }
1109 }
1110
1111 fn collection(&self, id: GlobalId) -> Result<&Collection, CollectionMissing> {
1112 self.collections.get(&id).ok_or(CollectionMissing(id))
1113 }
1114
1115 fn call<F>(&self, f: F)
1121 where
1122 F: FnOnce(&mut Instance) + Send + 'static,
1123 {
1124 self.client.call(f).expect("instance not dropped")
1125 }
1126
1127 async fn call_sync<F, R>(&self, f: F) -> R
1133 where
1134 F: FnOnce(&mut Instance) -> R + Send + 'static,
1135 R: Send + 'static,
1136 {
1137 self.client
1138 .call_sync(f)
1139 .await
1140 .expect("instance not dropped")
1141 }
1142
1143 pub fn acquire_read_hold(&self, id: GlobalId) -> Result<ReadHold, CollectionMissing> {
1145 let collection = self.collection(id)?;
1155 let since = collection.shared.lock_read_capabilities(|caps| {
1156 let since = caps.frontier().to_owned();
1157 caps.update_iter(since.iter().map(|t| (t.clone(), 1)));
1158 since
1159 });
1160
1161 let hold = ReadHold::new(id, since, self.client.read_hold_tx());
1162 Ok(hold)
1163 }
1164
1165 fn get_time_dependence(
1167 &self,
1168 id: GlobalId,
1169 ) -> Result<Option<TimeDependence>, CollectionMissing> {
1170 Ok(self.collection(id)?.time_dependence.clone())
1171 }
1172
1173 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
1175 let Self {
1177 client: _,
1178 replicas,
1179 collections,
1180 } = self;
1181
1182 let instance = self.call_sync(|i| i.dump()).await?;
1183 let replicas: Vec<_> = replicas.iter().map(|id| id.to_string()).collect();
1184 let collections: BTreeMap<_, _> = collections
1185 .iter()
1186 .map(|(id, c)| (id.to_string(), format!("{c:?}")))
1187 .collect();
1188
1189 Ok(serde_json::json!({
1190 "instance": instance,
1191 "replicas": replicas,
1192 "collections": collections,
1193 }))
1194 }
1195}
1196
1197#[derive(Debug)]
1198struct Collection {
1199 write_only: bool,
1201 compute_dependencies: BTreeSet<GlobalId>,
1202 shared: SharedCollectionState,
1203 time_dependence: Option<TimeDependence>,
1206}
1207
1208impl Collection {
1209 fn new_log() -> Self {
1210 let as_of = Antichain::from_elem(Timestamp::MIN);
1211 Self {
1212 write_only: false,
1213 compute_dependencies: Default::default(),
1214 shared: SharedCollectionState::new(as_of),
1215 time_dependence: Some(TimeDependence::default()),
1216 }
1217 }
1218
1219 fn frontiers(&self) -> CollectionFrontiers {
1220 let read_frontier = self
1221 .shared
1222 .lock_read_capabilities(|c| c.frontier().to_owned());
1223 let write_frontier = self.shared.lock_write_frontier(|f| f.clone());
1224 CollectionFrontiers {
1225 read_frontier,
1226 write_frontier,
1227 }
1228 }
1229}
1230
1231#[derive(Clone, Debug)]
1233pub struct CollectionFrontiers {
1234 pub read_frontier: Antichain<Timestamp>,
1236 pub write_frontier: Antichain<Timestamp>,
1238}
1239
1240impl Default for CollectionFrontiers {
1241 fn default() -> Self {
1242 Self {
1243 read_frontier: Antichain::from_elem(Timestamp::MIN),
1244 write_frontier: Antichain::from_elem(Timestamp::MIN),
1245 }
1246 }
1247}