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::cursor::BatchCursor;
21use differential_dataflow::trace::implementations::BatchContainer;
22use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
23use mz_compute_client::logging::LoggingConfig;
24use mz_compute_client::protocol::command::{
25 ComputeCommand, ComputeParameters, InstanceConfig, Peek, PeekTarget,
26};
27use mz_compute_client::protocol::history::ComputeCommandHistory;
28use mz_compute_client::protocol::response::{
29 ComputeResponse, CopyToResponse, FrontiersResponse, PeekResponse, SubscribeResponse,
30};
31use mz_compute_types::dataflows::DataflowDescription;
32use mz_compute_types::dyncfgs::{
33 ENABLE_PEEK_RESPONSE_STASH, PEEK_RESPONSE_STASH_BATCH_MAX_RUNS,
34 PEEK_RESPONSE_STASH_THRESHOLD_BYTES, PEEK_STASH_BATCH_SIZE, PEEK_STASH_NUM_BATCHES,
35};
36use mz_compute_types::plan::render_plan::RenderPlan;
37use mz_dyncfg::ConfigSet;
38use mz_expr::row::RowCollection;
39use mz_expr::{RowComparator, SafeMfpPlan};
40use mz_ore::cast::{CastFrom, CastLossy};
41use mz_ore::collections::CollectionExt;
42use mz_ore::metrics::{MetricsRegistry, UIntGauge};
43use mz_ore::now::EpochMillis;
44use mz_ore::soft_panic_or_log;
45use mz_ore::task::AbortOnDropHandle;
46use mz_ore::tracing::{OpenTelemetryContext, TracingHandle};
47use mz_persist_client::Diagnostics;
48use mz_persist_client::cache::PersistClientCache;
49use mz_persist_client::cfg::USE_CRITICAL_SINCE_SNAPSHOT;
50use mz_persist_client::read::ReadHandle;
51use mz_persist_types::PersistLocation;
52use mz_persist_types::codec_impls::UnitSchema;
53use mz_repr::fixed_length::ExtendDatums;
54use mz_repr::{DatumVec, Diff, GlobalId, Row, RowArena, Timestamp};
55use mz_storage_operators::stats::StatsCursor;
56use mz_storage_types::StorageDiff;
57use mz_storage_types::controller::CollectionMetadata;
58use mz_storage_types::dyncfgs::ORE_OVERFLOWING_BEHAVIOR;
59use mz_storage_types::sources::SourceData;
60use mz_storage_types::time_dependence::TimeDependence;
61use mz_txn_wal::operator::TxnsContext;
62use mz_txn_wal::txn_cache::TxnsCache;
63use timely::dataflow::operators::probe;
64use timely::order::PartialOrder;
65use timely::progress::frontier::Antichain;
66use timely::worker::Worker as TimelyWorker;
67use tokio::sync::{oneshot, watch};
68use tracing::{Level, debug, error, info, span, trace, warn};
69use uuid::Uuid;
70
71use crate::arrangement::manager::{TraceBundle, TraceManager};
72use crate::logging;
73use crate::logging::compute::{CollectionLogging, ComputeEvent, PeekEvent};
74use crate::logging::initialize::LoggingTraces;
75use crate::metrics::{CollectionMetrics, WorkerMetrics};
76use crate::render::{LinearJoinSpec, StartSignal};
77use crate::server::{ComputeInstanceContext, ResponseSender};
78
79mod peek_result_iterator;
80mod peek_stash;
81
82pub struct ComputeState {
87 pub collections: BTreeMap<GlobalId, CollectionState>,
96 pub traces: TraceManager,
98 pub subscribe_response_buffer: Rc<RefCell<Vec<(GlobalId, SubscribeResponse)>>>,
103 pub copy_to_response_buffer: Rc<RefCell<Vec<(GlobalId, CopyToResponse)>>>,
108 pub pending_peeks: BTreeMap<Uuid, PendingPeek>,
110 pub peek_stash_persist_location: Option<PersistLocation>,
112 pub compute_logger: Option<logging::compute::Logger>,
114 pub persist_clients: Arc<PersistClientCache>,
117 pub txns_ctx: TxnsContext,
119 pub command_history: ComputeCommandHistory<UIntGauge>,
121 max_result_size: u64,
123 pub linear_join_spec: LinearJoinSpec,
125 pub metrics: WorkerMetrics,
127 tracing_handle: Arc<TracingHandle>,
129 pub context: ComputeInstanceContext,
131 pub worker_config: Rc<ConfigSet>,
145
146 pub metrics_registry: MetricsRegistry,
148
149 pub workers_per_process: usize,
151
152 suspended_collections: BTreeMap<GlobalId, Rc<dyn Any>>,
158
159 pub server_maintenance_interval: Duration,
162
163 pub init_system_time: EpochMillis,
167
168 pub replica_expiration: Antichain<Timestamp>,
172
173 pub storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
175}
176
177impl ComputeState {
178 pub fn new(
180 persist_clients: Arc<PersistClientCache>,
181 txns_ctx: TxnsContext,
182 metrics: WorkerMetrics,
183 tracing_handle: Arc<TracingHandle>,
184 context: ComputeInstanceContext,
185 metrics_registry: MetricsRegistry,
186 workers_per_process: usize,
187 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
188 ) -> Self {
189 let traces = TraceManager::new(metrics.clone());
190 let command_history = ComputeCommandHistory::new(metrics.for_history());
191
192 Self {
193 collections: Default::default(),
194 traces,
195 subscribe_response_buffer: Default::default(),
196 copy_to_response_buffer: Default::default(),
197 pending_peeks: Default::default(),
198 peek_stash_persist_location: None,
199 compute_logger: None,
200 persist_clients,
201 txns_ctx,
202 command_history,
203 max_result_size: u64::MAX,
204 linear_join_spec: Default::default(),
205 metrics,
206 tracing_handle,
207 context,
208 worker_config: mz_dyncfgs::all_dyncfgs().into(),
209 metrics_registry,
210 workers_per_process,
211 suspended_collections: Default::default(),
212 server_maintenance_interval: Duration::ZERO,
213 init_system_time: mz_ore::now::SYSTEM_TIME(),
214 replica_expiration: Antichain::default(),
215 storage_log_reader,
216 }
217 }
218
219 pub fn expect_collection_mut(&mut self, id: GlobalId) -> &mut CollectionState {
223 self.collections
224 .get_mut(&id)
225 .expect("collection must exist")
226 }
227
228 pub fn input_probe_for(
234 &mut self,
235 input_id: GlobalId,
236 collection_ids: impl Iterator<Item = GlobalId>,
237 ) -> probe::Handle<Timestamp> {
238 let probe = probe::Handle::default();
239 for id in collection_ids {
240 if let Some(collection) = self.collections.get_mut(&id) {
241 collection.input_probes.insert(input_id, probe.clone());
242 }
243 }
244 probe
245 }
246
247 fn apply_worker_config(&mut self) {
249 use mz_compute_types::dyncfgs::*;
250
251 let config = &self.worker_config;
252
253 self.linear_join_spec = LinearJoinSpec::from_config(config);
254
255 if ENABLE_LGALLOC.get(config) {
256 if let Some(path) = &self.context.scratch_directory {
257 let clear_bytes = LGALLOC_SLOW_CLEAR_BYTES.get(config);
258 let eager_return = ENABLE_LGALLOC_EAGER_RECLAMATION.get(config);
259 let file_growth_dampener = LGALLOC_FILE_GROWTH_DAMPENER.get(config);
260 let interval = LGALLOC_BACKGROUND_INTERVAL.get(config);
261 let local_buffer_bytes = LGALLOC_LOCAL_BUFFER_BYTES.get(config);
262 info!(
263 ?path,
264 backgrund_interval=?interval,
265 clear_bytes,
266 eager_return,
267 file_growth_dampener,
268 local_buffer_bytes,
269 "enabling lgalloc"
270 );
271 let background_worker_config = lgalloc::BackgroundWorkerConfig {
272 interval,
273 clear_bytes,
274 };
275 lgalloc::lgalloc_set_config(
276 lgalloc::LgAlloc::new()
277 .enable()
278 .with_path(path.clone())
279 .with_background_config(background_worker_config)
280 .eager_return(eager_return)
281 .file_growth_dampener(file_growth_dampener)
282 .local_buffer_bytes(local_buffer_bytes),
283 );
284 } else {
285 debug!("not enabling lgalloc, scratch directory not specified");
286 }
287 } else {
288 info!("disabling lgalloc");
289 lgalloc::lgalloc_set_config(lgalloc::LgAlloc::new().disable());
290 }
291
292 #[cfg(unix)]
298 if let Some(path) = &self.context.scratch_directory {
299 mz_ore::pager::set_scratch_dir(path.clone());
300 mz_ore::pager::set_backend(mz_ore::pager::Backend::File);
301 } else {
302 mz_ore::pager::set_backend(mz_ore::pager::Backend::Swap);
303 }
304
305 crate::memory_limiter::apply_limiter_config(config);
306
307 mz_ore::region::ENABLE_LGALLOC_REGION.store(
308 ENABLE_COLUMNATION_LGALLOC.get(config),
309 std::sync::atomic::Ordering::Relaxed,
310 );
311
312 {
327 use mz_ore::pager::Backend;
328 use mz_timely_util::column_pager::{Codec, apply_tiered_config};
329
330 let enabled = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
331 let codec = COLUMN_PAGED_BATCHER_LZ4.get(config).then_some(Codec::Lz4);
332 let swap_pageout = COLUMN_PAGED_BATCHER_SWAP_PAGEOUT.get(config);
333
334 const MIB: usize = 1024 * 1024;
339 const DEFAULT_MEM_LIMIT: usize = 4 * 1024 * MIB;
340 let mem_limit = crate::memory_limiter::get_memory_limit().unwrap_or(DEFAULT_MEM_LIMIT);
341 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config).max(0.0);
342 let total = usize::cast_lossy(f64::cast_lossy(mem_limit) * fraction).max(128 * MIB);
343
344 let backend = if self.context.scratch_directory.is_some() {
345 Backend::File
346 } else {
347 Backend::Swap
348 };
349
350 debug!(
351 enabled,
352 ?backend,
353 ?codec,
354 swap_pageout,
355 fraction,
356 mem_limit,
357 budget_bytes = total,
358 "column-paged batcher: applying tiered config",
359 );
360 apply_tiered_config(enabled, total, backend, codec, swap_pageout);
361 }
362
363 {
380 use mz_timely_util::pool_config::{PoolPagerConfig, apply_pool_config};
381
382 let compute_spill = ENABLE_COLUMN_PAGED_BATCHER_SPILL.get(config);
383 let storage_spill = mz_storage_types::dyncfgs::ENABLE_UPSERT_PAGED_SPILL.get(config);
384 if !(compute_spill || storage_spill) {
385 debug!("chunk spill: gates off, leaving the buffer pool uninstalled");
386 } else {
387 let spill_threads = COLUMN_PAGED_BATCHER_SPILL_WORKER_COUNT.get(config);
388 let eager_backing = COLUMN_PAGED_BATCHER_EAGER_BACKING.get(config);
389
390 const MIB: usize = 1024 * 1024;
397 const DEFAULT_RAM: usize = 4 * 1024 * MIB;
398 let ram = mz_ore::memory::physical_memory_bytes().unwrap_or(DEFAULT_RAM);
399 let of_ram =
400 |fraction: f64| usize::cast_lossy(f64::cast_lossy(ram) * fraction.max(0.0));
401 let fraction = COLUMN_PAGED_BATCHER_BUDGET_FRACTION.get(config);
402 let total = of_ram(fraction).max(128 * MIB);
403 let rss_target = of_ram(COLUMN_PAGED_BATCHER_POOL_RSS_TARGET_FRACTION.get(config));
408
409 let applied = apply_pool_config(PoolPagerConfig {
410 budget_bytes: total,
411 spill_threads,
412 eager_backing,
413 rss_target_bytes: rss_target,
414 });
415 if applied {
416 info!(
417 compute_spill,
418 storage_spill,
419 fraction,
420 ram,
421 budget_bytes = total,
422 spill_threads,
423 eager_backing,
424 rss_target_bytes = rss_target,
425 "chunk spill: applying buffer-pool config",
426 );
427 } else {
428 warn!("chunk spill: buffer pool unavailable; chunks stay resident");
429 }
430 }
431 }
432
433 self.server_maintenance_interval = COMPUTE_SERVER_MAINTENANCE_INTERVAL.get(config);
436
437 let overflowing_behavior = ORE_OVERFLOWING_BEHAVIOR.get(config);
438 match overflowing_behavior.parse() {
439 Ok(behavior) => mz_ore::overflowing::set_behavior(behavior),
440 Err(err) => {
441 error!(
442 err,
443 overflowing_behavior, "Invalid value for ore_overflowing_behavior"
444 );
445 }
446 }
447 }
448
449 pub fn apply_expiration_offset(&mut self, offset: Duration) {
455 if self.replica_expiration.is_empty() {
456 let offset: EpochMillis = offset
457 .as_millis()
458 .try_into()
459 .expect("duration must fit within u64");
460 let replica_expiration_millis = self.init_system_time + offset;
461 let replica_expiration = Timestamp::from(replica_expiration_millis);
462
463 info!(
464 offset = %offset,
465 replica_expiration_millis = %replica_expiration_millis,
466 replica_expiration_utc = %mz_ore::now::to_datetime(replica_expiration_millis),
467 "setting replica expiration",
468 );
469 self.replica_expiration = Antichain::from_elem(replica_expiration);
470
471 self.metrics
473 .replica_expiration_timestamp_seconds
474 .set(replica_expiration.into());
475 }
476 }
477
478 pub fn dataflow_max_inflight_bytes(&self) -> Option<usize> {
481 use mz_compute_types::dyncfgs::{
482 DATAFLOW_MAX_INFLIGHT_BYTES, DATAFLOW_MAX_INFLIGHT_BYTES_CC,
483 };
484
485 if self.persist_clients.cfg.is_cc_active {
486 DATAFLOW_MAX_INFLIGHT_BYTES_CC.get(&self.worker_config)
487 } else {
488 DATAFLOW_MAX_INFLIGHT_BYTES.get(&self.worker_config)
489 }
490 }
491}
492
493pub(crate) struct ActiveComputeState<'a> {
495 pub timely_worker: &'a mut TimelyWorker,
497 pub compute_state: &'a mut ComputeState,
499 pub response_tx: &'a mut ResponseSender,
501}
502
503pub struct SinkToken(#[allow(dead_code)] Box<dyn Any>);
505
506impl SinkToken {
507 pub fn new(t: Box<dyn Any>) -> Self {
509 Self(t)
510 }
511}
512
513impl<'a> ActiveComputeState<'a> {
514 #[mz_ore::instrument(level = "debug")]
516 pub fn handle_compute_command(&mut self, cmd: ComputeCommand) {
517 use ComputeCommand::*;
518
519 self.compute_state.command_history.push(cmd.clone());
520
521 let timer = self
523 .compute_state
524 .metrics
525 .handle_command_duration_seconds
526 .for_command(&cmd)
527 .start_timer();
528
529 match cmd {
530 Hello { .. } => panic!("Hello must be captured before"),
531 CreateInstance(instance_config) => self.handle_create_instance(*instance_config),
532 InitializationComplete => (),
533 UpdateConfiguration(params) => self.handle_update_configuration(*params),
534 CreateDataflow(dataflow) => self.handle_create_dataflow(*dataflow),
535 Schedule(id) => self.handle_schedule(id),
536 AllowCompaction { id, frontier } => self.handle_allow_compaction(id, frontier),
537 Peek(peek) => {
538 peek.otel_ctx.attach_as_parent();
539 self.handle_peek(*peek)
540 }
541 CancelPeek { uuid } => self.handle_cancel_peek(uuid),
542 AllowWrites(id) => {
543 self.handle_allow_writes(id);
544 }
545 }
546
547 timer.observe_duration();
548 }
549
550 fn handle_create_instance(&mut self, config: InstanceConfig) {
551 config
556 .initial_config
557 .apply(&self.compute_state.worker_config);
558
559 self.compute_state.apply_worker_config();
561
562 mz_row_spine::DICTIONARY_COMPRESSION.store(
568 config.arrangement_dictionary_compression,
569 std::sync::atomic::Ordering::Relaxed,
570 );
571
572 if let Some(offset) = config.expiration_offset {
573 self.compute_state.apply_expiration_offset(offset);
574 }
575
576 let storage_log_reader = self.compute_state.storage_log_reader.take();
577 self.initialize_logging(config.logging, storage_log_reader);
578
579 self.compute_state.peek_stash_persist_location = Some(config.peek_stash_persist_location);
580 }
581
582 fn handle_update_configuration(&mut self, params: ComputeParameters) {
583 debug!("Applying configuration update: {params:?}");
584
585 let ComputeParameters {
586 workload_class,
587 max_result_size,
588 tracing,
589 grpc_client: _grpc_client,
590 dyncfg_updates,
591 } = params;
592
593 if let Some(v) = workload_class {
594 self.compute_state.metrics.set_workload_class(v);
595 }
596 if let Some(v) = max_result_size {
597 self.compute_state.max_result_size = v;
598 }
599
600 tracing.apply(self.compute_state.tracing_handle.as_ref());
601
602 dyncfg_updates.apply(&self.compute_state.worker_config);
603 self.compute_state
604 .persist_clients
605 .cfg()
606 .apply_from(&dyncfg_updates);
607
608 mz_metrics::update_dyncfg(&dyncfg_updates);
612
613 self.compute_state.apply_worker_config();
614 }
615
616 fn handle_create_dataflow(
617 &mut self,
618 dataflow: DataflowDescription<RenderPlan, CollectionMetadata>,
619 ) {
620 let dataflow_index = Rc::new(self.timely_worker.next_dataflow_index());
621 let as_of = dataflow.as_of.clone().unwrap();
622
623 let dataflow_expiration = dataflow
624 .time_dependence
625 .as_ref()
626 .map(|time_dependence| {
627 self.determine_dataflow_expiration(time_dependence, &dataflow.until)
628 })
629 .unwrap_or_default();
630
631 let until = dataflow.until.meet(&dataflow_expiration);
633
634 if dataflow.is_transient() {
635 debug!(
636 name = %dataflow.debug_name,
637 import_ids = %dataflow.display_import_ids(),
638 export_ids = %dataflow.display_export_ids(),
639 as_of = ?as_of.elements(),
640 time_dependence = ?dataflow.time_dependence,
641 expiration = ?dataflow_expiration.elements(),
642 expiration_datetime = ?dataflow_expiration
643 .as_option()
644 .map(|t| mz_ore::now::to_datetime(t.into())),
645 plan_until = ?dataflow.until.elements(),
646 until = ?until.elements(),
647 "creating dataflow",
648 );
649 } else {
650 info!(
651 name = %dataflow.debug_name,
652 import_ids = %dataflow.display_import_ids(),
653 export_ids = %dataflow.display_export_ids(),
654 as_of = ?as_of.elements(),
655 time_dependence = ?dataflow.time_dependence,
656 expiration = ?dataflow_expiration.elements(),
657 expiration_datetime = ?dataflow_expiration
658 .as_option()
659 .map(|t| mz_ore::now::to_datetime(t.into())),
660 plan_until = ?dataflow.until.elements(),
661 until = ?until.elements(),
662 "creating dataflow",
663 );
664 };
665
666 let subscribe_copy_ids: BTreeSet<_> = dataflow
667 .subscribe_ids()
668 .chain(dataflow.copy_to_ids())
669 .collect();
670
671 let starts_immediately = dataflow.import_ids().next().is_none();
680
681 for object_id in dataflow.export_ids() {
683 let is_subscribe_or_copy = subscribe_copy_ids.contains(&object_id);
684 let metrics = self.compute_state.metrics.for_collection(object_id);
685 let mut collection = CollectionState::new(
686 Rc::clone(&dataflow_index),
687 is_subscribe_or_copy,
688 as_of.clone(),
689 metrics,
690 );
691
692 if let Some(logger) = self.compute_state.compute_logger.clone() {
693 let logging = CollectionLogging::new(
694 object_id,
695 logger,
696 *dataflow_index,
697 dataflow.import_ids(),
698 );
699 if starts_immediately {
700 logging.set_hydration_start();
701 }
702 collection.logging = Some(logging);
703 }
704
705 collection.reset_reported_frontiers(ReportedFrontier::NotReported {
706 lower: as_of.clone(),
707 });
708
709 let existing = self.compute_state.collections.insert(object_id, collection);
710 if existing.is_some() {
711 error!(
712 id = ?object_id,
713 "existing collection for newly created dataflow",
714 );
715 }
716 }
717
718 let (start_signal, suspension_token) = StartSignal::new();
719 for id in dataflow.export_ids() {
720 self.compute_state
721 .suspended_collections
722 .insert(id, Rc::clone(&suspension_token));
723 }
724
725 crate::render::build_compute_dataflow(
726 self.timely_worker,
727 self.compute_state,
728 dataflow,
729 start_signal,
730 until,
731 dataflow_expiration,
732 );
733 }
734
735 fn handle_schedule(&mut self, id: GlobalId) {
736 let suspension_token = self.compute_state.suspended_collections.remove(&id);
742 drop(suspension_token);
743
744 if let Some(collection) = self.compute_state.collections.get(&id) {
745 if let Some(logging) = &collection.logging {
746 logging.set_hydration_start();
747 }
748 }
749 }
750
751 fn handle_allow_compaction(&mut self, id: GlobalId, frontier: Antichain<Timestamp>) {
752 if frontier.is_empty() {
753 self.drop_collection(id);
755 } else {
756 self.compute_state
757 .traces
758 .allow_compaction(id, frontier.borrow());
759 }
760 }
761
762 #[mz_ore::instrument(level = "debug")]
763 fn handle_peek(&mut self, peek: Peek) {
764 let pending = match &peek.target {
765 PeekTarget::Index { id } => {
766 let trace_bundle = self.compute_state.traces.get(id).unwrap().clone();
768 PendingPeek::index(peek, trace_bundle)
769 }
770 PeekTarget::Persist { metadata, .. } => {
771 let metadata = metadata.clone();
772 PendingPeek::persist(
773 peek,
774 Arc::clone(&self.compute_state.persist_clients),
775 metadata,
776 usize::cast_from(self.compute_state.max_result_size),
777 self.timely_worker,
778 )
779 }
780 };
781
782 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
784 logger.log(&pending.as_log_event(true));
785 }
786
787 self.process_peek(&mut Antichain::new(), pending);
788 }
789
790 fn handle_cancel_peek(&mut self, uuid: Uuid) {
791 if let Some(peek) = self.compute_state.pending_peeks.remove(&uuid) {
792 self.send_peek_response(peek, PeekResponse::Canceled);
793 }
794 }
795
796 fn handle_allow_writes(&mut self, id: GlobalId) {
797 self.compute_state.persist_clients.cfg().enable_compaction();
801
802 if let Some(collection) = self.compute_state.collections.get_mut(&id) {
803 collection.allow_writes();
804 } else {
805 soft_panic_or_log!("allow writes for unknown collection {id}");
806 }
807 }
808
809 fn drop_collection(&mut self, id: GlobalId) {
811 let collection = self
812 .compute_state
813 .collections
814 .remove(&id)
815 .expect("dropped untracked collection");
816
817 self.compute_state.traces.remove(&id);
819 self.compute_state.suspended_collections.remove(&id);
821
822 if let Ok(index) = Rc::try_unwrap(collection.dataflow_index) {
824 self.timely_worker.drop_dataflow(index);
825 }
826
827 if !collection.is_subscribe_or_copy {
832 let reported = collection.reported_frontiers;
833 let write_frontier = (!reported.write_frontier.is_empty()).then(Antichain::new);
834 let input_frontier = (!reported.input_frontier.is_empty()).then(Antichain::new);
835 let output_frontier = (!reported.output_frontier.is_empty()).then(Antichain::new);
836
837 let frontiers = FrontiersResponse {
838 write_frontier,
839 input_frontier,
840 output_frontier,
841 };
842 if frontiers.has_updates() {
843 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
844 }
845 }
846 }
847
848 pub fn initialize_logging(
850 &mut self,
851 config: LoggingConfig,
852 storage_log_reader: Option<crate::server::StorageTimelyLogReader>,
853 ) {
854 if self.compute_state.compute_logger.is_some() {
855 panic!("dataflow server has already initialized logging");
856 }
857
858 let LoggingTraces {
859 traces,
860 dataflow_index,
861 compute_logger: logger,
862 } = logging::initialize(
863 self.timely_worker,
864 &config,
865 self.compute_state.metrics_registry.clone(),
866 Rc::clone(&self.compute_state.worker_config),
867 self.compute_state.workers_per_process,
868 storage_log_reader,
869 );
870
871 let dataflow_index = Rc::new(dataflow_index);
872 let mut log_index_ids = config.index_logs;
873 for (log, trace) in traces {
874 let id = log_index_ids
876 .remove(&log)
877 .expect("`logging::initialize` does not invent logs");
878 self.compute_state.traces.set(id, trace);
879
880 let is_subscribe_or_copy = false;
882 let as_of = Antichain::from_elem(Timestamp::MIN);
883 let metrics = self.compute_state.metrics.for_collection(id);
884 let mut collection = CollectionState::new(
885 Rc::clone(&dataflow_index),
886 is_subscribe_or_copy,
887 as_of,
888 metrics,
889 );
890
891 let logging =
892 CollectionLogging::new(id, logger.clone(), *dataflow_index, std::iter::empty());
893 logging.set_hydration_start();
898 collection.logging = Some(logging);
899
900 let existing = self.compute_state.collections.insert(id, collection);
901 if existing.is_some() {
902 error!(
903 id = ?id,
904 "existing collection for newly initialized logging export",
905 );
906 }
907 }
908
909 assert!(
911 log_index_ids.is_empty(),
912 "failed to create requested logging indexes: {log_index_ids:?}",
913 );
914
915 self.compute_state.compute_logger = Some(logger);
916 }
917
918 pub fn report_frontiers(&mut self) {
920 let mut responses = Vec::new();
921
922 let mut new_frontier = Antichain::new();
924
925 for (&id, collection) in self.compute_state.collections.iter_mut() {
926 if collection.is_subscribe_or_copy {
929 continue;
930 }
931
932 let reported = collection.reported_frontiers();
933
934 new_frontier.clear();
936 if let Some(traces) = self.compute_state.traces.get_mut(&id) {
937 assert!(
938 collection.sink_write_frontier.is_none(),
939 "collection {id} has multiple frontiers"
940 );
941 traces.oks_mut().read_upper(&mut new_frontier);
942 } else if let Some(frontier) = &collection.sink_write_frontier {
943 new_frontier.clone_from(&frontier.borrow());
944 } else {
945 error!(id = ?id, "collection without write frontier");
946 continue;
947 }
948 let new_write_frontier = reported
949 .write_frontier
950 .allows_reporting(&new_frontier)
951 .then(|| new_frontier.clone());
952
953 if let Some(probe) = &collection.compute_probe {
966 if *collection.read_only_rx.borrow() {
967 new_frontier.clear();
968 }
969 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
970 }
971 let new_output_frontier = reported
972 .output_frontier
973 .allows_reporting(&new_frontier)
974 .then(|| new_frontier.clone());
975
976 new_frontier.clear();
978 for probe in collection.input_probes.values() {
979 probe.with_frontier(|frontier| new_frontier.extend(frontier.iter().copied()));
980 }
981 let new_input_frontier = reported
982 .input_frontier
983 .allows_reporting(&new_frontier)
984 .then(|| new_frontier.clone());
985
986 if let Some(frontier) = &new_write_frontier {
987 collection
988 .set_reported_write_frontier(ReportedFrontier::Reported(frontier.clone()));
989 }
990 if let Some(frontier) = &new_input_frontier {
991 collection
992 .set_reported_input_frontier(ReportedFrontier::Reported(frontier.clone()));
993 }
994 if let Some(frontier) = &new_output_frontier {
995 collection
996 .set_reported_output_frontier(ReportedFrontier::Reported(frontier.clone()));
997 }
998
999 let response = FrontiersResponse {
1000 write_frontier: new_write_frontier,
1001 input_frontier: new_input_frontier,
1002 output_frontier: new_output_frontier,
1003 };
1004 if response.has_updates() {
1005 responses.push((id, response));
1006 }
1007 }
1008
1009 for (id, frontiers) in responses {
1010 self.send_compute_response(ComputeResponse::Frontiers(id, frontiers));
1011 }
1012 }
1013
1014 pub(crate) fn report_metrics(&self) {
1016 if let Some(expiration) = self.compute_state.replica_expiration.as_option() {
1017 let now = Duration::from_millis(mz_ore::now::SYSTEM_TIME()).as_secs_f64();
1018 let expiration = Duration::from_millis(<u64>::from(expiration)).as_secs_f64();
1019 let remaining = expiration - now;
1020 self.compute_state
1021 .metrics
1022 .replica_expiration_remaining_seconds
1023 .set(remaining)
1024 }
1025 }
1026
1027 fn process_peek(&mut self, upper: &mut Antichain<Timestamp>, mut peek: PendingPeek) {
1029 let response = match &mut peek {
1030 PendingPeek::Index(peek) => {
1031 let start = Instant::now();
1032
1033 let peek_stash_eligible = peek
1034 .peek
1035 .finishing
1036 .is_streamable(peek.peek.result_desc.arity());
1037
1038 let peek_stash_enabled = {
1039 let enabled = ENABLE_PEEK_RESPONSE_STASH.get(&self.compute_state.worker_config);
1040 let peek_persist_stash_available =
1041 self.compute_state.peek_stash_persist_location.is_some();
1042 if !peek_persist_stash_available && enabled {
1043 error!("missing peek_stash_persist_location but peek stash is enabled");
1044 }
1045 enabled && peek_persist_stash_available
1046 };
1047
1048 let peek_stash_threshold_bytes =
1049 PEEK_RESPONSE_STASH_THRESHOLD_BYTES.get(&self.compute_state.worker_config);
1050
1051 let metrics = IndexPeekMetrics {
1052 seek_fulfillment_seconds: &self
1053 .compute_state
1054 .metrics
1055 .index_peek_seek_fulfillment_seconds,
1056 frontier_check_seconds: &self
1057 .compute_state
1058 .metrics
1059 .index_peek_frontier_check_seconds,
1060 error_scan_seconds: &self.compute_state.metrics.index_peek_error_scan_seconds,
1061 cursor_setup_seconds: &self
1062 .compute_state
1063 .metrics
1064 .index_peek_cursor_setup_seconds,
1065 row_iteration_seconds: &self
1066 .compute_state
1067 .metrics
1068 .index_peek_row_iteration_seconds,
1069 row_iteration_rows: &self.compute_state.metrics.index_peek_row_iteration_rows,
1070 result_sort_seconds: &self.compute_state.metrics.index_peek_result_sort_seconds,
1071 result_sort_rows: &self.compute_state.metrics.index_peek_result_sort_rows,
1072 row_collection_seconds: &self
1073 .compute_state
1074 .metrics
1075 .index_peek_row_collection_seconds,
1076 };
1077
1078 let status = peek.seek_fulfillment(
1079 upper,
1080 self.compute_state.max_result_size,
1081 peek_stash_enabled && peek_stash_eligible,
1082 peek_stash_threshold_bytes,
1083 &metrics,
1084 );
1085
1086 self.compute_state
1087 .metrics
1088 .index_peek_total_seconds
1089 .observe(start.elapsed().as_secs_f64());
1090
1091 match status {
1092 PeekStatus::Ready(result) => Some(result),
1093 PeekStatus::NotReady => None,
1094 PeekStatus::UsePeekStash => {
1095 let _span =
1096 span!(parent: &peek.span, Level::DEBUG, "process_stash_peek").entered();
1097
1098 let peek_stash_batch_max_runs = PEEK_RESPONSE_STASH_BATCH_MAX_RUNS
1099 .get(&self.compute_state.worker_config);
1100
1101 let stash_task = peek_stash::StashingPeek::start_upload(
1102 Arc::clone(&self.compute_state.persist_clients),
1103 self.compute_state
1104 .peek_stash_persist_location
1105 .as_ref()
1106 .expect("verified above"),
1107 peek.peek.clone(),
1108 peek.trace_bundle.clone(),
1109 peek_stash_batch_max_runs,
1110 );
1111
1112 self.compute_state
1113 .pending_peeks
1114 .insert(peek.peek.uuid, PendingPeek::Stash(stash_task));
1115 return;
1116 }
1117 }
1118 }
1119 PendingPeek::Persist(peek) => peek.result.try_recv().ok().map(|(result, duration)| {
1120 self.compute_state
1121 .metrics
1122 .persist_peek_seconds
1123 .observe(duration.as_secs_f64());
1124 result
1125 }),
1126 PendingPeek::Stash(stashing_peek) => {
1127 let num_batches = PEEK_STASH_NUM_BATCHES.get(&self.compute_state.worker_config);
1128 let batch_size = PEEK_STASH_BATCH_SIZE.get(&self.compute_state.worker_config);
1129 stashing_peek.pump_rows(num_batches, batch_size);
1130
1131 if let Ok((response, duration)) = stashing_peek.result.try_recv() {
1132 self.compute_state
1133 .metrics
1134 .stashed_peek_seconds
1135 .observe(duration.as_secs_f64());
1136 trace!(?stashing_peek.peek, ?duration, "finished stashing peek response in persist");
1137
1138 Some(response)
1139 } else {
1140 None
1141 }
1142 }
1143 };
1144
1145 if let Some(response) = response {
1146 let _span = span!(parent: peek.span(), Level::DEBUG, "process_peek_response").entered();
1147 self.send_peek_response(peek, response)
1148 } else {
1149 let uuid = peek.peek().uuid;
1150 self.compute_state.pending_peeks.insert(uuid, peek);
1151 }
1152 }
1153
1154 pub fn process_peeks(&mut self) {
1156 let mut upper = Antichain::new();
1157 let pending_peeks = std::mem::take(&mut self.compute_state.pending_peeks);
1158 for (_uuid, peek) in pending_peeks {
1159 self.process_peek(&mut upper, peek);
1160 }
1161 }
1162
1163 #[mz_ore::instrument(level = "debug")]
1168 fn send_peek_response(&mut self, peek: PendingPeek, response: PeekResponse) {
1169 let log_event = peek.as_log_event(false);
1170 self.send_compute_response(ComputeResponse::PeekResponse(
1172 peek.peek().uuid,
1173 response,
1174 OpenTelemetryContext::obtain(),
1175 ));
1176
1177 if let Some(logger) = self.compute_state.compute_logger.as_mut() {
1179 logger.log(&log_event);
1180 }
1181 }
1182
1183 pub fn process_subscribes(&mut self) {
1185 let mut subscribe_responses = self.compute_state.subscribe_response_buffer.borrow_mut();
1186 for (sink_id, mut response) in subscribe_responses.drain(..) {
1187 if let Some(collection) = self.compute_state.collections.get_mut(&sink_id) {
1189 let new_frontier = match &response {
1190 SubscribeResponse::Batch(b) => b.upper.clone(),
1191 SubscribeResponse::DroppedAt(_) => Antichain::new(),
1192 };
1193
1194 let reported = collection.reported_frontiers();
1195 assert!(
1196 reported.write_frontier.allows_reporting(&new_frontier),
1197 "subscribe write frontier regression: {:?} -> {:?}",
1198 reported.write_frontier,
1199 new_frontier,
1200 );
1201 assert!(
1202 reported.input_frontier.allows_reporting(&new_frontier),
1203 "subscribe input frontier regression: {:?} -> {:?}",
1204 reported.input_frontier,
1205 new_frontier,
1206 );
1207
1208 collection
1209 .set_reported_write_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1210 collection
1211 .set_reported_input_frontier(ReportedFrontier::Reported(new_frontier.clone()));
1212 collection.set_reported_output_frontier(ReportedFrontier::Reported(new_frontier));
1213 } else {
1214 }
1217
1218 response
1219 .to_error_if_exceeds(usize::try_from(self.compute_state.max_result_size).unwrap());
1220 self.send_compute_response(ComputeResponse::SubscribeResponse(sink_id, response));
1221 }
1222 }
1223
1224 pub fn process_copy_tos(&self) {
1226 let mut responses = self.compute_state.copy_to_response_buffer.borrow_mut();
1227 for (sink_id, response) in responses.drain(..) {
1228 self.send_compute_response(ComputeResponse::CopyToResponse(sink_id, response));
1229 }
1230 }
1231
1232 fn send_compute_response(&self, response: ComputeResponse) {
1234 let _ = self.response_tx.send(response);
1237 }
1238
1239 pub(crate) fn check_expiration(&self) {
1241 let now = mz_ore::now::SYSTEM_TIME();
1242 if self.compute_state.replica_expiration.less_than(&now.into()) {
1243 let now_datetime = mz_ore::now::to_datetime(now);
1244 let expiration_datetime = self
1245 .compute_state
1246 .replica_expiration
1247 .as_option()
1248 .map(Into::into)
1249 .map(mz_ore::now::to_datetime);
1250
1251 error!(
1254 now,
1255 now_datetime = ?now_datetime,
1256 expiration = ?self.compute_state.replica_expiration.elements(),
1257 expiration_datetime = ?expiration_datetime,
1258 "replica expired"
1259 );
1260
1261 assert!(
1263 !self.compute_state.replica_expiration.less_than(&now.into()),
1264 "replica expired. now: {now} ({now_datetime:?}), expiration: {:?} ({expiration_datetime:?})",
1265 self.compute_state.replica_expiration.elements(),
1266 );
1267 }
1268 }
1269
1270 pub fn determine_dataflow_expiration(
1276 &self,
1277 time_dependence: &TimeDependence,
1278 until: &Antichain<Timestamp>,
1279 ) -> Antichain<Timestamp> {
1280 let iter = self
1285 .compute_state
1286 .replica_expiration
1287 .iter()
1288 .filter_map(|t| time_dependence.apply(*t))
1289 .filter_map(|t| Timestamp::try_step_forward(&t))
1290 .filter(|expiration| !until.less_equal(expiration));
1291 Antichain::from_iter(iter)
1292 }
1293}
1294
1295pub enum PendingPeek {
1300 Index(IndexPeek),
1302 Persist(PersistPeek),
1304 Stash(peek_stash::StashingPeek),
1307}
1308
1309impl PendingPeek {
1310 pub fn as_log_event(&self, installed: bool) -> ComputeEvent {
1312 let peek = self.peek();
1313 let (id, peek_type) = match &peek.target {
1314 PeekTarget::Index { id } => (*id, logging::compute::PeekType::Index),
1315 PeekTarget::Persist { id, .. } => (*id, logging::compute::PeekType::Persist),
1316 };
1317 let uuid = peek.uuid.into_bytes();
1318 ComputeEvent::Peek(PeekEvent {
1319 id,
1320 time: peek.timestamp,
1321 uuid,
1322 peek_type,
1323 installed,
1324 })
1325 }
1326
1327 fn index(peek: Peek, mut trace_bundle: TraceBundle) -> Self {
1328 let empty_frontier = Antichain::new();
1329 let timestamp_frontier = Antichain::from_elem(peek.timestamp);
1330 trace_bundle
1331 .oks_mut()
1332 .set_logical_compaction(timestamp_frontier.borrow());
1333 trace_bundle
1334 .errs_mut()
1335 .set_logical_compaction(timestamp_frontier.borrow());
1336 trace_bundle
1337 .oks_mut()
1338 .set_physical_compaction(empty_frontier.borrow());
1339 trace_bundle
1340 .errs_mut()
1341 .set_physical_compaction(empty_frontier.borrow());
1342
1343 PendingPeek::Index(IndexPeek {
1344 peek,
1345 trace_bundle,
1346 span: tracing::Span::current(),
1347 })
1348 }
1349
1350 fn persist(
1351 peek: Peek,
1352 persist_clients: Arc<PersistClientCache>,
1353 metadata: CollectionMetadata,
1354 max_result_size: usize,
1355 timely_worker: &TimelyWorker,
1356 ) -> Self {
1357 let active_worker = {
1358 let chosen_index = usize::cast_from(peek.uuid.hashed()) % timely_worker.peers();
1360 chosen_index == timely_worker.index()
1361 };
1362 let activator = timely_worker.sync_activator_for([].into());
1363 let peek_uuid = peek.uuid;
1364
1365 let (result_tx, result_rx) = oneshot::channel();
1366 let timestamp = peek.timestamp;
1367 let mfp_plan = peek.map_filter_project.clone();
1368 let max_results_needed = peek
1369 .finishing
1370 .limit
1371 .map(|l| usize::cast_from(u64::from(l)))
1372 .unwrap_or(usize::MAX)
1373 + peek.finishing.offset;
1374 let order_by = peek.finishing.order_by.clone();
1375
1376 let literal_constraint = peek
1378 .literal_constraints
1379 .clone()
1380 .map(|rows| rows.into_element());
1381
1382 let task_handle = mz_ore::task::spawn(|| "persist::peek", async move {
1383 let start = Instant::now();
1384 let result = if active_worker {
1385 PersistPeek::do_peek(
1386 &persist_clients,
1387 metadata,
1388 timestamp,
1389 literal_constraint,
1390 mfp_plan,
1391 max_result_size,
1392 max_results_needed,
1393 )
1394 .await
1395 } else {
1396 Ok(vec![])
1397 };
1398 let result = match result {
1399 Ok(rows) => PeekResponse::Rows(vec![RowCollection::new(rows, &order_by)]),
1400 Err(e) => PeekResponse::Error(e.to_string()),
1401 };
1402 match result_tx.send((result, start.elapsed())) {
1403 Ok(()) => {}
1404 Err((_result, elapsed)) => {
1405 debug!(duration =? elapsed, "dropping result for cancelled peek {peek_uuid}")
1406 }
1407 }
1408 match activator.activate() {
1409 Ok(()) => {}
1410 Err(_) => {
1411 debug!("unable to wake timely after completed peek {peek_uuid}");
1412 }
1413 }
1414 });
1415 PendingPeek::Persist(PersistPeek {
1416 peek,
1417 _abort_handle: task_handle.abort_on_drop(),
1418 result: result_rx,
1419 span: tracing::Span::current(),
1420 })
1421 }
1422
1423 fn span(&self) -> &tracing::Span {
1424 match self {
1425 PendingPeek::Index(p) => &p.span,
1426 PendingPeek::Persist(p) => &p.span,
1427 PendingPeek::Stash(p) => &p.span,
1428 }
1429 }
1430
1431 pub(crate) fn peek(&self) -> &Peek {
1432 match self {
1433 PendingPeek::Index(p) => &p.peek,
1434 PendingPeek::Persist(p) => &p.peek,
1435 PendingPeek::Stash(p) => &p.peek,
1436 }
1437 }
1438}
1439
1440pub struct PersistPeek {
1445 pub(crate) peek: Peek,
1446 _abort_handle: AbortOnDropHandle<()>,
1449 result: oneshot::Receiver<(PeekResponse, Duration)>,
1451 span: tracing::Span,
1453}
1454
1455impl PersistPeek {
1456 async fn do_peek(
1457 persist_clients: &PersistClientCache,
1458 metadata: CollectionMetadata,
1459 as_of: Timestamp,
1460 literal_constraint: Option<Row>,
1461 mfp_plan: SafeMfpPlan,
1462 max_result_size: usize,
1463 mut limit_remaining: usize,
1464 ) -> Result<Vec<(Row, NonZeroUsize)>, String> {
1465 let client = persist_clients
1466 .open(metadata.persist_location)
1467 .await
1468 .map_err(|e| e.to_string())?;
1469
1470 let mut reader: ReadHandle<SourceData, (), Timestamp, StorageDiff> = client
1471 .open_leased_reader(
1472 metadata.data_shard,
1473 Arc::new(metadata.relation_desc.clone()),
1474 Arc::new(UnitSchema),
1475 Diagnostics::from_purpose("persist::peek"),
1476 USE_CRITICAL_SINCE_SNAPSHOT.get(client.dyncfgs()),
1477 )
1478 .await
1479 .map_err(|e| e.to_string())?;
1480
1481 let mut txns_read = if let Some(txns_id) = metadata.txns_shard {
1488 Some(TxnsCache::open(&client, txns_id, Some(metadata.data_shard)).await)
1489 } else {
1490 None
1491 };
1492
1493 let metrics = client.metrics();
1494
1495 let mut cursor = StatsCursor::new(
1496 &mut reader,
1497 txns_read.as_mut(),
1498 metrics,
1499 &mfp_plan,
1500 &metadata.relation_desc,
1501 Antichain::from_elem(as_of),
1502 )
1503 .await
1504 .map_err(|since| {
1505 format!("attempted to peek at {as_of}, but the since has advanced to {since:?}")
1506 })?;
1507
1508 let mut result = vec![];
1510 let mut datum_vec = DatumVec::new();
1511 let mut row_builder = Row::default();
1512 let arena = RowArena::new();
1513 let mut total_size = 0usize;
1514
1515 let literal_len = match &literal_constraint {
1516 None => 0,
1517 Some(row) => row.iter().count(),
1518 };
1519
1520 'collect: while limit_remaining > 0 {
1521 let Some(batch) = cursor.next().await else {
1522 break;
1523 };
1524 for (data, _, d) in batch {
1525 let row = data.map_err(|e| e.to_string())?;
1526
1527 if let Some(literal) = &literal_constraint {
1528 match row.iter().take(literal_len).cmp(literal.iter()) {
1529 Ordering::Less => continue,
1530 Ordering::Equal => {}
1531 Ordering::Greater => break 'collect,
1532 }
1533 }
1534
1535 let count: usize = d.try_into().map_err(|_| {
1536 error!(
1537 shard = %metadata.data_shard, diff = d, ?row,
1538 "persist peek encountered negative multiplicities",
1539 );
1540 format!(
1541 "Invalid data in source, \
1542 saw retractions ({}) for row that does not exist: {:?}",
1543 -d, row,
1544 )
1545 })?;
1546 let Some(count) = NonZeroUsize::new(count) else {
1547 continue;
1548 };
1549 let mut datum_local = datum_vec.borrow_with(&row);
1550 let eval_result = mfp_plan
1551 .evaluate_into(&mut datum_local, &arena, &mut row_builder)
1552 .map(|row| row.cloned())
1553 .map_err(|e| e.to_string())?;
1554 if let Some(row) = eval_result {
1555 total_size = total_size
1556 .saturating_add(row.byte_len())
1557 .saturating_add(std::mem::size_of::<NonZeroUsize>());
1558 if total_size > max_result_size {
1559 return Err(format!(
1560 "result exceeds max size of {}",
1561 ByteSize::b(u64::cast_from(max_result_size))
1562 ));
1563 }
1564 result.push((row, count));
1565 limit_remaining = limit_remaining.saturating_sub(count.get());
1566 if limit_remaining == 0 {
1567 break;
1568 }
1569 }
1570 }
1571 }
1572
1573 Ok(result)
1574 }
1575}
1576
1577pub struct IndexPeek {
1579 peek: Peek,
1580 trace_bundle: TraceBundle,
1582 span: tracing::Span,
1584}
1585
1586pub(crate) struct IndexPeekMetrics<'a> {
1591 pub seek_fulfillment_seconds: &'a prometheus::Histogram,
1592 pub frontier_check_seconds: &'a prometheus::Histogram,
1593 pub error_scan_seconds: &'a prometheus::Histogram,
1594 pub cursor_setup_seconds: &'a prometheus::Histogram,
1595 pub row_iteration_seconds: &'a prometheus::Histogram,
1596 pub row_iteration_rows: &'a prometheus::Histogram,
1597 pub result_sort_seconds: &'a prometheus::Histogram,
1598 pub result_sort_rows: &'a prometheus::Histogram,
1599 pub row_collection_seconds: &'a prometheus::Histogram,
1600}
1601
1602impl IndexPeek {
1603 fn seek_fulfillment(
1616 &mut self,
1617 upper: &mut Antichain<Timestamp>,
1618 max_result_size: u64,
1619 peek_stash_eligible: bool,
1620 peek_stash_threshold_bytes: usize,
1621 metrics: &IndexPeekMetrics<'_>,
1622 ) -> PeekStatus {
1623 let method_start = Instant::now();
1624
1625 self.trace_bundle.oks_mut().read_upper(upper);
1626 if upper.less_equal(&self.peek.timestamp) {
1627 return PeekStatus::NotReady;
1628 }
1629 self.trace_bundle.errs_mut().read_upper(upper);
1630 if upper.less_equal(&self.peek.timestamp) {
1631 return PeekStatus::NotReady;
1632 }
1633
1634 let read_frontier = self.trace_bundle.compaction_frontier();
1635 if !read_frontier.less_equal(&self.peek.timestamp) {
1636 let error = format!(
1637 "Arrangement compaction frontier ({:?}) is beyond the time of the attempted read ({})",
1638 read_frontier.elements(),
1639 self.peek.timestamp,
1640 );
1641 return PeekStatus::Ready(PeekResponse::Error(error));
1642 }
1643
1644 metrics
1645 .frontier_check_seconds
1646 .observe(method_start.elapsed().as_secs_f64());
1647
1648 let result = self.collect_finished_data(
1649 max_result_size,
1650 peek_stash_eligible,
1651 peek_stash_threshold_bytes,
1652 metrics,
1653 );
1654
1655 metrics
1656 .seek_fulfillment_seconds
1657 .observe(method_start.elapsed().as_secs_f64());
1658
1659 result
1660 }
1661
1662 fn collect_finished_data(
1664 &mut self,
1665 max_result_size: u64,
1666 peek_stash_eligible: bool,
1667 peek_stash_threshold_bytes: usize,
1668 metrics: &IndexPeekMetrics<'_>,
1669 ) -> PeekStatus {
1670 let error_scan_start = Instant::now();
1671
1672 let (mut cursor, storage) = self.trace_bundle.errs_mut().cursor();
1675 while cursor.key_valid(&storage) {
1676 let mut copies = Diff::ZERO;
1677 cursor.map_times(&storage, |time, diff| {
1678 if time.less_equal(&self.peek.timestamp) {
1679 copies += diff;
1680 }
1681 });
1682 if copies.is_negative() {
1683 let error = cursor.key(&storage);
1684 error!(
1685 target = %self.peek.target.id(), diff = %copies, %error,
1686 "index peek encountered negative multiplicities in error trace",
1687 );
1688 return PeekStatus::Ready(PeekResponse::Error(format!(
1689 "Invalid data in source errors, \
1690 saw retractions ({}) for row that does not exist: {}",
1691 -copies, error,
1692 )));
1693 }
1694 if copies.is_positive() {
1695 return PeekStatus::Ready(PeekResponse::Error(cursor.key(&storage).to_string()));
1696 }
1697 cursor.step_key(&storage);
1698 }
1699
1700 metrics
1701 .error_scan_seconds
1702 .observe(error_scan_start.elapsed().as_secs_f64());
1703
1704 Self::collect_ok_finished_data(
1705 &self.peek,
1706 self.trace_bundle.oks_mut(),
1707 max_result_size,
1708 peek_stash_eligible,
1709 peek_stash_threshold_bytes,
1710 metrics,
1711 )
1712 }
1713
1714 fn collect_ok_finished_data<Tr>(
1716 peek: &Peek,
1717 oks_handle: &mut Tr,
1718 max_result_size: u64,
1719 peek_stash_eligible: bool,
1720 peek_stash_threshold_bytes: usize,
1721 metrics: &IndexPeekMetrics<'_>,
1722 ) -> PeekStatus
1723 where
1724 Tr: TraceReader<Batch: Navigable>,
1725 for<'a> BatchCursor<Tr>: Cursor<
1726 Key<'a>: ExtendDatums + Eq,
1727 KeyContainer: BatchContainer<Owned = Row>,
1728 Val<'a>: ExtendDatums,
1729 TimeGat<'a>: PartialOrder<Timestamp>,
1730 DiffGat<'a> = &'a Diff,
1731 >,
1732 {
1733 let max_result_size = usize::cast_from(max_result_size);
1734 let count_byte_size = size_of::<NonZeroUsize>();
1735
1736 let cursor_setup_start = Instant::now();
1738
1739 let mut peek_iterator = peek_result_iterator::PeekResultIterator::new(
1742 peek.target.id().clone(),
1743 peek.map_filter_project.clone(),
1744 peek.timestamp,
1745 peek.literal_constraints.clone().as_deref_mut(),
1746 oks_handle,
1747 );
1748
1749 metrics
1750 .cursor_setup_seconds
1751 .observe(cursor_setup_start.elapsed().as_secs_f64());
1752
1753 let mut results = Vec::new();
1755 let mut total_size: usize = 0;
1756
1757 let max_results = peek.finishing.num_rows_needed();
1763
1764 let comparator = RowComparator::new(peek.finishing.order_by.as_slice());
1765
1766 let row_iteration_start = Instant::now();
1768 let mut sort_time_accum = Duration::ZERO;
1769 let mut rows_sorted = 0usize;
1770
1771 while let Some(row) = peek_iterator.next() {
1772 let row: (Row, _) = match row {
1773 Ok(row) => row,
1774 Err(err) => return PeekStatus::Ready(PeekResponse::Error(err)),
1775 };
1776 let (row, copies) = row;
1777 let copies: NonZeroUsize = NonZeroUsize::try_from(copies).expect("fits into usize");
1778
1779 total_size = total_size
1780 .saturating_add(row.byte_len())
1781 .saturating_add(count_byte_size);
1782 if peek_stash_eligible && total_size > peek_stash_threshold_bytes {
1783 return PeekStatus::UsePeekStash;
1784 }
1785 if total_size > max_result_size {
1786 return PeekStatus::Ready(PeekResponse::Error(format!(
1787 "result exceeds max size of {}",
1788 ByteSize::b(u64::cast_from(max_result_size))
1789 )));
1790 }
1791
1792 results.push((row, copies));
1793
1794 if let Some(max_results) = max_results {
1797 if max_results
1810 .checked_mul(2)
1811 .is_some_and(|threshold| results.len() >= threshold)
1812 {
1813 if peek.finishing.order_by.is_empty() {
1814 results.truncate(max_results);
1815 metrics
1816 .row_iteration_seconds
1817 .observe(row_iteration_start.elapsed().as_secs_f64());
1818 metrics
1819 .row_iteration_rows
1820 .observe(f64::cast_lossy(peek_iterator.rows_processed()));
1821 metrics
1822 .result_sort_seconds
1823 .observe(sort_time_accum.as_secs_f64());
1824 metrics
1825 .result_sort_rows
1826 .observe(f64::cast_lossy(rows_sorted));
1827 let row_collection_start = Instant::now();
1828 let collection = RowCollection::new(results, &peek.finishing.order_by);
1829 metrics
1830 .row_collection_seconds
1831 .observe(row_collection_start.elapsed().as_secs_f64());
1832 return PeekStatus::Ready(PeekResponse::Rows(vec![collection]));
1833 } else {
1834 let sort_start = Instant::now();
1843 rows_sorted = rows_sorted.saturating_add(results.len());
1844 results.sort_by(|left, right| {
1845 comparator.compare_rows(&left.0, &right.0, || left.0.cmp(&right.0))
1846 });
1847 sort_time_accum += sort_start.elapsed();
1848 let dropped = results.drain(max_results..);
1849 let dropped_size =
1850 dropped
1851 .into_iter()
1852 .fold(0, |acc: usize, (row, _count): (Row, _)| {
1853 acc.saturating_add(
1854 row.byte_len().saturating_add(count_byte_size),
1855 )
1856 });
1857 total_size = total_size.saturating_sub(dropped_size);
1858 }
1859 }
1860 }
1861 }
1862
1863 metrics
1864 .row_iteration_seconds
1865 .observe(row_iteration_start.elapsed().as_secs_f64());
1866 metrics
1867 .row_iteration_rows
1868 .observe(f64::cast_lossy(peek_iterator.rows_processed()));
1869 metrics
1870 .result_sort_seconds
1871 .observe(sort_time_accum.as_secs_f64());
1872 metrics
1873 .result_sort_rows
1874 .observe(f64::cast_lossy(rows_sorted));
1875
1876 let row_collection_start = Instant::now();
1877 let collection = RowCollection::new(results, &peek.finishing.order_by);
1878 metrics
1879 .row_collection_seconds
1880 .observe(row_collection_start.elapsed().as_secs_f64());
1881 PeekStatus::Ready(PeekResponse::Rows(vec![collection]))
1882 }
1883}
1884
1885enum PeekStatus {
1888 NotReady,
1891 UsePeekStash,
1894 Ready(PeekResponse),
1896}
1897
1898#[derive(Debug)]
1900struct ReportedFrontiers {
1901 write_frontier: ReportedFrontier,
1903 input_frontier: ReportedFrontier,
1905 output_frontier: ReportedFrontier,
1907}
1908
1909impl ReportedFrontiers {
1910 fn new() -> Self {
1912 Self {
1913 write_frontier: ReportedFrontier::new(),
1914 input_frontier: ReportedFrontier::new(),
1915 output_frontier: ReportedFrontier::new(),
1916 }
1917 }
1918}
1919
1920#[derive(Clone, Debug)]
1922pub enum ReportedFrontier {
1923 Reported(Antichain<Timestamp>),
1925 NotReported {
1927 lower: Antichain<Timestamp>,
1929 },
1930}
1931
1932impl ReportedFrontier {
1933 pub fn new() -> Self {
1935 let lower = Antichain::from_elem(timely::progress::Timestamp::minimum());
1936 Self::NotReported { lower }
1937 }
1938
1939 pub fn is_empty(&self) -> bool {
1941 match self {
1942 Self::Reported(frontier) => frontier.is_empty(),
1943 Self::NotReported { .. } => false,
1944 }
1945 }
1946
1947 fn allows_reporting(&self, other: &Antichain<Timestamp>) -> bool {
1953 match self {
1954 Self::Reported(frontier) => PartialOrder::less_than(frontier, other),
1955 Self::NotReported { lower } => PartialOrder::less_equal(lower, other),
1956 }
1957 }
1958}
1959
1960pub struct CollectionState {
1962 reported_frontiers: ReportedFrontiers,
1964 dataflow_index: Rc<usize>,
1970 pub is_subscribe_or_copy: bool,
1976 as_of: Antichain<Timestamp>,
1980
1981 pub sink_token: Option<SinkToken>,
1986 pub sink_write_frontier: Option<Rc<RefCell<Antichain<Timestamp>>>>,
1990 pub input_probes: BTreeMap<GlobalId, probe::Handle<Timestamp>>,
1992 pub compute_probe: Option<probe::Handle<Timestamp>>,
1997 logging: Option<CollectionLogging>,
1999 metrics: CollectionMetrics,
2001 read_only_tx: watch::Sender<bool>,
2013 pub read_only_rx: watch::Receiver<bool>,
2015}
2016
2017impl CollectionState {
2018 fn new(
2019 dataflow_index: Rc<usize>,
2020 is_subscribe_or_copy: bool,
2021 as_of: Antichain<Timestamp>,
2022 metrics: CollectionMetrics,
2023 ) -> Self {
2024 let (read_only_tx, read_only_rx) = watch::channel(true);
2027
2028 Self {
2029 reported_frontiers: ReportedFrontiers::new(),
2030 dataflow_index,
2031 is_subscribe_or_copy,
2032 as_of,
2033 sink_token: None,
2034 sink_write_frontier: None,
2035 input_probes: Default::default(),
2036 compute_probe: None,
2037 logging: None,
2038 metrics,
2039 read_only_tx,
2040 read_only_rx,
2041 }
2042 }
2043
2044 fn reported_frontiers(&self) -> &ReportedFrontiers {
2046 &self.reported_frontiers
2047 }
2048
2049 pub fn reset_reported_frontiers(&mut self, frontier: ReportedFrontier) {
2051 self.reported_frontiers.write_frontier = frontier.clone();
2052 self.reported_frontiers.input_frontier = frontier.clone();
2053 self.reported_frontiers.output_frontier = frontier;
2054 }
2055
2056 fn set_reported_write_frontier(&mut self, frontier: ReportedFrontier) {
2058 if let Some(logging) = &mut self.logging {
2059 let time = match &frontier {
2060 ReportedFrontier::Reported(frontier) => frontier.get(0).copied(),
2061 ReportedFrontier::NotReported { .. } => Some(Timestamp::MIN),
2062 };
2063 logging.set_frontier(time);
2064 }
2065
2066 self.reported_frontiers.write_frontier = frontier;
2067 }
2068
2069 fn set_reported_input_frontier(&mut self, frontier: ReportedFrontier) {
2071 if let Some(logging) = &mut self.logging {
2073 for (id, probe) in &self.input_probes {
2074 let new_time = probe.with_frontier(|frontier| frontier.as_option().copied());
2075 logging.set_import_frontier(*id, new_time);
2076 }
2077 }
2078
2079 self.reported_frontiers.input_frontier = frontier;
2080 }
2081
2082 fn set_reported_output_frontier(&mut self, frontier: ReportedFrontier) {
2084 let already_hydrated = self.hydrated();
2085
2086 self.reported_frontiers.output_frontier = frontier;
2087
2088 if !already_hydrated && self.hydrated() {
2089 if let Some(logging) = &mut self.logging {
2090 logging.set_hydrated();
2091 }
2092 self.metrics.record_collection_hydrated();
2093 }
2094 }
2095
2096 fn hydrated(&self) -> bool {
2098 match &self.reported_frontiers.output_frontier {
2099 ReportedFrontier::Reported(frontier) => PartialOrder::less_than(&self.as_of, frontier),
2100 ReportedFrontier::NotReported { .. } => false,
2101 }
2102 }
2103
2104 fn allow_writes(&self) {
2106 info!(
2107 dataflow_index = *self.dataflow_index,
2108 export = ?self.logging.as_ref().map(|l| l.export_id()),
2109 "allowing writes for dataflow",
2110 );
2111 let _ = self.read_only_tx.send(false);
2112 }
2113}