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::RenderDataflowDescription;
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_dyncfg::{ConfigSet, ConfigValHandle};
34use mz_expr::SafeMfpPlan;
35use mz_expr::row::RowCollection;
36use mz_ore::cast::{CastFrom, CastLossy};
37use mz_ore::collections::CollectionExt;
38use mz_ore::metrics::{MetricsRegistry, UIntGauge};
39use mz_ore::now::EpochMillis;
40use mz_ore::soft_panic_or_log;
41use mz_ore::task::AbortOnDropHandle;
42use mz_ore::tracing::{OpenTelemetryContext, TracingHandle};
43use mz_persist_client::Diagnostics;
44use mz_persist_client::cache::PersistClientCache;
45use mz_persist_client::cfg::USE_CRITICAL_SINCE_SNAPSHOT;
46use mz_persist_client::read::ReadHandle;
47use mz_persist_types::PersistLocation;
48use mz_persist_types::codec_impls::UnitSchema;
49use mz_repr::{DatumVec, GlobalId, Row, RowArena, Timestamp};
50use mz_storage_operators::persist::SharedBatches;
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 persist_batches: SharedBatches,
220 pub txns_ctx: TxnsContext,
222 pub command_history: ComputeCommandHistory<UIntGauge>,
224 max_result_size: u64,
226 pub linear_join_spec: LinearJoinSpec,
228 pub metrics: WorkerMetrics,
230 tracing_handle: Arc<TracingHandle>,
232 pub context: ComputeInstanceContext,
234 pub worker_config: Rc<ConfigSet>,
248
249 pub metrics_registry: MetricsRegistry,
251
252 pub workers_per_process: usize,
254
255 pub peek_permits: Arc<PeekPermits>,
258
259 peek_walk_metrics: PeekWalkMetrics,
264
265 peek_budget: InlineBudget,
271
272 peek_passed_over: bool,
277
278 suspended_collections: BTreeMap<GlobalId, Rc<dyn Any>>,
284
285 pub server_maintenance_interval: Duration,
288
289 pub init_system_time: EpochMillis,
293
294 pub replica_expiration: Antichain<Timestamp>,
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 persist_batches: SharedBatches,
313 txns_ctx: TxnsContext,
314 metrics: WorkerMetrics,
315 tracing_handle: Arc<TracingHandle>,
316 context: ComputeInstanceContext,
317 metrics_registry: MetricsRegistry,
318 workers_per_process: usize,
319 peek_permits: Arc<PeekPermits>,
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 persist_batches,
338 txns_ctx,
339 command_history,
340 max_result_size: u64::MAX,
341 linear_join_spec: Default::default(),
342 metrics,
343 tracing_handle,
344 context,
345 worker_config,
346 metrics_registry,
347 workers_per_process,
348 peek_permits,
349 peek_walk_metrics,
350 peek_budget,
351 peek_passed_over: false,
352 suspended_collections: Default::default(),
353 server_maintenance_interval: Duration::ZERO,
354 init_system_time: mz_ore::now::SYSTEM_TIME(),
355 replica_expiration: Antichain::default(),
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 {
470 use mz_ore::pager::Backend;
471 use mz_timely_util::column_pager::{Codec, apply_tiered_config};
472
473 let enabled = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
474 let codec = COLUMN_PAGED_BATCHER_LZ4.get(config).then_some(Codec::Lz4);
475 let swap_pageout = COLUMN_PAGED_BATCHER_SWAP_PAGEOUT.get(config);
476
477 const MIB: usize = 1024 * 1024;
482 const DEFAULT_MEM_LIMIT: usize = 4 * 1024 * MIB;
483 let mem_limit = crate::memory_limiter::get_memory_limit().unwrap_or(DEFAULT_MEM_LIMIT);
484 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config).max(0.0);
485 let total = usize::cast_lossy(f64::cast_lossy(mem_limit) * fraction).max(128 * MIB);
486
487 let backend = if self.context.scratch_directory.is_some() {
488 Backend::File
489 } else {
490 Backend::Swap
491 };
492
493 debug!(
494 enabled,
495 ?backend,
496 ?codec,
497 swap_pageout,
498 fraction,
499 mem_limit,
500 budget_bytes = total,
501 "column-paged batcher: applying tiered config",
502 );
503 apply_tiered_config(enabled, total, backend, codec, swap_pageout);
504 }
505
506 {
523 use mz_timely_util::pool_config::{PoolPagerConfig, apply_pool_config};
524
525 let compute_spill = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
526 let storage_spill = mz_storage_types::dyncfgs::ENABLE_UPSERT_PAGED_SPILL.get(config);
527 let sink_spill = ENABLE_CORRECTION_V2_SPILL.get(config);
528 mz_timely_util::columnar::chunk::set_compute_spill_enabled(compute_spill);
530 mz_timely_util::columnar::chunk::set_sink_spill_enabled(sink_spill);
531 if !(compute_spill || storage_spill || sink_spill) {
532 debug!("chunk spill: gates off, leaving the buffer pool uninstalled");
533 } else {
534 let spill_threads = COLUMN_PAGED_BATCHER_SPILL_WORKER_COUNT.get(config);
535 let eager_backing = COLUMN_PAGED_BATCHER_EAGER_BACKING.get(config);
536
537 const MIB: usize = 1024 * 1024;
544 const DEFAULT_RAM: usize = 4 * 1024 * MIB;
545 let ram = mz_ore::memory::physical_memory_bytes().unwrap_or(DEFAULT_RAM);
546 let of_ram =
547 |fraction: f64| usize::cast_lossy(f64::cast_lossy(ram) * fraction.max(0.0));
548 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config);
549 let total = of_ram(fraction).max(128 * MIB);
550 let rss_target = of_ram(COLUMN_PAGED_BATCHER_POOL_RSS_TARGET_FRACTION.get(config));
555
556 let applied = apply_pool_config(PoolPagerConfig {
557 budget_bytes: total,
558 spill_threads,
559 eager_backing,
560 rss_target_bytes: rss_target,
561 });
562 if applied {
563 info!(
564 compute_spill,
565 storage_spill,
566 fraction,
567 ram,
568 budget_bytes = total,
569 spill_threads,
570 eager_backing,
571 rss_target_bytes = rss_target,
572 "chunk spill: applying buffer-pool config",
573 );
574 } else {
575 warn!("chunk spill: buffer pool unavailable; chunks stay resident");
576 }
577 }
578
579 let compress_min_depth =
583 u8::try_from(COLUMN_CHUNK_COMPRESS_MIN_DEPTH.get(config)).unwrap_or(u8::MAX);
584 mz_timely_util::columnar::chunk::set_compress_min_depth(compress_min_depth);
585 }
586
587 self.server_maintenance_interval = COMPUTE_SERVER_MAINTENANCE_INTERVAL.get(config);
590
591 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(config);
592 match overflowing_behavior.parse() {
593 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
594 Err(err) => {
595 error!(
596 err,
597 overflowing_behavior, "Invalid value for ore_overflowing_behavior"
598 );
599 }
600 }
601 }
602
603 pub fn apply_expiration_offset(&mut self, offset: Duration) {
609 if self.replica_expiration.is_empty() {
610 let offset: EpochMillis = offset
611 .as_millis()
612 .try_into()
613 .expect("duration must fit within u64");
614 let replica_expiration_millis = self.init_system_time + offset;
615 let replica_expiration = Timestamp::from(replica_expiration_millis);
616
617 info!(
618 offset = %offset,
619 replica_expiration_millis = %replica_expiration_millis,
620 replica_expiration_utc = %mz_ore::now::to_datetime(replica_expiration_millis),
621 "setting replica expiration",
622 );
623 self.replica_expiration = Antichain::from_elem(replica_expiration);
624
625 self.metrics
627 .replica_expiration_timestamp_seconds
628 .set(replica_expiration.into());
629 }
630 }
631
632 pub fn dataflow_max_inflight_bytes(&self) -> Option<usize> {
635 use mz_compute_types::dyncfgs::{
636 DATAFLOW_MAX_INFLIGHT_BYTES, DATAFLOW_MAX_INFLIGHT_BYTES_CC,
637 };
638
639 if self.persist_clients.cfg.is_cc_active {
640 DATAFLOW_MAX_INFLIGHT_BYTES_CC.get(&self.worker_config)
641 } else {
642 DATAFLOW_MAX_INFLIGHT_BYTES.get(&self.worker_config)
643 }
644 }
645}
646
647pub(crate) struct ActiveComputeState<'a> {
649 pub timely_worker: &'a mut TimelyWorker,
651 pub compute_state: &'a mut ComputeState,
653 pub response_tx: &'a mut ResponseSender,
655}
656
657pub struct SinkToken(#[allow(dead_code)] Box<dyn Any>);
659
660impl SinkToken {
661 pub fn new(t: Box<dyn Any>) -> Self {
663 Self(t)
664 }
665}
666
667impl<'a> ActiveComputeState<'a> {
668 #[mz_ore::instrument(level = "debug")]
670 pub fn handle_compute_command(&mut self, cmd: ComputeCommand) {
671 use ComputeCommand::*;
672
673 self.compute_state.command_history.push(cmd.clone());
674
675 let timer = self
677 .compute_state
678 .metrics
679 .handle_command_duration_seconds
680 .for_command(&cmd)
681 .start_timer();
682
683 match cmd {
684 Hello { .. } => panic!("Hello must be captured before"),
685 CreateInstance(instance_config) => self.handle_create_instance(*instance_config),
686 InitializationComplete => (),
687 UpdateConfiguration(params) => self.handle_update_configuration(*params),
688 CreateDataflow(dataflow) => self.handle_create_dataflow(*dataflow),
689 Schedule(id) => self.handle_schedule(id),
690 AllowCompaction { id, frontier } => self.handle_allow_compaction(id, frontier),
691 Peek(peek) => {
692 peek.otel_ctx.attach_as_parent();
693 self.handle_peek(*peek)
694 }
695 CancelPeek { uuid } => self.handle_cancel_peek(uuid),
696 AllowWrites(id) => {
697 self.handle_allow_writes(id);
698 }
699 }
700
701 timer.observe_duration();
702 }
703
704 fn handle_create_instance(&mut self, config: InstanceConfig) {
705 config
710 .initial_config
711 .apply(&self.compute_state.worker_config);
712
713 self.compute_state.apply_worker_config();
715
716 mz_row_spine::DICTIONARY_COMPRESSION.store(
722 config.arrangement_dictionary_compression,
723 std::sync::atomic::Ordering::Relaxed,
724 );
725
726 if let Some(offset) = config.expiration_offset {
727 self.compute_state.apply_expiration_offset(offset);
728 }
729
730 self.initialize_logging(config.logging);
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(&mut self, dataflow: RenderDataflowDescription<CollectionMetadata>) {
770 let dataflow_index = Rc::new(self.timely_worker.next_dataflow_index());
771 let as_of = dataflow.as_of.clone().unwrap();
772
773 let dataflow_expiration = dataflow
774 .time_dependence
775 .as_ref()
776 .map(|time_dependence| {
777 self.determine_dataflow_expiration(time_dependence, &dataflow.until)
778 })
779 .unwrap_or_default();
780
781 let until = dataflow.until.meet(&dataflow_expiration);
783
784 if dataflow.is_transient() {
785 debug!(
786 name = %dataflow.debug_name,
787 import_ids = %dataflow.display_import_ids(),
788 export_ids = %dataflow.display_export_ids(),
789 as_of = ?as_of.elements(),
790 time_dependence = ?dataflow.time_dependence,
791 expiration = ?dataflow_expiration.elements(),
792 expiration_datetime = ?dataflow_expiration
793 .as_option()
794 .map(|t| mz_ore::now::to_datetime(t.into())),
795 plan_until = ?dataflow.until.elements(),
796 until = ?until.elements(),
797 "creating dataflow",
798 );
799 } else {
800 info!(
801 name = %dataflow.debug_name,
802 import_ids = %dataflow.display_import_ids(),
803 export_ids = %dataflow.display_export_ids(),
804 as_of = ?as_of.elements(),
805 time_dependence = ?dataflow.time_dependence,
806 expiration = ?dataflow_expiration.elements(),
807 expiration_datetime = ?dataflow_expiration
808 .as_option()
809 .map(|t| mz_ore::now::to_datetime(t.into())),
810 plan_until = ?dataflow.until.elements(),
811 until = ?until.elements(),
812 "creating dataflow",
813 );
814 };
815
816 let subscribe_copy_ids: BTreeSet<_> = dataflow
817 .subscribe_ids()
818 .chain(dataflow.copy_to_ids())
819 .collect();
820
821 let starts_immediately = dataflow.import_ids().next().is_none();
830
831 for object_id in dataflow.export_ids() {
833 let is_subscribe_or_copy = subscribe_copy_ids.contains(&object_id);
834 let metrics = self.compute_state.metrics.for_collection(object_id);
835 let mut collection = CollectionState::new(
836 Rc::clone(&dataflow_index),
837 is_subscribe_or_copy,
838 as_of.clone(),
839 metrics,
840 );
841
842 if let Some(logger) = self.compute_state.compute_logger.clone() {
843 let logging = CollectionLogging::new(
844 object_id,
845 logger,
846 *dataflow_index,
847 dataflow.import_ids(),
848 );
849 if starts_immediately {
850 logging.set_hydration_start();
851 }
852 collection.logging = Some(logging);
853 }
854
855 collection.reset_reported_frontiers(ReportedFrontier::NotReported {
856 lower: as_of.clone(),
857 });
858
859 let existing = self.compute_state.collections.insert(object_id, collection);
860 if existing.is_some() {
861 error!(
862 id = ?object_id,
863 "existing collection for newly created dataflow",
864 );
865 }
866 }
867
868 let (start_signal, suspension_token) = StartSignal::new();
869 for id in dataflow.export_ids() {
870 self.compute_state
871 .suspended_collections
872 .insert(id, Rc::clone(&suspension_token));
873 }
874
875 crate::render::build_compute_dataflow(
876 self.timely_worker,
877 self.compute_state,
878 dataflow,
879 start_signal,
880 until,
881 dataflow_expiration,
882 );
883 }
884
885 fn handle_schedule(&mut self, id: GlobalId) {
886 let suspension_token = self.compute_state.suspended_collections.remove(&id);
892 drop(suspension_token);
893
894 if let Some(collection) = self.compute_state.collections.get(&id) {
895 if let Some(logging) = &collection.logging {
896 logging.set_hydration_start();
897 }
898 }
899 }
900
901 fn handle_allow_compaction(&mut self, id: GlobalId, frontier: Antichain<Timestamp>) {
902 if frontier.is_empty() {
903 self.drop_collection(id);
905 } else {
906 self.compute_state
907 .traces
908 .allow_compaction(id, frontier.borrow());
909 }
910 }
911
912 #[mz_ore::instrument(level = "debug")]
913 fn handle_peek(&mut self, peek: Peek) {
914 let pending = match &peek.target {
915 PeekTarget::Index { id } => {
916 let trace_bundle = self.compute_state.traces.get(id).unwrap().clone();
918 PendingPeek::index(peek, trace_bundle)
919 }
920 PeekTarget::Persist { metadata, .. } => {
921 let metadata = metadata.clone();
922 PendingPeek::persist(
923 peek,
924 Arc::clone(&self.compute_state.persist_clients),
925 metadata,
926 usize::cast_from(self.compute_state.max_result_size),
927 self.timely_worker,
928 PeekRowIterationConfig::new(&self.compute_state.worker_config),
929 )
930 }
931 };
932
933 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
935 logger.log(&pending.as_log_event(true));
936 }
937
938 match pending {
939 PendingPeek::Index(peek) => self.serve_index_peek(&mut Antichain::new(), peek),
940 pending => self.poll_pending_peek(pending),
941 }
942 }
943
944 fn handle_cancel_peek(&mut self, uuid: Uuid) {
945 let queued = &mut self.compute_state.queued_peeks;
946 if let Some(index) = queued.iter().position(|peek| peek.peek.uuid == uuid) {
947 let peek = queued.remove(index).expect("found above");
948 self.send_peek_response(PendingPeek::Index(peek), PeekResponse::Canceled);
949 return;
950 }
951
952 let pending = &mut self.compute_state.pending_peeks;
953 let Some(index) = pending.iter().position(|peek| peek.peek().uuid == uuid) else {
954 return;
955 };
956 let peek = pending.remove(index).expect("found above");
957 self.send_peek_response(peek, PeekResponse::Canceled);
958 }
959
960 fn handle_allow_writes(&mut self, id: GlobalId) {
961 self.compute_state.persist_clients.cfg().enable_compaction();
965
966 if let Some(collection) = self.compute_state.collections.get_mut(&id) {
967 collection.allow_writes();
968 } else {
969 soft_panic_or_log!("allow writes for unknown collection {id}");
970 }
971 }
972
973 fn drop_collection(&mut self, id: GlobalId) {
975 let collection = self
976 .compute_state
977 .collections
978 .remove(&id)
979 .expect("dropped untracked collection");
980
981 self.compute_state.traces.remove(&id);
983 self.compute_state.suspended_collections.remove(&id);
985
986 if let Ok(index) = Rc::try_unwrap(collection.dataflow_index) {
988 self.timely_worker.drop_dataflow(index);
989 }
990
991 if !collection.is_subscribe_or_copy {
996 let reported = collection.reported_frontiers;
997 let write_frontier = (!reported.write_frontier.is_empty()).then(Antichain::new);
998 let input_frontier = (!reported.input_frontier.is_empty()).then(Antichain::new);
999 let output_frontier = (!reported.output_frontier.is_empty()).then(Antichain::new);
1000
1001 let frontiers = FrontiersResponse {
1002 write_frontier,
1003 input_frontier,
1004 output_frontier,
1005 };
1006 if frontiers.has_updates() {
1007 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1008 }
1009 }
1010 }
1011
1012 pub fn initialize_logging(&mut self, config: LoggingConfig) {
1014 if self.compute_state.compute_logger.is_some() {
1015 panic!("dataflow server has already initialized logging");
1016 }
1017
1018 let LoggingTraces {
1019 traces,
1020 dataflow_index,
1021 compute_logger: logger,
1022 } = logging::initialize(
1023 self.timely_worker,
1024 &config,
1025 self.compute_state.metrics_registry.clone(),
1026 self.compute_state.metrics.for_logging(),
1027 Rc::clone(&self.compute_state.worker_config),
1028 self.compute_state.workers_per_process,
1029 );
1030
1031 let dataflow_index = Rc::new(dataflow_index);
1032 let mut log_index_ids = config.index_logs;
1033 for (log, trace) in traces {
1034 let id = log_index_ids
1036 .remove(&log)
1037 .expect("`logging::initialize` does not invent logs");
1038 self.compute_state.traces.set(id, trace);
1039
1040 let is_subscribe_or_copy = false;
1042 let as_of = Antichain::from_elem(Timestamp::MIN);
1043 let metrics = self.compute_state.metrics.for_collection(id);
1044 let mut collection = CollectionState::new(
1045 Rc::clone(&dataflow_index),
1046 is_subscribe_or_copy,
1047 as_of,
1048 metrics,
1049 );
1050
1051 let logging =
1052 CollectionLogging::new(id, logger.clone(), *dataflow_index, std::iter::empty());
1053 logging.set_hydration_start();
1058 collection.logging = Some(logging);
1059
1060 let existing = self.compute_state.collections.insert(id, collection);
1061 if existing.is_some() {
1062 error!(
1063 id = ?id,
1064 "existing collection for newly initialized logging export",
1065 );
1066 }
1067 }
1068
1069 assert!(
1071 log_index_ids.is_empty(),
1072 "failed to create requested logging indexes: {log_index_ids:?}",
1073 );
1074
1075 self.compute_state.compute_logger = Some(logger);
1076 }
1077
1078 pub fn report_frontiers(&mut self) {
1080 let mut responses = Vec::new();
1081
1082 let mut new_frontier = Antichain::new();
1084
1085 for (&id, collection) in self.compute_state.collections.iter_mut() {
1086 if collection.is_subscribe_or_copy {
1089 continue;
1090 }
1091
1092 let reported = collection.reported_frontiers();
1093
1094 new_frontier.clear();
1096 if let Some(traces) = self.compute_state.traces.get_mut(&id) {
1097 assert!(
1098 collection.sink_write_frontier.is_none(),
1099 "collection {id} has multiple frontiers"
1100 );
1101 traces.oks_mut().read_upper(&mut new_frontier);
1102 } else if let Some(frontier) = &collection.sink_write_frontier {
1103 new_frontier.clone_from(&frontier.borrow());
1104 } else {
1105 error!(id = ?id, "collection without write frontier");
1106 continue;
1107 }
1108 let new_write_frontier = reported
1109 .write_frontier
1110 .allows_reporting(&new_frontier)
1111 .then(|| new_frontier.clone());
1112
1113 if let Some(probe) = &collection.compute_probe {
1126 if *collection.read_only_rx.borrow() {
1127 new_frontier.clear();
1128 }
1129 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1130 }
1131 let new_output_frontier = reported
1132 .output_frontier
1133 .allows_reporting(&new_frontier)
1134 .then(|| new_frontier.clone());
1135
1136 new_frontier.clear();
1138 for probe in collection.input_probes.values() {
1139 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1140 }
1141 let new_input_frontier = reported
1142 .input_frontier
1143 .allows_reporting(&new_frontier)
1144 .then(|| new_frontier.clone());
1145
1146 if let Some(frontier) = &new_write_frontier {
1147 collection
1148 .set_reported_write_frontier(ReportedFrontier::Reported(frontier.clone()));
1149 }
1150 if let Some(frontier) = &new_input_frontier {
1151 collection
1152 .set_reported_input_frontier(ReportedFrontier::Reported(frontier.clone()));
1153 }
1154 if let Some(frontier) = &new_output_frontier {
1155 collection
1156 .set_reported_output_frontier(ReportedFrontier::Reported(frontier.clone()));
1157 }
1158
1159 let response = FrontiersResponse {
1160 write_frontier: new_write_frontier,
1161 input_frontier: new_input_frontier,
1162 output_frontier: new_output_frontier,
1163 };
1164 if response.has_updates() {
1165 responses.push((id, response));
1166 }
1167 }
1168
1169 for (id, frontiers) in responses {
1170 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1171 }
1172 }
1173
1174 pub(crate) fn report_metrics(&self) {
1176 if let Some(expiration) = self.compute_state.replica_expiration.as_option() {
1177 let now = Duration::from_millis(mz_ore::now::SYSTEM_TIME()).as_secs_f64();
1178 let expiration = Duration::from_millis(<u64>::from(expiration)).as_secs_f64();
1179 let remaining = expiration - now;
1180 self.compute_state
1181 .metrics
1182 .replica_expiration_remaining_seconds
1183 .set(remaining)
1184 }
1185 }
1186
1187 fn serve_index_peek(&mut self, upper: &mut Antichain<Timestamp>, peek: IndexPeek) {
1190 match self.compute_state.peek_budget.grant() {
1191 Some(fuel) => self.walk_index_peek(upper, peek, fuel),
1192 None => {
1193 self.compute_state.peek_passed_over = true;
1196 self.compute_state.queued_peeks.push_back(peek);
1197 }
1198 }
1199 }
1200
1201 fn walk_index_peek(
1204 &mut self,
1205 upper: &mut Antichain<Timestamp>,
1206 mut peek: IndexPeek,
1207 fuel: usize,
1208 ) {
1209 let start = Instant::now();
1210
1211 let row_iteration_limit = peek_row_iteration_limit(&self.compute_state.worker_config);
1212
1213 let peek_stash_eligible = peek
1214 .peek
1215 .finishing
1216 .is_streamable(peek.peek.result_desc.arity());
1217
1218 let has_stash_location = {
1222 let enabled = ENABLE_PEEK_RESPONSE_STASH.get(&self.compute_state.worker_config);
1223 let located = self.compute_state.peek_stash_persist_location.is_some();
1224 if !located && enabled {
1225 error!("missing peek_stash_persist_location but peek stash is enabled");
1226 }
1227 enabled && located
1228 };
1229
1230 let stash = StashBounds {
1231 eligible: peek_stash_eligible && has_stash_location,
1232 threshold_bytes: PEEK_RESPONSE_STASH_THRESHOLD_BYTES
1233 .get(&self.compute_state.worker_config),
1234 batch_bytes: PEEK_RESPONSE_STASH_BATCH_BYTES.get(&self.compute_state.worker_config),
1235 };
1236
1237 let metrics = IndexPeekMetrics {
1238 seek_fulfillment_seconds: &self
1239 .compute_state
1240 .metrics
1241 .index_peek_seek_fulfillment_seconds,
1242 frontier_check_seconds: &self.compute_state.metrics.index_peek_frontier_check_seconds,
1243 walk: &self.compute_state.peek_walk_metrics,
1244 };
1245
1246 let mut unspent = fuel;
1247 let status = peek.seek_fulfillment(
1248 upper,
1249 self.compute_state.max_result_size,
1250 stash,
1251 row_iteration_limit,
1252 &mut unspent,
1253 &metrics,
1254 );
1255
1256 self.compute_state
1259 .peek_budget
1260 .charge(fuel.saturating_sub(unspent));
1261
1262 self.compute_state
1263 .metrics
1264 .index_peek_total_seconds
1265 .observe(start.elapsed().as_secs_f64());
1266
1267 match status {
1268 PeekStatus::Ready(response) => {
1269 let _span =
1270 span!(parent: &peek.span, Level::DEBUG, "process_peek_response").entered();
1271 self.send_peek_response(PendingPeek::Index(peek), response);
1272 }
1273 PeekStatus::NotReady => self.compute_state.queued_peeks.push_back(peek),
1274 PeekStatus::Offload(scan) => {
1275 let _span = span!(parent: &peek.span, Level::DEBUG, "offload_index_peek").entered();
1276
1277 let permits = Arc::clone(&self.compute_state.peek_permits);
1278 let config = OffloadConfig::new(&self.compute_state.worker_config);
1279 let walk_metrics = self.compute_state.peek_walk_metrics.clone();
1280 let worker = std::thread::current();
1281 let stash = self
1284 .compute_state
1285 .peek_stash_persist_location
1286 .as_ref()
1287 .filter(|_| scan.stash_eligible())
1288 .cloned()
1289 .map(|location| {
1290 peek_stash::StashTarget::new(
1291 &peek.peek,
1292 Arc::clone(&self.compute_state.persist_clients),
1293 location,
1294 )
1295 });
1296
1297 let offloaded = OffloadedPeek::start(
1298 peek.peek,
1299 scan,
1300 stash,
1301 permits,
1302 config,
1303 walk_metrics,
1304 worker,
1305 );
1306
1307 self.compute_state
1308 .pending_peeks
1309 .push_back(PendingPeek::Offloaded(offloaded));
1310 }
1311 }
1312 }
1313
1314 fn poll_pending_peek(&mut self, mut pending: PendingPeek) {
1317 let response = match &mut pending {
1318 PendingPeek::Index(peek) => {
1321 soft_panic_or_log!(
1322 "index peek on {} polled as if a driver had taken it over",
1323 peek.peek.target.id()
1324 );
1325 None
1326 }
1327 PendingPeek::Persist(peek) => peek.result.try_recv().ok().map(|(result, duration)| {
1328 self.compute_state
1329 .metrics
1330 .persist_peek_seconds
1331 .observe(duration.as_secs_f64());
1332 result
1333 }),
1334 PendingPeek::Offloaded(offloaded) => match offloaded.result.try_recv() {
1335 Ok((response, duration)) => {
1336 self.compute_state
1339 .metrics
1340 .index_peek_offload_seconds
1341 .observe(duration.as_secs_f64());
1342
1343 trace!(?offloaded.peek, ?duration, "finished offloaded index peek walk");
1344 Some(response)
1345 }
1346 Err(oneshot::error::TryRecvError::Empty) => None,
1347 Err(oneshot::error::TryRecvError::Closed) => {
1354 soft_panic_or_log!(
1355 "offloaded walk of peek on {} ended without an outcome",
1356 offloaded.peek.target.id()
1357 );
1358 Some(PeekResponse::Error(PeekError::unstructured(
1359 "offloaded peek walk failed",
1360 )))
1361 }
1362 },
1363 };
1364
1365 if let Some(response) = response {
1366 let _span =
1367 span!(parent: pending.span(), Level::DEBUG, "process_peek_response").entered();
1368 self.send_peek_response(pending, response)
1369 } else {
1370 self.compute_state.pending_peeks.push_back(pending);
1371 }
1372 }
1373
1374 pub fn process_peeks(&mut self) {
1381 self.compute_state.peek_budget.start_activation();
1385
1386 self.compute_state.peek_passed_over = false;
1390
1391 if self.compute_state.pending_peeks.is_empty() && self.compute_state.queued_peeks.is_empty()
1393 {
1394 return;
1395 }
1396
1397 let mut upper = Antichain::new();
1398
1399 let mut pending_peeks = std::mem::take(&mut self.compute_state.pending_peeks);
1402 while let Some(peek) = pending_peeks.pop_front() {
1403 self.poll_pending_peek(peek);
1404 }
1405
1406 let mut queued_peeks = std::mem::take(&mut self.compute_state.queued_peeks);
1409 while let Some(peek) = queued_peeks.pop_front() {
1410 let Some(fuel) = self.compute_state.peek_budget.grant() else {
1411 queued_peeks.push_front(peek);
1412 break;
1413 };
1414 self.walk_index_peek(&mut upper, peek, fuel);
1415 }
1416
1417 self.compute_state.peek_passed_over = !queued_peeks.is_empty();
1420 let served = std::mem::replace(&mut self.compute_state.queued_peeks, queued_peeks);
1421 self.compute_state.queued_peeks.extend(served);
1422 }
1423
1424 #[mz_ore::instrument(level = "debug")]
1429 fn send_peek_response(&mut self, peek: PendingPeek, response: PeekResponse) {
1430 let log_event = peek.as_log_event(false);
1431 self.send_compute_response(ComputeResponse::PeekResponse(
1433 peek.peek().uuid,
1434 response,
1435 OpenTelemetryContext::obtain(),
1436 ));
1437
1438 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
1440 logger.log(&log_event);
1441 }
1442 }
1443
1444 pub fn process_subscribes(&mut self) {
1446 let mut subscribe_responses = self.compute_state.subscribe_response_buffer.borrow_mut();
1447 for (sink_id, mut response) in subscribe_responses.drain(..) {
1448 if let Some(collection) = self.compute_state.collections.get_mut(&sink_id) {
1450 let new_frontier = match &response {
1451 SubscribeResponse::Batch(b) => b.upper.clone(),
1452 SubscribeResponse::DroppedAt(_) => Antichain::new(),
1453 };
1454
1455 let reported = collection.reported_frontiers();
1456 assert!(
1457 reported.write_frontier.allows_reporting(&new_frontier),
1458 "subscribe write frontier regression: {:?} -> {:?}",
1459 reported.write_frontier,
1460 new_frontier,
1461 );
1462 assert!(
1463 reported.input_frontier.allows_reporting(&new_frontier),
1464 "subscribe input frontier regression: {:?} -> {:?}",
1465 reported.input_frontier,
1466 new_frontier,
1467 );
1468
1469 collection
1470 .set_reported_write_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1471 collection
1472 .set_reported_input_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1473 collection.set_reported_output_frontier(ReportedFrontier::Reported(new_frontier));
1474 } else {
1475 }
1478
1479 response
1480 .to_error_if_exceeds(usize::try_from(self.compute_state.max_result_size).unwrap());
1481 self.send_compute_response(ComputeResponse::SubscribeResponse(sink_id, response));
1482 }
1483 }
1484
1485 pub fn process_copy_tos(&self) {
1487 let mut responses = self.compute_state.copy_to_response_buffer.borrow_mut();
1488 for (sink_id, response) in responses.drain(..) {
1489 self.send_compute_response(ComputeResponse::CopyToResponse(sink_id, response));
1490 }
1491 }
1492
1493 fn send_compute_response(&self, response: ComputeResponse) {
1495 let _ = self.response_tx.send(response);
1498 }
1499
1500 pub(crate) fn check_expiration(&self) {
1502 let now = mz_ore::now::SYSTEM_TIME();
1503 if self.compute_state.replica_expiration.less_than(&now.into()) {
1504 let now_datetime = mz_ore::now::to_datetime(now);
1505 let expiration_datetime = self
1506 .compute_state
1507 .replica_expiration
1508 .as_option()
1509 .map(Into::into)
1510 .map(mz_ore::now::to_datetime);
1511
1512 error!(
1515 now,
1516 now_datetime = ?now_datetime,
1517 expiration = ?self.compute_state.replica_expiration.elements(),
1518 expiration_datetime = ?expiration_datetime,
1519 "replica expired"
1520 );
1521
1522 assert!(
1524 !self.compute_state.replica_expiration.less_than(&now.into()),
1525 "replica expired. now: {now} ({now_datetime:?}), expiration: {:?} ({expiration_datetime:?})",
1526 self.compute_state.replica_expiration.elements(),
1527 );
1528 }
1529 }
1530
1531 pub fn determine_dataflow_expiration(
1537 &self,
1538 time_dependence: &TimeDependence,
1539 until: &Antichain<Timestamp>,
1540 ) -> Antichain<Timestamp> {
1541 let iter = self
1546 .compute_state
1547 .replica_expiration
1548 .iter()
1549 .filter_map(|t| time_dependence.apply(*t))
1550 .filter_map(|t| Timestamp::try_step_forward(&t))
1551 .filter(|expiration| !until.less_equal(expiration));
1552 Antichain::from_iter(iter)
1553 }
1554}
1555
1556pub enum PendingPeek {
1561 Index(IndexPeek),
1563 Persist(PersistPeek),
1565 Offloaded(OffloadedPeek),
1568}
1569
1570impl PendingPeek {
1571 pub fn as_log_event(&self, installed: bool) -> ComputeEvent {
1573 let peek = self.peek();
1574 let (id, peek_type) = match &peek.target {
1575 PeekTarget::Index { id } => (*id, logging::compute::PeekType::Index),
1576 PeekTarget::Persist { id, .. } => (*id, logging::compute::PeekType::Persist),
1577 };
1578 let uuid = peek.uuid.into_bytes();
1579 ComputeEvent::Peek(PeekEvent {
1580 id,
1581 time: peek.timestamp,
1582 uuid,
1583 peek_type,
1584 installed,
1585 })
1586 }
1587
1588 fn index(peek: Peek, mut trace_bundle: TraceBundle) -> Self {
1589 let empty_frontier = Antichain::new();
1590 let timestamp_frontier = Antichain::from_elem(peek.timestamp);
1591 trace_bundle
1592 .oks_mut()
1593 .set_logical_compaction(timestamp_frontier.borrow());
1594 trace_bundle
1595 .errs_mut()
1596 .set_logical_compaction(timestamp_frontier.borrow());
1597 trace_bundle
1598 .oks_mut()
1599 .set_physical_compaction(empty_frontier.borrow());
1600 trace_bundle
1601 .errs_mut()
1602 .set_physical_compaction(empty_frontier.borrow());
1603
1604 PendingPeek::Index(IndexPeek {
1605 peek,
1606 trace_bundle,
1607 span: tracing::Span::current(),
1608 })
1609 }
1610
1611 fn persist(
1612 peek: Peek,
1613 persist_clients: Arc<PersistClientCache>,
1614 metadata: CollectionMetadata,
1615 max_result_size: usize,
1616 timely_worker: &TimelyWorker,
1617 row_iteration_config: PeekRowIterationConfig,
1618 ) -> Self {
1619 let active_worker = {
1620 let chosen_index = usize::cast_from(peek.uuid.hashed()) % timely_worker.peers();
1622 chosen_index == timely_worker.index()
1623 };
1624 let activator = timely_worker.sync_activator_for([].into());
1625 let peek_uuid = peek.uuid;
1626
1627 let (result_tx, result_rx) = oneshot::channel();
1628 let timestamp = peek.timestamp;
1629 let mfp_plan = peek.map_filter_project.clone();
1630 let max_results_needed = peek
1631 .finishing
1632 .limit
1633 .map(|l| usize::cast_from(u64::from(l)))
1634 .unwrap_or(usize::MAX)
1635 + peek.finishing.offset;
1636 let order_by = peek.finishing.order_by.clone();
1637
1638 let literal_constraint = peek
1640 .literal_constraints
1641 .clone()
1642 .map(|rows| rows.into_element());
1643
1644 let task_handle = mz_ore::task::spawn(|| "persist::peek", async move {
1645 let start = Instant::now();
1646 let result = if active_worker {
1647 PersistPeek::do_peek(
1648 &persist_clients,
1649 metadata,
1650 timestamp,
1651 literal_constraint,
1652 mfp_plan,
1653 max_result_size,
1654 max_results_needed,
1655 row_iteration_config,
1656 )
1657 .await
1658 } else {
1659 Ok(vec![])
1660 };
1661 let result = match result {
1662 Ok(rows) => PeekResponse::Rows(vec![RowCollection::new(rows, &order_by)]),
1663 Err(error) => PeekResponse::Error(error),
1664 };
1665 match result_tx.send((result, start.elapsed())) {
1666 Ok(()) => {}
1667 Err((_result, elapsed)) => {
1668 debug!(duration =? elapsed, "dropping result for cancelled peek {peek_uuid}")
1669 }
1670 }
1671 match activator.activate() {
1672 Ok(()) => {}
1673 Err(_) => {
1674 debug!("unable to wake timely after completed peek {peek_uuid}");
1675 }
1676 }
1677 });
1678 PendingPeek::Persist(PersistPeek {
1679 peek,
1680 _abort_handle: task_handle.abort_on_drop(),
1681 result: result_rx,
1682 span: tracing::Span::current(),
1683 })
1684 }
1685
1686 fn span(&self) -> &tracing::Span {
1687 match self {
1688 PendingPeek::Index(p) => &p.span,
1689 PendingPeek::Persist(p) => &p.span,
1690 PendingPeek::Offloaded(p) => &p.span,
1691 }
1692 }
1693
1694 pub(crate) fn peek(&self) -> &Peek {
1695 match self {
1696 PendingPeek::Index(p) => &p.peek,
1697 PendingPeek::Persist(p) => &p.peek,
1698 PendingPeek::Offloaded(p) => &p.peek,
1699 }
1700 }
1701}
1702
1703pub struct PersistPeek {
1708 pub(crate) peek: Peek,
1709 _abort_handle: AbortOnDropHandle<()>,
1712 result: oneshot::Receiver<(PeekResponse, Duration)>,
1714 span: tracing::Span,
1716}
1717
1718impl PersistPeek {
1719 async fn do_peek(
1720 persist_clients: &PersistClientCache,
1721 metadata: CollectionMetadata,
1722 as_of: Timestamp,
1723 literal_constraint: Option<Row>,
1724 mfp_plan: SafeMfpPlan,
1725 max_result_size: usize,
1726 mut limit_remaining: usize,
1727 row_iteration_config: PeekRowIterationConfig,
1728 ) -> Result<Vec<(Row, NonZeroUsize)>, PeekError> {
1729 let client = persist_clients
1730 .open(metadata.persist_location)
1731 .await
1732 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1733
1734 let mut reader: ReadHandle<SourceData, (), Timestamp, StorageDiff> = client
1735 .open_leased_reader(
1736 metadata.data_shard,
1737 Arc::new(metadata.relation_desc.clone()),
1738 Arc::new(UnitSchema),
1739 Diagnostics::from_purpose("persist::peek"),
1740 USE_CRITICAL_SINCE_SNAPSHOT.get(client.dyncfgs()),
1741 )
1742 .await
1743 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1744
1745 let mut txns_read = if let Some(txns_id) = metadata.txns_shard {
1752 Some(TxnsCache::open(&client, txns_id, Some(metadata.data_shard)).await)
1753 } else {
1754 None
1755 };
1756
1757 let metrics = client.metrics();
1758
1759 let mut cursor = StatsCursor::new(
1760 &mut reader,
1761 txns_read.as_mut(),
1762 metrics,
1763 &mfp_plan,
1764 &metadata.relation_desc,
1765 Antichain::from_elem(as_of),
1766 )
1767 .await
1768 .map_err(|since| {
1769 PeekError::unstructured(format!(
1770 "attempted to peek at {as_of}, but the since has advanced to {since:?}"
1771 ))
1772 })?;
1773
1774 let mut result = vec![];
1776 let mut datum_vec = DatumVec::new();
1777 let mut row_builder = Row::default();
1778 let arena = RowArena::new();
1779 let mut total_size = 0usize;
1780 let mut row_iteration_tracker = PeekRowIterationTracker::new(None, 0);
1781
1782 let literal_len = match &literal_constraint {
1783 None => 0,
1784 Some(row) => row.iter().count(),
1785 };
1786
1787 'collect: while limit_remaining > 0 {
1788 let Some(batch) = cursor.next().await else {
1789 break;
1790 };
1791 for (data, _, d) in batch {
1792 row_iteration_tracker.set_limit(row_iteration_config.current_limit());
1795 row_iteration_tracker.track_next()?;
1796
1797 let row = data.map_err(PeekError::from)?;
1798
1799 if let Some(literal) = &literal_constraint {
1800 match row.iter().take(literal_len).cmp(literal.iter()) {
1801 Ordering::Less => continue,
1802 Ordering::Equal => {}
1803 Ordering::Greater => break 'collect,
1804 }
1805 }
1806
1807 let count: usize = d.try_into().map_err(|_| {
1808 error!(
1809 shard = %metadata.data_shard, diff = d, ?row,
1810 "persist peek encountered negative multiplicities",
1811 );
1812 PeekError::unstructured(format!(
1813 "Invalid data in source, \
1814 saw retractions ({}) for row that does not exist: {:?}",
1815 -d, row,
1816 ))
1817 })?;
1818 let Some(count) = NonZeroUsize::new(count) else {
1819 continue;
1820 };
1821 let mut datum_local = datum_vec.borrow_with(&row);
1822 let eval_result = mfp_plan
1823 .evaluate_into(&mut datum_local, &arena, &mut row_builder)
1824 .map(|row| row.cloned())
1825 .map_err(PeekError::from)?;
1826 if let Some(row) = eval_result {
1827 total_size = total_size.saturating_add(entry_byte_len(&row));
1828 if total_size > max_result_size {
1829 return Err(PeekError::ResultExceedsMaxSize { max_result_size });
1830 }
1831 result.push((row, count));
1832 limit_remaining = limit_remaining.saturating_sub(count.get());
1833 if limit_remaining == 0 {
1834 break;
1835 }
1836 }
1837 }
1838 }
1839
1840 Ok(result)
1841 }
1842}
1843
1844pub struct IndexPeek {
1846 peek: Peek,
1847 trace_bundle: TraceBundle,
1849 span: tracing::Span,
1851}
1852
1853impl IndexPeek {
1854 fn seek_fulfillment(
1871 &mut self,
1872 upper: &mut Antichain<Timestamp>,
1873 max_result_size: u64,
1874 stash: StashBounds,
1875 row_iteration_limit: Option<usize>,
1876 fuel: &mut usize,
1877 metrics: &IndexPeekMetrics<'_>,
1878 ) -> PeekStatus {
1879 let method_start = Instant::now();
1880
1881 self.trace_bundle.oks_mut().read_upper(upper);
1882 if upper.less_equal(&self.peek.timestamp) {
1883 return PeekStatus::NotReady;
1884 }
1885 self.trace_bundle.errs_mut().read_upper(upper);
1886 if upper.less_equal(&self.peek.timestamp) {
1887 return PeekStatus::NotReady;
1888 }
1889
1890 let read_frontier = self.trace_bundle.compaction_frontier();
1891 if !read_frontier.less_equal(&self.peek.timestamp) {
1892 let error = format!(
1893 "Arrangement compaction frontier ({:?}) is beyond the time of the attempted read ({})",
1894 read_frontier.elements(),
1895 self.peek.timestamp,
1896 );
1897 return PeekStatus::Ready(PeekResponse::Error(PeekError::unstructured(error)));
1898 }
1899
1900 metrics
1901 .frontier_check_seconds
1902 .observe(method_start.elapsed().as_secs_f64());
1903
1904 let result =
1905 self.collect_finished_data(max_result_size, stash, row_iteration_limit, fuel, metrics);
1906
1907 metrics
1908 .seek_fulfillment_seconds
1909 .observe(method_start.elapsed().as_secs_f64());
1910
1911 result
1912 }
1913
1914 fn collect_finished_data(
1920 &mut self,
1921 max_result_size: u64,
1922 stash: StashBounds,
1923 row_iteration_limit: Option<usize>,
1924 fuel: &mut usize,
1925 metrics: &IndexPeekMetrics<'_>,
1926 ) -> PeekStatus {
1927 let peek = &self.peek;
1928 let (oks, errs) = self.trace_bundle.oks_errs_mut();
1929 let mut scan = PeekScan::new(peek, errs, oks, max_result_size, stash);
1930
1931 let outcome = scan.step(row_iteration_limit, fuel);
1932
1933 let phases = scan.phases();
1934 match outcome {
1935 ScanOutcome::Finished(result) => {
1938 metrics.walk.walked_inline();
1939 metrics.walk.observe_error_phase(&phases);
1940 PeekStatus::Ready(match result {
1941 Ok(rows) => {
1942 metrics.walk.observe_ok_phase(&phases);
1943 let start = Instant::now();
1944 let response = rows_response(rows, &self.peek.finishing.order_by);
1945 metrics.walk.observe_row_collection(start.elapsed());
1946 response
1947 }
1948 Err(error) => PeekResponse::Error(error),
1951 })
1952 }
1953 ScanOutcome::Suspended => PeekStatus::Offload(scan),
1962 }
1963 }
1964}
1965
1966enum PeekStatus {
1969 NotReady,
1972 Offload(IndexPeekScan),
1979 Ready(PeekResponse),
1981}
1982
1983#[derive(Debug)]
1985struct ReportedFrontiers {
1986 write_frontier: ReportedFrontier,
1988 input_frontier: ReportedFrontier,
1990 output_frontier: ReportedFrontier,
1992}
1993
1994impl ReportedFrontiers {
1995 fn new() -> Self {
1997 Self {
1998 write_frontier: ReportedFrontier::new(),
1999 input_frontier: ReportedFrontier::new(),
2000 output_frontier: ReportedFrontier::new(),
2001 }
2002 }
2003}
2004
2005#[derive(Clone, Debug)]
2007pub enum ReportedFrontier {
2008 Reported(Antichain<Timestamp>),
2010 NotReported {
2012 lower: Antichain<Timestamp>,
2014 },
2015}
2016
2017impl ReportedFrontier {
2018 pub fn new() -> Self {
2020 let lower = Antichain::from_elem(timely::progress::Timestamp::minimum());
2021 Self::NotReported { lower }
2022 }
2023
2024 pub fn is_empty(&self) -> bool {
2026 match self {
2027 Self::Reported(frontier) => frontier.is_empty(),
2028 Self::NotReported { .. } => false,
2029 }
2030 }
2031
2032 fn allows_reporting(&self, other: &Antichain<Timestamp>) -> bool {
2038 match self {
2039 Self::Reported(frontier) => PartialOrder::less_than(frontier, other),
2040 Self::NotReported { lower } => PartialOrder::less_equal(lower, other),
2041 }
2042 }
2043}
2044
2045pub struct CollectionState {
2047 reported_frontiers: ReportedFrontiers,
2049 dataflow_index: Rc<usize>,
2055 pub is_subscribe_or_copy: bool,
2061 as_of: Antichain<Timestamp>,
2065
2066 pub sink_token: Option<SinkToken>,
2071 pub sink_write_frontier: Option<Rc<RefCell<Antichain<Timestamp>>>>,
2075 pub input_probes: BTreeMap<GlobalId, probe::Handle<Timestamp>>,
2077 pub compute_probe: Option<probe::Handle<Timestamp>>,
2082 logging: Option<CollectionLogging>,
2084 metrics: CollectionMetrics,
2086 read_only_tx: watch::Sender<bool>,
2098 pub read_only_rx: watch::Receiver<bool>,
2100}
2101
2102impl CollectionState {
2103 fn new(
2104 dataflow_index: Rc<usize>,
2105 is_subscribe_or_copy: bool,
2106 as_of: Antichain<Timestamp>,
2107 metrics: CollectionMetrics,
2108 ) -> Self {
2109 let (read_only_tx, read_only_rx) = watch::channel(true);
2112
2113 Self {
2114 reported_frontiers: ReportedFrontiers::new(),
2115 dataflow_index,
2116 is_subscribe_or_copy,
2117 as_of,
2118 sink_token: None,
2119 sink_write_frontier: None,
2120 input_probes: Default::default(),
2121 compute_probe: None,
2122 logging: None,
2123 metrics,
2124 read_only_tx,
2125 read_only_rx,
2126 }
2127 }
2128
2129 fn reported_frontiers(&self) -> &ReportedFrontiers {
2131 &self.reported_frontiers
2132 }
2133
2134 pub fn reset_reported_frontiers(&mut self, frontier: ReportedFrontier) {
2136 self.reported_frontiers.write_frontier = frontier.clone();
2137 self.reported_frontiers.input_frontier = frontier.clone();
2138 self.reported_frontiers.output_frontier = frontier;
2139 }
2140
2141 fn set_reported_write_frontier(&mut self, frontier: ReportedFrontier) {
2143 if let Some(logging) = &mut self.logging {
2144 let time = match &frontier {
2145 ReportedFrontier::Reported(frontier) => frontier.get(0).copied(),
2146 ReportedFrontier::NotReported { .. } => Some(Timestamp::MIN),
2147 };
2148 logging.set_frontier(time);
2149 }
2150
2151 self.reported_frontiers.write_frontier = frontier;
2152 }
2153
2154 fn set_reported_input_frontier(&mut self, frontier: ReportedFrontier) {
2156 if let Some(logging) = &mut self.logging {
2158 for (id, probe) in &self.input_probes {
2159 let new_time = probe.with_frontier(|frontier| frontier.as_option().copied());
2160 logging.set_import_frontier(*id, new_time);
2161 }
2162 }
2163
2164 self.reported_frontiers.input_frontier = frontier;
2165 }
2166
2167 fn set_reported_output_frontier(&mut self, frontier: ReportedFrontier) {
2169 let already_hydrated = self.hydrated();
2170
2171 self.reported_frontiers.output_frontier = frontier;
2172
2173 if !already_hydrated && self.hydrated() {
2174 if let Some(logging) = &mut self.logging {
2175 logging.set_hydrated();
2176 }
2177 self.metrics.record_collection_hydrated();
2178 }
2179 }
2180
2181 fn hydrated(&self) -> bool {
2183 match &self.reported_frontiers.output_frontier {
2184 ReportedFrontier::Reported(frontier) => PartialOrder::less_than(&self.as_of, frontier),
2185 ReportedFrontier::NotReported { .. } => false,
2186 }
2187 }
2188
2189 fn allow_writes(&self) {
2191 info!(
2192 dataflow_index = *self.dataflow_index,
2193 export = ?self.logging.as_ref().map(|l| l.export_id()),
2194 "allowing writes for dataflow",
2195 );
2196 let _ = self.read_only_tx.send(false);
2197 }
2198}
2199
2200#[cfg(test)]
2201mod tests;
2202
2203#[cfg(test)]
2205pub(crate) mod index_peek_tests;
2206
2207#[cfg(test)]
2209mod peek_sweep_tests;