1use std::any::Any;
9use std::cell::RefCell;
10use std::cmp::Ordering;
11use std::collections::{BTreeMap, BTreeSet};
12use std::num::NonZeroUsize;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use bytesize::ByteSize;
18use differential_dataflow::Hashable;
19use differential_dataflow::lattice::Lattice;
20use differential_dataflow::trace::implementations::BatchContainer;
21use differential_dataflow::trace::{Cursor, TraceReader};
22use mz_compute_client::logging::LoggingConfig;
23use mz_compute_client::protocol::command::{
24 ComputeCommand, ComputeParameters, InstanceConfig, Peek, PeekTarget,
25};
26use mz_compute_client::protocol::history::ComputeCommandHistory;
27use mz_compute_client::protocol::response::{
28 ComputeResponse, CopyToResponse, FrontiersResponse, PeekResponse, SubscribeResponse,
29};
30use mz_compute_types::dataflows::DataflowDescription;
31use mz_compute_types::dyncfgs::{
32 ENABLE_PEEK_RESPONSE_STASH, PEEK_RESPONSE_STASH_BATCH_MAX_RUNS,
33 PEEK_RESPONSE_STASH_THRESHOLD_BYTES, PEEK_STASH_BATCH_SIZE, PEEK_STASH_NUM_BATCHES,
34};
35use mz_compute_types::plan::render_plan::RenderPlan;
36use mz_dyncfg::ConfigSet;
37use mz_expr::row::RowCollection;
38use mz_expr::{RowComparator, SafeMfpPlan};
39use mz_ore::cast::{CastFrom, CastLossy};
40use mz_ore::collections::CollectionExt;
41use mz_ore::metrics::{MetricsRegistry, UIntGauge};
42use mz_ore::now::EpochMillis;
43use mz_ore::soft_panic_or_log;
44use mz_ore::task::AbortOnDropHandle;
45use mz_ore::tracing::{OpenTelemetryContext, TracingHandle};
46use mz_persist_client::Diagnostics;
47use mz_persist_client::cache::PersistClientCache;
48use mz_persist_client::cfg::USE_CRITICAL_SINCE_SNAPSHOT;
49use mz_persist_client::read::ReadHandle;
50use mz_persist_types::PersistLocation;
51use mz_persist_types::codec_impls::UnitSchema;
52use mz_repr::fixed_length::ExtendDatums;
53use mz_repr::{DatumVec, Diff, GlobalId, Row, RowArena, Timestamp};
54use mz_storage_operators::stats::StatsCursor;
55use mz_storage_types::StorageDiff;
56use mz_storage_types::controller::CollectionMetadata;
57use mz_storage_types::dyncfgs::ORE_OVERFLOWING_BEHAVIOR;
58use mz_storage_types::sources::SourceData;
59use mz_storage_types::time_dependence::TimeDependence;
60use mz_txn_wal::operator::TxnsContext;
61use mz_txn_wal::txn_cache::TxnsCache;
62use timely::dataflow::operators::probe;
63use timely::order::PartialOrder;
64use timely::progress::frontier::Antichain;
65use timely::worker::Worker as TimelyWorker;
66use tokio::sync::{oneshot, watch};
67use tracing::{Level, debug, error, info, span, trace, warn};
68use uuid::Uuid;
69
70use crate::arrangement::manager::{TraceBundle, TraceManager};
71use crate::logging;
72use crate::logging::compute::{CollectionLogging, ComputeEvent, PeekEvent};
73use crate::logging::initialize::LoggingTraces;
74use crate::metrics::{CollectionMetrics, WorkerMetrics};
75use crate::render::{LinearJoinSpec, StartSignal};
76use crate::server::{ComputeInstanceContext, ResponseSender};
77
78mod peek_result_iterator;
79mod peek_stash;
80
81pub struct ComputeState {
86 pub collections: BTreeMap<GlobalId, CollectionState>,
95 pub traces: TraceManager,
97 pub subscribe_response_buffer: Rc<RefCell<Vec<(GlobalId, SubscribeResponse)>>>,
102 pub copy_to_response_buffer: Rc<RefCell<Vec<(GlobalId, CopyToResponse)>>>,
107 pub pending_peeks: BTreeMap<Uuid, PendingPeek>,
109 pub peek_stash_persist_location: Option<PersistLocation>,
111 pub compute_logger: Option<logging::compute::Logger>,
113 pub persist_clients: Arc<PersistClientCache>,
116 pub txns_ctx: TxnsContext,
118 pub command_history: ComputeCommandHistory<UIntGauge>,
120 max_result_size: u64,
122 pub linear_join_spec: LinearJoinSpec,
124 pub metrics: WorkerMetrics,
126 tracing_handle: Arc<TracingHandle>,
128 pub context: ComputeInstanceContext,
130 pub worker_config: Rc<ConfigSet>,
144
145 pub metrics_registry: MetricsRegistry,
147
148 pub workers_per_process: usize,
150
151 suspended_collections: BTreeMap<GlobalId, Rc<dyn Any>>,
157
158 pub server_maintenance_interval: Duration,
161
162 pub init_system_time: EpochMillis,
166
167 pub replica_expiration: Antichain<Timestamp>,
171
172 pub storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
174}
175
176impl ComputeState {
177 pub fn new(
179 persist_clients: Arc<PersistClientCache>,
180 txns_ctx: TxnsContext,
181 metrics: WorkerMetrics,
182 tracing_handle: Arc<TracingHandle>,
183 context: ComputeInstanceContext,
184 metrics_registry: MetricsRegistry,
185 workers_per_process: usize,
186 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
187 ) -> Self {
188 let traces = TraceManager::new(metrics.clone());
189 let command_history = ComputeCommandHistory::new(metrics.for_history());
190
191 Self {
192 collections: Default::default(),
193 traces,
194 subscribe_response_buffer: Default::default(),
195 copy_to_response_buffer: Default::default(),
196 pending_peeks: Default::default(),
197 peek_stash_persist_location: None,
198 compute_logger: None,
199 persist_clients,
200 txns_ctx,
201 command_history,
202 max_result_size: u64::MAX,
203 linear_join_spec: Default::default(),
204 metrics,
205 tracing_handle,
206 context,
207 worker_config: mz_dyncfgs::all_dyncfgs().into(),
208 metrics_registry,
209 workers_per_process,
210 suspended_collections: Default::default(),
211 server_maintenance_interval: Duration::ZERO,
212 init_system_time: mz_ore::now::SYSTEM_TIME(),
213 replica_expiration: Antichain::default(),
214 storage_log_reader,
215 }
216 }
217
218 pub fn expect_collection_mut(&mut self, id: GlobalId) -> &mut CollectionState {
222 self.collections
223 .get_mut(&id)
224 .expect("collection must exist")
225 }
226
227 pub fn input_probe_for(
233 &mut self,
234 input_id: GlobalId,
235 collection_ids: impl Iterator<Item = GlobalId>,
236 ) -> probe::Handle<Timestamp> {
237 let probe = probe::Handle::default();
238 for id in collection_ids {
239 if let Some(collection) = self.collections.get_mut(&id) {
240 collection.input_probes.insert(input_id, probe.clone());
241 }
242 }
243 probe
244 }
245
246 fn apply_worker_config(&mut self) {
248 use mz_compute_types::dyncfgs::*;
249
250 let config = &self.worker_config;
251
252 self.linear_join_spec = LinearJoinSpec::from_config(config);
253
254 if ENABLE_LGALLOC.get(config) {
255 if let Some(path) = &self.context.scratch_directory {
256 let clear_bytes = LGALLOC_SLOW_CLEAR_BYTES.get(config);
257 let eager_return = ENABLE_LGALLOC_EAGER_RECLAMATION.get(config);
258 let file_growth_dampener = LGALLOC_FILE_GROWTH_DAMPENER.get(config);
259 let interval = LGALLOC_BACKGROUND_INTERVAL.get(config);
260 let local_buffer_bytes = LGALLOC_LOCAL_BUFFER_BYTES.get(config);
261 info!(
262 ?path,
263 backgrund_interval=?interval,
264 clear_bytes,
265 eager_return,
266 file_growth_dampener,
267 local_buffer_bytes,
268 "enabling lgalloc"
269 );
270 let background_worker_config = lgalloc::BackgroundWorkerConfig {
271 interval,
272 clear_bytes,
273 };
274 lgalloc::lgalloc_set_config(
275 lgalloc::LgAlloc::new()
276 .enable()
277 .with_path(path.clone())
278 .with_background_config(background_worker_config)
279 .eager_return(eager_return)
280 .file_growth_dampener(file_growth_dampener)
281 .local_buffer_bytes(local_buffer_bytes),
282 );
283 } else {
284 debug!("not enabling lgalloc, scratch directory not specified");
285 }
286 } else {
287 info!("disabling lgalloc");
288 lgalloc::lgalloc_set_config(lgalloc::LgAlloc::new().disable());
289 }
290
291 #[cfg(unix)]
297 if let Some(path) = &self.context.scratch_directory {
298 mz_ore::pager::set_scratch_dir(path.clone());
299 mz_ore::pager::set_backend(mz_ore::pager::Backend::File);
300 } else {
301 mz_ore::pager::set_backend(mz_ore::pager::Backend::Swap);
302 }
303
304 crate::memory_limiter::apply_limiter_config(config);
305
306 mz_ore::region::ENABLE_LGALLOC_REGION.store(
307 ENABLE_COLUMNATION_LGALLOC.get(config),
308 std::sync::atomic::Ordering::Relaxed,
309 );
310
311 {
326 use mz_ore::pager::Backend;
327 use mz_timely_util::column_pager::{Codec, apply_tiered_config};
328
329 let enabled = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
330 let codec = COLUMN_PAGED_BATCHER_LZ4.get(config).then_some(Codec::Lz4);
331 let swap_pageout = COLUMN_PAGED_BATCHER_SWAP_PAGEOUT.get(config);
332
333 const MIB: usize = 1024 * 1024;
338 const DEFAULT_MEM_LIMIT: usize = 4 * 1024 * MIB;
339 let mem_limit = crate::memory_limiter::get_memory_limit().unwrap_or(DEFAULT_MEM_LIMIT);
340 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config).max(0.0);
341 let total = usize::cast_lossy(f64::cast_lossy(mem_limit) * fraction).max(128 * MIB);
342
343 let backend = if self.context.scratch_directory.is_some() {
344 Backend::File
345 } else {
346 Backend::Swap
347 };
348
349 debug!(
350 enabled,
351 ?backend,
352 ?codec,
353 swap_pageout,
354 fraction,
355 mem_limit,
356 budget_bytes = total,
357 "column-paged batcher: applying tiered config",
358 );
359 apply_tiered_config(enabled, total, backend, codec, swap_pageout);
360 }
361
362 self.server_maintenance_interval = COMPUTE_SERVER_MAINTENANCE_INTERVAL.get(config);
365
366 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(config);
367 match overflowing_behavior.parse() {
368 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
369 Err(err) => {
370 error!(
371 err,
372 overflowing_behavior, "Invalid value for ore_overflowing_behavior"
373 );
374 }
375 }
376 }
377
378 pub fn apply_expiration_offset(&mut self, offset: Duration) {
384 if self.replica_expiration.is_empty() {
385 let offset: EpochMillis = offset
386 .as_millis()
387 .try_into()
388 .expect("duration must fit within u64");
389 let replica_expiration_millis = self.init_system_time + offset;
390 let replica_expiration = Timestamp::from(replica_expiration_millis);
391
392 info!(
393 offset = %offset,
394 replica_expiration_millis = %replica_expiration_millis,
395 replica_expiration_utc = %mz_ore::now::to_datetime(replica_expiration_millis),
396 "setting replica expiration",
397 );
398 self.replica_expiration = Antichain::from_elem(replica_expiration);
399
400 self.metrics
402 .replica_expiration_timestamp_seconds
403 .set(replica_expiration.into());
404 }
405 }
406
407 pub fn dataflow_max_inflight_bytes(&self) -> Option<usize> {
410 use mz_compute_types::dyncfgs::{
411 DATAFLOW_MAX_INFLIGHT_BYTES, DATAFLOW_MAX_INFLIGHT_BYTES_CC,
412 };
413
414 if self.persist_clients.cfg.is_cc_active {
415 DATAFLOW_MAX_INFLIGHT_BYTES_CC.get(&self.worker_config)
416 } else {
417 DATAFLOW_MAX_INFLIGHT_BYTES.get(&self.worker_config)
418 }
419 }
420}
421
422pub(crate) struct ActiveComputeState<'a> {
424 pub timely_worker: &'a mut TimelyWorker,
426 pub compute_state: &'a mut ComputeState,
428 pub response_tx: &'a mut ResponseSender,
430}
431
432pub struct SinkToken(#[allow(dead_code)] Box<dyn Any>);
434
435impl SinkToken {
436 pub fn new(t: Box<dyn Any>) -> Self {
438 Self(t)
439 }
440}
441
442impl<'a> ActiveComputeState<'a> {
443 #[mz_ore::instrument(level = "debug")]
445 pub fn handle_compute_command(&mut self, cmd: ComputeCommand) {
446 use ComputeCommand::*;
447
448 self.compute_state.command_history.push(cmd.clone());
449
450 let timer = self
452 .compute_state
453 .metrics
454 .handle_command_duration_seconds
455 .for_command(&cmd)
456 .start_timer();
457
458 match cmd {
459 Hello { .. } => panic!("Hello must be captured before"),
460 CreateInstance(instance_config) => self.handle_create_instance(*instance_config),
461 InitializationComplete => (),
462 UpdateConfiguration(params) => self.handle_update_configuration(*params),
463 CreateDataflow(dataflow) => self.handle_create_dataflow(*dataflow),
464 Schedule(id) => self.handle_schedule(id),
465 AllowCompaction { id, frontier } => self.handle_allow_compaction(id, frontier),
466 Peek(peek) => {
467 peek.otel_ctx.attach_as_parent();
468 self.handle_peek(*peek)
469 }
470 CancelPeek { uuid } => self.handle_cancel_peek(uuid),
471 AllowWrites(id) => {
472 self.handle_allow_writes(id);
473 }
474 }
475
476 timer.observe_duration();
477 }
478
479 fn handle_create_instance(&mut self, config: InstanceConfig) {
480 config
485 .initial_config
486 .apply(&self.compute_state.worker_config);
487
488 self.compute_state.apply_worker_config();
490
491 mz_row_spine::DICTIONARY_COMPRESSION.store(
497 config.arrangement_dictionary_compression,
498 std::sync::atomic::Ordering::Relaxed,
499 );
500
501 if let Some(offset) = config.expiration_offset {
502 self.compute_state.apply_expiration_offset(offset);
503 }
504
505 let storage_log_reader = self.compute_state.storage_log_reader.take();
506 self.initialize_logging(config.logging, storage_log_reader);
507
508 self.compute_state.peek_stash_persist_location = Some(config.peek_stash_persist_location);
509 }
510
511 fn handle_update_configuration(&mut self, params: ComputeParameters) {
512 debug!("Applying configuration update: {params:?}");
513
514 let ComputeParameters {
515 workload_class,
516 max_result_size,
517 tracing,
518 grpc_client: _grpc_client,
519 dyncfg_updates,
520 } = params;
521
522 if let Some(v) = workload_class {
523 self.compute_state.metrics.set_workload_class(v);
524 }
525 if let Some(v) = max_result_size {
526 self.compute_state.max_result_size = v;
527 }
528
529 tracing.apply(self.compute_state.tracing_handle.as_ref());
530
531 dyncfg_updates.apply(&self.compute_state.worker_config);
532 self.compute_state
533 .persist_clients
534 .cfg()
535 .apply_from(&dyncfg_updates);
536
537 mz_metrics::update_dyncfg(&dyncfg_updates);
541
542 self.compute_state.apply_worker_config();
543 }
544
545 fn handle_create_dataflow(
546 &mut self,
547 dataflow: DataflowDescription<RenderPlan, CollectionMetadata>,
548 ) {
549 let dataflow_index = Rc::new(self.timely_worker.next_dataflow_index());
550 let as_of = dataflow.as_of.clone().unwrap();
551
552 let dataflow_expiration = dataflow
553 .time_dependence
554 .as_ref()
555 .map(|time_dependence| {
556 self.determine_dataflow_expiration(time_dependence, &dataflow.until)
557 })
558 .unwrap_or_default();
559
560 let until = dataflow.until.meet(&dataflow_expiration);
562
563 if dataflow.is_transient() {
564 debug!(
565 name = %dataflow.debug_name,
566 import_ids = %dataflow.display_import_ids(),
567 export_ids = %dataflow.display_export_ids(),
568 as_of = ?as_of.elements(),
569 time_dependence = ?dataflow.time_dependence,
570 expiration = ?dataflow_expiration.elements(),
571 expiration_datetime = ?dataflow_expiration
572 .as_option()
573 .map(|t| mz_ore::now::to_datetime(t.into())),
574 plan_until = ?dataflow.until.elements(),
575 until = ?until.elements(),
576 "creating dataflow",
577 );
578 } else {
579 info!(
580 name = %dataflow.debug_name,
581 import_ids = %dataflow.display_import_ids(),
582 export_ids = %dataflow.display_export_ids(),
583 as_of = ?as_of.elements(),
584 time_dependence = ?dataflow.time_dependence,
585 expiration = ?dataflow_expiration.elements(),
586 expiration_datetime = ?dataflow_expiration
587 .as_option()
588 .map(|t| mz_ore::now::to_datetime(t.into())),
589 plan_until = ?dataflow.until.elements(),
590 until = ?until.elements(),
591 "creating dataflow",
592 );
593 };
594
595 let subscribe_copy_ids: BTreeSet<_> = dataflow
596 .subscribe_ids()
597 .chain(dataflow.copy_to_ids())
598 .collect();
599
600 for object_id in dataflow.export_ids() {
602 let is_subscribe_or_copy = subscribe_copy_ids.contains(&object_id);
603 let metrics = self.compute_state.metrics.for_collection(object_id);
604 let mut collection = CollectionState::new(
605 Rc::clone(&dataflow_index),
606 is_subscribe_or_copy,
607 as_of.clone(),
608 metrics,
609 );
610
611 if let Some(logger) = self.compute_state.compute_logger.clone() {
612 let logging = CollectionLogging::new(
613 object_id,
614 logger,
615 *dataflow_index,
616 dataflow.import_ids(),
617 );
618 collection.logging = Some(logging);
619 }
620
621 collection.reset_reported_frontiers(ReportedFrontier::NotReported {
622 lower: as_of.clone(),
623 });
624
625 let existing = self.compute_state.collections.insert(object_id, collection);
626 if existing.is_some() {
627 error!(
628 id = ?object_id,
629 "existing collection for newly created dataflow",
630 );
631 }
632 }
633
634 let (start_signal, suspension_token) = StartSignal::new();
635 for id in dataflow.export_ids() {
636 self.compute_state
637 .suspended_collections
638 .insert(id, Rc::clone(&suspension_token));
639 }
640
641 crate::render::build_compute_dataflow(
642 self.timely_worker,
643 self.compute_state,
644 dataflow,
645 start_signal,
646 until,
647 dataflow_expiration,
648 );
649 }
650
651 fn handle_schedule(&mut self, id: GlobalId) {
652 let suspension_token = self.compute_state.suspended_collections.remove(&id);
658 drop(suspension_token);
659 }
660
661 fn handle_allow_compaction(&mut self, id: GlobalId, frontier: Antichain<Timestamp>) {
662 if frontier.is_empty() {
663 self.drop_collection(id);
665 } else {
666 self.compute_state
667 .traces
668 .allow_compaction(id, frontier.borrow());
669 }
670 }
671
672 #[mz_ore::instrument(level = "debug")]
673 fn handle_peek(&mut self, peek: Peek) {
674 let pending = match &peek.target {
675 PeekTarget::Index { id } => {
676 let trace_bundle = self.compute_state.traces.get(id).unwrap().clone();
678 PendingPeek::index(peek, trace_bundle)
679 }
680 PeekTarget::Persist { metadata, .. } => {
681 let metadata = metadata.clone();
682 PendingPeek::persist(
683 peek,
684 Arc::clone(&self.compute_state.persist_clients),
685 metadata,
686 usize::cast_from(self.compute_state.max_result_size),
687 self.timely_worker,
688 )
689 }
690 };
691
692 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
694 logger.log(&pending.as_log_event(true));
695 }
696
697 self.process_peek(&mut Antichain::new(), pending);
698 }
699
700 fn handle_cancel_peek(&mut self, uuid: Uuid) {
701 if let Some(peek) = self.compute_state.pending_peeks.remove(&uuid) {
702 self.send_peek_response(peek, PeekResponse::Canceled);
703 }
704 }
705
706 fn handle_allow_writes(&mut self, id: GlobalId) {
707 self.compute_state.persist_clients.cfg().enable_compaction();
711
712 if let Some(collection) = self.compute_state.collections.get_mut(&id) {
713 collection.allow_writes();
714 } else {
715 soft_panic_or_log!("allow writes for unknown collection {id}");
716 }
717 }
718
719 fn drop_collection(&mut self, id: GlobalId) {
721 let collection = self
722 .compute_state
723 .collections
724 .remove(&id)
725 .expect("dropped untracked collection");
726
727 self.compute_state.traces.remove(&id);
729 self.compute_state.suspended_collections.remove(&id);
731
732 if let Ok(index) = Rc::try_unwrap(collection.dataflow_index) {
734 self.timely_worker.drop_dataflow(index);
735 }
736
737 if !collection.is_subscribe_or_copy {
742 let reported = collection.reported_frontiers;
743 let write_frontier = (!reported.write_frontier.is_empty()).then(Antichain::new);
744 let input_frontier = (!reported.input_frontier.is_empty()).then(Antichain::new);
745 let output_frontier = (!reported.output_frontier.is_empty()).then(Antichain::new);
746
747 let frontiers = FrontiersResponse {
748 write_frontier,
749 input_frontier,
750 output_frontier,
751 };
752 if frontiers.has_updates() {
753 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
754 }
755 }
756 }
757
758 pub fn initialize_logging(
760 &mut self,
761 config: LoggingConfig,
762 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
763 ) {
764 if self.compute_state.compute_logger.is_some() {
765 panic!("dataflow server has already initialized logging");
766 }
767
768 let LoggingTraces {
769 traces,
770 dataflow_index,
771 compute_logger: logger,
772 } = logging::initialize(
773 self.timely_worker,
774 &config,
775 self.compute_state.metrics_registry.clone(),
776 Rc::clone(&self.compute_state.worker_config),
777 self.compute_state.workers_per_process,
778 storage_log_reader,
779 );
780
781 let dataflow_index = Rc::new(dataflow_index);
782 let mut log_index_ids = config.index_logs;
783 for (log, trace) in traces {
784 let id = log_index_ids
786 .remove(&log)
787 .expect("`logging::initialize` does not invent logs");
788 self.compute_state.traces.set(id, trace);
789
790 let is_subscribe_or_copy = false;
792 let as_of = Antichain::from_elem(Timestamp::MIN);
793 let metrics = self.compute_state.metrics.for_collection(id);
794 let mut collection = CollectionState::new(
795 Rc::clone(&dataflow_index),
796 is_subscribe_or_copy,
797 as_of,
798 metrics,
799 );
800
801 let logging =
802 CollectionLogging::new(id, logger.clone(), *dataflow_index, std::iter::empty());
803 collection.logging = Some(logging);
804
805 let existing = self.compute_state.collections.insert(id, collection);
806 if existing.is_some() {
807 error!(
808 id = ?id,
809 "existing collection for newly initialized logging export",
810 );
811 }
812 }
813
814 assert!(
816 log_index_ids.is_empty(),
817 "failed to create requested logging indexes: {log_index_ids:?}",
818 );
819
820 self.compute_state.compute_logger = Some(logger);
821 }
822
823 pub fn report_frontiers(&mut self) {
825 let mut responses = Vec::new();
826
827 let mut new_frontier = Antichain::new();
829
830 for (&id, collection) in self.compute_state.collections.iter_mut() {
831 if collection.is_subscribe_or_copy {
834 continue;
835 }
836
837 let reported = collection.reported_frontiers();
838
839 new_frontier.clear();
841 if let Some(traces) = self.compute_state.traces.get_mut(&id) {
842 assert!(
843 collection.sink_write_frontier.is_none(),
844 "collection {id} has multiple frontiers"
845 );
846 traces.oks_mut().read_upper(&mut new_frontier);
847 } else if let Some(frontier) = &collection.sink_write_frontier {
848 new_frontier.clone_from(&frontier.borrow());
849 } else {
850 error!(id = ?id, "collection without write frontier");
851 continue;
852 }
853 let new_write_frontier = reported
854 .write_frontier
855 .allows_reporting(&new_frontier)
856 .then(|| new_frontier.clone());
857
858 if let Some(probe) = &collection.compute_probe {
871 if *collection.read_only_rx.borrow() {
872 new_frontier.clear();
873 }
874 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
875 }
876 let new_output_frontier = reported
877 .output_frontier
878 .allows_reporting(&new_frontier)
879 .then(|| new_frontier.clone());
880
881 new_frontier.clear();
883 for probe in collection.input_probes.values() {
884 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
885 }
886 let new_input_frontier = reported
887 .input_frontier
888 .allows_reporting(&new_frontier)
889 .then(|| new_frontier.clone());
890
891 if let Some(frontier) = &new_write_frontier {
892 collection
893 .set_reported_write_frontier(ReportedFrontier::Reported(frontier.clone()));
894 }
895 if let Some(frontier) = &new_input_frontier {
896 collection
897 .set_reported_input_frontier(ReportedFrontier::Reported(frontier.clone()));
898 }
899 if let Some(frontier) = &new_output_frontier {
900 collection
901 .set_reported_output_frontier(ReportedFrontier::Reported(frontier.clone()));
902 }
903
904 let response = FrontiersResponse {
905 write_frontier: new_write_frontier,
906 input_frontier: new_input_frontier,
907 output_frontier: new_output_frontier,
908 };
909 if response.has_updates() {
910 responses.push((id, response));
911 }
912 }
913
914 for (id, frontiers) in responses {
915 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
916 }
917 }
918
919 pub(crate) fn report_metrics(&self) {
921 if let Some(expiration) = self.compute_state.replica_expiration.as_option() {
922 let now = Duration::from_millis(mz_ore::now::SYSTEM_TIME()).as_secs_f64();
923 let expiration = Duration::from_millis(<u64>::from(expiration)).as_secs_f64();
924 let remaining = expiration - now;
925 self.compute_state
926 .metrics
927 .replica_expiration_remaining_seconds
928 .set(remaining)
929 }
930 }
931
932 fn process_peek(&mut self, upper: &mut Antichain<Timestamp>, mut peek: PendingPeek) {
934 let response = match &mut peek {
935 PendingPeek::Index(peek) => {
936 let start = Instant::now();
937
938 let peek_stash_eligible = peek
939 .peek
940 .finishing
941 .is_streamable(peek.peek.result_desc.arity());
942
943 let peek_stash_enabled = {
944 let enabled = ENABLE_PEEK_RESPONSE_STASH.get(&self.compute_state.worker_config);
945 let peek_persist_stash_available =
946 self.compute_state.peek_stash_persist_location.is_some();
947 if !peek_persist_stash_available && enabled {
948 error!("missing peek_stash_persist_location but peek stash is enabled");
949 }
950 enabled && peek_persist_stash_available
951 };
952
953 let peek_stash_threshold_bytes =
954 PEEK_RESPONSE_STASH_THRESHOLD_BYTES.get(&self.compute_state.worker_config);
955
956 let metrics = IndexPeekMetrics {
957 seek_fulfillment_seconds: &self
958 .compute_state
959 .metrics
960 .index_peek_seek_fulfillment_seconds,
961 frontier_check_seconds: &self
962 .compute_state
963 .metrics
964 .index_peek_frontier_check_seconds,
965 error_scan_seconds: &self.compute_state.metrics.index_peek_error_scan_seconds,
966 cursor_setup_seconds: &self
967 .compute_state
968 .metrics
969 .index_peek_cursor_setup_seconds,
970 row_iteration_seconds: &self
971 .compute_state
972 .metrics
973 .index_peek_row_iteration_seconds,
974 result_sort_seconds: &self.compute_state.metrics.index_peek_result_sort_seconds,
975 row_collection_seconds: &self
976 .compute_state
977 .metrics
978 .index_peek_row_collection_seconds,
979 };
980
981 let status = peek.seek_fulfillment(
982 upper,
983 self.compute_state.max_result_size,
984 peek_stash_enabled && peek_stash_eligible,
985 peek_stash_threshold_bytes,
986 &metrics,
987 );
988
989 self.compute_state
990 .metrics
991 .index_peek_total_seconds
992 .observe(start.elapsed().as_secs_f64());
993
994 match status {
995 PeekStatus::Ready(result) => Some(result),
996 PeekStatus::NotReady => None,
997 PeekStatus::UsePeekStash => {
998 let _span =
999 span!(parent: &peek.span, Level::DEBUG, "process_stash_peek").entered();
1000
1001 let peek_stash_batch_max_runs = PEEK_RESPONSE_STASH_BATCH_MAX_RUNS
1002 .get(&self.compute_state.worker_config);
1003
1004 let stash_task = peek_stash::StashingPeek::start_upload(
1005 Arc::clone(&self.compute_state.persist_clients),
1006 self.compute_state
1007 .peek_stash_persist_location
1008 .as_ref()
1009 .expect("verified above"),
1010 peek.peek.clone(),
1011 peek.trace_bundle.clone(),
1012 peek_stash_batch_max_runs,
1013 );
1014
1015 self.compute_state
1016 .pending_peeks
1017 .insert(peek.peek.uuid, PendingPeek::Stash(stash_task));
1018 return;
1019 }
1020 }
1021 }
1022 PendingPeek::Persist(peek) => peek.result.try_recv().ok().map(|(result, duration)| {
1023 self.compute_state
1024 .metrics
1025 .persist_peek_seconds
1026 .observe(duration.as_secs_f64());
1027 result
1028 }),
1029 PendingPeek::Stash(stashing_peek) => {
1030 let num_batches = PEEK_STASH_NUM_BATCHES.get(&self.compute_state.worker_config);
1031 let batch_size = PEEK_STASH_BATCH_SIZE.get(&self.compute_state.worker_config);
1032 stashing_peek.pump_rows(num_batches, batch_size);
1033
1034 if let Ok((response, duration)) = stashing_peek.result.try_recv() {
1035 self.compute_state
1036 .metrics
1037 .stashed_peek_seconds
1038 .observe(duration.as_secs_f64());
1039 trace!(?stashing_peek.peek, ?duration, "finished stashing peek response in persist");
1040
1041 Some(response)
1042 } else {
1043 None
1044 }
1045 }
1046 };
1047
1048 if let Some(response) = response {
1049 let _span = span!(parent: peek.span(), Level::DEBUG, "process_peek_response").entered();
1050 self.send_peek_response(peek, response)
1051 } else {
1052 let uuid = peek.peek().uuid;
1053 self.compute_state.pending_peeks.insert(uuid, peek);
1054 }
1055 }
1056
1057 pub fn process_peeks(&mut self) {
1059 let mut upper = Antichain::new();
1060 let pending_peeks = std::mem::take(&mut self.compute_state.pending_peeks);
1061 for (_uuid, peek) in pending_peeks {
1062 self.process_peek(&mut upper, peek);
1063 }
1064 }
1065
1066 #[mz_ore::instrument(level = "debug")]
1071 fn send_peek_response(&mut self, peek: PendingPeek, response: PeekResponse) {
1072 let log_event = peek.as_log_event(false);
1073 self.send_compute_response(ComputeResponse::PeekResponse(
1075 peek.peek().uuid,
1076 response,
1077 OpenTelemetryContext::obtain(),
1078 ));
1079
1080 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
1082 logger.log(&log_event);
1083 }
1084 }
1085
1086 pub fn process_subscribes(&mut self) {
1088 let mut subscribe_responses = self.compute_state.subscribe_response_buffer.borrow_mut();
1089 for (sink_id, mut response) in subscribe_responses.drain(..) {
1090 if let Some(collection) = self.compute_state.collections.get_mut(&sink_id) {
1092 let new_frontier = match &response {
1093 SubscribeResponse::Batch(b) => b.upper.clone(),
1094 SubscribeResponse::DroppedAt(_) => Antichain::new(),
1095 };
1096
1097 let reported = collection.reported_frontiers();
1098 assert!(
1099 reported.write_frontier.allows_reporting(&new_frontier),
1100 "subscribe write frontier regression: {:?} -> {:?}",
1101 reported.write_frontier,
1102 new_frontier,
1103 );
1104 assert!(
1105 reported.input_frontier.allows_reporting(&new_frontier),
1106 "subscribe input frontier regression: {:?} -> {:?}",
1107 reported.input_frontier,
1108 new_frontier,
1109 );
1110
1111 collection
1112 .set_reported_write_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1113 collection
1114 .set_reported_input_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1115 collection.set_reported_output_frontier(ReportedFrontier::Reported(new_frontier));
1116 } else {
1117 }
1120
1121 response
1122 .to_error_if_exceeds(usize::try_from(self.compute_state.max_result_size).unwrap());
1123 self.send_compute_response(ComputeResponse::SubscribeResponse(sink_id, response));
1124 }
1125 }
1126
1127 pub fn process_copy_tos(&self) {
1129 let mut responses = self.compute_state.copy_to_response_buffer.borrow_mut();
1130 for (sink_id, response) in responses.drain(..) {
1131 self.send_compute_response(ComputeResponse::CopyToResponse(sink_id, response));
1132 }
1133 }
1134
1135 fn send_compute_response(&self, response: ComputeResponse) {
1137 let _ = self.response_tx.send(response);
1140 }
1141
1142 pub(crate) fn check_expiration(&self) {
1144 let now = mz_ore::now::SYSTEM_TIME();
1145 if self.compute_state.replica_expiration.less_than(&now.into()) {
1146 let now_datetime = mz_ore::now::to_datetime(now);
1147 let expiration_datetime = self
1148 .compute_state
1149 .replica_expiration
1150 .as_option()
1151 .map(Into::into)
1152 .map(mz_ore::now::to_datetime);
1153
1154 error!(
1157 now,
1158 now_datetime = ?now_datetime,
1159 expiration = ?self.compute_state.replica_expiration.elements(),
1160 expiration_datetime = ?expiration_datetime,
1161 "replica expired"
1162 );
1163
1164 assert!(
1166 !self.compute_state.replica_expiration.less_than(&now.into()),
1167 "replica expired. now: {now} ({now_datetime:?}), expiration: {:?} ({expiration_datetime:?})",
1168 self.compute_state.replica_expiration.elements(),
1169 );
1170 }
1171 }
1172
1173 pub fn determine_dataflow_expiration(
1179 &self,
1180 time_dependence: &TimeDependence,
1181 until: &Antichain<Timestamp>,
1182 ) -> Antichain<Timestamp> {
1183 let iter = self
1188 .compute_state
1189 .replica_expiration
1190 .iter()
1191 .filter_map(|t| time_dependence.apply(*t))
1192 .filter_map(|t| Timestamp::try_step_forward(&t))
1193 .filter(|expiration| !until.less_equal(expiration));
1194 Antichain::from_iter(iter)
1195 }
1196}
1197
1198pub enum PendingPeek {
1203 Index(IndexPeek),
1205 Persist(PersistPeek),
1207 Stash(peek_stash::StashingPeek),
1210}
1211
1212impl PendingPeek {
1213 pub fn as_log_event(&self, installed: bool) -> ComputeEvent {
1215 let peek = self.peek();
1216 let (id, peek_type) = match &peek.target {
1217 PeekTarget::Index { id } => (*id, logging::compute::PeekType::Index),
1218 PeekTarget::Persist { id, .. } => (*id, logging::compute::PeekType::Persist),
1219 };
1220 let uuid = peek.uuid.into_bytes();
1221 ComputeEvent::Peek(PeekEvent {
1222 id,
1223 time: peek.timestamp,
1224 uuid,
1225 peek_type,
1226 installed,
1227 })
1228 }
1229
1230 fn index(peek: Peek, mut trace_bundle: TraceBundle) -> Self {
1231 let empty_frontier = Antichain::new();
1232 let timestamp_frontier = Antichain::from_elem(peek.timestamp);
1233 trace_bundle
1234 .oks_mut()
1235 .set_logical_compaction(timestamp_frontier.borrow());
1236 trace_bundle
1237 .errs_mut()
1238 .set_logical_compaction(timestamp_frontier.borrow());
1239 trace_bundle
1240 .oks_mut()
1241 .set_physical_compaction(empty_frontier.borrow());
1242 trace_bundle
1243 .errs_mut()
1244 .set_physical_compaction(empty_frontier.borrow());
1245
1246 PendingPeek::Index(IndexPeek {
1247 peek,
1248 trace_bundle,
1249 span: tracing::Span::current(),
1250 })
1251 }
1252
1253 fn persist(
1254 peek: Peek,
1255 persist_clients: Arc<PersistClientCache>,
1256 metadata: CollectionMetadata,
1257 max_result_size: usize,
1258 timely_worker: &TimelyWorker,
1259 ) -> Self {
1260 let active_worker = {
1261 let chosen_index = usize::cast_from(peek.uuid.hashed()) % timely_worker.peers();
1263 chosen_index == timely_worker.index()
1264 };
1265 let activator = timely_worker.sync_activator_for([].into());
1266 let peek_uuid = peek.uuid;
1267
1268 let (result_tx, result_rx) = oneshot::channel();
1269 let timestamp = peek.timestamp;
1270 let mfp_plan = peek.map_filter_project.clone();
1271 let max_results_needed = peek
1272 .finishing
1273 .limit
1274 .map(|l| usize::cast_from(u64::from(l)))
1275 .unwrap_or(usize::MAX)
1276 + peek.finishing.offset;
1277 let order_by = peek.finishing.order_by.clone();
1278
1279 let literal_constraint = peek
1281 .literal_constraints
1282 .clone()
1283 .map(|rows| rows.into_element());
1284
1285 let task_handle = mz_ore::task::spawn(|| "persist::peek", async move {
1286 let start = Instant::now();
1287 let result = if active_worker {
1288 PersistPeek::do_peek(
1289 &persist_clients,
1290 metadata,
1291 timestamp,
1292 literal_constraint,
1293 mfp_plan,
1294 max_result_size,
1295 max_results_needed,
1296 )
1297 .await
1298 } else {
1299 Ok(vec![])
1300 };
1301 let result = match result {
1302 Ok(rows) => PeekResponse::Rows(vec![RowCollection::new(rows, &order_by)]),
1303 Err(e) => PeekResponse::Error(e.to_string()),
1304 };
1305 match result_tx.send((result, start.elapsed())) {
1306 Ok(()) => {}
1307 Err((_result, elapsed)) => {
1308 debug!(duration =? elapsed, "dropping result for cancelled peek {peek_uuid}")
1309 }
1310 }
1311 match activator.activate() {
1312 Ok(()) => {}
1313 Err(_) => {
1314 debug!("unable to wake timely after completed peek {peek_uuid}");
1315 }
1316 }
1317 });
1318 PendingPeek::Persist(PersistPeek {
1319 peek,
1320 _abort_handle: task_handle.abort_on_drop(),
1321 result: result_rx,
1322 span: tracing::Span::current(),
1323 })
1324 }
1325
1326 fn span(&self) -> &tracing::Span {
1327 match self {
1328 PendingPeek::Index(p) => &p.span,
1329 PendingPeek::Persist(p) => &p.span,
1330 PendingPeek::Stash(p) => &p.span,
1331 }
1332 }
1333
1334 pub(crate) fn peek(&self) -> &Peek {
1335 match self {
1336 PendingPeek::Index(p) => &p.peek,
1337 PendingPeek::Persist(p) => &p.peek,
1338 PendingPeek::Stash(p) => &p.peek,
1339 }
1340 }
1341}
1342
1343pub struct PersistPeek {
1348 pub(crate) peek: Peek,
1349 _abort_handle: AbortOnDropHandle<()>,
1352 result: oneshot::Receiver<(PeekResponse, Duration)>,
1354 span: tracing::Span,
1356}
1357
1358impl PersistPeek {
1359 async fn do_peek(
1360 persist_clients: &PersistClientCache,
1361 metadata: CollectionMetadata,
1362 as_of: Timestamp,
1363 literal_constraint: Option<Row>,
1364 mfp_plan: SafeMfpPlan,
1365 max_result_size: usize,
1366 mut limit_remaining: usize,
1367 ) -> Result<Vec<(Row, NonZeroUsize)>, String> {
1368 let client = persist_clients
1369 .open(metadata.persist_location)
1370 .await
1371 .map_err(|e| e.to_string())?;
1372
1373 let mut reader: ReadHandle<SourceData, (), Timestamp, StorageDiff> = client
1374 .open_leased_reader(
1375 metadata.data_shard,
1376 Arc::new(metadata.relation_desc.clone()),
1377 Arc::new(UnitSchema),
1378 Diagnostics::from_purpose("persist::peek"),
1379 USE_CRITICAL_SINCE_SNAPSHOT.get(client.dyncfgs()),
1380 )
1381 .await
1382 .map_err(|e| e.to_string())?;
1383
1384 let mut txns_read = if let Some(txns_id) = metadata.txns_shard {
1391 Some(TxnsCache::open(&client, txns_id, Some(metadata.data_shard)).await)
1392 } else {
1393 None
1394 };
1395
1396 let metrics = client.metrics();
1397
1398 let mut cursor = StatsCursor::new(
1399 &mut reader,
1400 txns_read.as_mut(),
1401 metrics,
1402 &mfp_plan,
1403 &metadata.relation_desc,
1404 Antichain::from_elem(as_of),
1405 )
1406 .await
1407 .map_err(|since| {
1408 format!("attempted to peek at {as_of}, but the since has advanced to {since:?}")
1409 })?;
1410
1411 let mut result = vec![];
1413 let mut datum_vec = DatumVec::new();
1414 let mut row_builder = Row::default();
1415 let arena = RowArena::new();
1416 let mut total_size = 0usize;
1417
1418 let literal_len = match &literal_constraint {
1419 None => 0,
1420 Some(row) => row.iter().count(),
1421 };
1422
1423 'collect: while limit_remaining > 0 {
1424 let Some(batch) = cursor.next().await else {
1425 break;
1426 };
1427 for (data, _, d) in batch {
1428 let row = data.map_err(|e| e.to_string())?;
1429
1430 if let Some(literal) = &literal_constraint {
1431 match row.iter().take(literal_len).cmp(literal.iter()) {
1432 Ordering::Less => continue,
1433 Ordering::Equal => {}
1434 Ordering::Greater => break 'collect,
1435 }
1436 }
1437
1438 let count: usize = d.try_into().map_err(|_| {
1439 error!(
1440 shard = %metadata.data_shard, diff = d, ?row,
1441 "persist peek encountered negative multiplicities",
1442 );
1443 format!(
1444 "Invalid data in source, \
1445 saw retractions ({}) for row that does not exist: {:?}",
1446 -d, row,
1447 )
1448 })?;
1449 let Some(count) = NonZeroUsize::new(count) else {
1450 continue;
1451 };
1452 let mut datum_local = datum_vec.borrow_with(&row);
1453 let eval_result = mfp_plan
1454 .evaluate_into(&mut datum_local, &arena, &mut row_builder)
1455 .map(|row| row.cloned())
1456 .map_err(|e| e.to_string())?;
1457 if let Some(row) = eval_result {
1458 total_size = total_size
1459 .saturating_add(row.byte_len())
1460 .saturating_add(std::mem::size_of::<NonZeroUsize>());
1461 if total_size > max_result_size {
1462 return Err(format!(
1463 "result exceeds max size of {}",
1464 ByteSize::b(u64::cast_from(max_result_size))
1465 ));
1466 }
1467 result.push((row, count));
1468 limit_remaining = limit_remaining.saturating_sub(count.get());
1469 if limit_remaining == 0 {
1470 break;
1471 }
1472 }
1473 }
1474 }
1475
1476 Ok(result)
1477 }
1478}
1479
1480pub struct IndexPeek {
1482 peek: Peek,
1483 trace_bundle: TraceBundle,
1485 span: tracing::Span,
1487}
1488
1489pub(crate) struct IndexPeekMetrics<'a> {
1494 pub seek_fulfillment_seconds: &'a prometheus::Histogram,
1495 pub frontier_check_seconds: &'a prometheus::Histogram,
1496 pub error_scan_seconds: &'a prometheus::Histogram,
1497 pub cursor_setup_seconds: &'a prometheus::Histogram,
1498 pub row_iteration_seconds: &'a prometheus::Histogram,
1499 pub result_sort_seconds: &'a prometheus::Histogram,
1500 pub row_collection_seconds: &'a prometheus::Histogram,
1501}
1502
1503impl IndexPeek {
1504 fn seek_fulfillment(
1517 &mut self,
1518 upper: &mut Antichain<Timestamp>,
1519 max_result_size: u64,
1520 peek_stash_eligible: bool,
1521 peek_stash_threshold_bytes: usize,
1522 metrics: &IndexPeekMetrics<'_>,
1523 ) -> PeekStatus {
1524 let method_start = Instant::now();
1525
1526 self.trace_bundle.oks_mut().read_upper(upper);
1527 if upper.less_equal(&self.peek.timestamp) {
1528 return PeekStatus::NotReady;
1529 }
1530 self.trace_bundle.errs_mut().read_upper(upper);
1531 if upper.less_equal(&self.peek.timestamp) {
1532 return PeekStatus::NotReady;
1533 }
1534
1535 let read_frontier = self.trace_bundle.compaction_frontier();
1536 if !read_frontier.less_equal(&self.peek.timestamp) {
1537 let error = format!(
1538 "Arrangement compaction frontier ({:?}) is beyond the time of the attempted read ({})",
1539 read_frontier.elements(),
1540 self.peek.timestamp,
1541 );
1542 return PeekStatus::Ready(PeekResponse::Error(error));
1543 }
1544
1545 metrics
1546 .frontier_check_seconds
1547 .observe(method_start.elapsed().as_secs_f64());
1548
1549 let result = self.collect_finished_data(
1550 max_result_size,
1551 peek_stash_eligible,
1552 peek_stash_threshold_bytes,
1553 metrics,
1554 );
1555
1556 metrics
1557 .seek_fulfillment_seconds
1558 .observe(method_start.elapsed().as_secs_f64());
1559
1560 result
1561 }
1562
1563 fn collect_finished_data(
1565 &mut self,
1566 max_result_size: u64,
1567 peek_stash_eligible: bool,
1568 peek_stash_threshold_bytes: usize,
1569 metrics: &IndexPeekMetrics<'_>,
1570 ) -> PeekStatus {
1571 let error_scan_start = Instant::now();
1572
1573 let (mut cursor, storage) = self.trace_bundle.errs_mut().cursor();
1576 while cursor.key_valid(&storage) {
1577 let mut copies = Diff::ZERO;
1578 cursor.map_times(&storage, |time, diff| {
1579 if time.less_equal(&self.peek.timestamp) {
1580 copies += diff;
1581 }
1582 });
1583 if copies.is_negative() {
1584 let error = cursor.key(&storage);
1585 error!(
1586 target = %self.peek.target.id(), diff = %copies, %error,
1587 "index peek encountered negative multiplicities in error trace",
1588 );
1589 return PeekStatus::Ready(PeekResponse::Error(format!(
1590 "Invalid data in source errors, \
1591 saw retractions ({}) for row that does not exist: {}",
1592 -copies, error,
1593 )));
1594 }
1595 if copies.is_positive() {
1596 return PeekStatus::Ready(PeekResponse::Error(cursor.key(&storage).to_string()));
1597 }
1598 cursor.step_key(&storage);
1599 }
1600
1601 metrics
1602 .error_scan_seconds
1603 .observe(error_scan_start.elapsed().as_secs_f64());
1604
1605 Self::collect_ok_finished_data(
1606 &self.peek,
1607 self.trace_bundle.oks_mut(),
1608 max_result_size,
1609 peek_stash_eligible,
1610 peek_stash_threshold_bytes,
1611 metrics,
1612 )
1613 }
1614
1615 fn collect_ok_finished_data<Tr>(
1617 peek: &Peek,
1618 oks_handle: &mut Tr,
1619 max_result_size: u64,
1620 peek_stash_eligible: bool,
1621 peek_stash_threshold_bytes: usize,
1622 metrics: &IndexPeekMetrics<'_>,
1623 ) -> PeekStatus
1624 where
1625 for<'a> Tr: TraceReader<
1626 Key<'a>: ExtendDatums + Eq,
1627 KeyContainer: BatchContainer<Owned = Row>,
1628 Val<'a>: ExtendDatums,
1629 TimeGat<'a>: PartialOrder<Timestamp>,
1630 DiffGat<'a> = &'a Diff,
1631 >,
1632 {
1633 let max_result_size = usize::cast_from(max_result_size);
1634 let count_byte_size = size_of::<NonZeroUsize>();
1635
1636 let cursor_setup_start = Instant::now();
1638
1639 let mut peek_iterator = peek_result_iterator::PeekResultIterator::new(
1642 peek.target.id().clone(),
1643 peek.map_filter_project.clone(),
1644 peek.timestamp,
1645 peek.literal_constraints.clone().as_deref_mut(),
1646 oks_handle,
1647 );
1648
1649 metrics
1650 .cursor_setup_seconds
1651 .observe(cursor_setup_start.elapsed().as_secs_f64());
1652
1653 let mut results = Vec::new();
1655 let mut total_size: usize = 0;
1656
1657 let max_results = peek.finishing.num_rows_needed();
1663
1664 let comparator = RowComparator::new(peek.finishing.order_by.as_slice());
1665
1666 let row_iteration_start = Instant::now();
1668 let mut sort_time_accum = Duration::ZERO;
1669
1670 while let Some(row) = peek_iterator.next() {
1671 let row: (Row, _) = match row {
1672 Ok(row) => row,
1673 Err(err) => return PeekStatus::Ready(PeekResponse::Error(err)),
1674 };
1675 let (row, copies) = row;
1676 let copies: NonZeroUsize = NonZeroUsize::try_from(copies).expect("fits into usize");
1677
1678 total_size = total_size
1679 .saturating_add(row.byte_len())
1680 .saturating_add(count_byte_size);
1681 if peek_stash_eligible && total_size > peek_stash_threshold_bytes {
1682 return PeekStatus::UsePeekStash;
1683 }
1684 if total_size > max_result_size {
1685 return PeekStatus::Ready(PeekResponse::Error(format!(
1686 "result exceeds max size of {}",
1687 ByteSize::b(u64::cast_from(max_result_size))
1688 )));
1689 }
1690
1691 results.push((row, copies));
1692
1693 if let Some(max_results) = max_results {
1696 if results.len() >= 2 * max_results {
1700 if peek.finishing.order_by.is_empty() {
1701 results.truncate(max_results);
1702 metrics
1703 .row_iteration_seconds
1704 .observe(row_iteration_start.elapsed().as_secs_f64());
1705 metrics
1706 .result_sort_seconds
1707 .observe(sort_time_accum.as_secs_f64());
1708 let row_collection_start = Instant::now();
1709 let collection = RowCollection::new(results, &peek.finishing.order_by);
1710 metrics
1711 .row_collection_seconds
1712 .observe(row_collection_start.elapsed().as_secs_f64());
1713 return PeekStatus::Ready(PeekResponse::Rows(vec![collection]));
1714 } else {
1715 let sort_start = Instant::now();
1724 results.sort_by(|left, right| {
1725 comparator.compare_rows(&left.0, &right.0, || left.0.cmp(&right.0))
1726 });
1727 sort_time_accum += sort_start.elapsed();
1728 let dropped = results.drain(max_results..);
1729 let dropped_size =
1730 dropped
1731 .into_iter()
1732 .fold(0, |acc: usize, (row, _count): (Row, _)| {
1733 acc.saturating_add(
1734 row.byte_len().saturating_add(count_byte_size),
1735 )
1736 });
1737 total_size = total_size.saturating_sub(dropped_size);
1738 }
1739 }
1740 }
1741 }
1742
1743 metrics
1744 .row_iteration_seconds
1745 .observe(row_iteration_start.elapsed().as_secs_f64());
1746 metrics
1747 .result_sort_seconds
1748 .observe(sort_time_accum.as_secs_f64());
1749
1750 let row_collection_start = Instant::now();
1751 let collection = RowCollection::new(results, &peek.finishing.order_by);
1752 metrics
1753 .row_collection_seconds
1754 .observe(row_collection_start.elapsed().as_secs_f64());
1755 PeekStatus::Ready(PeekResponse::Rows(vec![collection]))
1756 }
1757}
1758
1759enum PeekStatus {
1762 NotReady,
1765 UsePeekStash,
1768 Ready(PeekResponse),
1770}
1771
1772#[derive(Debug)]
1774struct ReportedFrontiers {
1775 write_frontier: ReportedFrontier,
1777 input_frontier: ReportedFrontier,
1779 output_frontier: ReportedFrontier,
1781}
1782
1783impl ReportedFrontiers {
1784 fn new() -> Self {
1786 Self {
1787 write_frontier: ReportedFrontier::new(),
1788 input_frontier: ReportedFrontier::new(),
1789 output_frontier: ReportedFrontier::new(),
1790 }
1791 }
1792}
1793
1794#[derive(Clone, Debug)]
1796pub enum ReportedFrontier {
1797 Reported(Antichain<Timestamp>),
1799 NotReported {
1801 lower: Antichain<Timestamp>,
1803 },
1804}
1805
1806impl ReportedFrontier {
1807 pub fn new() -> Self {
1809 let lower = Antichain::from_elem(timely::progress::Timestamp::minimum());
1810 Self::NotReported { lower }
1811 }
1812
1813 pub fn is_empty(&self) -> bool {
1815 match self {
1816 Self::Reported(frontier) => frontier.is_empty(),
1817 Self::NotReported { .. } => false,
1818 }
1819 }
1820
1821 fn allows_reporting(&self, other: &Antichain<Timestamp>) -> bool {
1827 match self {
1828 Self::Reported(frontier) => PartialOrder::less_than(frontier, other),
1829 Self::NotReported { lower } => PartialOrder::less_equal(lower, other),
1830 }
1831 }
1832}
1833
1834pub struct CollectionState {
1836 reported_frontiers: ReportedFrontiers,
1838 dataflow_index: Rc<usize>,
1844 pub is_subscribe_or_copy: bool,
1850 as_of: Antichain<Timestamp>,
1854
1855 pub sink_token: Option<SinkToken>,
1860 pub sink_write_frontier: Option<Rc<RefCell<Antichain<Timestamp>>>>,
1864 pub input_probes: BTreeMap<GlobalId, probe::Handle<Timestamp>>,
1866 pub compute_probe: Option<probe::Handle<Timestamp>>,
1871 logging: Option<CollectionLogging>,
1873 metrics: CollectionMetrics,
1875 read_only_tx: watch::Sender<bool>,
1887 pub read_only_rx: watch::Receiver<bool>,
1889}
1890
1891impl CollectionState {
1892 fn new(
1893 dataflow_index: Rc<usize>,
1894 is_subscribe_or_copy: bool,
1895 as_of: Antichain<Timestamp>,
1896 metrics: CollectionMetrics,
1897 ) -> Self {
1898 let (read_only_tx, read_only_rx) = watch::channel(true);
1901
1902 Self {
1903 reported_frontiers: ReportedFrontiers::new(),
1904 dataflow_index,
1905 is_subscribe_or_copy,
1906 as_of,
1907 sink_token: None,
1908 sink_write_frontier: None,
1909 input_probes: Default::default(),
1910 compute_probe: None,
1911 logging: None,
1912 metrics,
1913 read_only_tx,
1914 read_only_rx,
1915 }
1916 }
1917
1918 fn reported_frontiers(&self) -> &ReportedFrontiers {
1920 &self.reported_frontiers
1921 }
1922
1923 pub fn reset_reported_frontiers(&mut self, frontier: ReportedFrontier) {
1925 self.reported_frontiers.write_frontier = frontier.clone();
1926 self.reported_frontiers.input_frontier = frontier.clone();
1927 self.reported_frontiers.output_frontier = frontier;
1928 }
1929
1930 fn set_reported_write_frontier(&mut self, frontier: ReportedFrontier) {
1932 if let Some(logging) = &mut self.logging {
1933 let time = match &frontier {
1934 ReportedFrontier::Reported(frontier) => frontier.get(0).copied(),
1935 ReportedFrontier::NotReported { .. } => Some(Timestamp::MIN),
1936 };
1937 logging.set_frontier(time);
1938 }
1939
1940 self.reported_frontiers.write_frontier = frontier;
1941 }
1942
1943 fn set_reported_input_frontier(&mut self, frontier: ReportedFrontier) {
1945 if let Some(logging) = &mut self.logging {
1947 for (id, probe) in &self.input_probes {
1948 let new_time = probe.with_frontier(|frontier| frontier.as_option().copied());
1949 logging.set_import_frontier(*id, new_time);
1950 }
1951 }
1952
1953 self.reported_frontiers.input_frontier = frontier;
1954 }
1955
1956 fn set_reported_output_frontier(&mut self, frontier: ReportedFrontier) {
1958 let already_hydrated = self.hydrated();
1959
1960 self.reported_frontiers.output_frontier = frontier;
1961
1962 if !already_hydrated && self.hydrated() {
1963 if let Some(logging) = &mut self.logging {
1964 logging.set_hydrated();
1965 }
1966 self.metrics.record_collection_hydrated();
1967 }
1968 }
1969
1970 fn hydrated(&self) -> bool {
1972 match &self.reported_frontiers.output_frontier {
1973 ReportedFrontier::Reported(frontier) => PartialOrder::less_than(&self.as_of, frontier),
1974 ReportedFrontier::NotReported { .. } => false,
1975 }
1976 }
1977
1978 fn allow_writes(&self) {
1980 info!(
1981 dataflow_index = *self.dataflow_index,
1982 export = ?self.logging.as_ref().map(|l| l.export_id()),
1983 "allowing writes for dataflow",
1984 );
1985 let _ = self.read_only_tx.send(false);
1986 }
1987}