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 let scratch = self.context.scratch_directory.clone();
394
395 self.linear_join_spec = LinearJoinSpec::from_config(config);
396
397 if ENABLE_LGALLOC.get(config) {
398 if let Some(path) = &scratch {
399 let clear_bytes = LGALLOC_SLOW_CLEAR_BYTES.get(config);
400 let eager_return = ENABLE_LGALLOC_EAGER_RECLAMATION.get(config);
401 let file_growth_dampener = LGALLOC_FILE_GROWTH_DAMPENER.get(config);
402 let interval = LGALLOC_BACKGROUND_INTERVAL.get(config);
403 let local_buffer_bytes = LGALLOC_LOCAL_BUFFER_BYTES.get(config);
404 info!(
405 ?path,
406 backgrund_interval=?interval,
407 clear_bytes,
408 eager_return,
409 file_growth_dampener,
410 local_buffer_bytes,
411 "enabling lgalloc"
412 );
413 let background_worker_config = lgalloc::BackgroundWorkerConfig {
414 interval,
415 clear_bytes,
416 };
417 lgalloc::lgalloc_set_config(
418 lgalloc::LgAlloc::new()
419 .enable()
420 .with_path(path.clone())
421 .with_background_config(background_worker_config)
422 .eager_return(eager_return)
423 .file_growth_dampener(file_growth_dampener)
424 .local_buffer_bytes(local_buffer_bytes),
425 );
426 } else {
427 debug!("not enabling lgalloc, scratch directory not specified");
428 }
429 } else {
430 info!("disabling lgalloc");
431 lgalloc::lgalloc_set_config(lgalloc::LgAlloc::new().disable());
432 }
433
434 #[cfg(unix)]
440 if let Some(path) = &scratch {
441 mz_ore::pager::set_scratch_dir(path.clone());
442 mz_ore::pager::set_backend(mz_ore::pager::Backend::File);
443 } else {
444 mz_ore::pager::set_backend(mz_ore::pager::Backend::Swap);
445 }
446
447 crate::memory_limiter::apply_limiter_config(config);
448
449 mz_ore::region::ENABLE_LGALLOC_REGION.store(
450 ENABLE_COLUMNATION_LGALLOC.get(config),
451 std::sync::atomic::Ordering::Relaxed,
452 );
453
454 {
472 use mz_ore::pager::Backend;
473 use mz_timely_util::column_pager::{Codec, apply_tiered_config};
474
475 let enabled = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
476 let codec = COLUMN_PAGED_BATCHER_LZ4.get(config).then_some(Codec::Lz4);
477 let swap_pageout = COLUMN_PAGED_BATCHER_SWAP_PAGEOUT.get(config);
478
479 const MIB: usize = 1024 * 1024;
484 const DEFAULT_MEM_LIMIT: usize = 4 * 1024 * MIB;
485 let mem_limit = crate::memory_limiter::get_memory_limit().unwrap_or(DEFAULT_MEM_LIMIT);
486 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config).max(0.0);
487 let total = usize::cast_lossy(f64::cast_lossy(mem_limit) * fraction).max(128 * MIB);
488
489 let backend = if scratch.is_some() {
490 Backend::File
491 } else {
492 Backend::Swap
493 };
494
495 debug!(
496 enabled,
497 ?backend,
498 ?codec,
499 swap_pageout,
500 fraction,
501 mem_limit,
502 budget_bytes = total,
503 "column-paged batcher: applying tiered config",
504 );
505 apply_tiered_config(enabled, total, backend, codec, swap_pageout);
506 }
507
508 {
526 use mz_storage_types::dyncfgs::{ENABLE_UPSERT_PAGED_SPILL, ENABLE_UPSERT_V2};
527 use mz_timely_util::pool_config::{
528 ExtentBackend, PoolPagerConfig, apply_pool_config, spill_threads_for_workers,
529 };
530
531 let compute_spill = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
532 let storage_spill = ENABLE_UPSERT_PAGED_SPILL.get(config)
533 && ENABLE_UPSERT_V2.get(config)
534 && self.context.swap_available;
535 let sink_spill = ENABLE_CORRECTION_V2_SPILL.get(config);
536 mz_timely_util::columnar::chunk::set_compute_spill_enabled(compute_spill);
538 mz_timely_util::columnar::chunk::set_sink_spill_enabled(sink_spill);
539 if !(compute_spill || storage_spill || sink_spill) {
540 debug!("chunk spill: gates off, leaving the buffer pool uninstalled");
541 } else {
542 let spill_fraction = COLUMN_PAGED_BATCHER_SPILL_WORKER_FRACTION.get(config);
543 let spill_threads =
544 spill_threads_for_workers(self.workers_per_process, spill_fraction);
545 let eager_backing = COLUMN_PAGED_BATCHER_EAGER_BACKING.get(config);
546
547 const MIB: usize = 1024 * 1024;
554 const DEFAULT_RAM: usize = 4 * 1024 * MIB;
555 let ram = mz_ore::memory::physical_memory_bytes().unwrap_or(DEFAULT_RAM);
556 let of_ram =
557 |fraction: f64| usize::cast_lossy(f64::cast_lossy(ram) * fraction.max(0.0));
558 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config);
559 let total = of_ram(fraction).max(128 * MIB);
560 let rss_target = of_ram(COLUMN_PAGED_BATCHER_POOL_RSS_TARGET_FRACTION.get(config));
565
566 let backend = match &scratch {
572 Some(scratch) if ENABLE_COLUMN_PAGED_BATCHER_FILE_EXTENTS.get(config) => {
573 ExtentBackend::File {
574 dir: scratch.join("pool"),
575 capacity_bytes: None,
576 }
577 }
578 _ => ExtentBackend::Swap,
579 };
580 let applied = apply_pool_config(
581 PoolPagerConfig {
582 budget_bytes: total,
583 spill_threads,
584 eager_backing,
585 rss_target_bytes: rss_target,
586 file_release_interval: COLUMN_PAGED_BATCHER_FILE_RELEASE_INTERVAL
587 .get(config),
588 file_release_bytes: COLUMN_PAGED_BATCHER_FILE_RELEASE_BYTES.get(config),
589 },
590 backend,
591 );
592 if applied {
593 info!(
594 compute_spill,
595 storage_spill,
596 fraction,
597 ram,
598 budget_bytes = total,
599 spill_fraction,
600 spill_threads,
601 eager_backing,
602 rss_target_bytes = rss_target,
603 "chunk spill: applying buffer-pool config",
604 );
605 } else {
606 warn!("chunk spill: buffer pool unavailable; chunks stay resident");
607 }
608 }
609
610 let compress_min_depth =
614 u8::try_from(COLUMN_CHUNK_COMPRESS_MIN_DEPTH.get(config)).unwrap_or(u8::MAX);
615 mz_timely_util::columnar::chunk::set_compress_min_depth(compress_min_depth);
616 }
617
618 self.server_maintenance_interval = COMPUTE_SERVER_MAINTENANCE_INTERVAL.get(config);
621
622 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(config);
623 match overflowing_behavior.parse() {
624 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
625 Err(err) => {
626 error!(
627 err,
628 overflowing_behavior, "Invalid value for ore_overflowing_behavior"
629 );
630 }
631 }
632 }
633
634 pub fn apply_expiration_offset(&mut self, offset: Duration) {
640 if self.replica_expiration.is_empty() {
641 let offset: EpochMillis = offset
642 .as_millis()
643 .try_into()
644 .expect("duration must fit within u64");
645 let replica_expiration_millis = self.init_system_time + offset;
646 let replica_expiration = Timestamp::from(replica_expiration_millis);
647
648 info!(
649 offset = %offset,
650 replica_expiration_millis = %replica_expiration_millis,
651 replica_expiration_utc = %mz_ore::now::to_datetime(replica_expiration_millis),
652 "setting replica expiration",
653 );
654 self.replica_expiration = Antichain::from_elem(replica_expiration);
655
656 self.metrics
658 .replica_expiration_timestamp_seconds
659 .set(replica_expiration.into());
660 }
661 }
662
663 pub fn dataflow_max_inflight_bytes(&self) -> Option<usize> {
666 use mz_compute_types::dyncfgs::{
667 DATAFLOW_MAX_INFLIGHT_BYTES, DATAFLOW_MAX_INFLIGHT_BYTES_CC,
668 };
669
670 if self.persist_clients.cfg.is_cc_active {
671 DATAFLOW_MAX_INFLIGHT_BYTES_CC.get(&self.worker_config)
672 } else {
673 DATAFLOW_MAX_INFLIGHT_BYTES.get(&self.worker_config)
674 }
675 }
676}
677
678pub(crate) struct ActiveComputeState<'a> {
680 pub timely_worker: &'a mut TimelyWorker,
682 pub compute_state: &'a mut ComputeState,
684 pub response_tx: &'a mut ResponseSender,
686}
687
688pub struct SinkToken(#[allow(dead_code)] Box<dyn Any>);
690
691impl SinkToken {
692 pub fn new(t: Box<dyn Any>) -> Self {
694 Self(t)
695 }
696}
697
698impl<'a> ActiveComputeState<'a> {
699 #[mz_ore::instrument(level = "debug")]
701 pub fn handle_compute_command(&mut self, cmd: ComputeCommand) {
702 use ComputeCommand::*;
703
704 self.compute_state.command_history.push(cmd.clone());
705
706 let timer = self
708 .compute_state
709 .metrics
710 .handle_command_duration_seconds
711 .for_command(&cmd)
712 .start_timer();
713
714 match cmd {
715 Hello { .. } => panic!("Hello must be captured before"),
716 CreateInstance(instance_config) => self.handle_create_instance(*instance_config),
717 InitializationComplete => (),
718 UpdateConfiguration(params) => self.handle_update_configuration(*params),
719 CreateDataflow(dataflow) => self.handle_create_dataflow(*dataflow),
720 Schedule(id) => self.handle_schedule(id),
721 AllowCompaction { id, frontier } => self.handle_allow_compaction(id, frontier),
722 Peek(peek) => {
723 peek.otel_ctx.attach_as_parent();
724 self.handle_peek(*peek)
725 }
726 CancelPeek { uuid } => self.handle_cancel_peek(uuid),
727 AllowWrites(id) => {
728 self.handle_allow_writes(id);
729 }
730 }
731
732 timer.observe_duration();
733 }
734
735 fn handle_create_instance(&mut self, config: InstanceConfig) {
736 config
741 .initial_config
742 .apply(&self.compute_state.worker_config);
743
744 self.compute_state.apply_worker_config();
746
747 mz_row_spine::DICTIONARY_COMPRESSION.store(
753 config.arrangement_dictionary_compression,
754 std::sync::atomic::Ordering::Relaxed,
755 );
756
757 if let Some(offset) = config.expiration_offset {
758 self.compute_state.apply_expiration_offset(offset);
759 }
760
761 self.initialize_logging(config.logging);
762
763 self.compute_state.peek_stash_persist_location = Some(config.peek_stash_persist_location);
764 }
765
766 fn handle_update_configuration(&mut self, params: ComputeParameters) {
767 debug!("Applying configuration update: {params:?}");
768
769 let ComputeParameters {
770 workload_class,
771 max_result_size,
772 tracing,
773 grpc_client: _grpc_client,
774 dyncfg_updates,
775 } = params;
776
777 if let Some(v) = workload_class {
778 self.compute_state.metrics.set_workload_class(v);
779 }
780 if let Some(v) = max_result_size {
781 self.compute_state.max_result_size = v;
782 }
783
784 tracing.apply(self.compute_state.tracing_handle.as_ref());
785
786 dyncfg_updates.apply(&self.compute_state.worker_config);
787 self.compute_state
788 .persist_clients
789 .cfg()
790 .apply_from(&dyncfg_updates);
791
792 mz_metrics::update_dyncfg(&dyncfg_updates);
796
797 self.compute_state.apply_worker_config();
798 }
799
800 fn handle_create_dataflow(&mut self, dataflow: RenderDataflowDescription<CollectionMetadata>) {
801 let dataflow_index = Rc::new(self.timely_worker.next_dataflow_index());
802 let as_of = dataflow.as_of.clone().unwrap();
803
804 let dataflow_expiration = dataflow
805 .time_dependence
806 .as_ref()
807 .map(|time_dependence| {
808 self.determine_dataflow_expiration(time_dependence, &dataflow.until)
809 })
810 .unwrap_or_default();
811
812 let until = dataflow.until.meet(&dataflow_expiration);
814
815 if dataflow.is_transient() {
816 debug!(
817 name = %dataflow.debug_name,
818 import_ids = %dataflow.display_import_ids(),
819 export_ids = %dataflow.display_export_ids(),
820 as_of = ?as_of.elements(),
821 time_dependence = ?dataflow.time_dependence,
822 expiration = ?dataflow_expiration.elements(),
823 expiration_datetime = ?dataflow_expiration
824 .as_option()
825 .map(|t| mz_ore::now::to_datetime(t.into())),
826 plan_until = ?dataflow.until.elements(),
827 until = ?until.elements(),
828 "creating dataflow",
829 );
830 } else {
831 info!(
832 name = %dataflow.debug_name,
833 import_ids = %dataflow.display_import_ids(),
834 export_ids = %dataflow.display_export_ids(),
835 as_of = ?as_of.elements(),
836 time_dependence = ?dataflow.time_dependence,
837 expiration = ?dataflow_expiration.elements(),
838 expiration_datetime = ?dataflow_expiration
839 .as_option()
840 .map(|t| mz_ore::now::to_datetime(t.into())),
841 plan_until = ?dataflow.until.elements(),
842 until = ?until.elements(),
843 "creating dataflow",
844 );
845 };
846
847 let subscribe_copy_ids: BTreeSet<_> = dataflow
848 .subscribe_ids()
849 .chain(dataflow.copy_to_ids())
850 .collect();
851
852 let starts_immediately = dataflow.import_ids().next().is_none();
861
862 for object_id in dataflow.export_ids() {
864 let is_subscribe_or_copy = subscribe_copy_ids.contains(&object_id);
865 let metrics = self.compute_state.metrics.for_collection(object_id);
866 let mut collection = CollectionState::new(
867 Rc::clone(&dataflow_index),
868 is_subscribe_or_copy,
869 as_of.clone(),
870 metrics,
871 );
872
873 if let Some(logger) = self.compute_state.compute_logger.clone() {
874 let logging = CollectionLogging::new(
875 object_id,
876 logger,
877 *dataflow_index,
878 dataflow.import_ids(),
879 );
880 if starts_immediately {
881 logging.set_hydration_start();
882 }
883 collection.logging = Some(logging);
884 }
885
886 collection.reset_reported_frontiers(ReportedFrontier::NotReported {
887 lower: as_of.clone(),
888 });
889
890 let existing = self.compute_state.collections.insert(object_id, collection);
891 if existing.is_some() {
892 error!(
893 id = ?object_id,
894 "existing collection for newly created dataflow",
895 );
896 }
897 }
898
899 let (start_signal, suspension_token) = StartSignal::new();
900 for id in dataflow.export_ids() {
901 self.compute_state
902 .suspended_collections
903 .insert(id, Rc::clone(&suspension_token));
904 }
905
906 crate::render::build_compute_dataflow(
907 self.timely_worker,
908 self.compute_state,
909 dataflow,
910 start_signal,
911 until,
912 dataflow_expiration,
913 );
914 }
915
916 fn handle_schedule(&mut self, id: GlobalId) {
917 let suspension_token = self.compute_state.suspended_collections.remove(&id);
923 drop(suspension_token);
924
925 if let Some(collection) = self.compute_state.collections.get(&id) {
926 if let Some(logging) = &collection.logging {
927 logging.set_hydration_start();
928 }
929 }
930 }
931
932 fn handle_allow_compaction(&mut self, id: GlobalId, frontier: Antichain<Timestamp>) {
933 if frontier.is_empty() {
934 self.drop_collection(id);
936 } else {
937 self.compute_state
938 .traces
939 .allow_compaction(id, frontier.borrow());
940 }
941 }
942
943 #[mz_ore::instrument(level = "debug")]
944 fn handle_peek(&mut self, peek: Peek) {
945 let pending = match &peek.target {
946 PeekTarget::Index { id } => {
947 let trace_bundle = self.compute_state.traces.get(id).unwrap().clone();
949 PendingPeek::index(peek, trace_bundle)
950 }
951 PeekTarget::Persist { metadata, .. } => {
952 let metadata = metadata.clone();
953 PendingPeek::persist(
954 peek,
955 Arc::clone(&self.compute_state.persist_clients),
956 metadata,
957 usize::cast_from(self.compute_state.max_result_size),
958 self.timely_worker,
959 PeekRowIterationConfig::new(&self.compute_state.worker_config),
960 )
961 }
962 };
963
964 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
966 logger.log(&pending.as_log_event(true));
967 }
968
969 match pending {
970 PendingPeek::Index(peek) => self.serve_index_peek(&mut Antichain::new(), peek),
971 pending => self.poll_pending_peek(pending),
972 }
973 }
974
975 fn handle_cancel_peek(&mut self, uuid: Uuid) {
976 let queued = &mut self.compute_state.queued_peeks;
977 if let Some(index) = queued.iter().position(|peek| peek.peek.uuid == uuid) {
978 let peek = queued.remove(index).expect("found above");
979 self.send_peek_response(PendingPeek::Index(peek), PeekResponse::Canceled);
980 return;
981 }
982
983 let pending = &mut self.compute_state.pending_peeks;
984 let Some(index) = pending.iter().position(|peek| peek.peek().uuid == uuid) else {
985 return;
986 };
987 let peek = pending.remove(index).expect("found above");
988 self.send_peek_response(peek, PeekResponse::Canceled);
989 }
990
991 fn handle_allow_writes(&mut self, id: GlobalId) {
992 self.compute_state.persist_clients.cfg().enable_compaction();
996
997 if let Some(collection) = self.compute_state.collections.get_mut(&id) {
998 collection.allow_writes();
999 } else {
1000 soft_panic_or_log!("allow writes for unknown collection {id}");
1001 }
1002 }
1003
1004 fn drop_collection(&mut self, id: GlobalId) {
1006 let collection = self
1007 .compute_state
1008 .collections
1009 .remove(&id)
1010 .expect("dropped untracked collection");
1011
1012 self.compute_state.traces.remove(&id);
1014 self.compute_state.suspended_collections.remove(&id);
1016
1017 if let Ok(index) = Rc::try_unwrap(collection.dataflow_index) {
1019 self.timely_worker.drop_dataflow(index);
1020 }
1021
1022 if !collection.is_subscribe_or_copy {
1027 let reported = collection.reported_frontiers;
1028 let write_frontier = (!reported.write_frontier.is_empty()).then(Antichain::new);
1029 let input_frontier = (!reported.input_frontier.is_empty()).then(Antichain::new);
1030 let output_frontier = (!reported.output_frontier.is_empty()).then(Antichain::new);
1031
1032 let frontiers = FrontiersResponse {
1033 write_frontier,
1034 input_frontier,
1035 output_frontier,
1036 };
1037 if frontiers.has_updates() {
1038 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1039 }
1040 }
1041 }
1042
1043 pub fn initialize_logging(&mut self, config: LoggingConfig) {
1045 if self.compute_state.compute_logger.is_some() {
1046 panic!("dataflow server has already initialized logging");
1047 }
1048
1049 let LoggingTraces {
1050 traces,
1051 dataflow_index,
1052 compute_logger: logger,
1053 } = logging::initialize(
1054 self.timely_worker,
1055 &config,
1056 self.compute_state.metrics_registry.clone(),
1057 self.compute_state.metrics.for_logging(),
1058 Rc::clone(&self.compute_state.worker_config),
1059 self.compute_state.workers_per_process,
1060 );
1061
1062 let dataflow_index = Rc::new(dataflow_index);
1063 let mut log_index_ids = config.index_logs;
1064 for (log, trace) in traces {
1065 let id = log_index_ids
1067 .remove(&log)
1068 .expect("`logging::initialize` does not invent logs");
1069 self.compute_state.traces.set(id, trace);
1070
1071 let is_subscribe_or_copy = false;
1073 let as_of = Antichain::from_elem(Timestamp::MIN);
1074 let metrics = self.compute_state.metrics.for_collection(id);
1075 let mut collection = CollectionState::new(
1076 Rc::clone(&dataflow_index),
1077 is_subscribe_or_copy,
1078 as_of,
1079 metrics,
1080 );
1081
1082 let logging =
1083 CollectionLogging::new(id, logger.clone(), *dataflow_index, std::iter::empty());
1084 logging.set_hydration_start();
1089 collection.logging = Some(logging);
1090
1091 let existing = self.compute_state.collections.insert(id, collection);
1092 if existing.is_some() {
1093 error!(
1094 id = ?id,
1095 "existing collection for newly initialized logging export",
1096 );
1097 }
1098 }
1099
1100 assert!(
1102 log_index_ids.is_empty(),
1103 "failed to create requested logging indexes: {log_index_ids:?}",
1104 );
1105
1106 self.compute_state.compute_logger = Some(logger);
1107 }
1108
1109 pub fn report_frontiers(&mut self) {
1111 let mut responses = Vec::new();
1112
1113 let mut new_frontier = Antichain::new();
1115
1116 for (&id, collection) in self.compute_state.collections.iter_mut() {
1117 if collection.is_subscribe_or_copy {
1120 continue;
1121 }
1122
1123 let reported = collection.reported_frontiers();
1124
1125 new_frontier.clear();
1127 if let Some(traces) = self.compute_state.traces.get_mut(&id) {
1128 assert!(
1129 collection.sink_write_frontier.is_none(),
1130 "collection {id} has multiple frontiers"
1131 );
1132 traces.oks_mut().read_upper(&mut new_frontier);
1133 } else if let Some(frontier) = &collection.sink_write_frontier {
1134 new_frontier.clone_from(&frontier.borrow());
1135 } else {
1136 error!(id = ?id, "collection without write frontier");
1137 continue;
1138 }
1139 let new_write_frontier = reported
1140 .write_frontier
1141 .allows_reporting(&new_frontier)
1142 .then(|| new_frontier.clone());
1143
1144 if let Some(probe) = &collection.compute_probe {
1157 if *collection.read_only_rx.borrow() {
1158 new_frontier.clear();
1159 }
1160 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1161 }
1162 let new_output_frontier = reported
1163 .output_frontier
1164 .allows_reporting(&new_frontier)
1165 .then(|| new_frontier.clone());
1166
1167 new_frontier.clear();
1169 for probe in collection.input_probes.values() {
1170 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
1171 }
1172 let new_input_frontier = reported
1173 .input_frontier
1174 .allows_reporting(&new_frontier)
1175 .then(|| new_frontier.clone());
1176
1177 if let Some(frontier) = &new_write_frontier {
1178 collection
1179 .set_reported_write_frontier(ReportedFrontier::Reported(frontier.clone()));
1180 }
1181 if let Some(frontier) = &new_input_frontier {
1182 collection
1183 .set_reported_input_frontier(ReportedFrontier::Reported(frontier.clone()));
1184 }
1185 if let Some(frontier) = &new_output_frontier {
1186 collection
1187 .set_reported_output_frontier(ReportedFrontier::Reported(frontier.clone()));
1188 }
1189
1190 let response = FrontiersResponse {
1191 write_frontier: new_write_frontier,
1192 input_frontier: new_input_frontier,
1193 output_frontier: new_output_frontier,
1194 };
1195 if response.has_updates() {
1196 responses.push((id, response));
1197 }
1198 }
1199
1200 for (id, frontiers) in responses {
1201 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1202 }
1203 }
1204
1205 pub(crate) fn report_metrics(&self) {
1207 if let Some(expiration) = self.compute_state.replica_expiration.as_option() {
1208 let now = Duration::from_millis(mz_ore::now::SYSTEM_TIME()).as_secs_f64();
1209 let expiration = Duration::from_millis(<u64>::from(expiration)).as_secs_f64();
1210 let remaining = expiration - now;
1211 self.compute_state
1212 .metrics
1213 .replica_expiration_remaining_seconds
1214 .set(remaining)
1215 }
1216 }
1217
1218 fn serve_index_peek(&mut self, upper: &mut Antichain<Timestamp>, peek: IndexPeek) {
1221 match self.compute_state.peek_budget.grant() {
1222 Some(fuel) => self.walk_index_peek(upper, peek, fuel),
1223 None => {
1224 self.compute_state.peek_passed_over = true;
1227 self.compute_state.queued_peeks.push_back(peek);
1228 }
1229 }
1230 }
1231
1232 fn walk_index_peek(
1235 &mut self,
1236 upper: &mut Antichain<Timestamp>,
1237 mut peek: IndexPeek,
1238 fuel: usize,
1239 ) {
1240 let start = Instant::now();
1241
1242 let row_iteration_limit = peek_row_iteration_limit(&self.compute_state.worker_config);
1243
1244 let peek_stash_eligible = peek
1245 .peek
1246 .finishing
1247 .is_streamable(peek.peek.result_desc.arity());
1248
1249 let has_stash_location = {
1253 let enabled = ENABLE_PEEK_RESPONSE_STASH.get(&self.compute_state.worker_config);
1254 let located = self.compute_state.peek_stash_persist_location.is_some();
1255 if !located && enabled {
1256 error!("missing peek_stash_persist_location but peek stash is enabled");
1257 }
1258 enabled && located
1259 };
1260
1261 let stash = StashBounds {
1262 eligible: peek_stash_eligible && has_stash_location,
1263 threshold_bytes: PEEK_RESPONSE_STASH_THRESHOLD_BYTES
1264 .get(&self.compute_state.worker_config),
1265 batch_bytes: PEEK_RESPONSE_STASH_BATCH_BYTES.get(&self.compute_state.worker_config),
1266 };
1267
1268 let metrics = IndexPeekMetrics {
1269 seek_fulfillment_seconds: &self
1270 .compute_state
1271 .metrics
1272 .index_peek_seek_fulfillment_seconds,
1273 frontier_check_seconds: &self.compute_state.metrics.index_peek_frontier_check_seconds,
1274 walk: &self.compute_state.peek_walk_metrics,
1275 };
1276
1277 let mut unspent = fuel;
1278 let status = peek.seek_fulfillment(
1279 upper,
1280 self.compute_state.max_result_size,
1281 stash,
1282 row_iteration_limit,
1283 &mut unspent,
1284 &metrics,
1285 );
1286
1287 self.compute_state
1290 .peek_budget
1291 .charge(fuel.saturating_sub(unspent));
1292
1293 self.compute_state
1294 .metrics
1295 .index_peek_total_seconds
1296 .observe(start.elapsed().as_secs_f64());
1297
1298 match status {
1299 PeekStatus::Ready(response) => {
1300 let _span =
1301 span!(parent: &peek.span, Level::DEBUG, "process_peek_response").entered();
1302 self.send_peek_response(PendingPeek::Index(peek), response);
1303 }
1304 PeekStatus::NotReady => self.compute_state.queued_peeks.push_back(peek),
1305 PeekStatus::Offload(scan) => {
1306 let _span = span!(parent: &peek.span, Level::DEBUG, "offload_index_peek").entered();
1307
1308 let permits = Arc::clone(&self.compute_state.peek_permits);
1309 let config = OffloadConfig::new(&self.compute_state.worker_config);
1310 let walk_metrics = self.compute_state.peek_walk_metrics.clone();
1311 let worker = std::thread::current();
1312 let stash = self
1315 .compute_state
1316 .peek_stash_persist_location
1317 .as_ref()
1318 .filter(|_| scan.stash_eligible())
1319 .cloned()
1320 .map(|location| {
1321 peek_stash::StashTarget::new(
1322 &peek.peek,
1323 Arc::clone(&self.compute_state.persist_clients),
1324 location,
1325 )
1326 });
1327
1328 let offloaded = OffloadedPeek::start(
1329 peek.peek,
1330 scan,
1331 stash,
1332 permits,
1333 config,
1334 walk_metrics,
1335 worker,
1336 );
1337
1338 self.compute_state
1339 .pending_peeks
1340 .push_back(PendingPeek::Offloaded(offloaded));
1341 }
1342 }
1343 }
1344
1345 fn poll_pending_peek(&mut self, mut pending: PendingPeek) {
1348 let response = match &mut pending {
1349 PendingPeek::Index(peek) => {
1352 soft_panic_or_log!(
1353 "index peek on {} polled as if a driver had taken it over",
1354 peek.peek.target.id()
1355 );
1356 None
1357 }
1358 PendingPeek::Persist(peek) => peek.result.try_recv().ok().map(|(result, duration)| {
1359 self.compute_state
1360 .metrics
1361 .persist_peek_seconds
1362 .observe(duration.as_secs_f64());
1363 result
1364 }),
1365 PendingPeek::Offloaded(offloaded) => match offloaded.result.try_recv() {
1366 Ok((response, duration)) => {
1367 self.compute_state
1370 .metrics
1371 .index_peek_offload_seconds
1372 .observe(duration.as_secs_f64());
1373
1374 trace!(?offloaded.peek, ?duration, "finished offloaded index peek walk");
1375 Some(response)
1376 }
1377 Err(oneshot::error::TryRecvError::Empty) => None,
1378 Err(oneshot::error::TryRecvError::Closed) => {
1385 soft_panic_or_log!(
1386 "offloaded walk of peek on {} ended without an outcome",
1387 offloaded.peek.target.id()
1388 );
1389 Some(PeekResponse::Error(PeekError::unstructured(
1390 "offloaded peek walk failed",
1391 )))
1392 }
1393 },
1394 };
1395
1396 if let Some(response) = response {
1397 let _span =
1398 span!(parent: pending.span(), Level::DEBUG, "process_peek_response").entered();
1399 self.send_peek_response(pending, response)
1400 } else {
1401 self.compute_state.pending_peeks.push_back(pending);
1402 }
1403 }
1404
1405 pub fn process_peeks(&mut self) {
1412 self.compute_state.peek_budget.start_activation();
1416
1417 self.compute_state.peek_passed_over = false;
1421
1422 if self.compute_state.pending_peeks.is_empty() && self.compute_state.queued_peeks.is_empty()
1424 {
1425 return;
1426 }
1427
1428 let mut upper = Antichain::new();
1429
1430 let mut pending_peeks = std::mem::take(&mut self.compute_state.pending_peeks);
1433 while let Some(peek) = pending_peeks.pop_front() {
1434 self.poll_pending_peek(peek);
1435 }
1436
1437 let mut queued_peeks = std::mem::take(&mut self.compute_state.queued_peeks);
1440 while let Some(peek) = queued_peeks.pop_front() {
1441 let Some(fuel) = self.compute_state.peek_budget.grant() else {
1442 queued_peeks.push_front(peek);
1443 break;
1444 };
1445 self.walk_index_peek(&mut upper, peek, fuel);
1446 }
1447
1448 self.compute_state.peek_passed_over = !queued_peeks.is_empty();
1451 let served = std::mem::replace(&mut self.compute_state.queued_peeks, queued_peeks);
1452 self.compute_state.queued_peeks.extend(served);
1453 }
1454
1455 #[mz_ore::instrument(level = "debug")]
1460 fn send_peek_response(&mut self, peek: PendingPeek, response: PeekResponse) {
1461 let log_event = peek.as_log_event(false);
1462 self.send_compute_response(ComputeResponse::PeekResponse(
1464 peek.peek().uuid,
1465 response,
1466 OpenTelemetryContext::obtain(),
1467 ));
1468
1469 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
1471 logger.log(&log_event);
1472 }
1473 }
1474
1475 pub fn process_subscribes(&mut self) {
1477 let mut subscribe_responses = self.compute_state.subscribe_response_buffer.borrow_mut();
1478 for (sink_id, mut response) in subscribe_responses.drain(..) {
1479 if let Some(collection) = self.compute_state.collections.get_mut(&sink_id) {
1481 let new_frontier = match &response {
1482 SubscribeResponse::Batch(b) => b.upper.clone(),
1483 SubscribeResponse::DroppedAt(_) => Antichain::new(),
1484 };
1485
1486 let reported = collection.reported_frontiers();
1487 assert!(
1488 reported.write_frontier.allows_reporting(&new_frontier),
1489 "subscribe write frontier regression: {:?} -> {:?}",
1490 reported.write_frontier,
1491 new_frontier,
1492 );
1493 assert!(
1494 reported.input_frontier.allows_reporting(&new_frontier),
1495 "subscribe input frontier regression: {:?} -> {:?}",
1496 reported.input_frontier,
1497 new_frontier,
1498 );
1499
1500 collection
1501 .set_reported_write_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1502 collection
1503 .set_reported_input_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1504 collection.set_reported_output_frontier(ReportedFrontier::Reported(new_frontier));
1505 } else {
1506 }
1509
1510 response
1511 .to_error_if_exceeds(usize::try_from(self.compute_state.max_result_size).unwrap());
1512 self.send_compute_response(ComputeResponse::SubscribeResponse(sink_id, response));
1513 }
1514 }
1515
1516 pub fn process_copy_tos(&self) {
1518 let mut responses = self.compute_state.copy_to_response_buffer.borrow_mut();
1519 for (sink_id, response) in responses.drain(..) {
1520 self.send_compute_response(ComputeResponse::CopyToResponse(sink_id, response));
1521 }
1522 }
1523
1524 fn send_compute_response(&self, response: ComputeResponse) {
1526 let _ = self.response_tx.send(response);
1529 }
1530
1531 pub(crate) fn check_expiration(&self) {
1533 let now = mz_ore::now::SYSTEM_TIME();
1534 if self.compute_state.replica_expiration.less_than(&now.into()) {
1535 let now_datetime = mz_ore::now::to_datetime(now);
1536 let expiration_datetime = self
1537 .compute_state
1538 .replica_expiration
1539 .as_option()
1540 .map(Into::into)
1541 .map(mz_ore::now::to_datetime);
1542
1543 error!(
1546 now,
1547 now_datetime = ?now_datetime,
1548 expiration = ?self.compute_state.replica_expiration.elements(),
1549 expiration_datetime = ?expiration_datetime,
1550 "replica expired"
1551 );
1552
1553 assert!(
1555 !self.compute_state.replica_expiration.less_than(&now.into()),
1556 "replica expired. now: {now} ({now_datetime:?}), expiration: {:?} ({expiration_datetime:?})",
1557 self.compute_state.replica_expiration.elements(),
1558 );
1559 }
1560 }
1561
1562 pub fn determine_dataflow_expiration(
1568 &self,
1569 time_dependence: &TimeDependence,
1570 until: &Antichain<Timestamp>,
1571 ) -> Antichain<Timestamp> {
1572 let iter = self
1577 .compute_state
1578 .replica_expiration
1579 .iter()
1580 .filter_map(|t| time_dependence.apply(*t))
1581 .filter_map(|t| Timestamp::try_step_forward(&t))
1582 .filter(|expiration| !until.less_equal(expiration));
1583 Antichain::from_iter(iter)
1584 }
1585}
1586
1587pub enum PendingPeek {
1592 Index(IndexPeek),
1594 Persist(PersistPeek),
1596 Offloaded(OffloadedPeek),
1599}
1600
1601impl PendingPeek {
1602 pub fn as_log_event(&self, installed: bool) -> ComputeEvent {
1604 let peek = self.peek();
1605 let (id, peek_type) = match &peek.target {
1606 PeekTarget::Index { id } => (*id, logging::compute::PeekType::Index),
1607 PeekTarget::Persist { id, .. } => (*id, logging::compute::PeekType::Persist),
1608 };
1609 let uuid = peek.uuid.into_bytes();
1610 ComputeEvent::Peek(PeekEvent {
1611 id,
1612 time: peek.timestamp,
1613 uuid,
1614 peek_type,
1615 installed,
1616 })
1617 }
1618
1619 fn index(peek: Peek, mut trace_bundle: TraceBundle) -> Self {
1620 let empty_frontier = Antichain::new();
1621 let timestamp_frontier = Antichain::from_elem(peek.timestamp);
1622 trace_bundle
1623 .oks_mut()
1624 .set_logical_compaction(timestamp_frontier.borrow());
1625 trace_bundle
1626 .errs_mut()
1627 .set_logical_compaction(timestamp_frontier.borrow());
1628 trace_bundle
1629 .oks_mut()
1630 .set_physical_compaction(empty_frontier.borrow());
1631 trace_bundle
1632 .errs_mut()
1633 .set_physical_compaction(empty_frontier.borrow());
1634
1635 PendingPeek::Index(IndexPeek {
1636 peek,
1637 trace_bundle,
1638 span: tracing::Span::current(),
1639 })
1640 }
1641
1642 fn persist(
1643 peek: Peek,
1644 persist_clients: Arc<PersistClientCache>,
1645 metadata: CollectionMetadata,
1646 max_result_size: usize,
1647 timely_worker: &TimelyWorker,
1648 row_iteration_config: PeekRowIterationConfig,
1649 ) -> Self {
1650 let active_worker = {
1651 let chosen_index = usize::cast_from(peek.uuid.hashed()) % timely_worker.peers();
1653 chosen_index == timely_worker.index()
1654 };
1655 let activator = timely_worker.sync_activator_for([].into());
1656 let peek_uuid = peek.uuid;
1657
1658 let (result_tx, result_rx) = oneshot::channel();
1659 let timestamp = peek.timestamp;
1660 let mfp_plan = peek.map_filter_project.clone();
1661 let max_results_needed = peek
1662 .finishing
1663 .limit
1664 .map(|l| usize::cast_from(u64::from(l)))
1665 .unwrap_or(usize::MAX)
1666 + peek.finishing.offset;
1667 let order_by = peek.finishing.order_by.clone();
1668
1669 let literal_constraint = peek
1671 .literal_constraints
1672 .clone()
1673 .map(|rows| rows.into_element());
1674
1675 let task_handle = mz_ore::task::spawn(|| "persist::peek", async move {
1676 let start = Instant::now();
1677 let result = if active_worker {
1678 PersistPeek::do_peek(
1679 &persist_clients,
1680 metadata,
1681 timestamp,
1682 literal_constraint,
1683 mfp_plan,
1684 max_result_size,
1685 max_results_needed,
1686 row_iteration_config,
1687 )
1688 .await
1689 } else {
1690 Ok(vec![])
1691 };
1692 let result = match result {
1693 Ok(rows) => PeekResponse::Rows(vec![RowCollection::new(rows, &order_by)]),
1694 Err(error) => PeekResponse::Error(error),
1695 };
1696 match result_tx.send((result, start.elapsed())) {
1697 Ok(()) => {}
1698 Err((_result, elapsed)) => {
1699 debug!(duration =? elapsed, "dropping result for cancelled peek {peek_uuid}")
1700 }
1701 }
1702 match activator.activate() {
1703 Ok(()) => {}
1704 Err(_) => {
1705 debug!("unable to wake timely after completed peek {peek_uuid}");
1706 }
1707 }
1708 });
1709 PendingPeek::Persist(PersistPeek {
1710 peek,
1711 _abort_handle: task_handle.abort_on_drop(),
1712 result: result_rx,
1713 span: tracing::Span::current(),
1714 })
1715 }
1716
1717 fn span(&self) -> &tracing::Span {
1718 match self {
1719 PendingPeek::Index(p) => &p.span,
1720 PendingPeek::Persist(p) => &p.span,
1721 PendingPeek::Offloaded(p) => &p.span,
1722 }
1723 }
1724
1725 pub(crate) fn peek(&self) -> &Peek {
1726 match self {
1727 PendingPeek::Index(p) => &p.peek,
1728 PendingPeek::Persist(p) => &p.peek,
1729 PendingPeek::Offloaded(p) => &p.peek,
1730 }
1731 }
1732}
1733
1734pub struct PersistPeek {
1739 pub(crate) peek: Peek,
1740 _abort_handle: AbortOnDropHandle<()>,
1743 result: oneshot::Receiver<(PeekResponse, Duration)>,
1745 span: tracing::Span,
1747}
1748
1749impl PersistPeek {
1750 async fn do_peek(
1751 persist_clients: &PersistClientCache,
1752 metadata: CollectionMetadata,
1753 as_of: Timestamp,
1754 literal_constraint: Option<Row>,
1755 mfp_plan: SafeMfpPlan,
1756 max_result_size: usize,
1757 mut limit_remaining: usize,
1758 row_iteration_config: PeekRowIterationConfig,
1759 ) -> Result<Vec<(Row, NonZeroUsize)>, PeekError> {
1760 let client = persist_clients
1761 .open(metadata.persist_location)
1762 .await
1763 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1764
1765 let mut reader: ReadHandle<SourceData, (), Timestamp, StorageDiff> = client
1766 .open_leased_reader(
1767 metadata.data_shard,
1768 Arc::new(metadata.relation_desc.clone()),
1769 Arc::new(UnitSchema),
1770 Diagnostics::from_purpose("persist::peek"),
1771 USE_CRITICAL_SINCE_SNAPSHOT.get(client.dyncfgs()),
1772 )
1773 .await
1774 .map_err(|e| PeekError::unstructured(e.to_string()))?;
1775
1776 let mut txns_read = if let Some(txns_id) = metadata.txns_shard {
1783 Some(TxnsCache::open(&client, txns_id, Some(metadata.data_shard)).await)
1784 } else {
1785 None
1786 };
1787
1788 let metrics = client.metrics();
1789
1790 let mut cursor = StatsCursor::new(
1791 &mut reader,
1792 txns_read.as_mut(),
1793 metrics,
1794 &mfp_plan,
1795 &metadata.relation_desc,
1796 Antichain::from_elem(as_of),
1797 )
1798 .await
1799 .map_err(|since| {
1800 PeekError::unstructured(format!(
1801 "attempted to peek at {as_of}, but the since has advanced to {since:?}"
1802 ))
1803 })?;
1804
1805 let mut result = vec![];
1807 let mut datum_vec = DatumVec::new();
1808 let mut row_builder = Row::default();
1809 let arena = RowArena::new();
1810 let mut total_size = 0usize;
1811 let mut row_iteration_tracker = PeekRowIterationTracker::new(None, 0);
1812
1813 let literal_len = match &literal_constraint {
1814 None => 0,
1815 Some(row) => row.iter().count(),
1816 };
1817
1818 'collect: while limit_remaining > 0 {
1819 let Some(batch) = cursor.next().await else {
1820 break;
1821 };
1822 for (data, _, d) in batch {
1823 row_iteration_tracker.set_limit(row_iteration_config.current_limit());
1826 row_iteration_tracker.track_next()?;
1827
1828 let row = data.map_err(PeekError::from)?;
1829
1830 if let Some(literal) = &literal_constraint {
1831 match row.iter().take(literal_len).cmp(literal.iter()) {
1832 Ordering::Less => continue,
1833 Ordering::Equal => {}
1834 Ordering::Greater => break 'collect,
1835 }
1836 }
1837
1838 let count: usize = d.try_into().map_err(|_| {
1839 error!(
1840 shard = %metadata.data_shard, diff = d, ?row,
1841 "persist peek encountered negative multiplicities",
1842 );
1843 PeekError::unstructured(format!(
1844 "Invalid data in source, \
1845 saw retractions ({}) for row that does not exist: {:?}",
1846 -d, row,
1847 ))
1848 })?;
1849 let Some(count) = NonZeroUsize::new(count) else {
1850 continue;
1851 };
1852 let mut datum_local = datum_vec.borrow_with(&row);
1853 let eval_result = mfp_plan
1854 .evaluate_into(&mut datum_local, &arena, &mut row_builder)
1855 .map(|row| row.cloned())
1856 .map_err(PeekError::from)?;
1857 if let Some(row) = eval_result {
1858 total_size = total_size.saturating_add(entry_byte_len(&row));
1859 if total_size > max_result_size {
1860 return Err(PeekError::ResultExceedsMaxSize { max_result_size });
1861 }
1862 result.push((row, count));
1863 limit_remaining = limit_remaining.saturating_sub(count.get());
1864 if limit_remaining == 0 {
1865 break;
1866 }
1867 }
1868 }
1869 }
1870
1871 Ok(result)
1872 }
1873}
1874
1875pub struct IndexPeek {
1877 peek: Peek,
1878 trace_bundle: TraceBundle,
1880 span: tracing::Span,
1882}
1883
1884impl IndexPeek {
1885 fn seek_fulfillment(
1902 &mut self,
1903 upper: &mut Antichain<Timestamp>,
1904 max_result_size: u64,
1905 stash: StashBounds,
1906 row_iteration_limit: Option<usize>,
1907 fuel: &mut usize,
1908 metrics: &IndexPeekMetrics<'_>,
1909 ) -> PeekStatus {
1910 let method_start = Instant::now();
1911
1912 self.trace_bundle.oks_mut().read_upper(upper);
1913 if upper.less_equal(&self.peek.timestamp) {
1914 return PeekStatus::NotReady;
1915 }
1916 self.trace_bundle.errs_mut().read_upper(upper);
1917 if upper.less_equal(&self.peek.timestamp) {
1918 return PeekStatus::NotReady;
1919 }
1920
1921 let read_frontier = self.trace_bundle.compaction_frontier();
1922 if !read_frontier.less_equal(&self.peek.timestamp) {
1923 let error = format!(
1924 "Arrangement compaction frontier ({:?}) is beyond the time of the attempted read ({})",
1925 read_frontier.elements(),
1926 self.peek.timestamp,
1927 );
1928 return PeekStatus::Ready(PeekResponse::Error(PeekError::unstructured(error)));
1929 }
1930
1931 metrics
1932 .frontier_check_seconds
1933 .observe(method_start.elapsed().as_secs_f64());
1934
1935 let result =
1936 self.collect_finished_data(max_result_size, stash, row_iteration_limit, fuel, metrics);
1937
1938 metrics
1939 .seek_fulfillment_seconds
1940 .observe(method_start.elapsed().as_secs_f64());
1941
1942 result
1943 }
1944
1945 fn collect_finished_data(
1951 &mut self,
1952 max_result_size: u64,
1953 stash: StashBounds,
1954 row_iteration_limit: Option<usize>,
1955 fuel: &mut usize,
1956 metrics: &IndexPeekMetrics<'_>,
1957 ) -> PeekStatus {
1958 let peek = &self.peek;
1959 let (oks, errs) = self.trace_bundle.oks_errs_mut();
1960 let mut scan = PeekScan::new(peek, errs, oks, max_result_size, stash);
1961
1962 let outcome = scan.step(row_iteration_limit, fuel);
1963
1964 let phases = scan.phases();
1965 match outcome {
1966 ScanOutcome::Finished(result) => {
1969 metrics.walk.walked_inline();
1970 metrics.walk.observe_error_phase(&phases);
1971 PeekStatus::Ready(match result {
1972 Ok(rows) => {
1973 metrics.walk.observe_ok_phase(&phases);
1974 let start = Instant::now();
1975 let response = rows_response(rows, &self.peek.finishing.order_by);
1976 metrics.walk.observe_row_collection(start.elapsed());
1977 response
1978 }
1979 Err(error) => PeekResponse::Error(error),
1982 })
1983 }
1984 ScanOutcome::Suspended => PeekStatus::Offload(scan),
1993 }
1994 }
1995}
1996
1997enum PeekStatus {
2000 NotReady,
2003 Offload(IndexPeekScan),
2010 Ready(PeekResponse),
2012}
2013
2014#[derive(Debug)]
2016struct ReportedFrontiers {
2017 write_frontier: ReportedFrontier,
2019 input_frontier: ReportedFrontier,
2021 output_frontier: ReportedFrontier,
2023}
2024
2025impl ReportedFrontiers {
2026 fn new() -> Self {
2028 Self {
2029 write_frontier: ReportedFrontier::new(),
2030 input_frontier: ReportedFrontier::new(),
2031 output_frontier: ReportedFrontier::new(),
2032 }
2033 }
2034}
2035
2036#[derive(Clone, Debug)]
2038pub enum ReportedFrontier {
2039 Reported(Antichain<Timestamp>),
2041 NotReported {
2043 lower: Antichain<Timestamp>,
2045 },
2046}
2047
2048impl ReportedFrontier {
2049 pub fn new() -> Self {
2051 let lower = Antichain::from_elem(timely::progress::Timestamp::minimum());
2052 Self::NotReported { lower }
2053 }
2054
2055 pub fn is_empty(&self) -> bool {
2057 match self {
2058 Self::Reported(frontier) => frontier.is_empty(),
2059 Self::NotReported { .. } => false,
2060 }
2061 }
2062
2063 fn allows_reporting(&self, other: &Antichain<Timestamp>) -> bool {
2069 match self {
2070 Self::Reported(frontier) => PartialOrder::less_than(frontier, other),
2071 Self::NotReported { lower } => PartialOrder::less_equal(lower, other),
2072 }
2073 }
2074}
2075
2076pub struct CollectionState {
2078 reported_frontiers: ReportedFrontiers,
2080 dataflow_index: Rc<usize>,
2086 pub is_subscribe_or_copy: bool,
2092 as_of: Antichain<Timestamp>,
2096
2097 pub sink_token: Option<SinkToken>,
2102 pub sink_write_frontier: Option<Rc<RefCell<Antichain<Timestamp>>>>,
2106 pub input_probes: BTreeMap<GlobalId, probe::Handle<Timestamp>>,
2108 pub compute_probe: Option<probe::Handle<Timestamp>>,
2113 logging: Option<CollectionLogging>,
2115 metrics: CollectionMetrics,
2117 read_only_tx: watch::Sender<bool>,
2129 pub read_only_rx: watch::Receiver<bool>,
2131}
2132
2133impl CollectionState {
2134 fn new(
2135 dataflow_index: Rc<usize>,
2136 is_subscribe_or_copy: bool,
2137 as_of: Antichain<Timestamp>,
2138 metrics: CollectionMetrics,
2139 ) -> Self {
2140 let (read_only_tx, read_only_rx) = watch::channel(true);
2143
2144 Self {
2145 reported_frontiers: ReportedFrontiers::new(),
2146 dataflow_index,
2147 is_subscribe_or_copy,
2148 as_of,
2149 sink_token: None,
2150 sink_write_frontier: None,
2151 input_probes: Default::default(),
2152 compute_probe: None,
2153 logging: None,
2154 metrics,
2155 read_only_tx,
2156 read_only_rx,
2157 }
2158 }
2159
2160 fn reported_frontiers(&self) -> &ReportedFrontiers {
2162 &self.reported_frontiers
2163 }
2164
2165 pub fn reset_reported_frontiers(&mut self, frontier: ReportedFrontier) {
2167 self.reported_frontiers.write_frontier = frontier.clone();
2168 self.reported_frontiers.input_frontier = frontier.clone();
2169 self.reported_frontiers.output_frontier = frontier;
2170 }
2171
2172 fn set_reported_write_frontier(&mut self, frontier: ReportedFrontier) {
2174 if let Some(logging) = &mut self.logging {
2175 let time = match &frontier {
2176 ReportedFrontier::Reported(frontier) => frontier.get(0).copied(),
2177 ReportedFrontier::NotReported { .. } => Some(Timestamp::MIN),
2178 };
2179 logging.set_frontier(time);
2180 }
2181
2182 self.reported_frontiers.write_frontier = frontier;
2183 }
2184
2185 fn set_reported_input_frontier(&mut self, frontier: ReportedFrontier) {
2187 if let Some(logging) = &mut self.logging {
2189 for (id, probe) in &self.input_probes {
2190 let new_time = probe.with_frontier(|frontier| frontier.as_option().copied());
2191 logging.set_import_frontier(*id, new_time);
2192 }
2193 }
2194
2195 self.reported_frontiers.input_frontier = frontier;
2196 }
2197
2198 fn set_reported_output_frontier(&mut self, frontier: ReportedFrontier) {
2200 let already_hydrated = self.hydrated();
2201
2202 self.reported_frontiers.output_frontier = frontier;
2203
2204 if !already_hydrated && self.hydrated() {
2205 if let Some(logging) = &mut self.logging {
2206 logging.set_hydrated();
2207 }
2208 self.metrics.record_collection_hydrated();
2209 }
2210 }
2211
2212 fn hydrated(&self) -> bool {
2214 match &self.reported_frontiers.output_frontier {
2215 ReportedFrontier::Reported(frontier) => PartialOrder::less_than(&self.as_of, frontier),
2216 ReportedFrontier::NotReported { .. } => false,
2217 }
2218 }
2219
2220 fn allow_writes(&self) {
2222 info!(
2223 dataflow_index = *self.dataflow_index,
2224 export = ?self.logging.as_ref().map(|l| l.export_id()),
2225 "allowing writes for dataflow",
2226 );
2227 let _ = self.read_only_tx.send(false);
2228 }
2229}
2230
2231#[cfg(test)]
2232mod tests;
2233
2234#[cfg(test)]
2236pub(crate) mod index_peek_tests;
2237
2238#[cfg(test)]
2240mod peek_sweep_tests;