1use std::any::Any;
9use std::cell::RefCell;
10use std::cmp::Ordering;
11use std::collections::{BTreeMap, BTreeSet, VecDeque};
12use std::num::NonZeroUsize;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use differential_dataflow::Hashable;
18use differential_dataflow::lattice::Lattice;
19use differential_dataflow::trace::TraceReader;
20use mz_compute_client::logging::LoggingConfig;
21use mz_compute_client::protocol::command::{
22 ComputeCommand, ComputeParameters, InstanceConfig, Peek, PeekTarget,
23};
24use mz_compute_client::protocol::history::ComputeCommandHistory;
25use mz_compute_client::protocol::response::{
26 ComputeResponse, CopyToResponse, FrontiersResponse, PeekError, PeekResponse, SubscribeResponse,
27};
28use mz_compute_types::dataflows::DataflowDescription;
29use mz_compute_types::dyncfgs::{
30 ENABLE_PEEK_RESPONSE_STASH, ENABLE_PEEK_ROW_ITERATION_LIMIT, PEEK_RESPONSE_STASH_BATCH_BYTES,
31 PEEK_RESPONSE_STASH_THRESHOLD_BYTES, PEEK_ROW_ITERATION_LIMIT,
32};
33use mz_compute_types::plan::render_plan::RenderPlan;
34use mz_dyncfg::{ConfigSet, ConfigValHandle};
35use mz_expr::SafeMfpPlan;
36use mz_expr::row::RowCollection;
37use mz_ore::cast::{CastFrom, CastLossy};
38use mz_ore::collections::CollectionExt;
39use mz_ore::metrics::{MetricsRegistry, UIntGauge};
40use mz_ore::now::EpochMillis;
41use mz_ore::soft_panic_or_log;
42use mz_ore::task::AbortOnDropHandle;
43use mz_ore::tracing::{OpenTelemetryContext, TracingHandle};
44use mz_persist_client::Diagnostics;
45use mz_persist_client::cache::PersistClientCache;
46use mz_persist_client::cfg::USE_CRITICAL_SINCE_SNAPSHOT;
47use mz_persist_client::read::ReadHandle;
48use mz_persist_types::PersistLocation;
49use mz_persist_types::codec_impls::UnitSchema;
50use mz_repr::{DatumVec, GlobalId, Row, RowArena, Timestamp};
51use mz_storage_operators::stats::StatsCursor;
52use mz_storage_types::StorageDiff;
53use mz_storage_types::controller::CollectionMetadata;
54use mz_storage_types::dyncfgs::ORE_OVERFLOWING_BEHAVIOR;
55use mz_storage_types::sources::SourceData;
56use mz_storage_types::time_dependence::TimeDependence;
57use mz_txn_wal::operator::TxnsContext;
58use mz_txn_wal::txn_cache::TxnsCache;
59use timely::dataflow::operators::probe;
60use timely::order::PartialOrder;
61use timely::progress::frontier::Antichain;
62use timely::worker::Worker as TimelyWorker;
63use tokio::sync::{oneshot, watch};
64use tracing::{Level, debug, error, info, span, trace, warn};
65use uuid::Uuid;
66
67use crate::arrangement::manager::{TraceBundle, TraceManager};
68use crate::compute_state::peek_budget::InlineBudget;
69use crate::compute_state::peek_metrics::{IndexPeekMetrics, PeekWalkMetrics};
70pub(crate) use crate::compute_state::peek_offload::PeekPermits;
71use crate::compute_state::peek_offload::{OffloadConfig, OffloadedPeek};
72use crate::compute_state::peek_scan::{
73 IndexPeekScan, PeekScan, ScanOutcome, StashBounds, entry_byte_len, rows_response,
74};
75use crate::logging;
76use crate::logging::compute::{CollectionLogging, ComputeEvent, PeekEvent};
77use crate::logging::initialize::LoggingTraces;
78use crate::metrics::{CollectionMetrics, WorkerMetrics};
79use crate::render::{LinearJoinSpec, StartSignal};
80use crate::server::{ComputeInstanceContext, ResponseSender};
81
82mod error_scan;
83mod peek_budget;
84mod peek_metrics;
85mod peek_offload;
86mod peek_result_iterator;
87mod peek_scan;
88mod peek_stash;
89
90#[derive(Clone, Debug)]
95struct PeekRowIterationConfig {
96 enabled: ConfigValHandle<bool>,
97 limit: ConfigValHandle<usize>,
98}
99
100impl PeekRowIterationConfig {
101 fn new(config: &ConfigSet) -> Self {
102 Self {
103 enabled: ENABLE_PEEK_ROW_ITERATION_LIMIT.handle(config),
104 limit: PEEK_ROW_ITERATION_LIMIT.handle(config),
105 }
106 }
107
108 fn current_limit(&self) -> Option<usize> {
109 self.enabled.get().then(|| self.limit.get())
110 }
111}
112
113#[derive(Debug)]
122pub(crate) struct PeekRowIterationTracker {
123 limit: Option<usize>,
124 rows_iterated: usize,
125}
126
127impl PeekRowIterationTracker {
128 fn new(limit: Option<usize>, rows_iterated: usize) -> Self {
129 Self {
130 limit,
131 rows_iterated,
132 }
133 }
134
135 fn set_limit(&mut self, limit: Option<usize>) {
140 self.limit = limit;
141 }
142
143 fn rows_iterated(&self) -> usize {
144 self.rows_iterated
145 }
146
147 fn add_rows_iterated(&mut self, rows_iterated: usize) {
152 self.rows_iterated = self.rows_iterated.saturating_add(rows_iterated);
153 }
154
155 fn track_next(&mut self) -> Result<(), PeekError> {
156 if let Some(limit) = self.limit
157 && self.rows_iterated >= limit
158 {
159 return Err(PeekError::RowIterationLimitExceeded { limit });
160 }
161
162 self.rows_iterated = self.rows_iterated.saturating_add(1);
163 Ok(())
164 }
165}
166
167fn peek_row_iteration_limit(config: &ConfigSet) -> Option<usize> {
168 ENABLE_PEEK_ROW_ITERATION_LIMIT
169 .get(config)
170 .then(|| PEEK_ROW_ITERATION_LIMIT.get(config))
171}
172
173pub struct ComputeState {
178 pub collections: BTreeMap<GlobalId, CollectionState>,
187 pub traces: TraceManager,
189 pub subscribe_response_buffer: Rc<RefCell<Vec<(GlobalId, SubscribeResponse)>>>,
194 pub copy_to_response_buffer: Rc<RefCell<Vec<(GlobalId, CopyToResponse)>>>,
199 pub queued_peeks: VecDeque<IndexPeek>,
205 pub pending_peeks: VecDeque<PendingPeek>,
210 pub peek_stash_persist_location: Option<PersistLocation>,
212 pub compute_logger: Option<logging::compute::Logger>,
214 pub persist_clients: Arc<PersistClientCache>,
217 pub txns_ctx: TxnsContext,
219 pub command_history: ComputeCommandHistory<UIntGauge>,
221 max_result_size: u64,
223 pub linear_join_spec: LinearJoinSpec,
225 pub metrics: WorkerMetrics,
227 tracing_handle: Arc<TracingHandle>,
229 pub context: ComputeInstanceContext,
231 pub worker_config: Rc<ConfigSet>,
245
246 pub metrics_registry: MetricsRegistry,
248
249 pub workers_per_process: usize,
251
252 pub peek_permits: Arc<PeekPermits>,
255
256 peek_walk_metrics: PeekWalkMetrics,
261
262 peek_budget: InlineBudget,
268
269 peek_passed_over: bool,
274
275 suspended_collections: BTreeMap<GlobalId, Rc<dyn Any>>,
281
282 pub server_maintenance_interval: Duration,
285
286 pub init_system_time: EpochMillis,
290
291 pub replica_expiration: Antichain<Timestamp>,
295
296 pub storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
298}
299
300impl ComputeState {
301 pub(crate) fn peeks_awaiting_turn(&self) -> bool {
306 self.peek_passed_over
307 }
308
309 pub fn new(
311 persist_clients: Arc<PersistClientCache>,
312 txns_ctx: TxnsContext,
313 metrics: WorkerMetrics,
314 tracing_handle: Arc<TracingHandle>,
315 context: ComputeInstanceContext,
316 metrics_registry: MetricsRegistry,
317 workers_per_process: usize,
318 peek_permits: Arc<PeekPermits>,
319 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
320 ) -> Self {
321 let worker_config: Rc<ConfigSet> = mz_dyncfgs::all_dyncfgs().into();
322 let traces = TraceManager::new(metrics.clone());
323 let command_history = ComputeCommandHistory::new(metrics.for_history());
324 let peek_walk_metrics = PeekWalkMetrics::new(&metrics);
325 let peek_budget = InlineBudget::new(&worker_config);
326
327 Self {
328 collections: Default::default(),
329 traces,
330 subscribe_response_buffer: Default::default(),
331 copy_to_response_buffer: Default::default(),
332 queued_peeks: Default::default(),
333 pending_peeks: Default::default(),
334 peek_stash_persist_location: None,
335 compute_logger: None,
336 persist_clients,
337 txns_ctx,
338 command_history,
339 max_result_size: u64::MAX,
340 linear_join_spec: Default::default(),
341 metrics,
342 tracing_handle,
343 context,
344 worker_config,
345 metrics_registry,
346 workers_per_process,
347 peek_permits,
348 peek_walk_metrics,
349 peek_budget,
350 peek_passed_over: false,
351 suspended_collections: Default::default(),
352 server_maintenance_interval: Duration::ZERO,
353 init_system_time: mz_ore::now::SYSTEM_TIME(),
354 replica_expiration: Antichain::default(),
355 storage_log_reader,
356 }
357 }
358
359 pub fn expect_collection_mut(&mut self, id: GlobalId) -> &mut CollectionState {
363 self.collections
364 .get_mut(&id)
365 .expect("collection must exist")
366 }
367
368 pub fn input_probe_for(
374 &mut self,
375 input_id: GlobalId,
376 collection_ids: impl Iterator<Item = GlobalId>,
377 ) -> probe::Handle<Timestamp> {
378 let probe = probe::Handle::default();
379 for id in collection_ids {
380 if let Some(collection) = self.collections.get_mut(&id) {
381 collection.input_probes.insert(input_id, probe.clone());
382 }
383 }
384 probe
385 }
386
387 fn apply_worker_config(&mut self) {
389 use mz_compute_types::dyncfgs::*;
390
391 let config = &self.worker_config;
392
393 self.linear_join_spec = LinearJoinSpec::from_config(config);
394
395 if ENABLE_LGALLOC.get(config) {
396 if let Some(path) = &self.context.scratch_directory {
397 let clear_bytes = LGALLOC_SLOW_CLEAR_BYTES.get(config);
398 let eager_return = ENABLE_LGALLOC_EAGER_RECLAMATION.get(config);
399 let file_growth_dampener = LGALLOC_FILE_GROWTH_DAMPENER.get(config);
400 let interval = LGALLOC_BACKGROUND_INTERVAL.get(config);
401 let local_buffer_bytes = LGALLOC_LOCAL_BUFFER_BYTES.get(config);
402 info!(
403 ?path,
404 backgrund_interval=?interval,
405 clear_bytes,
406 eager_return,
407 file_growth_dampener,
408 local_buffer_bytes,
409 "enabling lgalloc"
410 );
411 let background_worker_config = lgalloc::BackgroundWorkerConfig {
412 interval,
413 clear_bytes,
414 };
415 lgalloc::lgalloc_set_config(
416 lgalloc::LgAlloc::new()
417 .enable()
418 .with_path(path.clone())
419 .with_background_config(background_worker_config)
420 .eager_return(eager_return)
421 .file_growth_dampener(file_growth_dampener)
422 .local_buffer_bytes(local_buffer_bytes),
423 );
424 } else {
425 debug!("not enabling lgalloc, scratch directory not specified");
426 }
427 } else {
428 info!("disabling lgalloc");
429 lgalloc::lgalloc_set_config(lgalloc::LgAlloc::new().disable());
430 }
431
432 #[cfg(unix)]
438 if let Some(path) = &self.context.scratch_directory {
439 mz_ore::pager::set_scratch_dir(path.clone());
440 mz_ore::pager::set_backend(mz_ore::pager::Backend::File);
441 } else {
442 mz_ore::pager::set_backend(mz_ore::pager::Backend::Swap);
443 }
444
445 crate::memory_limiter::apply_limiter_config(config);
446
447 mz_ore::region::ENABLE_LGALLOC_REGION.store(
448 ENABLE_COLUMNATION_LGALLOC.get(config),
449 std::sync::atomic::Ordering::Relaxed,
450 );
451
452 {
467 use mz_ore::pager::Backend;
468 use mz_timely_util::column_pager::{Codec, apply_tiered_config};
469
470 let enabled = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
471 let codec = COLUMN_PAGED_BATCHER_LZ4.get(config).then_some(Codec::Lz4);
472 let swap_pageout = COLUMN_PAGED_BATCHER_SWAP_PAGEOUT.get(config);
473
474 const MIB: usize = 1024 * 1024;
479 const DEFAULT_MEM_LIMIT: usize = 4 * 1024 * MIB;
480 let mem_limit = crate::memory_limiter::get_memory_limit().unwrap_or(DEFAULT_MEM_LIMIT);
481 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config).max(0.0);
482 let total = usize::cast_lossy(f64::cast_lossy(mem_limit) * fraction).max(128 * MIB);
483
484 let backend = if self.context.scratch_directory.is_some() {
485 Backend::File
486 } else {
487 Backend::Swap
488 };
489
490 debug!(
491 enabled,
492 ?backend,
493 ?codec,
494 swap_pageout,
495 fraction,
496 mem_limit,
497 budget_bytes = total,
498 "column-paged batcher: applying tiered config",
499 );
500 apply_tiered_config(enabled, total, backend, codec, swap_pageout);
501 }
502
503 {
520 use mz_timely_util::pool_config::{PoolPagerConfig, apply_pool_config};
521
522 let compute_spill = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
523 let storage_spill = mz_storage_types::dyncfgs::ENABLE_UPSERT_PAGED_SPILL.get(config);
524 mz_timely_util::columnar::chunk::set_compute_spill_enabled(compute_spill);
530 if !(compute_spill || storage_spill) {
531 debug!("chunk spill: gates off, leaving the buffer pool uninstalled");
532 } else {
533 let spill_threads = COLUMN_PAGED_BATCHER_SPILL_WORKER_COUNT.get(config);
534 let eager_backing = COLUMN_PAGED_BATCHER_EAGER_BACKING.get(config);
535
536 const MIB: usize = 1024 * 1024;
543 const DEFAULT_RAM: usize = 4 * 1024 * MIB;
544 let ram = mz_ore::memory::physical_memory_bytes().unwrap_or(DEFAULT_RAM);
545 let of_ram =
546 |fraction: f64| usize::cast_lossy(f64::cast_lossy(ram) * fraction.max(0.0));
547 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config);
548 let total = of_ram(fraction).max(128 * MIB);
549 let rss_target = of_ram(COLUMN_PAGED_BATCHER_POOL_RSS_TARGET_FRACTION.get(config));
554
555 let applied = apply_pool_config(PoolPagerConfig {
556 budget_bytes: total,
557 spill_threads,
558 eager_backing,
559 rss_target_bytes: rss_target,
560 });
561 if applied {
562 info!(
563 compute_spill,
564 storage_spill,
565 fraction,
566 ram,
567 budget_bytes = total,
568 spill_threads,
569 eager_backing,
570 rss_target_bytes = rss_target,
571 "chunk spill: applying buffer-pool config",
572 );
573 } else {
574 warn!("chunk spill: buffer pool unavailable; chunks stay resident");
575 }
576 }
577
578 let compress_min_depth =
582 u8::try_from(COLUMN_CHUNK_COMPRESS_MIN_DEPTH.get(config)).unwrap_or(u8::MAX);
583 mz_timely_util::columnar::chunk::set_compress_min_depth(compress_min_depth);
584 }
585
586 self.server_maintenance_interval = COMPUTE_SERVER_MAINTENANCE_INTERVAL.get(config);
589
590 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(config);
591 match overflowing_behavior.parse() {
592 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
593 Err(err) => {
594 error!(
595 err,
596 overflowing_behavior, "Invalid value for ore_overflowing_behavior"
597 );
598 }
599 }
600 }
601
602 pub fn apply_expiration_offset(&mut self, offset: Duration) {
608 if self.replica_expiration.is_empty() {
609 let offset: EpochMillis = offset
610 .as_millis()
611 .try_into()
612 .expect("duration must fit within u64");
613 let replica_expiration_millis = self.init_system_time + offset;
614 let replica_expiration = Timestamp::from(replica_expiration_millis);
615
616 info!(
617 offset = %offset,
618 replica_expiration_millis = %replica_expiration_millis,
619 replica_expiration_utc = %mz_ore::now::to_datetime(replica_expiration_millis),
620 "setting replica expiration",
621 );
622 self.replica_expiration = Antichain::from_elem(replica_expiration);
623
624 self.metrics
626 .replica_expiration_timestamp_seconds
627 .set(replica_expiration.into());
628 }
629 }
630
631 pub fn dataflow_max_inflight_bytes(&self) -> Option<usize> {
634 use mz_compute_types::dyncfgs::{
635 DATAFLOW_MAX_INFLIGHT_BYTES, DATAFLOW_MAX_INFLIGHT_BYTES_CC,
636 };
637
638 if self.persist_clients.cfg.is_cc_active {
639 DATAFLOW_MAX_INFLIGHT_BYTES_CC.get(&self.worker_config)
640 } else {
641 DATAFLOW_MAX_INFLIGHT_BYTES.get(&self.worker_config)
642 }
643 }
644}
645
646pub(crate) struct ActiveComputeState<'a> {
648 pub timely_worker: &'a mut TimelyWorker,
650 pub compute_state: &'a mut ComputeState,
652 pub response_tx: &'a mut ResponseSender,
654}
655
656pub struct SinkToken(#[allow(dead_code)] Box<dyn Any>);
658
659impl SinkToken {
660 pub fn new(t: Box<dyn Any>) -> Self {
662 Self(t)
663 }
664}
665
666impl<'a> ActiveComputeState<'a> {
667 #[mz_ore::instrument(level = "debug")]
669 pub fn handle_compute_command(&mut self, cmd: ComputeCommand) {
670 use ComputeCommand::*;
671
672 self.compute_state.command_history.push(cmd.clone());
673
674 let timer = self
676 .compute_state
677 .metrics
678 .handle_command_duration_seconds
679 .for_command(&cmd)
680 .start_timer();
681
682 match cmd {
683 Hello { .. } => panic!("Hello must be captured before"),
684 CreateInstance(instance_config) => self.handle_create_instance(*instance_config),
685 InitializationComplete => (),
686 UpdateConfiguration(params) => self.handle_update_configuration(*params),
687 CreateDataflow(dataflow) => self.handle_create_dataflow(*dataflow),
688 Schedule(id) => self.handle_schedule(id),
689 AllowCompaction { id, frontier } => self.handle_allow_compaction(id, frontier),
690 Peek(peek) => {
691 peek.otel_ctx.attach_as_parent();
692 self.handle_peek(*peek)
693 }
694 CancelPeek { uuid } => self.handle_cancel_peek(uuid),
695 AllowWrites(id) => {
696 self.handle_allow_writes(id);
697 }
698 }
699
700 timer.observe_duration();
701 }
702
703 fn handle_create_instance(&mut self, config: InstanceConfig) {
704 config
709 .initial_config
710 .apply(&self.compute_state.worker_config);
711
712 self.compute_state.apply_worker_config();
714
715 mz_row_spine::DICTIONARY_COMPRESSION.store(
721 config.arrangement_dictionary_compression,
722 std::sync::atomic::Ordering::Relaxed,
723 );
724
725 if let Some(offset) = config.expiration_offset {
726 self.compute_state.apply_expiration_offset(offset);
727 }
728
729 let storage_log_reader = self.compute_state.storage_log_reader.take();
730 self.initialize_logging(config.logging, storage_log_reader);
731
732 self.compute_state.peek_stash_persist_location = Some(config.peek_stash_persist_location);
733 }
734
735 fn handle_update_configuration(&mut self, params: ComputeParameters) {
736 debug!("Applying configuration update: {params:?}");
737
738 let ComputeParameters {
739 workload_class,
740 max_result_size,
741 tracing,
742 grpc_client: _grpc_client,
743 dyncfg_updates,
744 } = params;
745
746 if let Some(v) = workload_class {
747 self.compute_state.metrics.set_workload_class(v);
748 }
749 if let Some(v) = max_result_size {
750 self.compute_state.max_result_size = v;
751 }
752
753 tracing.apply(self.compute_state.tracing_handle.as_ref());
754
755 dyncfg_updates.apply(&self.compute_state.worker_config);
756 self.compute_state
757 .persist_clients
758 .cfg()
759 .apply_from(&dyncfg_updates);
760
761 mz_metrics::update_dyncfg(&dyncfg_updates);
765
766 self.compute_state.apply_worker_config();
767 }
768
769 fn handle_create_dataflow(
770 &mut self,
771 dataflow: DataflowDescription<RenderPlan, CollectionMetadata>,
772 ) {
773 let dataflow_index = Rc::new(self.timely_worker.next_dataflow_index());
774 let as_of = dataflow.as_of.clone().unwrap();
775
776 let dataflow_expiration = dataflow
777 .time_dependence
778 .as_ref()
779 .map(|time_dependence| {
780 self.determine_dataflow_expiration(time_dependence, &dataflow.until)
781 })
782 .unwrap_or_default();
783
784 let until = dataflow.until.meet(&dataflow_expiration);
786
787 if dataflow.is_transient() {
788 debug!(
789 name = %dataflow.debug_name,
790 import_ids = %dataflow.display_import_ids(),
791 export_ids = %dataflow.display_export_ids(),
792 as_of = ?as_of.elements(),
793 time_dependence = ?dataflow.time_dependence,
794 expiration = ?dataflow_expiration.elements(),
795 expiration_datetime = ?dataflow_expiration
796 .as_option()
797 .map(|t| mz_ore::now::to_datetime(t.into())),
798 plan_until = ?dataflow.until.elements(),
799 until = ?until.elements(),
800 "creating dataflow",
801 );
802 } else {
803 info!(
804 name = %dataflow.debug_name,
805 import_ids = %dataflow.display_import_ids(),
806 export_ids = %dataflow.display_export_ids(),
807 as_of = ?as_of.elements(),
808 time_dependence = ?dataflow.time_dependence,
809 expiration = ?dataflow_expiration.elements(),
810 expiration_datetime = ?dataflow_expiration
811 .as_option()
812 .map(|t| mz_ore::now::to_datetime(t.into())),
813 plan_until = ?dataflow.until.elements(),
814 until = ?until.elements(),
815 "creating dataflow",
816 );
817 };
818
819 let subscribe_copy_ids: BTreeSet<_> = dataflow
820 .subscribe_ids()
821 .chain(dataflow.copy_to_ids())
822 .collect();
823
824 let starts_immediately = dataflow.import_ids().next().is_none();
833
834 for object_id in dataflow.export_ids() {
836 let is_subscribe_or_copy = subscribe_copy_ids.contains(&object_id);
837 let metrics = self.compute_state.metrics.for_collection(object_id);
838 let mut collection = CollectionState::new(
839 Rc::clone(&dataflow_index),
840 is_subscribe_or_copy,
841 as_of.clone(),
842 metrics,
843 );
844
845 if let Some(logger) = self.compute_state.compute_logger.clone() {
846 let logging = CollectionLogging::new(
847 object_id,
848 logger,
849 *dataflow_index,
850 dataflow.import_ids(),
851 );
852 if starts_immediately {
853 logging.set_hydration_start();
854 }
855 collection.logging = Some(logging);
856 }
857
858 collection.reset_reported_frontiers(ReportedFrontier::NotReported {
859 lower: as_of.clone(),
860 });
861
862 let existing = self.compute_state.collections.insert(object_id, collection);
863 if existing.is_some() {
864 error!(
865 id = ?object_id,
866 "existing collection for newly created dataflow",
867 );
868 }
869 }
870
871 let (start_signal, suspension_token) = StartSignal::new();
872 for id in dataflow.export_ids() {
873 self.compute_state
874 .suspended_collections
875 .insert(id, Rc::clone(&suspension_token));
876 }
877
878 crate::render::build_compute_dataflow(
879 self.timely_worker,
880 self.compute_state,
881 dataflow,
882 start_signal,
883 until,
884 dataflow_expiration,
885 );
886 }
887
888 fn handle_schedule(&mut self, id: GlobalId) {
889 let suspension_token = self.compute_state.suspended_collections.remove(&id);
895 drop(suspension_token);
896
897 if let Some(collection) = self.compute_state.collections.get(&id) {
898 if let Some(logging) = &collection.logging {
899 logging.set_hydration_start();
900 }
901 }
902 }
903
904 fn handle_allow_compaction(&mut self, id: GlobalId, frontier: Antichain<Timestamp>) {
905 if frontier.is_empty() {
906 self.drop_collection(id);
908 } else {
909 self.compute_state
910 .traces
911 .allow_compaction(id, frontier.borrow());
912 }
913 }
914
915 #[mz_ore::instrument(level = "debug")]
916 fn handle_peek(&mut self, peek: Peek) {
917 let pending = match &peek.target {
918 PeekTarget::Index { id } => {
919 let trace_bundle = self.compute_state.traces.get(id).unwrap().clone();
921 PendingPeek::index(peek, trace_bundle)
922 }
923 PeekTarget::Persist { metadata, .. } => {
924 let metadata = metadata.clone();
925 PendingPeek::persist(
926 peek,
927 Arc::clone(&self.compute_state.persist_clients),
928 metadata,
929 usize::cast_from(self.compute_state.max_result_size),
930 self.timely_worker,
931 PeekRowIterationConfig::new(&self.compute_state.worker_config),
932 )
933 }
934 };
935
936 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
938 logger.log(&pending.as_log_event(true));
939 }
940
941 match pending {
942 PendingPeek::Index(peek) => self.serve_index_peek(&mut Antichain::new(), peek),
943 pending => self.poll_pending_peek(pending),
944 }
945 }
946
947 fn handle_cancel_peek(&mut self, uuid: Uuid) {
948 let queued = &mut self.compute_state.queued_peeks;
949 if let Some(index) = queued.iter().position(|peek| peek.peek.uuid == uuid) {
950 let peek = queued.remove(index).expect("found above");
951 self.send_peek_response(PendingPeek::Index(peek), PeekResponse::Canceled);
952 return;
953 }
954
955 let pending = &mut self.compute_state.pending_peeks;
956 let Some(index) = pending.iter().position(|peek| peek.peek().uuid == uuid) else {
957 return;
958 };
959 let peek = pending.remove(index).expect("found above");
960 self.send_peek_response(peek, PeekResponse::Canceled);
961 }
962
963 fn handle_allow_writes(&mut self, id: GlobalId) {
964 self.compute_state.persist_clients.cfg().enable_compaction();
968
969 if let Some(collection) = self.compute_state.collections.get_mut(&id) {
970 collection.allow_writes();
971 } else {
972 soft_panic_or_log!("allow writes for unknown collection {id}");
973 }
974 }
975
976 fn drop_collection(&mut self, id: GlobalId) {
978 let collection = self
979 .compute_state
980 .collections
981 .remove(&id)
982 .expect("dropped untracked collection");
983
984 self.compute_state.traces.remove(&id);
986 self.compute_state.suspended_collections.remove(&id);
988
989 if let Ok(index) = Rc::try_unwrap(collection.dataflow_index) {
991 self.timely_worker.drop_dataflow(index);
992 }
993
994 if !collection.is_subscribe_or_copy {
999 let reported = collection.reported_frontiers;
1000 let write_frontier = (!reported.write_frontier.is_empty()).then(Antichain::new);
1001 let input_frontier = (!reported.input_frontier.is_empty()).then(Antichain::new);
1002 let output_frontier = (!reported.output_frontier.is_empty()).then(Antichain::new);
1003
1004 let frontiers = FrontiersResponse {
1005 write_frontier,
1006 input_frontier,
1007 output_frontier,
1008 };
1009 if frontiers.has_updates() {
1010 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1011 }
1012 }
1013 }
1014
1015 pub fn initialize_logging(
1017 &mut self,
1018 config: LoggingConfig,
1019 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
1020 ) {
1021 if self.compute_state.compute_logger.is_some() {
1022 panic!("dataflow server has already initialized logging");
1023 }
1024
1025 let LoggingTraces {
1026 traces,
1027 dataflow_index,
1028 compute_logger: logger,
1029 } = logging::initialize(
1030 self.timely_worker,
1031 &config,
1032 self.compute_state.metrics_registry.clone(),
1033 Rc::clone(&self.compute_state.worker_config),
1034 self.compute_state.workers_per_process,
1035 storage_log_reader,
1036 );
1037
1038 let dataflow_index = Rc::new(dataflow_index);
1039 let mut log_index_ids = config.index_logs;
1040 for (log, trace) in traces {
1041 let id = log_index_ids
1043 .remove(&log)
1044 .expect("`logging::initialize` does not invent logs");
1045 self.compute_state.traces.set(id, trace);
1046
1047 let is_subscribe_or_copy = false;
1049 let as_of = Antichain::from_elem(Timestamp::MIN);
1050 let metrics = self.compute_state.metrics.for_collection(id);
1051 let mut collection = CollectionState::new(
1052 Rc::clone(&dataflow_index),
1053 is_subscribe_or_copy,
1054 as_of,
1055 metrics,
1056 );
1057
1058 let logging =
1059 CollectionLogging::new(id, logger.clone(), *dataflow_index, std::iter::empty());
1060 logging.set_hydration_start();
1065 collection.logging = Some(logging);
1066
1067 let existing = self.compute_state.collections.insert(id, collection);
1068 if existing.is_some() {
1069 error!(
1070 id = ?id,
1071 "existing collection for newly initialized logging export",
1072 );
1073 }
1074 }
1075
1076 assert!(
1078 log_index_ids.is_empty(),
1079 "failed to create requested logging indexes: {log_index_ids:?}",
1080 );
1081
1082 self.compute_state.compute_logger = Some(logger);
1083 }
1084
1085 pub fn report_frontiers(&mut self) {
1087 let mut responses = Vec::new();
1088
1089 let mut new_frontier = Antichain::new();
1091
1092 for (&id, collection) in self.compute_state.collections.iter_mut() {
1093 if collection.is_subscribe_or_copy {
1096 continue;
1097 }
1098
1099 let reported = collection.reported_frontiers();
1100
1101 new_frontier.clear();
1103 if let Some(traces) = self.compute_state.traces.get_mut(&id) {
1104 assert!(
1105 collection.sink_write_frontier.is_none(),
1106 "collection {id} has multiple frontiers"
1107 );
1108 traces.oks_mut().read_upper(&mut new_frontier);
1109 } else if let Some(frontier) = &collection.sink_write_frontier {
1110 new_frontier.clone_from(&frontier.borrow());
1111 } else {
1112 error!(id = ?id, "collection without write frontier");
1113 continue;
1114 }
1115 let new_write_frontier = reported
1116 .write_frontier
1117 .allows_reporting(&new_frontier)
1118 .then(|| new_frontier.clone());
1119
1120 if let Some(probe) = &collection.compute_probe {
1133 if *collection.read_only_rx.borrow() {
1134 new_frontier.clear();
1135 }
1136 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1137 }
1138 let new_output_frontier = reported
1139 .output_frontier
1140 .allows_reporting(&new_frontier)
1141 .then(|| new_frontier.clone());
1142
1143 new_frontier.clear();
1145 for probe in collection.input_probes.values() {
1146 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1147 }
1148 let new_input_frontier = reported
1149 .input_frontier
1150 .allows_reporting(&new_frontier)
1151 .then(|| new_frontier.clone());
1152
1153 if let Some(frontier) = &new_write_frontier {
1154 collection
1155 .set_reported_write_frontier(ReportedFrontier::Reported(frontier.clone()));
1156 }
1157 if let Some(frontier) = &new_input_frontier {
1158 collection
1159 .set_reported_input_frontier(ReportedFrontier::Reported(frontier.clone()));
1160 }
1161 if let Some(frontier) = &new_output_frontier {
1162 collection
1163 .set_reported_output_frontier(ReportedFrontier::Reported(frontier.clone()));
1164 }
1165
1166 let response = FrontiersResponse {
1167 write_frontier: new_write_frontier,
1168 input_frontier: new_input_frontier,
1169 output_frontier: new_output_frontier,
1170 };
1171 if response.has_updates() {
1172 responses.push((id, response));
1173 }
1174 }
1175
1176 for (id, frontiers) in responses {
1177 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1178 }
1179 }
1180
1181 pub(crate) fn report_metrics(&self) {
1183 if let Some(expiration) = self.compute_state.replica_expiration.as_option() {
1184 let now = Duration::from_millis(mz_ore::now::SYSTEM_TIME()).as_secs_f64();
1185 let expiration = Duration::from_millis(<u64>::from(expiration)).as_secs_f64();
1186 let remaining = expiration - now;
1187 self.compute_state
1188 .metrics
1189 .replica_expiration_remaining_seconds
1190 .set(remaining)
1191 }
1192 }
1193
1194 fn serve_index_peek(&mut self, upper: &mut Antichain<Timestamp>, peek: IndexPeek) {
1197 match self.compute_state.peek_budget.grant() {
1198 Some(fuel) => self.walk_index_peek(upper, peek, fuel),
1199 None => {
1200 self.compute_state.peek_passed_over = true;
1203 self.compute_state.queued_peeks.push_back(peek);
1204 }
1205 }
1206 }
1207
1208 fn walk_index_peek(
1211 &mut self,
1212 upper: &mut Antichain<Timestamp>,
1213 mut peek: IndexPeek,
1214 fuel: usize,
1215 ) {
1216 let start = Instant::now();
1217
1218 let row_iteration_limit = peek_row_iteration_limit(&self.compute_state.worker_config);
1219
1220 let peek_stash_eligible = peek
1221 .peek
1222 .finishing
1223 .is_streamable(peek.peek.result_desc.arity());
1224
1225 let has_stash_location = {
1229 let enabled = ENABLE_PEEK_RESPONSE_STASH.get(&self.compute_state.worker_config);
1230 let located = self.compute_state.peek_stash_persist_location.is_some();
1231 if !located && enabled {
1232 error!("missing peek_stash_persist_location but peek stash is enabled");
1233 }
1234 enabled && located
1235 };
1236
1237 let stash = StashBounds {
1238 eligible: peek_stash_eligible && has_stash_location,
1239 threshold_bytes: PEEK_RESPONSE_STASH_THRESHOLD_BYTES
1240 .get(&self.compute_state.worker_config),
1241 batch_bytes: PEEK_RESPONSE_STASH_BATCH_BYTES.get(&self.compute_state.worker_config),
1242 };
1243
1244 let metrics = IndexPeekMetrics {
1245 seek_fulfillment_seconds: &self
1246 .compute_state
1247 .metrics
1248 .index_peek_seek_fulfillment_seconds,
1249 frontier_check_seconds: &self.compute_state.metrics.index_peek_frontier_check_seconds,
1250 walk: &self.compute_state.peek_walk_metrics,
1251 };
1252
1253 let mut unspent = fuel;
1254 let status = peek.seek_fulfillment(
1255 upper,
1256 self.compute_state.max_result_size,
1257 stash,
1258 row_iteration_limit,
1259 &mut unspent,
1260 &metrics,
1261 );
1262
1263 self.compute_state
1266 .peek_budget
1267 .charge(fuel.saturating_sub(unspent));
1268
1269 self.compute_state
1270 .metrics
1271 .index_peek_total_seconds
1272 .observe(start.elapsed().as_secs_f64());
1273
1274 match status {
1275 PeekStatus::Ready(response) => {
1276 let _span =
1277 span!(parent: &peek.span, Level::DEBUG, "process_peek_response").entered();
1278 self.send_peek_response(PendingPeek::Index(peek), response);
1279 }
1280 PeekStatus::NotReady => self.compute_state.queued_peeks.push_back(peek),
1281 PeekStatus::Offload(scan) => {
1282 let _span = span!(parent: &peek.span, Level::DEBUG, "offload_index_peek").entered();
1283
1284 let permits = Arc::clone(&self.compute_state.peek_permits);
1285 let config = OffloadConfig::new(&self.compute_state.worker_config);
1286 let walk_metrics = self.compute_state.peek_walk_metrics.clone();
1287 let worker = std::thread::current();
1288 let stash = self
1291 .compute_state
1292 .peek_stash_persist_location
1293 .as_ref()
1294 .filter(|_| scan.stash_eligible())
1295 .cloned()
1296 .map(|location| {
1297 peek_stash::StashTarget::new(
1298 &peek.peek,
1299 Arc::clone(&self.compute_state.persist_clients),
1300 location,
1301 )
1302 });
1303
1304 let offloaded = OffloadedPeek::start(
1305 peek.peek,
1306 scan,
1307 stash,
1308 permits,
1309 config,
1310 walk_metrics,
1311 worker,
1312 );
1313
1314 self.compute_state
1315 .pending_peeks
1316 .push_back(PendingPeek::Offloaded(offloaded));
1317 }
1318 }
1319 }
1320
1321 fn poll_pending_peek(&mut self, mut pending: PendingPeek) {
1324 let response = match &mut pending {
1325 PendingPeek::Index(peek) => {
1328 soft_panic_or_log!(
1329 "index peek on {} polled as if a driver had taken it over",
1330 peek.peek.target.id()
1331 );
1332 None
1333 }
1334 PendingPeek::Persist(peek) => peek.result.try_recv().ok().map(|(result, duration)| {
1335 self.compute_state
1336 .metrics
1337 .persist_peek_seconds
1338 .observe(duration.as_secs_f64());
1339 result
1340 }),
1341 PendingPeek::Offloaded(offloaded) => match offloaded.result.try_recv() {
1342 Ok((response, duration)) => {
1343 self.compute_state
1346 .metrics
1347 .index_peek_offload_seconds
1348 .observe(duration.as_secs_f64());
1349
1350 trace!(?offloaded.peek, ?duration, "finished offloaded index peek walk");
1351 Some(response)
1352 }
1353 Err(oneshot::error::TryRecvError::Empty) => None,
1354 Err(oneshot::error::TryRecvError::Closed) => {
1361 soft_panic_or_log!(
1362 "offloaded walk of peek on {} ended without an outcome",
1363 offloaded.peek.target.id()
1364 );
1365 Some(PeekResponse::Error(PeekError::unstructured(
1366 "offloaded peek walk failed",
1367 )))
1368 }
1369 },
1370 };
1371
1372 if let Some(response) = response {
1373 let _span =
1374 span!(parent: pending.span(), Level::DEBUG, "process_peek_response").entered();
1375 self.send_peek_response(pending, response)
1376 } else {
1377 self.compute_state.pending_peeks.push_back(pending);
1378 }
1379 }
1380
1381 pub fn process_peeks(&mut self) {
1388 self.compute_state.peek_budget.start_activation();
1392
1393 self.compute_state.peek_passed_over = false;
1397
1398 if self.compute_state.pending_peeks.is_empty() && self.compute_state.queued_peeks.is_empty()
1400 {
1401 return;
1402 }
1403
1404 let mut upper = Antichain::new();
1405
1406 let mut pending_peeks = std::mem::take(&mut self.compute_state.pending_peeks);
1409 while let Some(peek) = pending_peeks.pop_front() {
1410 self.poll_pending_peek(peek);
1411 }
1412
1413 let mut queued_peeks = std::mem::take(&mut self.compute_state.queued_peeks);
1416 while let Some(peek) = queued_peeks.pop_front() {
1417 let Some(fuel) = self.compute_state.peek_budget.grant() else {
1418 queued_peeks.push_front(peek);
1419 break;
1420 };
1421 self.walk_index_peek(&mut upper, peek, fuel);
1422 }
1423
1424 self.compute_state.peek_passed_over = !queued_peeks.is_empty();
1427 let served = std::mem::replace(&mut self.compute_state.queued_peeks, queued_peeks);
1428 self.compute_state.queued_peeks.extend(served);
1429 }
1430
1431 #[mz_ore::instrument(level = "debug")]
1436 fn send_peek_response(&mut self, peek: PendingPeek, response: PeekResponse) {
1437 let log_event = peek.as_log_event(false);
1438 self.send_compute_response(ComputeResponse::PeekResponse(
1440 peek.peek().uuid,
1441 response,
1442 OpenTelemetryContext::obtain(),
1443 ));
1444
1445 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
1447 logger.log(&log_event);
1448 }
1449 }
1450
1451 pub fn process_subscribes(&mut self) {
1453 let mut subscribe_responses = self.compute_state.subscribe_response_buffer.borrow_mut();
1454 for (sink_id, mut response) in subscribe_responses.drain(..) {
1455 if let Some(collection) = self.compute_state.collections.get_mut(&sink_id) {
1457 let new_frontier = match &response {
1458 SubscribeResponse::Batch(b) => b.upper.clone(),
1459 SubscribeResponse::DroppedAt(_) => Antichain::new(),
1460 };
1461
1462 let reported = collection.reported_frontiers();
1463 assert!(
1464 reported.write_frontier.allows_reporting(&new_frontier),
1465 "subscribe write frontier regression: {:?} -> {:?}",
1466 reported.write_frontier,
1467 new_frontier,
1468 );
1469 assert!(
1470 reported.input_frontier.allows_reporting(&new_frontier),
1471 "subscribe input frontier regression: {:?} -> {:?}",
1472 reported.input_frontier,
1473 new_frontier,
1474 );
1475
1476 collection
1477 .set_reported_write_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1478 collection
1479 .set_reported_input_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1480 collection.set_reported_output_frontier(ReportedFrontier::Reported(new_frontier));
1481 } else {
1482 }
1485
1486 response
1487 .to_error_if_exceeds(usize::try_from(self.compute_state.max_result_size).unwrap());
1488 self.send_compute_response(ComputeResponse::SubscribeResponse(sink_id, response));
1489 }
1490 }
1491
1492 pub fn process_copy_tos(&self) {
1494 let mut responses = self.compute_state.copy_to_response_buffer.borrow_mut();
1495 for (sink_id, response) in responses.drain(..) {
1496 self.send_compute_response(ComputeResponse::CopyToResponse(sink_id, response));
1497 }
1498 }
1499
1500 fn send_compute_response(&self, response: ComputeResponse) {
1502 let _ = self.response_tx.send(response);
1505 }
1506
1507 pub(crate) fn check_expiration(&self) {
1509 let now = mz_ore::now::SYSTEM_TIME();
1510 if self.compute_state.replica_expiration.less_than(&now.into()) {
1511 let now_datetime = mz_ore::now::to_datetime(now);
1512 let expiration_datetime = self
1513 .compute_state
1514 .replica_expiration
1515 .as_option()
1516 .map(Into::into)
1517 .map(mz_ore::now::to_datetime);
1518
1519 error!(
1522 now,
1523 now_datetime = ?now_datetime,
1524 expiration = ?self.compute_state.replica_expiration.elements(),
1525 expiration_datetime = ?expiration_datetime,
1526 "replica expired"
1527 );
1528
1529 assert!(
1531 !self.compute_state.replica_expiration.less_than(&now.into()),
1532 "replica expired. now: {now} ({now_datetime:?}), expiration: {:?} ({expiration_datetime:?})",
1533 self.compute_state.replica_expiration.elements(),
1534 );
1535 }
1536 }
1537
1538 pub fn determine_dataflow_expiration(
1544 &self,
1545 time_dependence: &TimeDependence,
1546 until: &Antichain<Timestamp>,
1547 ) -> Antichain<Timestamp> {
1548 let iter = self
1553 .compute_state
1554 .replica_expiration
1555 .iter()
1556 .filter_map(|t| time_dependence.apply(*t))
1557 .filter_map(|t| Timestamp::try_step_forward(&t))
1558 .filter(|expiration| !until.less_equal(expiration));
1559 Antichain::from_iter(iter)
1560 }
1561}
1562
1563pub enum PendingPeek {
1568 Index(IndexPeek),
1570 Persist(PersistPeek),
1572 Offloaded(OffloadedPeek),
1575}
1576
1577impl PendingPeek {
1578 pub fn as_log_event(&self, installed: bool) -> ComputeEvent {
1580 let peek = self.peek();
1581 let (id, peek_type) = match &peek.target {
1582 PeekTarget::Index { id } => (*id, logging::compute::PeekType::Index),
1583 PeekTarget::Persist { id, .. } => (*id, logging::compute::PeekType::Persist),
1584 };
1585 let uuid = peek.uuid.into_bytes();
1586 ComputeEvent::Peek(PeekEvent {
1587 id,
1588 time: peek.timestamp,
1589 uuid,
1590 peek_type,
1591 installed,
1592 })
1593 }
1594
1595 fn index(peek: Peek, mut trace_bundle: TraceBundle) -> Self {
1596 let empty_frontier = Antichain::new();
1597 let timestamp_frontier = Antichain::from_elem(peek.timestamp);
1598 trace_bundle
1599 .oks_mut()
1600 .set_logical_compaction(timestamp_frontier.borrow());
1601 trace_bundle
1602 .errs_mut()
1603 .set_logical_compaction(timestamp_frontier.borrow());
1604 trace_bundle
1605 .oks_mut()
1606 .set_physical_compaction(empty_frontier.borrow());
1607 trace_bundle
1608 .errs_mut()
1609 .set_physical_compaction(empty_frontier.borrow());
1610
1611 PendingPeek::Index(IndexPeek {
1612 peek,
1613 trace_bundle,
1614 span: tracing::Span::current(),
1615 })
1616 }
1617
1618 fn persist(
1619 peek: Peek,
1620 persist_clients: Arc<PersistClientCache>,
1621 metadata: CollectionMetadata,
1622 max_result_size: usize,
1623 timely_worker: &TimelyWorker,
1624 row_iteration_config: PeekRowIterationConfig,
1625 ) -> Self {
1626 let active_worker = {
1627 let chosen_index = usize::cast_from(peek.uuid.hashed()) % timely_worker.peers();
1629 chosen_index == timely_worker.index()
1630 };
1631 let activator = timely_worker.sync_activator_for([].into());
1632 let peek_uuid = peek.uuid;
1633
1634 let (result_tx, result_rx) = oneshot::channel();
1635 let timestamp = peek.timestamp;
1636 let mfp_plan = peek.map_filter_project.clone();
1637 let max_results_needed = peek
1638 .finishing
1639 .limit
1640 .map(|l| usize::cast_from(u64::from(l)))
1641 .unwrap_or(usize::MAX)
1642 + peek.finishing.offset;
1643 let order_by = peek.finishing.order_by.clone();
1644
1645 let literal_constraint = peek
1647 .literal_constraints
1648 .clone()
1649 .map(|rows| rows.into_element());
1650
1651 let task_handle = mz_ore::task::spawn(|| "persist::peek", async move {
1652 let start = Instant::now();
1653 let result = if active_worker {
1654 PersistPeek::do_peek(
1655 &persist_clients,
1656 metadata,
1657 timestamp,
1658 literal_constraint,
1659 mfp_plan,
1660 max_result_size,
1661 max_results_needed,
1662 row_iteration_config,
1663 )
1664 .await
1665 } else {
1666 Ok(vec![])
1667 };
1668 let result = match result {
1669 Ok(rows) => PeekResponse::Rows(vec![RowCollection::new(rows, &order_by)]),
1670 Err(error) => PeekResponse::Error(error),
1671 };
1672 match result_tx.send((result, start.elapsed())) {
1673 Ok(()) => {}
1674 Err((_result, elapsed)) => {
1675 debug!(duration =? elapsed, "dropping result for cancelled peek {peek_uuid}")
1676 }
1677 }
1678 match activator.activate() {
1679 Ok(()) => {}
1680 Err(_) => {
1681 debug!("unable to wake timely after completed peek {peek_uuid}");
1682 }
1683 }
1684 });
1685 PendingPeek::Persist(PersistPeek {
1686 peek,
1687 _abort_handle: task_handle.abort_on_drop(),
1688 result: result_rx,
1689 span: tracing::Span::current(),
1690 })
1691 }
1692
1693 fn span(&self) -> &tracing::Span {
1694 match self {
1695 PendingPeek::Index(p) => &p.span,
1696 PendingPeek::Persist(p) => &p.span,
1697 PendingPeek::Offloaded(p) => &p.span,
1698 }
1699 }
1700
1701 pub(crate) fn peek(&self) -> &Peek {
1702 match self {
1703 PendingPeek::Index(p) => &p.peek,
1704 PendingPeek::Persist(p) => &p.peek,
1705 PendingPeek::Offloaded(p) => &p.peek,
1706 }
1707 }
1708}
1709
1710pub struct PersistPeek {
1715 pub(crate) peek: Peek,
1716 _abort_handle: AbortOnDropHandle<()>,
1719 result: oneshot::Receiver<(PeekResponse, Duration)>,
1721 span: tracing::Span,
1723}
1724
1725impl PersistPeek {
1726 async fn do_peek(
1727 persist_clients: &PersistClientCache,
1728 metadata: CollectionMetadata,
1729 as_of: Timestamp,
1730 literal_constraint: Option<Row>,
1731 mfp_plan: SafeMfpPlan,
1732 max_result_size: usize,
1733 mut limit_remaining: usize,
1734 row_iteration_config: PeekRowIterationConfig,
1735 ) -> Result<Vec<(Row, NonZeroUsize)>, PeekError> {
1736 let client = persist_clients
1737 .open(metadata.persist_location)
1738 .await
1739 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1740
1741 let mut reader: ReadHandle<SourceData, (), Timestamp, StorageDiff> = client
1742 .open_leased_reader(
1743 metadata.data_shard,
1744 Arc::new(metadata.relation_desc.clone()),
1745 Arc::new(UnitSchema),
1746 Diagnostics::from_purpose("persist::peek"),
1747 USE_CRITICAL_SINCE_SNAPSHOT.get(client.dyncfgs()),
1748 )
1749 .await
1750 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1751
1752 let mut txns_read = if let Some(txns_id) = metadata.txns_shard {
1759 Some(TxnsCache::open(&client, txns_id, Some(metadata.data_shard)).await)
1760 } else {
1761 None
1762 };
1763
1764 let metrics = client.metrics();
1765
1766 let mut cursor = StatsCursor::new(
1767 &mut reader,
1768 txns_read.as_mut(),
1769 metrics,
1770 &mfp_plan,
1771 &metadata.relation_desc,
1772 Antichain::from_elem(as_of),
1773 )
1774 .await
1775 .map_err(|since| {
1776 PeekError::unstructured(format!(
1777 "attempted to peek at {as_of}, but the since has advanced to {since:?}"
1778 ))
1779 })?;
1780
1781 let mut result = vec![];
1783 let mut datum_vec = DatumVec::new();
1784 let mut row_builder = Row::default();
1785 let arena = RowArena::new();
1786 let mut total_size = 0usize;
1787 let mut row_iteration_tracker = PeekRowIterationTracker::new(None, 0);
1788
1789 let literal_len = match &literal_constraint {
1790 None => 0,
1791 Some(row) => row.iter().count(),
1792 };
1793
1794 'collect: while limit_remaining > 0 {
1795 let Some(batch) = cursor.next().await else {
1796 break;
1797 };
1798 for (data, _, d) in batch {
1799 row_iteration_tracker.set_limit(row_iteration_config.current_limit());
1802 row_iteration_tracker.track_next()?;
1803
1804 let row = data.map_err(PeekError::from)?;
1805
1806 if let Some(literal) = &literal_constraint {
1807 match row.iter().take(literal_len).cmp(literal.iter()) {
1808 Ordering::Less => continue,
1809 Ordering::Equal => {}
1810 Ordering::Greater => break 'collect,
1811 }
1812 }
1813
1814 let count: usize = d.try_into().map_err(|_| {
1815 error!(
1816 shard = %metadata.data_shard, diff = d, ?row,
1817 "persist peek encountered negative multiplicities",
1818 );
1819 PeekError::unstructured(format!(
1820 "Invalid data in source, \
1821 saw retractions ({}) for row that does not exist: {:?}",
1822 -d, row,
1823 ))
1824 })?;
1825 let Some(count) = NonZeroUsize::new(count) else {
1826 continue;
1827 };
1828 let mut datum_local = datum_vec.borrow_with(&row);
1829 let eval_result = mfp_plan
1830 .evaluate_into(&mut datum_local, &arena, &mut row_builder)
1831 .map(|row| row.cloned())
1832 .map_err(PeekError::from)?;
1833 if let Some(row) = eval_result {
1834 total_size = total_size.saturating_add(entry_byte_len(&row));
1835 if total_size > max_result_size {
1836 return Err(PeekError::ResultExceedsMaxSize { max_result_size });
1837 }
1838 result.push((row, count));
1839 limit_remaining = limit_remaining.saturating_sub(count.get());
1840 if limit_remaining == 0 {
1841 break;
1842 }
1843 }
1844 }
1845 }
1846
1847 Ok(result)
1848 }
1849}
1850
1851pub struct IndexPeek {
1853 peek: Peek,
1854 trace_bundle: TraceBundle,
1856 span: tracing::Span,
1858}
1859
1860impl IndexPeek {
1861 fn seek_fulfillment(
1878 &mut self,
1879 upper: &mut Antichain<Timestamp>,
1880 max_result_size: u64,
1881 stash: StashBounds,
1882 row_iteration_limit: Option<usize>,
1883 fuel: &mut usize,
1884 metrics: &IndexPeekMetrics<'_>,
1885 ) -> PeekStatus {
1886 let method_start = Instant::now();
1887
1888 self.trace_bundle.oks_mut().read_upper(upper);
1889 if upper.less_equal(&self.peek.timestamp) {
1890 return PeekStatus::NotReady;
1891 }
1892 self.trace_bundle.errs_mut().read_upper(upper);
1893 if upper.less_equal(&self.peek.timestamp) {
1894 return PeekStatus::NotReady;
1895 }
1896
1897 let read_frontier = self.trace_bundle.compaction_frontier();
1898 if !read_frontier.less_equal(&self.peek.timestamp) {
1899 let error = format!(
1900 "Arrangement compaction frontier ({:?}) is beyond the time of the attempted read ({})",
1901 read_frontier.elements(),
1902 self.peek.timestamp,
1903 );
1904 return PeekStatus::Ready(PeekResponse::Error(PeekError::unstructured(error)));
1905 }
1906
1907 metrics
1908 .frontier_check_seconds
1909 .observe(method_start.elapsed().as_secs_f64());
1910
1911 let result =
1912 self.collect_finished_data(max_result_size, stash, row_iteration_limit, fuel, metrics);
1913
1914 metrics
1915 .seek_fulfillment_seconds
1916 .observe(method_start.elapsed().as_secs_f64());
1917
1918 result
1919 }
1920
1921 fn collect_finished_data(
1927 &mut self,
1928 max_result_size: u64,
1929 stash: StashBounds,
1930 row_iteration_limit: Option<usize>,
1931 fuel: &mut usize,
1932 metrics: &IndexPeekMetrics<'_>,
1933 ) -> PeekStatus {
1934 let peek = &self.peek;
1935 let (oks, errs) = self.trace_bundle.oks_errs_mut();
1936 let mut scan = PeekScan::new(peek, errs, oks, max_result_size, stash);
1937
1938 let outcome = scan.step(row_iteration_limit, fuel);
1939
1940 let phases = scan.phases();
1941 match outcome {
1942 ScanOutcome::Finished(result) => {
1945 metrics.walk.walked_inline();
1946 metrics.walk.observe_error_phase(&phases);
1947 PeekStatus::Ready(match result {
1948 Ok(rows) => {
1949 metrics.walk.observe_ok_phase(&phases);
1950 let start = Instant::now();
1951 let response = rows_response(rows, &self.peek.finishing.order_by);
1952 metrics.walk.observe_row_collection(start.elapsed());
1953 response
1954 }
1955 Err(error) => PeekResponse::Error(error),
1958 })
1959 }
1960 ScanOutcome::Suspended => PeekStatus::Offload(scan),
1969 }
1970 }
1971}
1972
1973enum PeekStatus {
1976 NotReady,
1979 Offload(IndexPeekScan),
1986 Ready(PeekResponse),
1988}
1989
1990#[derive(Debug)]
1992struct ReportedFrontiers {
1993 write_frontier: ReportedFrontier,
1995 input_frontier: ReportedFrontier,
1997 output_frontier: ReportedFrontier,
1999}
2000
2001impl ReportedFrontiers {
2002 fn new() -> Self {
2004 Self {
2005 write_frontier: ReportedFrontier::new(),
2006 input_frontier: ReportedFrontier::new(),
2007 output_frontier: ReportedFrontier::new(),
2008 }
2009 }
2010}
2011
2012#[derive(Clone, Debug)]
2014pub enum ReportedFrontier {
2015 Reported(Antichain<Timestamp>),
2017 NotReported {
2019 lower: Antichain<Timestamp>,
2021 },
2022}
2023
2024impl ReportedFrontier {
2025 pub fn new() -> Self {
2027 let lower = Antichain::from_elem(timely::progress::Timestamp::minimum());
2028 Self::NotReported { lower }
2029 }
2030
2031 pub fn is_empty(&self) -> bool {
2033 match self {
2034 Self::Reported(frontier) => frontier.is_empty(),
2035 Self::NotReported { .. } => false,
2036 }
2037 }
2038
2039 fn allows_reporting(&self, other: &Antichain<Timestamp>) -> bool {
2045 match self {
2046 Self::Reported(frontier) => PartialOrder::less_than(frontier, other),
2047 Self::NotReported { lower } => PartialOrder::less_equal(lower, other),
2048 }
2049 }
2050}
2051
2052pub struct CollectionState {
2054 reported_frontiers: ReportedFrontiers,
2056 dataflow_index: Rc<usize>,
2062 pub is_subscribe_or_copy: bool,
2068 as_of: Antichain<Timestamp>,
2072
2073 pub sink_token: Option<SinkToken>,
2078 pub sink_write_frontier: Option<Rc<RefCell<Antichain<Timestamp>>>>,
2082 pub input_probes: BTreeMap<GlobalId, probe::Handle<Timestamp>>,
2084 pub compute_probe: Option<probe::Handle<Timestamp>>,
2089 logging: Option<CollectionLogging>,
2091 metrics: CollectionMetrics,
2093 read_only_tx: watch::Sender<bool>,
2105 pub read_only_rx: watch::Receiver<bool>,
2107}
2108
2109impl CollectionState {
2110 fn new(
2111 dataflow_index: Rc<usize>,
2112 is_subscribe_or_copy: bool,
2113 as_of: Antichain<Timestamp>,
2114 metrics: CollectionMetrics,
2115 ) -> Self {
2116 let (read_only_tx, read_only_rx) = watch::channel(true);
2119
2120 Self {
2121 reported_frontiers: ReportedFrontiers::new(),
2122 dataflow_index,
2123 is_subscribe_or_copy,
2124 as_of,
2125 sink_token: None,
2126 sink_write_frontier: None,
2127 input_probes: Default::default(),
2128 compute_probe: None,
2129 logging: None,
2130 metrics,
2131 read_only_tx,
2132 read_only_rx,
2133 }
2134 }
2135
2136 fn reported_frontiers(&self) -> &ReportedFrontiers {
2138 &self.reported_frontiers
2139 }
2140
2141 pub fn reset_reported_frontiers(&mut self, frontier: ReportedFrontier) {
2143 self.reported_frontiers.write_frontier = frontier.clone();
2144 self.reported_frontiers.input_frontier = frontier.clone();
2145 self.reported_frontiers.output_frontier = frontier;
2146 }
2147
2148 fn set_reported_write_frontier(&mut self, frontier: ReportedFrontier) {
2150 if let Some(logging) = &mut self.logging {
2151 let time = match &frontier {
2152 ReportedFrontier::Reported(frontier) => frontier.get(0).copied(),
2153 ReportedFrontier::NotReported { .. } => Some(Timestamp::MIN),
2154 };
2155 logging.set_frontier(time);
2156 }
2157
2158 self.reported_frontiers.write_frontier = frontier;
2159 }
2160
2161 fn set_reported_input_frontier(&mut self, frontier: ReportedFrontier) {
2163 if let Some(logging) = &mut self.logging {
2165 for (id, probe) in &self.input_probes {
2166 let new_time = probe.with_frontier(|frontier| frontier.as_option().copied());
2167 logging.set_import_frontier(*id, new_time);
2168 }
2169 }
2170
2171 self.reported_frontiers.input_frontier = frontier;
2172 }
2173
2174 fn set_reported_output_frontier(&mut self, frontier: ReportedFrontier) {
2176 let already_hydrated = self.hydrated();
2177
2178 self.reported_frontiers.output_frontier = frontier;
2179
2180 if !already_hydrated && self.hydrated() {
2181 if let Some(logging) = &mut self.logging {
2182 logging.set_hydrated();
2183 }
2184 self.metrics.record_collection_hydrated();
2185 }
2186 }
2187
2188 fn hydrated(&self) -> bool {
2190 match &self.reported_frontiers.output_frontier {
2191 ReportedFrontier::Reported(frontier) => PartialOrder::less_than(&self.as_of, frontier),
2192 ReportedFrontier::NotReported { .. } => false,
2193 }
2194 }
2195
2196 fn allow_writes(&self) {
2198 info!(
2199 dataflow_index = *self.dataflow_index,
2200 export = ?self.logging.as_ref().map(|l| l.export_id()),
2201 "allowing writes for dataflow",
2202 );
2203 let _ = self.read_only_tx.send(false);
2204 }
2205}
2206
2207#[cfg(test)]
2208mod tests;
2209
2210#[cfg(test)]
2212pub(crate) mod index_peek_tests;
2213
2214#[cfg(test)]
2216mod peek_sweep_tests;