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::soft_assert_or_log;
56use mz_ore::tracing::OpenTelemetryContext;
57use mz_persist_types::PersistLocation;
58use mz_repr::{GlobalId, RelationDesc, Row, Timestamp};
59use mz_storage_client::controller::StorageController;
60use mz_storage_types::dyncfgs::ORE_OVERFLOWING_BEHAVIOR;
61use mz_storage_types::read_holds::ReadHold;
62use mz_storage_types::read_policy::ReadPolicy;
63use mz_storage_types::time_dependence::{TimeDependence, TimeDependenceError};
64use prometheus::proto::LabelPair;
65use serde::{Deserialize, Serialize};
66use timely::PartialOrder;
67use timely::progress::Antichain;
68use tokio::sync::{mpsc, oneshot};
69use tokio::time::{self, MissedTickBehavior};
70use uuid::Uuid;
71
72use crate::controller::error::{
73 CollectionLookupError, CollectionMissing, CollectionUpdateError, DataflowCreationError,
74 HydrationCheckBadTarget, InstanceExists, InstanceMissing, PeekError, ReadPolicyError,
75 ReplicaCreationError, ReplicaDropError,
76};
77use crate::controller::instance::{Instance, SharedCollectionState};
78use crate::controller::introspection::{IntrospectionUpdates, spawn_introspection_sink};
79use crate::controller::replica::ReplicaConfig;
80use crate::logging::{LogVariant, LoggingConfig};
81use crate::metrics::ComputeControllerMetrics;
82use crate::protocol::command::{ComputeParameters, PeekTarget};
83use crate::protocol::response::{PeekResponse, SubscribeBatch};
84
85mod instance;
86mod introspection;
87mod replica;
88mod sequential_hydration;
89
90pub mod error;
91pub mod instance_client;
92pub use instance_client::InstanceClient;
93
94pub(crate) type StorageCollections =
95 Arc<dyn mz_storage_client::storage_collections::StorageCollections + Send + Sync>;
96
97#[derive(Debug)]
99pub enum ComputeControllerResponse {
100 PeekNotification(Uuid, PeekNotification, OpenTelemetryContext),
102 SubscribeResponse(GlobalId, SubscribeBatch),
104 CopyToResponse(GlobalId, Result<u64, anyhow::Error>),
115 FrontierUpper {
120 id: GlobalId,
122 upper: Antichain<Timestamp>,
124 },
125}
126
127#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
129pub enum PeekNotification {
130 Success {
132 rows: u64,
134 result_size: u64,
136 },
137 Error(String),
139 Canceled,
141}
142
143impl PeekNotification {
144 fn new(peek_response: &PeekResponse, offset: usize, limit: Option<usize>) -> Self {
147 match peek_response {
148 PeekResponse::Rows(rows) => {
149 let num_rows = u64::cast_from(RowCollection::offset_limit(
150 rows.iter().map(|r| r.count()).sum(),
151 offset,
152 limit,
153 ));
154 let result_size = u64::cast_from(rows.iter().map(|r| r.byte_len()).sum::<usize>());
155
156 tracing::trace!(?num_rows, ?result_size, "inline peek result");
157
158 Self::Success {
159 rows: num_rows,
160 result_size,
161 }
162 }
163 PeekResponse::Stashed(stashed_response) => {
164 let rows = stashed_response.num_rows(offset, limit);
165 let result_size = stashed_response.size_bytes();
166
167 tracing::trace!(?rows, ?result_size, "stashed peek result");
168
169 Self::Success {
170 rows: u64::cast_from(rows),
171 result_size: u64::cast_from(result_size),
172 }
173 }
174 PeekResponse::Error(err) => Self::Error(err.clone()),
175 PeekResponse::Canceled => Self::Canceled,
176 }
177 }
178}
179
180pub struct ComputeController {
182 instances: BTreeMap<ComputeInstanceId, InstanceState>,
183 instance_workload_classes: Arc<Mutex<BTreeMap<ComputeInstanceId, Option<String>>>>,
187 build_info: &'static BuildInfo,
188 storage_collections: StorageCollections,
190 initialized: bool,
192 read_only: bool,
198 config: ComputeParameters,
200 peek_stash_persist_location: PersistLocation,
202 stashed_response: Option<ComputeControllerResponse>,
204 metrics: ComputeControllerMetrics,
206 now: NowFn,
208 wallclock_lag: WallclockLagFn<Timestamp>,
210 dyncfg: Arc<ConfigSet>,
215
216 response_rx: mpsc::UnboundedReceiver<ComputeControllerResponse>,
218 response_tx: mpsc::UnboundedSender<ComputeControllerResponse>,
220 introspection_rx: Option<mpsc::UnboundedReceiver<IntrospectionUpdates>>,
225 introspection_tx: mpsc::UnboundedSender<IntrospectionUpdates>,
227
228 maintenance_ticker: tokio::time::Interval,
230 maintenance_scheduled: bool,
232}
233
234impl ComputeController {
235 pub fn new(
237 build_info: &'static BuildInfo,
238 storage_collections: StorageCollections,
239 read_only: bool,
240 metrics_registry: &MetricsRegistry,
241 peek_stash_persist_location: PersistLocation,
242 controller_metrics: ControllerMetrics,
243 now: NowFn,
244 wallclock_lag: WallclockLagFn<Timestamp>,
245 ) -> Self {
246 let (response_tx, response_rx) = mpsc::unbounded_channel();
247 let (introspection_tx, introspection_rx) = mpsc::unbounded_channel();
248
249 let mut maintenance_ticker = time::interval(Duration::from_secs(1));
250 maintenance_ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
251
252 let instance_workload_classes = Arc::new(Mutex::new(BTreeMap::<
253 ComputeInstanceId,
254 Option<String>,
255 >::new()));
256
257 metrics_registry.register_postprocessor({
261 let instance_workload_classes = Arc::clone(&instance_workload_classes);
262 move |metrics| {
263 let instance_workload_classes = instance_workload_classes
264 .lock()
265 .expect("lock poisoned")
266 .iter()
267 .map(|(id, workload_class)| (id.to_string(), workload_class.clone()))
268 .collect::<BTreeMap<String, Option<String>>>();
269 for metric in metrics {
270 'metric: for metric in metric.mut_metric() {
271 for label in metric.get_label() {
272 if label.name() == "instance_id" {
273 if let Some(workload_class) = instance_workload_classes
274 .get(label.value())
275 .cloned()
276 .flatten()
277 {
278 let mut label = LabelPair::default();
279 label.set_name("workload_class".into());
280 label.set_value(workload_class.clone());
281
282 let mut labels = metric.take_label();
283 labels.push(label);
284 metric.set_label(labels);
285 }
286 continue 'metric;
287 }
288 }
289 }
290 }
291 }
292 });
293
294 let metrics = ComputeControllerMetrics::new(metrics_registry, controller_metrics);
295
296 Self {
297 instances: BTreeMap::new(),
298 instance_workload_classes,
299 build_info,
300 storage_collections,
301 initialized: false,
302 read_only,
303 config: Default::default(),
304 peek_stash_persist_location,
305 stashed_response: None,
306 metrics,
307 now,
308 wallclock_lag,
309 dyncfg: Arc::new(mz_dyncfgs::all_dyncfgs()),
310 response_rx,
311 response_tx,
312 introspection_rx: Some(introspection_rx),
313 introspection_tx,
314 maintenance_ticker,
315 maintenance_scheduled: false,
316 }
317 }
318
319 pub fn start_introspection_sink(&mut self, storage_controller: &dyn StorageController) {
324 if let Some(rx) = self.introspection_rx.take() {
325 spawn_introspection_sink(rx, storage_controller);
326 }
327 }
328
329 pub fn instance_exists(&self, id: ComputeInstanceId) -> bool {
331 self.instances.contains_key(&id)
332 }
333
334 fn instance(&self, id: ComputeInstanceId) -> Result<&InstanceState, InstanceMissing> {
336 self.instances.get(&id).ok_or(InstanceMissing(id))
337 }
338
339 pub fn instance_client(
341 &self,
342 id: ComputeInstanceId,
343 ) -> Result<InstanceClient, InstanceMissing> {
344 self.instance(id).map(|instance| instance.client.clone())
345 }
346
347 fn instance_mut(
349 &mut self,
350 id: ComputeInstanceId,
351 ) -> Result<&mut InstanceState, InstanceMissing> {
352 self.instances.get_mut(&id).ok_or(InstanceMissing(id))
353 }
354
355 pub fn collection_ids(
357 &self,
358 instance_id: ComputeInstanceId,
359 ) -> Result<impl Iterator<Item = GlobalId> + '_, InstanceMissing> {
360 let instance = self.instance(instance_id)?;
361 let ids = instance.collections.keys().copied();
362 Ok(ids)
363 }
364
365 pub fn collection_frontiers(
370 &self,
371 collection_id: GlobalId,
372 instance_id: Option<ComputeInstanceId>,
373 ) -> Result<CollectionFrontiers, CollectionLookupError> {
374 let collection = match instance_id {
375 Some(id) => self.instance(id)?.collection(collection_id)?,
376 None => self
377 .instances
378 .values()
379 .find_map(|i| i.collections.get(&collection_id))
380 .ok_or(CollectionMissing(collection_id))?,
381 };
382
383 Ok(collection.frontiers())
384 }
385
386 pub fn collection_reverse_dependencies(
388 &self,
389 instance_id: ComputeInstanceId,
390 id: GlobalId,
391 ) -> Result<impl Iterator<Item = GlobalId> + '_, InstanceMissing> {
392 let instance = self.instance(instance_id)?;
393 let collections = instance.collections.iter();
394 let ids = collections
395 .filter_map(move |(cid, c)| c.compute_dependencies.contains(&id).then_some(*cid));
396 Ok(ids)
397 }
398
399 pub async fn collection_hydrated(
405 &self,
406 instance_id: ComputeInstanceId,
407 collection_id: GlobalId,
408 ) -> Result<bool, anyhow::Error> {
409 let instance = self.instance(instance_id)?;
410
411 let res = instance
412 .call_sync(move |i| i.collection_hydrated(collection_id))
413 .await?;
414
415 Ok(res)
416 }
417
418 pub fn collections_hydrated_for_replicas(
425 &self,
426 instance_id: ComputeInstanceId,
427 replicas: Vec<ReplicaId>,
428 exclude_collections: BTreeSet<GlobalId>,
429 ) -> Result<oneshot::Receiver<bool>, anyhow::Error> {
430 let instance = self.instance(instance_id)?;
431
432 if !instance.replicas.is_empty()
434 && !replicas.iter().any(|id| instance.replicas.contains(id))
435 {
436 return Err(HydrationCheckBadTarget(replicas).into());
437 }
438
439 let (tx, rx) = oneshot::channel();
440 instance.call(move |i| {
441 let result = i
442 .collections_hydrated_on_replicas(Some(replicas), &exclude_collections)
443 .expect("validated");
444 let _ = tx.send(result);
445 });
446
447 Ok(rx)
448 }
449
450 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
454 let Self {
461 instances,
462 instance_workload_classes,
463 build_info: _,
464 storage_collections: _,
465 initialized,
466 read_only,
467 config: _,
468 peek_stash_persist_location: _,
469 stashed_response,
470 metrics: _,
471 now: _,
472 wallclock_lag: _,
473 dyncfg: _,
474 response_rx: _,
475 response_tx: _,
476 introspection_rx: _,
477 introspection_tx: _,
478 maintenance_ticker: _,
479 maintenance_scheduled,
480 } = self;
481
482 let mut instances_dump = BTreeMap::new();
483 for (id, instance) in instances {
484 let dump = instance.dump().await?;
485 instances_dump.insert(id.to_string(), dump);
486 }
487
488 let instance_workload_classes: BTreeMap<_, _> = instance_workload_classes
489 .lock()
490 .expect("lock poisoned")
491 .iter()
492 .map(|(id, wc)| (id.to_string(), format!("{wc:?}")))
493 .collect();
494
495 Ok(serde_json::json!({
496 "instances": instances_dump,
497 "instance_workload_classes": instance_workload_classes,
498 "initialized": initialized,
499 "read_only": read_only,
500 "stashed_response": format!("{stashed_response:?}"),
501 "maintenance_scheduled": maintenance_scheduled,
502 }))
503 }
504}
505
506impl ComputeController {
507 pub fn create_instance(
509 &mut self,
510 id: ComputeInstanceId,
511 arranged_logs: BTreeMap<LogVariant, GlobalId>,
512 workload_class: Option<String>,
513 ) -> Result<(), InstanceExists> {
514 if self.instances.contains_key(&id) {
515 return Err(InstanceExists(id));
516 }
517
518 let mut collections = BTreeMap::new();
519 let mut logs = Vec::with_capacity(arranged_logs.len());
520 for (&log, &id) in &arranged_logs {
521 let collection = Collection::new_log();
522 let shared = collection.shared.clone();
523 collections.insert(id, collection);
524 logs.push((log, id, shared));
525 }
526
527 let client = InstanceClient::spawn(
528 id,
529 self.build_info,
530 Arc::clone(&self.storage_collections),
531 self.peek_stash_persist_location.clone(),
532 logs,
533 self.metrics.for_instance(id),
534 self.now.clone(),
535 self.wallclock_lag.clone(),
536 Arc::clone(&self.dyncfg),
537 self.response_tx.clone(),
538 self.introspection_tx.clone(),
539 self.read_only,
540 );
541
542 let instance = InstanceState::new(client, collections);
543 self.instances.insert(id, instance);
544
545 self.instance_workload_classes
546 .lock()
547 .expect("lock poisoned")
548 .insert(id, workload_class.clone());
549
550 let instance = self.instances.get_mut(&id).expect("instance just added");
551 if self.initialized {
552 instance.call(Instance::initialization_complete);
553 }
554
555 let mut config_params = self.config.clone();
561 config_params.workload_class = Some(workload_class);
562 instance.call(|i| i.update_configuration(config_params));
563
564 Ok(())
565 }
566
567 pub fn update_instance_workload_class(
569 &mut self,
570 id: ComputeInstanceId,
571 workload_class: Option<String>,
572 ) -> Result<(), InstanceMissing> {
573 let _ = self.instance(id)?;
575
576 self.instance_workload_classes
577 .lock()
578 .expect("lock poisoned")
579 .insert(id, workload_class);
580
581 self.update_configuration(Default::default());
583
584 Ok(())
585 }
586
587 pub fn drop_instance(&mut self, id: ComputeInstanceId) {
593 if let Some(instance) = self.instances.remove(&id) {
594 instance.call(|i| i.shutdown());
595 }
596
597 self.instance_workload_classes
598 .lock()
599 .expect("lock poisoned")
600 .remove(&id);
601 }
602
603 pub fn dyncfg(&self) -> &Arc<ConfigSet> {
605 &self.dyncfg
606 }
607
608 pub fn update_configuration(&mut self, config_params: ComputeParameters) {
610 config_params.dyncfg_updates.apply(&self.dyncfg);
612
613 let instance_workload_classes = self
614 .instance_workload_classes
615 .lock()
616 .expect("lock poisoned");
617
618 for (id, instance) in self.instances.iter_mut() {
621 let mut params = config_params.clone();
622 params.workload_class = Some(instance_workload_classes[id].clone());
623 instance.call(|i| i.update_configuration(params));
624 }
625
626 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(&self.dyncfg);
627 match overflowing_behavior.parse() {
628 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
629 Err(err) => {
630 tracing::error!(
631 err,
632 overflowing_behavior,
633 "Invalid value for ore_overflowing_behavior"
634 );
635 }
636 }
637
638 self.config.update(config_params);
640 }
641
642 pub fn update_replica_dyncfg_overrides(
651 &mut self,
652 mut overrides: BTreeMap<ComputeInstanceId, BTreeMap<ReplicaId, ConfigUpdates>>,
653 ) {
654 for (id, instance) in self.instances.iter_mut() {
655 let instance_overrides = overrides.remove(id).unwrap_or_default();
656 instance.call(move |i| i.update_replica_dyncfg_overrides(instance_overrides));
657 }
658 }
659
660 pub fn initialization_complete(&mut self) {
666 self.initialized = true;
667 for instance in self.instances.values_mut() {
668 instance.call(Instance::initialization_complete);
669 }
670 }
671
672 pub async fn ready(&mut self) {
680 if self.stashed_response.is_some() {
681 return;
683 }
684 if self.maintenance_scheduled {
685 return;
687 }
688
689 tokio::select! {
690 resp = self.response_rx.recv() => {
691 let resp = resp.expect("`self.response_tx` not dropped");
692 self.stashed_response = Some(resp);
693 }
694 _ = self.maintenance_ticker.tick() => {
695 self.maintenance_scheduled = true;
696 },
697 }
698 }
699
700 pub fn add_replica_to_instance(
702 &mut self,
703 instance_id: ComputeInstanceId,
704 replica_id: ReplicaId,
705 location: ClusterReplicaLocation,
706 config: ComputeReplicaConfig,
707 ) -> Result<(), ReplicaCreationError> {
708 use ReplicaCreationError::*;
709
710 let instance = self.instance(instance_id)?;
711
712 if instance.replicas.contains(&replica_id) {
714 return Err(ReplicaExists(replica_id));
715 }
716
717 let (enable_logging, interval) = match config.logging.interval {
718 Some(interval) => (true, interval),
719 None => (false, Duration::from_secs(1)),
720 };
721
722 let expiration_offset = COMPUTE_REPLICA_EXPIRATION_OFFSET.get(&self.dyncfg);
723
724 let arrangement_dictionary_compression = ENABLE_ARRANGEMENT_DICTIONARY_COMPRESSION_ALPHA
731 .get(&self.dyncfg)
732 && config.arrangement_compression;
733
734 let replica_config = ReplicaConfig {
735 location,
736 logging: LoggingConfig {
737 interval,
738 enable_logging,
739 log_logging: config.logging.log_logging,
740 index_logs: Default::default(),
741 },
742 grpc_client: self.config.grpc_client.clone(),
743 expiration_offset: (!expiration_offset.is_zero()).then_some(expiration_offset),
744 arrangement_dictionary_compression,
745 };
746
747 let instance = self.instance_mut(instance_id).expect("validated");
748 instance.replicas.insert(replica_id);
749
750 instance.call(move |i| {
751 i.add_replica(replica_id, replica_config, None)
752 .expect("validated")
753 });
754
755 Ok(())
756 }
757
758 pub fn drop_replica(
760 &mut self,
761 instance_id: ComputeInstanceId,
762 replica_id: ReplicaId,
763 ) -> Result<(), ReplicaDropError> {
764 use ReplicaDropError::*;
765
766 let instance = self.instance_mut(instance_id)?;
767
768 if !instance.replicas.contains(&replica_id) {
770 return Err(ReplicaMissing(replica_id));
771 }
772
773 instance.replicas.remove(&replica_id);
774
775 instance.call(move |i| i.remove_replica(replica_id).expect("validated"));
776
777 Ok(())
778 }
779
780 pub fn create_dataflow(
790 &mut self,
791 instance_id: ComputeInstanceId,
792 mut dataflow: DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
793 target_replica: Option<ReplicaId>,
794 ) -> Result<(), DataflowCreationError> {
795 use DataflowCreationError::*;
796
797 let instance = self.instance(instance_id)?;
798
799 if let Some(replica_id) = target_replica {
801 if !instance.replicas.contains(&replica_id) {
802 return Err(ReplicaMissing(replica_id));
803 }
804 assert!(
805 dataflow.exported_index_ids().next().is_none(),
806 "Replica-targeted indexes are not supported"
807 );
808 }
809
810 let as_of = dataflow.as_of.as_ref().ok_or(MissingAsOf)?;
812 if as_of.is_empty() && dataflow.subscribe_ids().next().is_some() {
813 return Err(EmptyAsOfForSubscribe);
814 }
815 if as_of.is_empty() && dataflow.copy_to_ids().next().is_some() {
816 return Err(EmptyAsOfForCopyTo);
817 }
818
819 soft_assert_or_log!(
826 !dataflow.index_exports.is_empty() || !dataflow.sink_exports.is_empty(),
827 "dataflow {} has no exports",
828 dataflow.debug_name,
829 );
830
831 let used_imports = dataflow.used_import_ids();
843
844 soft_assert_or_log!(
851 dataflow.import_ids().all(|id| used_imports.contains(&id)),
852 "dataflow {} imports collections no export reads: imports {:?}, read {:?}",
853 dataflow.debug_name,
854 dataflow.import_ids().collect::<Vec<_>>(),
855 used_imports,
856 );
857
858 let storage_ids = dataflow.imported_source_ids().collect();
860 let mut import_read_holds = self.storage_collections.acquire_read_holds(storage_ids)?;
861 for id in dataflow.imported_index_ids() {
862 let read_hold = instance.acquire_read_hold(id)?;
863 import_read_holds.push(read_hold);
864 }
865 for hold in &import_read_holds {
866 if PartialOrder::less_than(as_of, hold.since()) {
867 return Err(SinceViolation(hold.id()));
868 }
869 }
870
871 for id in dataflow.persist_sink_ids() {
873 if self.storage_collections.check_exists(id).is_err() {
874 return Err(CollectionMissing(id));
875 }
876 }
877 let time_dependence = self
878 .determine_time_dependence(instance_id, &dataflow, &used_imports)
879 .expect("must exist");
880
881 let instance = self.instance_mut(instance_id).expect("validated");
882
883 let mut shared_collection_state = BTreeMap::new();
884 for id in dataflow.export_ids() {
885 let shared = SharedCollectionState::new(as_of.clone());
886 let collection = Collection {
887 write_only: dataflow.sink_exports.contains_key(&id),
888 compute_dependencies: dataflow.imported_index_ids().collect(),
889 shared: shared.clone(),
890 time_dependence: time_dependence.clone(),
891 };
892 instance.collections.insert(id, collection);
893 shared_collection_state.insert(id, shared);
894 }
895
896 dataflow.time_dependence = time_dependence;
897
898 instance.call(move |i| {
899 i.create_dataflow(
900 dataflow,
901 import_read_holds,
902 shared_collection_state,
903 target_replica,
904 )
905 .expect("validated")
906 });
907
908 Ok(())
909 }
910
911 pub fn drop_collections(
914 &mut self,
915 instance_id: ComputeInstanceId,
916 collection_ids: Vec<GlobalId>,
917 ) -> Result<(), CollectionUpdateError> {
918 let instance = self.instance_mut(instance_id)?;
919
920 for id in &collection_ids {
922 instance.collection(*id)?;
923 }
924
925 for id in &collection_ids {
926 instance.collections.remove(id);
927 }
928
929 instance.call(|i| i.drop_collections(collection_ids).expect("validated"));
930
931 Ok(())
932 }
933
934 pub fn peek(
941 &self,
942 instance_id: ComputeInstanceId,
943 peek_target: PeekTarget,
944 literal_constraints: Option<Vec<Row>>,
945 uuid: Uuid,
946 timestamp: Timestamp,
947 result_desc: RelationDesc,
948 finishing: RowSetFinishing,
949 map_filter_project: mz_expr::SafeMfpPlan,
950 read_hold: ReadHold,
951 target_replica: Option<ReplicaId>,
952 peek_response_tx: oneshot::Sender<PeekResponse>,
953 ) -> Result<(), PeekError> {
954 use PeekError::*;
955
956 let instance = self.instance(instance_id)?;
957
958 if let Some(replica_id) = target_replica {
960 if !instance.replicas.contains(&replica_id) {
961 return Err(ReplicaMissing(replica_id));
962 }
963 }
964
965 if read_hold.id() != peek_target.id() {
968 return Err(ReadHoldIdMismatch(read_hold.id()));
969 }
970 if !read_hold.since().less_equal(×tamp) {
971 return Err(SinceViolation(peek_target.id()));
972 }
973
974 instance.call(move |i| {
975 i.peek(
976 peek_target,
977 literal_constraints,
978 uuid,
979 timestamp,
980 result_desc,
981 finishing,
982 map_filter_project,
983 read_hold,
984 target_replica,
985 peek_response_tx,
986 )
987 .expect("validated")
988 });
989
990 Ok(())
991 }
992
993 pub fn cancel_peek(
1003 &self,
1004 instance_id: ComputeInstanceId,
1005 uuid: Uuid,
1006 reason: PeekResponse,
1007 ) -> Result<(), InstanceMissing> {
1008 self.instance(instance_id)?
1009 .call(move |i| i.cancel_peek(uuid, reason));
1010 Ok(())
1011 }
1012
1013 pub fn set_read_policy(
1025 &self,
1026 instance_id: ComputeInstanceId,
1027 policies: Vec<(GlobalId, ReadPolicy)>,
1028 ) -> Result<(), ReadPolicyError> {
1029 use ReadPolicyError::*;
1030
1031 let instance = self.instance(instance_id)?;
1032
1033 for (id, _) in &policies {
1035 let collection = instance.collection(*id)?;
1036 if collection.write_only {
1037 return Err(WriteOnlyCollection(*id));
1038 }
1039 }
1040
1041 self.instance(instance_id)?
1042 .call(|i| i.set_read_policy(policies).expect("validated"));
1043
1044 Ok(())
1045 }
1046
1047 pub fn acquire_read_hold(
1049 &self,
1050 instance_id: ComputeInstanceId,
1051 collection_id: GlobalId,
1052 ) -> Result<ReadHold, CollectionUpdateError> {
1053 let read_hold = self
1054 .instance(instance_id)?
1055 .acquire_read_hold(collection_id)?;
1056 Ok(read_hold)
1057 }
1058
1059 fn determine_time_dependence(
1073 &self,
1074 instance_id: ComputeInstanceId,
1075 dataflow: &DataflowDescription<mz_compute_types::plan::LirRelationExpr, ()>,
1076 used_imports: &BTreeSet<GlobalId>,
1077 ) -> Result<Option<TimeDependence>, TimeDependenceError> {
1078 let instance = self
1079 .instance(instance_id)
1080 .map_err(|err| TimeDependenceError::InstanceMissing(err.0))?;
1081 let mut time_dependencies = Vec::new();
1082
1083 for id in dataflow
1084 .imported_index_ids()
1085 .filter(|id| used_imports.contains(id))
1086 {
1087 let dependence = instance
1088 .get_time_dependence(id)
1089 .map_err(|err| TimeDependenceError::CollectionMissing(err.0))?;
1090 time_dependencies.push(dependence);
1091 }
1092
1093 'source: for id in dataflow
1094 .imported_source_ids()
1095 .filter(|id| used_imports.contains(id))
1096 {
1097 for instance in self.instances.values() {
1100 if let Ok(dependence) = instance.get_time_dependence(id) {
1101 time_dependencies.push(dependence);
1102 continue 'source;
1103 }
1104 }
1105
1106 time_dependencies.push(self.storage_collections.determine_time_dependence(id)?);
1108 }
1109
1110 Ok(TimeDependence::merge(
1111 time_dependencies,
1112 dataflow.refresh_schedule.as_ref(),
1113 ))
1114 }
1115
1116 #[mz_ore::instrument(level = "debug")]
1118 pub fn process(&mut self) -> Option<ComputeControllerResponse> {
1119 if self.maintenance_scheduled {
1121 self.maintain();
1122 self.maintenance_scheduled = false;
1123 }
1124
1125 self.stashed_response.take()
1127 }
1128
1129 #[mz_ore::instrument(level = "debug")]
1130 fn maintain(&mut self) {
1131 for instance in self.instances.values_mut() {
1133 instance.call(Instance::maintain);
1134 }
1135 }
1136
1137 pub fn allow_writes(
1141 &mut self,
1142 instance_id: ComputeInstanceId,
1143 collection_id: GlobalId,
1144 ) -> Result<(), CollectionUpdateError> {
1145 if self.read_only {
1146 tracing::debug!("Skipping allow_writes in read-only mode");
1147 return Ok(());
1148 }
1149
1150 let instance = self.instance_mut(instance_id)?;
1151
1152 instance.collection(collection_id)?;
1154
1155 instance.call(move |i| i.allow_writes(collection_id).expect("validated"));
1156
1157 Ok(())
1158 }
1159}
1160
1161#[derive(Debug)]
1162struct InstanceState {
1163 client: InstanceClient,
1164 replicas: BTreeSet<ReplicaId>,
1165 collections: BTreeMap<GlobalId, Collection>,
1166}
1167
1168impl InstanceState {
1169 fn new(client: InstanceClient, collections: BTreeMap<GlobalId, Collection>) -> Self {
1170 Self {
1171 client,
1172 replicas: Default::default(),
1173 collections,
1174 }
1175 }
1176
1177 fn collection(&self, id: GlobalId) -> Result<&Collection, CollectionMissing> {
1178 self.collections.get(&id).ok_or(CollectionMissing(id))
1179 }
1180
1181 fn call<F>(&self, f: F)
1187 where
1188 F: FnOnce(&mut Instance) + Send + 'static,
1189 {
1190 self.client.call(f).expect("instance not dropped")
1191 }
1192
1193 async fn call_sync<F, R>(&self, f: F) -> R
1199 where
1200 F: FnOnce(&mut Instance) -> R + Send + 'static,
1201 R: Send + 'static,
1202 {
1203 self.client
1204 .call_sync(f)
1205 .await
1206 .expect("instance not dropped")
1207 }
1208
1209 pub fn acquire_read_hold(&self, id: GlobalId) -> Result<ReadHold, CollectionMissing> {
1211 let collection = self.collection(id)?;
1221 let since = collection.shared.lock_read_capabilities(|caps| {
1222 let since = caps.frontier().to_owned();
1223 caps.update_iter(since.iter().map(|t| (t.clone(), 1)));
1224 since
1225 });
1226
1227 let hold = ReadHold::new(id, since, self.client.read_hold_tx());
1228 Ok(hold)
1229 }
1230
1231 fn get_time_dependence(
1233 &self,
1234 id: GlobalId,
1235 ) -> Result<Option<TimeDependence>, CollectionMissing> {
1236 Ok(self.collection(id)?.time_dependence.clone())
1237 }
1238
1239 pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
1241 let Self {
1243 client: _,
1244 replicas,
1245 collections,
1246 } = self;
1247
1248 let instance = self.call_sync(|i| i.dump()).await?;
1249 let replicas: Vec<_> = replicas.iter().map(|id| id.to_string()).collect();
1250 let collections: BTreeMap<_, _> = collections
1251 .iter()
1252 .map(|(id, c)| (id.to_string(), format!("{c:?}")))
1253 .collect();
1254
1255 Ok(serde_json::json!({
1256 "instance": instance,
1257 "replicas": replicas,
1258 "collections": collections,
1259 }))
1260 }
1261}
1262
1263#[derive(Debug)]
1264struct Collection {
1265 write_only: bool,
1267 compute_dependencies: BTreeSet<GlobalId>,
1268 shared: SharedCollectionState,
1269 time_dependence: Option<TimeDependence>,
1272}
1273
1274impl Collection {
1275 fn new_log() -> Self {
1276 let as_of = Antichain::from_elem(Timestamp::MIN);
1277 Self {
1278 write_only: false,
1279 compute_dependencies: Default::default(),
1280 shared: SharedCollectionState::new(as_of),
1281 time_dependence: Some(TimeDependence::default()),
1282 }
1283 }
1284
1285 fn frontiers(&self) -> CollectionFrontiers {
1286 let read_frontier = self
1287 .shared
1288 .lock_read_capabilities(|c| c.frontier().to_owned());
1289 let write_frontier = self.shared.lock_write_frontier(|f| f.clone());
1290 CollectionFrontiers {
1291 read_frontier,
1292 write_frontier,
1293 }
1294 }
1295}
1296
1297#[derive(Clone, Debug)]
1299pub struct CollectionFrontiers {
1300 pub read_frontier: Antichain<Timestamp>,
1302 pub write_frontier: Antichain<Timestamp>,
1304}
1305
1306impl Default for CollectionFrontiers {
1307 fn default() -> Self {
1308 Self {
1309 read_frontier: Antichain::from_elem(Timestamp::MIN),
1310 write_frontier: Antichain::from_elem(Timestamp::MIN),
1311 }
1312 }
1313}