1use std::fmt::{self, Debug};
13use std::marker::PhantomData;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use anyhow::anyhow;
18use arrow::array::{Array, ArrayRef, AsArray, BooleanArray, Int64Array};
19use arrow::compute::FilterBuilder;
20use differential_dataflow::difference::Monoid;
21use differential_dataflow::lattice::Lattice;
22use differential_dataflow::trace::Description;
23use itertools::EitherOrBoth;
24use mz_dyncfg::{Config, ConfigSet, ConfigValHandle, ParameterScope};
25use mz_ore::bytes::SegmentedBytes;
26use mz_ore::cast::CastFrom;
27use mz_ore::{soft_assert_or_log, soft_panic_no_log, soft_panic_or_log};
28use mz_persist::indexed::columnar::arrow::{realloc_any, realloc_array};
29use mz_persist::indexed::columnar::parquet::BlobTraceBatchPartReader;
30use mz_persist::indexed::columnar::{ColumnarRecords, ColumnarRecordsStructuredExt};
31use mz_persist::indexed::encoding::{BlobTraceBatchPart, BlobTraceUpdates};
32use mz_persist::location::{Blob, SeqNo};
33use mz_persist::metrics::ColumnarMetrics;
34use mz_persist_types::arrow::ArrayOrd;
35use mz_persist_types::columnar::{ColumnDecoder, Schema, data_type};
36use mz_persist_types::part::Codec64Mut;
37use mz_persist_types::schema::backward_compatible;
38use mz_persist_types::stats::PartStats;
39use mz_persist_types::{Codec, Codec64};
40use mz_proto::RustType;
41use serde::{Deserialize, Serialize};
42use timely::PartialOrder;
43use timely::progress::frontier::AntichainRef;
44use timely::progress::{Antichain, Timestamp};
45use tracing::{Instrument, debug, debug_span, trace_span};
46
47use crate::ShardId;
48use crate::cfg::PersistConfig;
49use crate::error::InvalidUsage;
50use crate::internal::apply::Applier;
51use crate::internal::encoding::{LazyInlineBatchPart, LazyPartStats, LazyProto, Schemas};
52use crate::internal::machine::retry_external;
53use crate::internal::metrics::{Metrics, MetricsPermits, ReadMetrics, ShardMetrics};
54use crate::internal::paths::BlobKey;
55use crate::internal::state::{
56 BatchPart, HollowBatchPart, ProtoHollowBatchPart, ProtoInlineBatchPart,
57};
58use crate::read::LeasedReaderId;
59use crate::schema::{PartMigration, SchemaCache};
60
61pub(crate) const FETCH_SEMAPHORE_COST_ADJUSTMENT: Config<f64> = Config::new(
62 "persist_fetch_semaphore_cost_adjustment",
63 1.2,
67 "\
68 An adjustment multiplied by encoded_size_bytes to approximate an upper \
69 bound on the size in lgalloc, which includes the decoded version.",
70 ParameterScope::Environment,
71);
72
73pub(crate) const FETCH_SEMAPHORE_PERMIT_ADJUSTMENT: Config<f64> = Config::new(
74 "persist_fetch_semaphore_permit_adjustment",
75 1.0,
76 "\
77 A limit on the number of outstanding persist bytes being fetched and \
78 parsed, expressed as a multiplier of the process's memory limit. This data \
79 all spills to lgalloc, so values > 1.0 are safe. Only applied to cc \
80 replicas.",
81 ParameterScope::Environment,
82);
83
84pub(crate) const PART_DECODE_FORMAT: Config<&'static str> = Config::new(
85 "persist_part_decode_format",
86 PartDecodeFormat::default().as_str(),
87 "\
88 Format we'll use to decode a Persist Part, either 'row', \
89 'row_with_validate', or 'arrow' (Materialize).",
90 ParameterScope::Environment,
91);
92
93pub(crate) const PART_DECODE_BATCH_ROWS: Config<Option<usize>> = Config::new(
94 "persist_part_decode_batch_rows",
95 None,
96 "\
97 Maximum number of rows of a fetched blob part that a reader decodes at \
98 once. Unset decodes the whole part at once.",
99 ParameterScope::Environment,
100);
101
102pub(crate) const OPTIMIZE_IGNORED_DATA_FETCH: Config<bool> = Config::new(
103 "persist_optimize_ignored_data_fetch",
104 true,
105 "CYA to allow opt-out of a performance optimization to skip fetching ignored data",
106 ParameterScope::Environment,
107);
108
109pub(crate) const VALIDATE_PART_BOUNDS_ON_READ: Config<bool> = Config::new(
110 "persist_validate_part_bounds_on_read",
111 false,
112 "Validate the part lower <= the batch lower and the part upper <= batch upper,\
113 for the batch containing that part",
114 ParameterScope::Environment,
115);
116
117#[derive(Debug, Clone)]
118pub(crate) struct FetchConfig {
119 pub(crate) validate_bounds_on_read: bool,
120 pub(crate) part_decode_batch_rows: Option<usize>,
123}
124
125impl FetchConfig {
126 pub fn from_persist_config(cfg: &PersistConfig) -> Self {
127 Self {
128 validate_bounds_on_read: VALIDATE_PART_BOUNDS_ON_READ.get(cfg),
129 part_decode_batch_rows: PART_DECODE_BATCH_ROWS.get(cfg),
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub(crate) struct BatchFetcherConfig {
136 pub(crate) part_decode_format: ConfigValHandle<String>,
137 pub(crate) validate_bounds_on_read: ConfigValHandle<bool>,
138 pub(crate) part_decode_batch_rows: ConfigValHandle<Option<usize>>,
139}
140
141impl BatchFetcherConfig {
142 pub fn new(value: &PersistConfig) -> Self {
143 Self {
144 part_decode_format: PART_DECODE_FORMAT.handle(value),
145 validate_bounds_on_read: VALIDATE_PART_BOUNDS_ON_READ.handle(value),
146 part_decode_batch_rows: PART_DECODE_BATCH_ROWS.handle(value),
147 }
148 }
149
150 pub fn part_decode_format(&self) -> PartDecodeFormat {
151 PartDecodeFormat::from_str(self.part_decode_format.get().as_str())
152 }
153
154 pub fn fetch_config(&self) -> FetchConfig {
156 FetchConfig {
157 validate_bounds_on_read: self.validate_bounds_on_read.get(),
158 part_decode_batch_rows: self.part_decode_batch_rows.get(),
159 }
160 }
161}
162
163#[derive(Debug)]
165pub struct BatchFetcher<K, V, T, D>
166where
167 T: Timestamp + Lattice + Codec64,
168 K: Debug + Codec,
170 V: Debug + Codec,
171 D: Monoid + Codec64 + Send + Sync,
172{
173 pub(crate) cfg: BatchFetcherConfig,
174 pub(crate) blob: Arc<dyn Blob>,
175 pub(crate) metrics: Arc<Metrics>,
176 pub(crate) shard_metrics: Arc<ShardMetrics>,
177 pub(crate) shard_id: ShardId,
178 pub(crate) read_schemas: Schemas<K, V>,
179 pub(crate) schema_cache: SchemaCache<K, V, T, D>,
180 pub(crate) is_transient: bool,
181
182 pub(crate) _phantom: PhantomData<fn() -> (K, V, T, D)>,
185}
186
187impl<K, V, T, D> Clone for BatchFetcher<K, V, T, D>
193where
194 T: Timestamp + Lattice + Codec64,
195 K: Debug + Codec,
196 V: Debug + Codec,
197 D: Monoid + Codec64 + Send + Sync,
198{
199 fn clone(&self) -> Self {
200 Self {
201 cfg: self.cfg.clone(),
202 blob: Arc::clone(&self.blob),
203 metrics: Arc::clone(&self.metrics),
204 shard_metrics: Arc::clone(&self.shard_metrics),
205 shard_id: self.shard_id.clone(),
206 read_schemas: self.read_schemas.clone(),
207 schema_cache: self.schema_cache.clone(),
208 is_transient: self.is_transient,
209 _phantom: PhantomData,
210 }
211 }
212}
213
214impl<K, V, T, D> BatchFetcher<K, V, T, D>
215where
216 K: Debug + Codec,
217 V: Debug + Codec,
218 T: Timestamp + Lattice + Codec64 + Sync,
219 D: Monoid + Codec64 + Send + Sync,
220{
221 pub async fn fetch_leased_part(
226 &mut self,
227 part: ExchangeableBatchPart<T>,
228 ) -> Result<Result<FetchedBlob<K, V, T, D>, BlobKey>, InvalidUsage<T>> {
229 let ExchangeableBatchPart {
230 shard_id,
231 encoded_size_bytes: _,
232 desc,
233 filter,
234 filter_pushdown_audit,
235 part,
236 reader_id: _,
237 } = part;
238 let part: BatchPart<T> = part.decode_to().expect("valid part");
239 if shard_id != self.shard_id {
240 return Err(InvalidUsage::BatchNotFromThisShard {
241 batch_shard: shard_id,
242 handle_shard: self.shard_id.clone(),
243 });
244 }
245
246 let migration =
247 PartMigration::new(&part, self.read_schemas.clone(), &mut self.schema_cache)
248 .await
249 .unwrap_or_else(|read_schemas| {
250 panic!(
251 "could not decode part {:?} with schema: {:?}",
252 part.schema_id(),
253 read_schemas
254 )
255 });
256
257 let (buf, fetch_permit) = match &part {
258 BatchPart::Hollow(x) => {
259 let fetch_permit = self
260 .metrics
261 .semaphore
262 .acquire_fetch_permits(x.encoded_size_bytes)
263 .await;
264 let read_metrics = if self.is_transient {
265 &self.metrics.read.unindexed
266 } else {
267 &self.metrics.read.batch_fetcher
268 };
269 let buf = fetch_batch_part_blob(
270 &shard_id,
271 self.blob.as_ref(),
272 &self.metrics,
273 &self.shard_metrics,
274 read_metrics,
275 x,
276 )
277 .await;
278 let buf = match buf {
279 Ok(buf) => buf,
280 Err(key) => return Ok(Err(key)),
281 };
282 let buf = FetchedBlobBuf::Hollow {
283 buf,
284 part: x.clone(),
285 };
286 (buf, Some(Arc::new(fetch_permit)))
287 }
288 BatchPart::Inline {
289 updates,
290 ts_rewrite,
291 ..
292 } => {
293 let buf = FetchedBlobBuf::Inline {
294 desc: desc.clone(),
295 updates: updates.clone(),
296 ts_rewrite: ts_rewrite.clone(),
297 };
298 (buf, None)
299 }
300 };
301 let fetched_blob = FetchedBlob {
302 metrics: Arc::clone(&self.metrics),
303 read_metrics: self.metrics.read.batch_fetcher.clone(),
304 buf,
305 registered_desc: desc.clone(),
306 migration,
307 filter: filter.clone(),
308 filter_pushdown_audit,
309 structured_part_audit: self.cfg.part_decode_format(),
310 fetch_permit,
311 _phantom: PhantomData,
312 fetch_config: self.cfg.fetch_config(),
313 };
314 Ok(Ok(fetched_blob))
315 }
316
317 pub async fn missing_blob_diagnostics(
320 &self,
321 reader_id: &LeasedReaderId,
322 ) -> MissingBlobDiagnosis {
323 missing_blob_diagnostics(self.schema_cache.applier(), reader_id.clone()).await
324 }
325}
326
327const MISSING_BLOB_DIAGNOSIS_TIMEOUT: Duration = Duration::from_secs(30);
329
330#[derive(Debug)]
332pub enum MissingBlobDiagnosis {
333 LeaseExpired {
335 reader_id: LeasedReaderId,
337 },
338 ShardTombstoned {
340 reader_id: LeasedReaderId,
342 },
343 LeasePresent {
345 reader_id: LeasedReaderId,
347 lease_state: String,
349 },
350 Unknown {
352 reader_id: LeasedReaderId,
354 },
355}
356
357impl fmt::Display for MissingBlobDiagnosis {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 match self {
360 MissingBlobDiagnosis::LeaseExpired { reader_id } => write!(
361 f,
362 "reader {reader_id} has been expired out of state; \
363 the process likely failed to heartbeat it within the lease duration \
364 (machine sleep, CPU/memory starvation, or a partition from consensus?)"
365 ),
366 MissingBlobDiagnosis::ShardTombstoned { reader_id } => write!(
367 f,
368 "reader {reader_id}: the shard has been tombstoned, which removes every reader"
369 ),
370 MissingBlobDiagnosis::LeasePresent {
371 reader_id,
372 lease_state,
373 } => write!(
374 f,
375 "reader {reader_id} is still present in state ({lease_state}); \
376 a missing blob despite a live lease indicates a GC bug"
377 ),
378 MissingBlobDiagnosis::Unknown { reader_id } => write!(
379 f,
380 "reader {reader_id}: could not refresh state within {}s to diagnose the lease; \
381 partitioned from consensus?",
382 MISSING_BLOB_DIAGNOSIS_TIMEOUT.as_secs()
383 ),
384 }
385 }
386}
387
388pub(crate) async fn missing_blob_diagnostics<K, V, T, D>(
395 applier: &Applier<K, V, T, D>,
396 reader_id: LeasedReaderId,
397) -> MissingBlobDiagnosis
398where
399 K: Debug + Codec,
400 V: Debug + Codec,
401 T: Timestamp + Lattice + Codec64 + Sync,
402 D: Monoid + Codec64,
403{
404 let refresh = applier.fetch_and_update_state(None);
408 if tokio::time::timeout(MISSING_BLOB_DIAGNOSIS_TIMEOUT, refresh)
409 .await
410 .is_err()
411 {
412 return MissingBlobDiagnosis::Unknown { reader_id };
413 }
414 if applier.is_tombstone() {
415 return MissingBlobDiagnosis::ShardTombstoned { reader_id };
416 }
417 match applier.reader_lease(reader_id.clone()) {
418 Some(lease_state) => MissingBlobDiagnosis::LeasePresent {
419 reader_id,
420 lease_state: format!("{lease_state:?}"),
421 },
422 None => MissingBlobDiagnosis::LeaseExpired { reader_id },
423 }
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
427pub(crate) enum FetchBatchFilter<T> {
428 Snapshot {
429 as_of: Antichain<T>,
430 },
431 Listen {
432 as_of: Antichain<T>,
433 lower: Antichain<T>,
434 },
435 Compaction {
436 since: Antichain<T>,
437 },
438}
439
440impl<T: Timestamp + Lattice> FetchBatchFilter<T> {
441 pub(crate) fn filter_ts(&self, t: &mut T) -> bool {
442 match self {
443 FetchBatchFilter::Snapshot { as_of } => {
444 if as_of.less_than(t) {
446 return false;
447 }
448 t.advance_by(as_of.borrow());
449 true
450 }
451 FetchBatchFilter::Listen { as_of, lower } => {
452 if !as_of.less_than(t) {
454 return false;
455 }
456
457 if !lower.less_equal(t) {
465 return false;
466 }
467 true
468 }
469 FetchBatchFilter::Compaction { since } => {
470 t.advance_by(since.borrow());
471 true
472 }
473 }
474 }
475}
476
477pub(crate) async fn fetch_leased_part<K, V, T, D>(
482 cfg: &PersistConfig,
483 part: &LeasedBatchPart<T>,
484 blob: &dyn Blob,
485 metrics: Arc<Metrics>,
486 read_metrics: &ReadMetrics,
487 shard_metrics: &ShardMetrics,
488 reader_id: &LeasedReaderId,
489 read_schemas: Schemas<K, V>,
490 schema_cache: &mut SchemaCache<K, V, T, D>,
491) -> FetchedPart<K, V, T, D>
492where
493 K: Debug + Codec,
494 V: Debug + Codec,
495 T: Timestamp + Lattice + Codec64 + Sync,
496 D: Monoid + Codec64 + Send + Sync,
497{
498 let fetch_config = FetchConfig::from_persist_config(cfg);
499 let fetched = match &part.part {
500 BatchPart::Hollow(x) => fetch_batch_part_blob(
501 &part.shard_id,
502 blob,
503 &metrics,
504 shard_metrics,
505 read_metrics,
506 x,
507 )
508 .await
509 .map(|buf| {
510 PartSource::from_hollow_blob(
511 &fetch_config,
512 &metrics,
513 read_metrics,
514 part.desc.clone(),
515 x,
516 &buf,
517 )
518 }),
519 BatchPart::Inline {
520 updates,
521 ts_rewrite,
522 ..
523 } => Ok(PartSource::Whole(EncodedPart::from_inline(
524 &fetch_config,
525 &metrics,
526 read_metrics.clone(),
527 part.desc.clone(),
528 updates,
529 ts_rewrite.as_ref(),
530 ))),
531 };
532 let source = match fetched {
533 Ok(x) => x,
534 Err(blob_key) => {
535 let diagnostics =
544 missing_blob_diagnostics(schema_cache.applier(), reader_id.clone()).await;
545 panic!("could not fetch batch part {}: {}", blob_key, diagnostics)
546 }
547 };
548 let part_cfg = BatchFetcherConfig::new(cfg);
549 let migration = PartMigration::new(&part.part, read_schemas, schema_cache)
550 .await
551 .unwrap_or_else(|read_schemas| {
552 panic!(
553 "could not decode part {:?} with schema: {:?}",
554 part.part.schema_id(),
555 read_schemas
556 )
557 });
558 FetchedPart::new(
559 metrics,
560 source,
561 migration,
562 part.filter.clone(),
563 part.filter_pushdown_audit,
564 part_cfg.part_decode_format(),
565 part.part.stats(),
566 )
567}
568
569pub(crate) async fn fetch_batch_part_blob<T>(
570 shard_id: &ShardId,
571 blob: &dyn Blob,
572 metrics: &Metrics,
573 shard_metrics: &ShardMetrics,
574 read_metrics: &ReadMetrics,
575 part: &HollowBatchPart<T>,
576) -> Result<SegmentedBytes, BlobKey> {
577 let now = Instant::now();
578 let get_span = debug_span!("fetch_batch::get");
579 let blob_key = part.key.complete(shard_id);
580 let value = retry_external(&metrics.retries.external.fetch_batch_get, || async {
581 shard_metrics.blob_gets.inc();
582 blob.get(&blob_key)
586 .await
587 .map_err(|err| err.context(format!("blob {blob_key}")))
588 })
589 .instrument(get_span.clone())
590 .await
591 .ok_or(blob_key)?;
592
593 drop(get_span);
594
595 read_metrics.part_count.inc();
596 read_metrics.part_bytes.inc_by(u64::cast_from(value.len()));
597 read_metrics.seconds.inc_by(now.elapsed().as_secs_f64());
598
599 Ok(value)
600}
601
602pub(crate) fn decode_batch_part_blob<T>(
603 cfg: &FetchConfig,
604 metrics: &Metrics,
605 read_metrics: &ReadMetrics,
606 registered_desc: Description<T>,
607 part: &HollowBatchPart<T>,
608 buf: &SegmentedBytes,
609) -> EncodedPart<T>
610where
611 T: Timestamp + Lattice + Codec64,
612{
613 trace_span!("fetch_batch::decode").in_scope(|| {
614 let parsed = metrics
615 .codecs
616 .batch
617 .decode(|| BlobTraceBatchPart::decode(buf, &metrics.columnar))
618 .map_err(|err| anyhow!("couldn't decode batch at key {}: {}", part.key, err))
619 .expect("internal error: invalid encoded state");
624 read_metrics
625 .part_goodbytes
626 .inc_by(u64::cast_from(parsed.updates.goodbytes()));
627 EncodedPart::from_hollow(cfg, read_metrics.clone(), registered_desc, part, parsed)
628 })
629}
630
631pub(crate) async fn fetch_batch_part<T>(
632 cfg: &FetchConfig,
633 shard_id: &ShardId,
634 blob: &dyn Blob,
635 metrics: &Metrics,
636 shard_metrics: &ShardMetrics,
637 read_metrics: &ReadMetrics,
638 registered_desc: &Description<T>,
639 part: &HollowBatchPart<T>,
640) -> Result<EncodedPart<T>, BlobKey>
641where
642 T: Timestamp + Lattice + Codec64,
643{
644 let buf =
645 fetch_batch_part_blob(shard_id, blob, metrics, shard_metrics, read_metrics, part).await?;
646 let part = decode_batch_part_blob(
647 cfg,
648 metrics,
649 read_metrics,
650 registered_desc.clone(),
651 part,
652 &buf,
653 );
654 Ok(part)
655}
656
657#[derive(Clone, Debug)]
664pub struct Lease(Arc<SeqNo>);
665
666impl Lease {
667 pub fn new(seqno: SeqNo) -> Self {
669 Self(Arc::new(seqno))
670 }
671
672 pub fn seqno(&self) -> SeqNo {
674 *self.0
675 }
676
677 pub fn count(&self) -> usize {
679 Arc::strong_count(&self.0)
680 }
681}
682
683#[derive(Debug)]
709pub struct LeasedBatchPart<T> {
710 pub(crate) metrics: Arc<Metrics>,
711 pub(crate) shard_id: ShardId,
712 pub(crate) filter: FetchBatchFilter<T>,
713 pub(crate) desc: Description<T>,
714 pub(crate) part: BatchPart<T>,
715 pub(crate) lease: Lease,
718 pub(crate) reader_id: LeasedReaderId,
722 pub(crate) filter_pushdown_audit: bool,
723 pub(crate) bounds_truncated: bool,
728}
729
730impl<T> LeasedBatchPart<T>
731where
732 T: Timestamp + Codec64,
733{
734 pub(crate) fn into_exchangeable_part(self) -> (ExchangeableBatchPart<T>, Lease) {
744 let lease = self.lease.clone();
746 let part = ExchangeableBatchPart {
747 shard_id: self.shard_id,
748 encoded_size_bytes: self.part.encoded_size_bytes(),
749 desc: self.desc.clone(),
750 filter: self.filter.clone(),
751 part: LazyProto::from(&self.part.into_proto()),
752 reader_id: self.reader_id.clone(),
753 filter_pushdown_audit: self.filter_pushdown_audit,
754 };
755 (part, lease)
756 }
757
758 pub fn encoded_size_bytes(&self) -> usize {
760 self.part.encoded_size_bytes()
761 }
762
763 pub fn request_filter_pushdown_audit(&mut self) {
768 self.filter_pushdown_audit = true;
769 }
770
771 pub fn stats(&self) -> Option<PartStats> {
776 self.part.stats().and_then(|x| x.try_decode().ok())
777 }
778
779 pub fn maybe_optimize(&mut self, cfg: &ConfigSet, key: ArrayRef, val: ArrayRef) {
782 assert_eq!(key.len(), 1, "expect a single-row key array");
783 assert_eq!(val.len(), 1, "expect a single-row val array");
784 let as_of = match &self.filter {
785 FetchBatchFilter::Snapshot { as_of } => as_of,
786 FetchBatchFilter::Listen { .. } | FetchBatchFilter::Compaction { .. } => return,
787 };
788 if !OPTIMIZE_IGNORED_DATA_FETCH.get(cfg) {
789 return;
790 }
791 if self.bounds_truncated {
795 return;
796 }
797 let (diffs_sum, _stats) = match &self.part {
798 BatchPart::Hollow(x) => (x.diffs_sum, x.stats.as_ref()),
799 BatchPart::Inline { .. } => return,
800 };
801 debug!(
802 "try_optimize_ignored_data_fetch diffs_sum={:?} as_of={:?} lower={:?} upper={:?}",
803 diffs_sum.map(i64::decode),
805 as_of.elements(),
806 self.desc.lower().elements(),
807 self.desc.upper().elements()
808 );
809 let as_of = match &as_of.elements() {
810 &[as_of] => as_of,
811 _ => return,
812 };
813 let eligible = self.desc.upper().less_equal(as_of) && self.desc.since().less_equal(as_of);
819 if !eligible {
820 return;
821 }
822 let Some(diffs_sum) = diffs_sum else {
823 return;
824 };
825
826 debug!(
827 "try_optimize_ignored_data_fetch faked {:?} diffs at ts {:?} skipping fetch of {} bytes",
828 i64::decode(diffs_sum),
830 as_of,
831 self.part.encoded_size_bytes(),
832 );
833 self.metrics.pushdown.parts_faked_count.inc();
834 self.metrics
835 .pushdown
836 .parts_faked_bytes
837 .inc_by(u64::cast_from(self.part.encoded_size_bytes()));
838 let timestamps = {
839 let mut col = Codec64Mut::with_capacity(1);
840 col.push(as_of);
841 col.finish()
842 };
843 let diffs = {
844 let mut col = Codec64Mut::with_capacity(1);
845 col.push_raw(diffs_sum);
846 col.finish()
847 };
848 let updates = BlobTraceUpdates::Structured {
849 key_values: ColumnarRecordsStructuredExt { key, val },
850 timestamps,
851 diffs,
852 };
853 let faked_data = LazyInlineBatchPart::from(&ProtoInlineBatchPart {
854 desc: Some(self.desc.into_proto()),
855 index: 0,
856 updates: Some(updates.into_proto()),
857 });
858 self.part = BatchPart::Inline {
859 updates: faked_data,
860 ts_rewrite: None,
861 schema_id: None,
862 deprecated_schema_id: None,
863 };
864 }
865}
866
867impl<T> Drop for LeasedBatchPart<T> {
868 fn drop(&mut self) {
870 self.metrics.lease.dropped_part.inc()
871 }
872}
873
874#[derive(Debug)]
879pub struct FetchedBlob<K: Codec, V: Codec, T, D> {
880 metrics: Arc<Metrics>,
881 read_metrics: ReadMetrics,
882 buf: FetchedBlobBuf<T>,
883 registered_desc: Description<T>,
884 migration: PartMigration<K, V>,
885 filter: FetchBatchFilter<T>,
886 filter_pushdown_audit: bool,
887 structured_part_audit: PartDecodeFormat,
888 fetch_permit: Option<Arc<MetricsPermits>>,
889 fetch_config: FetchConfig,
890 _phantom: PhantomData<fn() -> D>,
891}
892
893#[derive(Debug, Clone)]
894enum FetchedBlobBuf<T> {
895 Hollow {
896 buf: SegmentedBytes,
897 part: HollowBatchPart<T>,
898 },
899 Inline {
900 desc: Description<T>,
901 updates: LazyInlineBatchPart,
902 ts_rewrite: Option<Antichain<T>>,
903 },
904}
905
906impl<K: Codec, V: Codec, T: Clone, D> Clone for FetchedBlob<K, V, T, D> {
907 fn clone(&self) -> Self {
908 Self {
909 metrics: Arc::clone(&self.metrics),
910 read_metrics: self.read_metrics.clone(),
911 buf: self.buf.clone(),
912 registered_desc: self.registered_desc.clone(),
913 migration: self.migration.clone(),
914 filter: self.filter.clone(),
915 filter_pushdown_audit: self.filter_pushdown_audit.clone(),
916 fetch_permit: self.fetch_permit.clone(),
917 structured_part_audit: self.structured_part_audit.clone(),
918 fetch_config: self.fetch_config.clone(),
919 _phantom: self._phantom.clone(),
920 }
921 }
922}
923
924pub struct ShardSourcePart<K: Codec, V: Codec, T, D> {
927 pub part: FetchedPart<K, V, T, D>,
929 fetch_permit: Option<Arc<MetricsPermits>>,
930}
931
932impl<K, V, T: Debug, D: Debug> Debug for ShardSourcePart<K, V, T, D>
933where
934 K: Codec + Debug,
935 <K as Codec>::Storage: Debug,
936 V: Codec + Debug,
937 <V as Codec>::Storage: Debug,
938{
939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940 let ShardSourcePart { part, fetch_permit } = self;
941 f.debug_struct("ShardSourcePart")
942 .field("part", part)
943 .field("fetch_permit", fetch_permit)
944 .finish()
945 }
946}
947
948impl<K: Codec, V: Codec, T: Timestamp + Lattice + Codec64, D> FetchedBlob<K, V, T, D> {
949 pub fn parse(&self) -> ShardSourcePart<K, V, T, D> {
951 self.parse_internal(&self.fetch_config)
952 }
953
954 pub(crate) fn parse_internal(&self, cfg: &FetchConfig) -> ShardSourcePart<K, V, T, D> {
956 let (part, stats) = match &self.buf {
957 FetchedBlobBuf::Hollow { buf, part } => {
958 let source = PartSource::from_hollow_blob(
959 cfg,
960 &self.metrics,
961 &self.read_metrics,
962 self.registered_desc.clone(),
963 part,
964 buf,
965 );
966 (source, part.stats.as_ref())
967 }
968 FetchedBlobBuf::Inline {
969 desc,
970 updates,
971 ts_rewrite,
972 } => {
973 let parsed = EncodedPart::from_inline(
974 cfg,
975 &self.metrics,
976 self.read_metrics.clone(),
977 desc.clone(),
978 updates,
979 ts_rewrite.as_ref(),
980 );
981 (PartSource::Whole(parsed), None)
982 }
983 };
984 let part = FetchedPart::new(
985 Arc::clone(&self.metrics),
986 part,
987 self.migration.clone(),
988 self.filter.clone(),
989 self.filter_pushdown_audit,
990 self.structured_part_audit,
991 stats,
992 );
993 ShardSourcePart {
994 part,
995 fetch_permit: self.fetch_permit.clone(),
996 }
997 }
998
999 pub fn stats(&self) -> Option<PartStats> {
1004 match &self.buf {
1005 FetchedBlobBuf::Hollow { part, .. } => {
1006 part.stats.as_ref().and_then(|x| x.try_decode().ok())
1007 }
1008 FetchedBlobBuf::Inline { .. } => None,
1009 }
1010 }
1011}
1012
1013#[derive(Debug)]
1018pub struct FetchedPart<K: Codec, V: Codec, T, D> {
1019 metrics: Arc<Metrics>,
1020 ts_filter: FetchBatchFilter<T>,
1021 rows: Option<DecodedRows<K, V>>,
1024 batches: Option<EncodedPartBatches<T>>,
1026 part_decode_format: PartDecodeFormat,
1027 part_rows: usize,
1029 migration: PartMigration<K, V>,
1030 filter_pushdown_audit: Option<LazyPartStats>,
1031 peek_stash: Option<((K, V), T, D)>,
1032 key_storage: Option<K::Storage>,
1033 val_storage: Option<V::Storage>,
1034
1035 _phantom: PhantomData<fn() -> D>,
1036}
1037
1038#[derive(Debug)]
1040struct DecodedRows<K: Codec, V: Codec> {
1041 part: EitherOrBoth<
1044 ColumnarRecords,
1045 (
1046 <K::Schema as Schema<K>>::Decoder,
1047 <V::Schema as Schema<V>>::Decoder,
1048 ),
1049 >,
1050 timestamps: Int64Array,
1051 diffs: Int64Array,
1052 cursor: usize,
1053}
1054
1055impl<K: Codec, V: Codec, T: Timestamp + Lattice + Codec64, D> FetchedPart<K, V, T, D> {
1056 pub(crate) fn new(
1057 metrics: Arc<Metrics>,
1058 source: PartSource<T>,
1059 migration: PartMigration<K, V>,
1060 ts_filter: FetchBatchFilter<T>,
1061 filter_pushdown_audit: bool,
1062 part_decode_format: PartDecodeFormat,
1063 stats: Option<&LazyPartStats>,
1064 ) -> Self {
1065 let (updates, batches, part_rows) = match source {
1066 PartSource::Whole(part) => {
1067 let part_rows = part.updates().len();
1068 (Some(part.normalize(&metrics.columnar)), None, part_rows)
1069 }
1070 PartSource::Batched(batches) => {
1071 let part_rows = batches.reader.num_rows();
1072 (None, Some(batches), part_rows)
1073 }
1074 };
1075 let part_len = u64::cast_from(part_rows);
1076 match &migration {
1077 PartMigration::SameSchema { .. } => metrics.schema.migration_count_same.inc(),
1078 PartMigration::Schemaless { .. } => {
1079 metrics.schema.migration_count_codec.inc();
1080 metrics.schema.migration_len_legacy_codec.inc_by(part_len);
1081 }
1082 PartMigration::Either { .. } => {
1083 metrics.schema.migration_count_either.inc();
1084 match part_decode_format {
1085 PartDecodeFormat::Row {
1086 validate_structured: false,
1087 } => metrics.schema.migration_len_either_codec.inc_by(part_len),
1088 PartDecodeFormat::Row {
1089 validate_structured: true,
1090 } => {
1091 metrics.schema.migration_len_either_codec.inc_by(part_len);
1092 metrics.schema.migration_len_either_arrow.inc_by(part_len);
1093 }
1094 PartDecodeFormat::Arrow => {
1095 metrics.schema.migration_len_either_arrow.inc_by(part_len)
1096 }
1097 }
1098 }
1099 }
1100
1101 let filter_pushdown_audit = if filter_pushdown_audit {
1102 stats.cloned()
1103 } else {
1104 None
1105 };
1106
1107 let mut ret = FetchedPart {
1108 metrics,
1109 ts_filter,
1110 rows: None,
1111 peek_stash: None,
1112 batches,
1113 part_decode_format,
1114 part_rows,
1115 migration,
1116 filter_pushdown_audit,
1117 key_storage: None,
1118 val_storage: None,
1119 _phantom: PhantomData,
1120 };
1121 ret.rows = updates.map(|updates| ret.decode_rows(updates));
1122 ret
1123 }
1124
1125 fn next_batch(&mut self) -> bool {
1130 let Some(batches) = self.batches.as_mut() else {
1131 return false;
1132 };
1133 self.rows = None;
1136 match batches.next_updates(&self.metrics) {
1137 Some(updates) => {
1138 self.rows = Some(self.decode_rows(updates));
1139 true
1140 }
1141 None => {
1142 self.batches = None;
1143 false
1144 }
1145 }
1146 }
1147
1148 fn decode_rows(&self, updates: BlobTraceUpdates) -> DecodedRows<K, V> {
1150 let metrics = &*self.metrics;
1151 let migration = &self.migration;
1152 let downcast_structured = |structured: ColumnarRecordsStructuredExt,
1153 structured_only: bool| {
1154 let key_size_before = ArrayOrd::new(&structured.key).goodbytes();
1155
1156 let structured = match migration {
1157 PartMigration::SameSchema { .. } => structured,
1158 PartMigration::Schemaless { read } if structured_only => {
1159 let start = Instant::now();
1161 let read_key = data_type::<K>(&*read.key).ok()?;
1162 let read_val = data_type::<V>(&*read.val).ok()?;
1163 let key_migration = backward_compatible(structured.key.data_type(), &read_key)?;
1164 let val_migration = backward_compatible(structured.val.data_type(), &read_val)?;
1165 let key = key_migration.migrate(structured.key);
1166 let val = val_migration.migrate(structured.val);
1167 metrics
1168 .schema
1169 .migration_migrate_seconds
1170 .inc_by(start.elapsed().as_secs_f64());
1171 ColumnarRecordsStructuredExt { key, val }
1172 }
1173 PartMigration::Schemaless { .. } => return None,
1174 PartMigration::Either {
1175 write: _,
1176 read: _,
1177 key_migration,
1178 val_migration,
1179 } => {
1180 let start = Instant::now();
1181 let key = key_migration.migrate(structured.key);
1182 let val = val_migration.migrate(structured.val);
1183 metrics
1184 .schema
1185 .migration_migrate_seconds
1186 .inc_by(start.elapsed().as_secs_f64());
1187 ColumnarRecordsStructuredExt { key, val }
1188 }
1189 };
1190
1191 let read_schema = migration.codec_read();
1192 let key = K::Schema::decoder_any(&*read_schema.key, &*structured.key);
1193 let val = V::Schema::decoder_any(&*read_schema.val, &*structured.val);
1194
1195 match &key {
1196 Ok(key_decoder) => {
1197 let key_size_after = key_decoder.goodbytes();
1198 let key_diff = key_size_before.saturating_sub(key_size_after);
1199 metrics
1200 .pushdown
1201 .parts_projection_trimmed_bytes
1202 .inc_by(u64::cast_from(key_diff));
1203 }
1204 Err(e) => {
1205 soft_panic_or_log!("failed to create decoder: {e:#?}");
1206 }
1207 }
1208
1209 Some((key.ok()?, val.ok()?))
1210 };
1211
1212 let timestamps = updates.timestamps().clone();
1213 let diffs = updates.diffs().clone();
1214 let part = match updates {
1215 BlobTraceUpdates::Row(records) => EitherOrBoth::Left(records),
1217 BlobTraceUpdates::Structured { key_values, .. } => EitherOrBoth::Right(
1218 downcast_structured(key_values, true).expect("valid schemas for structured data"),
1221 ),
1222 BlobTraceUpdates::Both(records, ext) => match self.part_decode_format {
1224 PartDecodeFormat::Row {
1225 validate_structured: false,
1226 } => EitherOrBoth::Left(records),
1227 PartDecodeFormat::Row {
1228 validate_structured: true,
1229 } => match downcast_structured(ext, false) {
1230 Some(decoders) => EitherOrBoth::Both(records, decoders),
1231 None => EitherOrBoth::Left(records),
1232 },
1233 PartDecodeFormat::Arrow => match downcast_structured(ext, false) {
1234 Some(decoders) => EitherOrBoth::Right(decoders),
1235 None => EitherOrBoth::Left(records),
1236 },
1237 },
1238 };
1239
1240 DecodedRows {
1241 part,
1242 timestamps,
1243 diffs,
1244 cursor: 0,
1245 }
1246 }
1247
1248 pub fn is_filter_pushdown_audit(&self) -> Option<impl std::fmt::Debug + use<K, V, T, D>> {
1254 self.filter_pushdown_audit.clone()
1255 }
1256}
1257
1258#[derive(Debug)]
1261pub(crate) struct EncodedPart<T> {
1262 part: BlobTraceBatchPart<T>,
1263 normalization: PartNormalization<T>,
1264}
1265
1266#[derive(Debug)]
1274pub(crate) enum PartSource<T> {
1275 Whole(EncodedPart<T>),
1276 Batched(EncodedPartBatches<T>),
1277}
1278
1279impl<T: Timestamp + Lattice + Codec64> PartSource<T> {
1280 pub(crate) fn from_hollow_blob(
1283 cfg: &FetchConfig,
1284 metrics: &Metrics,
1285 read_metrics: &ReadMetrics,
1286 registered_desc: Description<T>,
1287 part: &HollowBatchPart<T>,
1288 buf: &SegmentedBytes,
1289 ) -> Self {
1290 match cfg.part_decode_batch_rows {
1291 None => PartSource::Whole(decode_batch_part_blob(
1292 cfg,
1293 metrics,
1294 read_metrics,
1295 registered_desc,
1296 part,
1297 buf,
1298 )),
1299 Some(batch_rows) => PartSource::Batched(EncodedPartBatches::new(
1300 cfg,
1301 batch_rows,
1302 metrics,
1303 read_metrics.clone(),
1304 registered_desc,
1305 part,
1306 buf.clone(),
1307 )),
1308 }
1309 }
1310}
1311
1312#[derive(Debug)]
1315struct PartNormalization<T> {
1316 metrics: ReadMetrics,
1317 registered_desc: Description<T>,
1318 needs_truncation: bool,
1319 ts_rewrite: Option<Antichain<T>>,
1320}
1321
1322#[derive(Debug)]
1327pub(crate) struct EncodedPartBatches<T> {
1328 reader: BlobTraceBatchPartReader<T>,
1329 normalization: PartNormalization<T>,
1330}
1331
1332impl<T: Timestamp + Lattice + Codec64> EncodedPartBatches<T> {
1333 pub(crate) fn new(
1334 cfg: &FetchConfig,
1335 batch_rows: usize,
1336 metrics: &Metrics,
1337 read_metrics: ReadMetrics,
1338 registered_desc: Description<T>,
1339 part: &HollowBatchPart<T>,
1340 buf: SegmentedBytes,
1341 ) -> Self {
1342 let reader = metrics
1343 .codecs
1344 .batch
1345 .decode(|| BlobTraceBatchPartReader::new(buf, batch_rows))
1346 .map_err(|err| anyhow!("couldn't decode batch at key {}: {}", part.key, err))
1347 .expect("internal error: invalid encoded state");
1350 let normalization = PartNormalization::new(
1351 cfg,
1352 read_metrics,
1353 registered_desc,
1354 &part.key.0,
1355 part.ts_rewrite.as_ref(),
1356 reader.desc(),
1357 );
1358 EncodedPartBatches {
1359 reader,
1360 normalization,
1361 }
1362 }
1363
1364 fn next_updates(&mut self, metrics: &Metrics) -> Option<BlobTraceUpdates> {
1367 let start = Instant::now();
1368 let updates = self
1369 .reader
1370 .next_updates(&metrics.columnar)?
1371 .expect("internal error: invalid encoded state");
1372 metrics
1375 .codecs
1376 .batch
1377 .decode_seconds
1378 .inc_by(start.elapsed().as_secs_f64());
1379 self.normalization
1380 .metrics
1381 .part_goodbytes
1382 .inc_by(u64::cast_from(updates.goodbytes()));
1383 Some(self.normalization.normalize(&updates, &metrics.columnar))
1384 }
1385}
1386
1387impl<K, V, T, D> FetchedPart<K, V, T, D>
1388where
1389 K: Debug + Codec,
1390 V: Debug + Codec,
1391 T: Timestamp + Lattice + Codec64,
1392 D: Monoid + Codec64 + Send + Sync,
1393{
1394 pub fn next_with_storage(
1399 &mut self,
1400 key: &mut Option<K>,
1401 val: &mut Option<V>,
1402 ) -> Option<((K, V), T, D)> {
1403 let mut consolidated = self.peek_stash.take();
1404 loop {
1405 let row = match &mut self.rows {
1407 Some(rows) if rows.cursor < rows.timestamps.len() => {
1408 let idx = rows.cursor;
1409 rows.cursor += 1;
1410 Some((idx, rows.timestamps.values()[idx], rows.diffs.values()[idx]))
1411 }
1412 _ => None,
1413 };
1414 let next = if let Some((next_idx, t, d)) = row {
1415 let mut t = T::decode(t.to_le_bytes());
1418 if !self.ts_filter.filter_ts(&mut t) {
1419 continue;
1420 }
1421 let d = D::decode(d.to_le_bytes());
1422 if d.is_zero() {
1423 continue;
1424 }
1425 let kv = self.decode_kv(next_idx, key, val);
1426 (kv, t, d)
1427 } else if self.next_batch() {
1428 continue;
1431 } else {
1432 break;
1433 };
1434
1435 if let Some((kv, t, d)) = &mut consolidated {
1437 let (kv_next, t_next, d_next) = &next;
1438 if kv == kv_next && t == t_next {
1439 d.plus_equals(d_next);
1440 if d.is_zero() {
1441 consolidated = None;
1442 }
1443 } else {
1444 self.peek_stash = Some(next);
1445 break;
1446 }
1447 } else {
1448 consolidated = Some(next);
1449 }
1450 }
1451
1452 let (kv, t, d) = consolidated?;
1453
1454 Some((kv, t, d))
1455 }
1456
1457 fn decode_kv(&mut self, index: usize, key: &mut Option<K>, val: &mut Option<V>) -> (K, V) {
1458 let decoded = self
1459 .rows
1460 .as_ref()
1461 .expect("decoded rows present while the cursor is in bounds")
1462 .part
1463 .as_ref()
1464 .map_left(|codec| {
1465 let ((ck, cv), _, _) = codec.get(index).expect("valid index");
1466 let (k, v) = Self::decode_codec(
1467 &*self.metrics,
1468 self.migration.codec_read(),
1469 ck,
1470 cv,
1471 key,
1472 val,
1473 &mut self.key_storage,
1474 &mut self.val_storage,
1475 );
1476 (k.expect("valid legacy key"), v.expect("valid legacy value"))
1477 })
1478 .map_right(|(structured_key, structured_val)| {
1479 self.decode_structured(index, structured_key, structured_val, key, val)
1480 });
1481
1482 match decoded {
1483 EitherOrBoth::Both((k, v), (k_s, v_s)) => {
1484 let is_valid = self
1486 .metrics
1487 .columnar
1488 .arrow()
1489 .key()
1490 .report_valid(|| k_s == k);
1491 if !is_valid {
1492 soft_panic_no_log!("structured key did not match, {k_s:?} != {k:?}");
1493 }
1494 let is_valid = self
1496 .metrics
1497 .columnar
1498 .arrow()
1499 .val()
1500 .report_valid(|| v_s == v);
1501 if !is_valid {
1502 soft_panic_no_log!("structured val did not match, {v_s:?} != {v:?}");
1503 }
1504
1505 (k, v)
1506 }
1507 EitherOrBoth::Left(kv) => kv,
1508 EitherOrBoth::Right(kv) => kv,
1509 }
1510 }
1511
1512 fn decode_codec(
1513 metrics: &Metrics,
1514 read_schemas: &Schemas<K, V>,
1515 key_buf: &[u8],
1516 val_buf: &[u8],
1517 key: &mut Option<K>,
1518 val: &mut Option<V>,
1519 key_storage: &mut Option<K::Storage>,
1520 val_storage: &mut Option<V::Storage>,
1521 ) -> (Result<K, String>, Result<V, String>) {
1522 let k = metrics.codecs.key.decode(|| match key.take() {
1523 Some(mut key) => {
1524 match K::decode_from(&mut key, key_buf, key_storage, &read_schemas.key) {
1525 Ok(()) => Ok(key),
1526 Err(err) => Err(err),
1527 }
1528 }
1529 None => K::decode(key_buf, &read_schemas.key),
1530 });
1531 let v = metrics.codecs.val.decode(|| match val.take() {
1532 Some(mut val) => {
1533 match V::decode_from(&mut val, val_buf, val_storage, &read_schemas.val) {
1534 Ok(()) => Ok(val),
1535 Err(err) => Err(err),
1536 }
1537 }
1538 None => V::decode(val_buf, &read_schemas.val),
1539 });
1540 (k, v)
1541 }
1542
1543 fn decode_structured(
1544 &self,
1545 idx: usize,
1546 keys: &<K::Schema as Schema<K>>::Decoder,
1547 vals: &<V::Schema as Schema<V>>::Decoder,
1548 key: &mut Option<K>,
1549 val: &mut Option<V>,
1550 ) -> (K, V) {
1551 let mut key = key.take().unwrap_or_default();
1552 keys.decode(idx, &mut key);
1553
1554 let mut val = val.take().unwrap_or_default();
1555 vals.decode(idx, &mut val);
1556
1557 (key, val)
1558 }
1559}
1560
1561impl<K, V, T, D> Iterator for FetchedPart<K, V, T, D>
1562where
1563 K: Debug + Codec,
1564 V: Debug + Codec,
1565 T: Timestamp + Lattice + Codec64,
1566 D: Monoid + Codec64 + Send + Sync,
1567{
1568 type Item = ((K, V), T, D);
1569
1570 fn next(&mut self) -> Option<Self::Item> {
1571 self.next_with_storage(&mut None, &mut None)
1572 }
1573
1574 fn size_hint(&self) -> (usize, Option<usize>) {
1575 let max_len = self.part_rows;
1577 (0, Some(max_len))
1578 }
1579}
1580
1581impl<T> EncodedPart<T>
1582where
1583 T: Timestamp + Lattice + Codec64,
1584{
1585 pub async fn fetch(
1586 cfg: &FetchConfig,
1587 shard_id: &ShardId,
1588 blob: &dyn Blob,
1589 metrics: &Metrics,
1590 shard_metrics: &ShardMetrics,
1591 read_metrics: &ReadMetrics,
1592 registered_desc: &Description<T>,
1593 part: &BatchPart<T>,
1594 ) -> Result<Self, BlobKey> {
1595 match part {
1596 BatchPart::Hollow(x) => {
1597 fetch_batch_part(
1598 cfg,
1599 shard_id,
1600 blob,
1601 metrics,
1602 shard_metrics,
1603 read_metrics,
1604 registered_desc,
1605 x,
1606 )
1607 .await
1608 }
1609 BatchPart::Inline {
1610 updates,
1611 ts_rewrite,
1612 ..
1613 } => Ok(EncodedPart::from_inline(
1614 cfg,
1615 metrics,
1616 read_metrics.clone(),
1617 registered_desc.clone(),
1618 updates,
1619 ts_rewrite.as_ref(),
1620 )),
1621 }
1622 }
1623
1624 pub(crate) fn from_inline(
1625 cfg: &FetchConfig,
1626 metrics: &Metrics,
1627 read_metrics: ReadMetrics,
1628 desc: Description<T>,
1629 x: &LazyInlineBatchPart,
1630 ts_rewrite: Option<&Antichain<T>>,
1631 ) -> Self {
1632 let parsed = x.decode(&metrics.columnar).expect("valid inline part");
1633 Self::new(cfg, read_metrics, desc, "inline", ts_rewrite, parsed)
1634 }
1635
1636 pub(crate) fn from_hollow(
1637 cfg: &FetchConfig,
1638 metrics: ReadMetrics,
1639 registered_desc: Description<T>,
1640 part: &HollowBatchPart<T>,
1641 parsed: BlobTraceBatchPart<T>,
1642 ) -> Self {
1643 Self::new(
1644 cfg,
1645 metrics,
1646 registered_desc,
1647 &part.key.0,
1648 part.ts_rewrite.as_ref(),
1649 parsed,
1650 )
1651 }
1652
1653 pub(crate) fn new(
1654 cfg: &FetchConfig,
1655 metrics: ReadMetrics,
1656 registered_desc: Description<T>,
1657 printable_name: &str,
1658 ts_rewrite: Option<&Antichain<T>>,
1659 parsed: BlobTraceBatchPart<T>,
1660 ) -> Self {
1661 let normalization = PartNormalization::new(
1662 cfg,
1663 metrics,
1664 registered_desc,
1665 printable_name,
1666 ts_rewrite,
1667 &parsed.desc,
1668 );
1669 EncodedPart {
1670 part: parsed,
1671 normalization,
1672 }
1673 }
1674
1675 pub(crate) fn maybe_unconsolidated(&self) -> bool {
1676 self.part.desc.since().borrow() == AntichainRef::new(&[T::minimum()])
1679 }
1680
1681 pub(crate) fn updates(&self) -> &BlobTraceUpdates {
1682 &self.part.updates
1683 }
1684
1685 pub(crate) fn normalize(&self, metrics: &ColumnarMetrics) -> BlobTraceUpdates {
1687 self.normalization.normalize(&self.part.updates, metrics)
1688 }
1689}
1690
1691impl<T: Timestamp + Lattice + Codec64> PartNormalization<T> {
1692 fn new(
1693 cfg: &FetchConfig,
1694 metrics: ReadMetrics,
1695 registered_desc: Description<T>,
1696 printable_name: &str,
1697 ts_rewrite: Option<&Antichain<T>>,
1698 inline_desc: &Description<T>,
1699 ) -> Self {
1700 let needs_truncation = inline_desc.lower() != registered_desc.lower()
1715 || inline_desc.upper() != registered_desc.upper();
1716 if needs_truncation {
1717 if cfg.validate_bounds_on_read {
1718 soft_assert_or_log!(
1719 PartialOrder::less_equal(inline_desc.lower(), registered_desc.lower()),
1720 "key={} inline={:?} registered={:?}",
1721 printable_name,
1722 inline_desc,
1723 registered_desc
1724 );
1725
1726 if ts_rewrite.is_none() {
1727 soft_assert_or_log!(
1732 PartialOrder::less_equal(registered_desc.upper(), inline_desc.upper()),
1733 "key={} inline={:?} registered={:?}",
1734 printable_name,
1735 inline_desc,
1736 registered_desc
1737 );
1738 }
1739 }
1740 assert_eq!(
1745 inline_desc.since(),
1746 &Antichain::from_elem(T::minimum()),
1747 "key={} inline={:?} registered={:?}",
1748 printable_name,
1749 inline_desc,
1750 registered_desc
1751 );
1752 } else {
1753 soft_assert_or_log!(
1754 PartialOrder::less_equal(inline_desc.since(), registered_desc.since()),
1755 "key={} inline={:?} registered={:?}",
1756 printable_name,
1757 inline_desc,
1758 registered_desc
1759 );
1760 assert_eq!(
1761 inline_desc.lower(),
1762 registered_desc.lower(),
1763 "key={} inline={:?} registered={:?}",
1764 printable_name,
1765 inline_desc,
1766 registered_desc
1767 );
1768 assert_eq!(
1769 inline_desc.upper(),
1770 registered_desc.upper(),
1771 "key={} inline={:?} registered={:?}",
1772 printable_name,
1773 inline_desc,
1774 registered_desc
1775 );
1776 }
1777
1778 PartNormalization {
1779 metrics,
1780 registered_desc,
1781 needs_truncation,
1782 ts_rewrite: ts_rewrite.cloned(),
1783 }
1784 }
1785
1786 fn normalize(&self, updates: &BlobTraceUpdates, metrics: &ColumnarMetrics) -> BlobTraceUpdates {
1788 let updates = updates.clone();
1789 if !self.needs_truncation && self.ts_rewrite.is_none() {
1790 return updates;
1791 }
1792
1793 let mut codec = updates
1794 .records()
1795 .map(|r| (r.keys().clone(), r.vals().clone()));
1796 let mut structured = updates.structured().cloned();
1797 let mut timestamps = updates.timestamps().clone();
1798 let mut diffs = updates.diffs().clone();
1799
1800 if let Some(rewrite) = self.ts_rewrite.as_ref() {
1801 timestamps = arrow::compute::unary(×tamps, |i: i64| {
1802 let mut t = T::decode(i.to_le_bytes());
1803 t.advance_by(rewrite.borrow());
1804 i64::from_le_bytes(T::encode(&t))
1805 });
1806 }
1807
1808 let reallocated = if self.needs_truncation {
1809 let filter = BooleanArray::from_unary(×tamps, |i| {
1810 let t = T::decode(i.to_le_bytes());
1811 let truncate_t = {
1812 !self.registered_desc.lower().less_equal(&t)
1813 || self.registered_desc.upper().less_equal(&t)
1814 };
1815 !truncate_t
1816 });
1817 if filter.false_count() == 0 {
1818 false
1820 } else {
1821 let filter = FilterBuilder::new(&filter).optimize().build();
1822 let do_filter = |array: &dyn Array| filter.filter(array).expect("valid filter len");
1823 if let Some((keys, vals)) = codec {
1824 codec = Some((
1825 realloc_array(do_filter(&keys).as_binary(), metrics),
1826 realloc_array(do_filter(&vals).as_binary(), metrics),
1827 ));
1828 }
1829 if let Some(ext) = structured {
1830 structured = Some(ColumnarRecordsStructuredExt {
1831 key: realloc_any(do_filter(&*ext.key), metrics),
1832 val: realloc_any(do_filter(&*ext.val), metrics),
1833 });
1834 }
1835 timestamps = realloc_array(do_filter(×tamps).as_primitive(), metrics);
1836 diffs = realloc_array(do_filter(&diffs).as_primitive(), metrics);
1837 true
1838 }
1839 } else {
1840 false
1841 };
1842
1843 if self.ts_rewrite.is_some() && !reallocated {
1844 timestamps = realloc_array(×tamps, metrics);
1845 }
1846
1847 if self.ts_rewrite.is_some() {
1848 self.metrics
1849 .ts_rewrite
1850 .inc_by(u64::cast_from(timestamps.len()));
1851 }
1852
1853 match (codec, structured) {
1854 (Some((key, value)), None) => {
1855 BlobTraceUpdates::Row(ColumnarRecords::new(key, value, timestamps, diffs))
1856 }
1857 (Some((key, value)), Some(ext)) => {
1858 BlobTraceUpdates::Both(ColumnarRecords::new(key, value, timestamps, diffs), ext)
1859 }
1860 (None, Some(ext)) => BlobTraceUpdates::Structured {
1861 key_values: ext,
1862 timestamps,
1863 diffs,
1864 },
1865 (None, None) => unreachable!(),
1866 }
1867 }
1868}
1869
1870#[derive(Debug, Serialize, Deserialize, Clone)]
1879pub struct ExchangeableBatchPart<T> {
1880 shard_id: ShardId,
1881 encoded_size_bytes: usize,
1883 desc: Description<T>,
1884 filter: FetchBatchFilter<T>,
1885 part: LazyProto<ProtoHollowBatchPart>,
1886 reader_id: LeasedReaderId,
1889 filter_pushdown_audit: bool,
1890}
1891
1892impl<T> ExchangeableBatchPart<T> {
1893 pub fn encoded_size_bytes(&self) -> usize {
1895 self.encoded_size_bytes
1896 }
1897
1898 pub fn reader_id(&self) -> &LeasedReaderId {
1900 &self.reader_id
1901 }
1902}
1903
1904#[derive(Debug, Copy, Clone)]
1908pub enum PartDecodeFormat {
1909 Row {
1911 validate_structured: bool,
1913 },
1914 Arrow,
1916}
1917
1918impl PartDecodeFormat {
1919 pub const fn default() -> Self {
1921 PartDecodeFormat::Arrow
1922 }
1923
1924 pub fn from_str(s: &str) -> Self {
1927 match s {
1928 "row" => PartDecodeFormat::Row {
1929 validate_structured: false,
1930 },
1931 "row_with_validate" => PartDecodeFormat::Row {
1932 validate_structured: true,
1933 },
1934 "arrow" => PartDecodeFormat::Arrow,
1935 x => {
1936 let default = PartDecodeFormat::default();
1937 soft_panic_or_log!("Invalid part decode format: '{x}', falling back to {default}");
1938 default
1939 }
1940 }
1941 }
1942
1943 pub const fn as_str(&self) -> &'static str {
1945 match self {
1946 PartDecodeFormat::Row {
1947 validate_structured: false,
1948 } => "row",
1949 PartDecodeFormat::Row {
1950 validate_structured: true,
1951 } => "row_with_validate",
1952 PartDecodeFormat::Arrow => "arrow",
1953 }
1954 }
1955}
1956
1957impl fmt::Display for PartDecodeFormat {
1958 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1959 f.write_str(self.as_str())
1960 }
1961}
1962
1963#[mz_ore::test]
1964fn client_exchange_data() {
1965 fn is_exchange_data<T: timely::ExchangeData>() {}
1969 is_exchange_data::<ExchangeableBatchPart<u64>>();
1970 is_exchange_data::<ExchangeableBatchPart<u64>>();
1971}
1972
1973#[cfg(test)]
1974mod tests;