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(&self, reader_id: &LeasedReaderId) -> String {
320 missing_blob_diagnostics(self.schema_cache.applier(), reader_id).await
321 }
322}
323
324pub(crate) async fn missing_blob_diagnostics<K, V, T, D>(
335 applier: &Applier<K, V, T, D>,
336 reader_id: &LeasedReaderId,
337) -> String
338where
339 K: Debug + Codec,
340 V: Debug + Codec,
341 T: Timestamp + Lattice + Codec64 + Sync,
342 D: Monoid + Codec64,
343{
344 let refresh = applier.fetch_and_update_state(None);
348 if tokio::time::timeout(Duration::from_secs(30), refresh)
349 .await
350 .is_err()
351 {
352 return format!(
353 "reader {reader_id}: could not refresh state within 30s to diagnose the lease; \
354 partitioned from consensus?"
355 );
356 }
357 match applier.reader_lease(reader_id.clone()) {
358 Some(lease_state) => format!(
359 "reader {reader_id} is still present in state ({lease_state:?}); \
360 a missing blob despite a live lease indicates a GC bug"
361 ),
362 None => format!(
363 "reader {reader_id} has been expired out of state; \
364 the process likely failed to heartbeat it within the lease duration \
365 (machine sleep, CPU/memory starvation, or a partition from consensus?)"
366 ),
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
371pub(crate) enum FetchBatchFilter<T> {
372 Snapshot {
373 as_of: Antichain<T>,
374 },
375 Listen {
376 as_of: Antichain<T>,
377 lower: Antichain<T>,
378 },
379 Compaction {
380 since: Antichain<T>,
381 },
382}
383
384impl<T: Timestamp + Lattice> FetchBatchFilter<T> {
385 pub(crate) fn filter_ts(&self, t: &mut T) -> bool {
386 match self {
387 FetchBatchFilter::Snapshot { as_of } => {
388 if as_of.less_than(t) {
390 return false;
391 }
392 t.advance_by(as_of.borrow());
393 true
394 }
395 FetchBatchFilter::Listen { as_of, lower } => {
396 if !as_of.less_than(t) {
398 return false;
399 }
400
401 if !lower.less_equal(t) {
409 return false;
410 }
411 true
412 }
413 FetchBatchFilter::Compaction { since } => {
414 t.advance_by(since.borrow());
415 true
416 }
417 }
418 }
419}
420
421pub(crate) async fn fetch_leased_part<K, V, T, D>(
426 cfg: &PersistConfig,
427 part: &LeasedBatchPart<T>,
428 blob: &dyn Blob,
429 metrics: Arc<Metrics>,
430 read_metrics: &ReadMetrics,
431 shard_metrics: &ShardMetrics,
432 reader_id: &LeasedReaderId,
433 read_schemas: Schemas<K, V>,
434 schema_cache: &mut SchemaCache<K, V, T, D>,
435) -> FetchedPart<K, V, T, D>
436where
437 K: Debug + Codec,
438 V: Debug + Codec,
439 T: Timestamp + Lattice + Codec64 + Sync,
440 D: Monoid + Codec64 + Send + Sync,
441{
442 let fetch_config = FetchConfig::from_persist_config(cfg);
443 let fetched = match &part.part {
444 BatchPart::Hollow(x) => fetch_batch_part_blob(
445 &part.shard_id,
446 blob,
447 &metrics,
448 shard_metrics,
449 read_metrics,
450 x,
451 )
452 .await
453 .map(|buf| {
454 PartSource::from_hollow_blob(
455 &fetch_config,
456 &metrics,
457 read_metrics,
458 part.desc.clone(),
459 x,
460 &buf,
461 )
462 }),
463 BatchPart::Inline {
464 updates,
465 ts_rewrite,
466 ..
467 } => Ok(PartSource::Whole(EncodedPart::from_inline(
468 &fetch_config,
469 &metrics,
470 read_metrics.clone(),
471 part.desc.clone(),
472 updates,
473 ts_rewrite.as_ref(),
474 ))),
475 };
476 let source = match fetched {
477 Ok(x) => x,
478 Err(blob_key) => {
479 let diagnostics = missing_blob_diagnostics(schema_cache.applier(), reader_id).await;
488 panic!("could not fetch batch part {}: {}", blob_key, diagnostics)
489 }
490 };
491 let part_cfg = BatchFetcherConfig::new(cfg);
492 let migration = PartMigration::new(&part.part, read_schemas, schema_cache)
493 .await
494 .unwrap_or_else(|read_schemas| {
495 panic!(
496 "could not decode part {:?} with schema: {:?}",
497 part.part.schema_id(),
498 read_schemas
499 )
500 });
501 FetchedPart::new(
502 metrics,
503 source,
504 migration,
505 part.filter.clone(),
506 part.filter_pushdown_audit,
507 part_cfg.part_decode_format(),
508 part.part.stats(),
509 )
510}
511
512pub(crate) async fn fetch_batch_part_blob<T>(
513 shard_id: &ShardId,
514 blob: &dyn Blob,
515 metrics: &Metrics,
516 shard_metrics: &ShardMetrics,
517 read_metrics: &ReadMetrics,
518 part: &HollowBatchPart<T>,
519) -> Result<SegmentedBytes, BlobKey> {
520 let now = Instant::now();
521 let get_span = debug_span!("fetch_batch::get");
522 let blob_key = part.key.complete(shard_id);
523 let value = retry_external(&metrics.retries.external.fetch_batch_get, || async {
524 shard_metrics.blob_gets.inc();
525 blob.get(&blob_key)
529 .await
530 .map_err(|err| err.context(format!("blob {blob_key}")))
531 })
532 .instrument(get_span.clone())
533 .await
534 .ok_or(blob_key)?;
535
536 drop(get_span);
537
538 read_metrics.part_count.inc();
539 read_metrics.part_bytes.inc_by(u64::cast_from(value.len()));
540 read_metrics.seconds.inc_by(now.elapsed().as_secs_f64());
541
542 Ok(value)
543}
544
545pub(crate) fn decode_batch_part_blob<T>(
546 cfg: &FetchConfig,
547 metrics: &Metrics,
548 read_metrics: &ReadMetrics,
549 registered_desc: Description<T>,
550 part: &HollowBatchPart<T>,
551 buf: &SegmentedBytes,
552) -> EncodedPart<T>
553where
554 T: Timestamp + Lattice + Codec64,
555{
556 trace_span!("fetch_batch::decode").in_scope(|| {
557 let parsed = metrics
558 .codecs
559 .batch
560 .decode(|| BlobTraceBatchPart::decode(buf, &metrics.columnar))
561 .map_err(|err| anyhow!("couldn't decode batch at key {}: {}", part.key, err))
562 .expect("internal error: invalid encoded state");
567 read_metrics
568 .part_goodbytes
569 .inc_by(u64::cast_from(parsed.updates.goodbytes()));
570 EncodedPart::from_hollow(cfg, read_metrics.clone(), registered_desc, part, parsed)
571 })
572}
573
574pub(crate) async fn fetch_batch_part<T>(
575 cfg: &FetchConfig,
576 shard_id: &ShardId,
577 blob: &dyn Blob,
578 metrics: &Metrics,
579 shard_metrics: &ShardMetrics,
580 read_metrics: &ReadMetrics,
581 registered_desc: &Description<T>,
582 part: &HollowBatchPart<T>,
583) -> Result<EncodedPart<T>, BlobKey>
584where
585 T: Timestamp + Lattice + Codec64,
586{
587 let buf =
588 fetch_batch_part_blob(shard_id, blob, metrics, shard_metrics, read_metrics, part).await?;
589 let part = decode_batch_part_blob(
590 cfg,
591 metrics,
592 read_metrics,
593 registered_desc.clone(),
594 part,
595 &buf,
596 );
597 Ok(part)
598}
599
600#[derive(Clone, Debug)]
607pub struct Lease(Arc<SeqNo>);
608
609impl Lease {
610 pub fn new(seqno: SeqNo) -> Self {
612 Self(Arc::new(seqno))
613 }
614
615 pub fn seqno(&self) -> SeqNo {
617 *self.0
618 }
619
620 pub fn count(&self) -> usize {
622 Arc::strong_count(&self.0)
623 }
624}
625
626#[derive(Debug)]
652pub struct LeasedBatchPart<T> {
653 pub(crate) metrics: Arc<Metrics>,
654 pub(crate) shard_id: ShardId,
655 pub(crate) filter: FetchBatchFilter<T>,
656 pub(crate) desc: Description<T>,
657 pub(crate) part: BatchPart<T>,
658 pub(crate) lease: Lease,
661 pub(crate) reader_id: LeasedReaderId,
665 pub(crate) filter_pushdown_audit: bool,
666 pub(crate) bounds_truncated: bool,
671}
672
673impl<T> LeasedBatchPart<T>
674where
675 T: Timestamp + Codec64,
676{
677 pub(crate) fn into_exchangeable_part(self) -> (ExchangeableBatchPart<T>, Lease) {
687 let lease = self.lease.clone();
689 let part = ExchangeableBatchPart {
690 shard_id: self.shard_id,
691 encoded_size_bytes: self.part.encoded_size_bytes(),
692 desc: self.desc.clone(),
693 filter: self.filter.clone(),
694 part: LazyProto::from(&self.part.into_proto()),
695 reader_id: self.reader_id.clone(),
696 filter_pushdown_audit: self.filter_pushdown_audit,
697 };
698 (part, lease)
699 }
700
701 pub fn encoded_size_bytes(&self) -> usize {
703 self.part.encoded_size_bytes()
704 }
705
706 pub fn request_filter_pushdown_audit(&mut self) {
711 self.filter_pushdown_audit = true;
712 }
713
714 pub fn stats(&self) -> Option<PartStats> {
719 self.part.stats().and_then(|x| x.try_decode().ok())
720 }
721
722 pub fn maybe_optimize(&mut self, cfg: &ConfigSet, key: ArrayRef, val: ArrayRef) {
725 assert_eq!(key.len(), 1, "expect a single-row key array");
726 assert_eq!(val.len(), 1, "expect a single-row val array");
727 let as_of = match &self.filter {
728 FetchBatchFilter::Snapshot { as_of } => as_of,
729 FetchBatchFilter::Listen { .. } | FetchBatchFilter::Compaction { .. } => return,
730 };
731 if !OPTIMIZE_IGNORED_DATA_FETCH.get(cfg) {
732 return;
733 }
734 if self.bounds_truncated {
738 return;
739 }
740 let (diffs_sum, _stats) = match &self.part {
741 BatchPart::Hollow(x) => (x.diffs_sum, x.stats.as_ref()),
742 BatchPart::Inline { .. } => return,
743 };
744 debug!(
745 "try_optimize_ignored_data_fetch diffs_sum={:?} as_of={:?} lower={:?} upper={:?}",
746 diffs_sum.map(i64::decode),
748 as_of.elements(),
749 self.desc.lower().elements(),
750 self.desc.upper().elements()
751 );
752 let as_of = match &as_of.elements() {
753 &[as_of] => as_of,
754 _ => return,
755 };
756 let eligible = self.desc.upper().less_equal(as_of) && self.desc.since().less_equal(as_of);
762 if !eligible {
763 return;
764 }
765 let Some(diffs_sum) = diffs_sum else {
766 return;
767 };
768
769 debug!(
770 "try_optimize_ignored_data_fetch faked {:?} diffs at ts {:?} skipping fetch of {} bytes",
771 i64::decode(diffs_sum),
773 as_of,
774 self.part.encoded_size_bytes(),
775 );
776 self.metrics.pushdown.parts_faked_count.inc();
777 self.metrics
778 .pushdown
779 .parts_faked_bytes
780 .inc_by(u64::cast_from(self.part.encoded_size_bytes()));
781 let timestamps = {
782 let mut col = Codec64Mut::with_capacity(1);
783 col.push(as_of);
784 col.finish()
785 };
786 let diffs = {
787 let mut col = Codec64Mut::with_capacity(1);
788 col.push_raw(diffs_sum);
789 col.finish()
790 };
791 let updates = BlobTraceUpdates::Structured {
792 key_values: ColumnarRecordsStructuredExt { key, val },
793 timestamps,
794 diffs,
795 };
796 let faked_data = LazyInlineBatchPart::from(&ProtoInlineBatchPart {
797 desc: Some(self.desc.into_proto()),
798 index: 0,
799 updates: Some(updates.into_proto()),
800 });
801 self.part = BatchPart::Inline {
802 updates: faked_data,
803 ts_rewrite: None,
804 schema_id: None,
805 deprecated_schema_id: None,
806 };
807 }
808}
809
810impl<T> Drop for LeasedBatchPart<T> {
811 fn drop(&mut self) {
813 self.metrics.lease.dropped_part.inc()
814 }
815}
816
817#[derive(Debug)]
822pub struct FetchedBlob<K: Codec, V: Codec, T, D> {
823 metrics: Arc<Metrics>,
824 read_metrics: ReadMetrics,
825 buf: FetchedBlobBuf<T>,
826 registered_desc: Description<T>,
827 migration: PartMigration<K, V>,
828 filter: FetchBatchFilter<T>,
829 filter_pushdown_audit: bool,
830 structured_part_audit: PartDecodeFormat,
831 fetch_permit: Option<Arc<MetricsPermits>>,
832 fetch_config: FetchConfig,
833 _phantom: PhantomData<fn() -> D>,
834}
835
836#[derive(Debug, Clone)]
837enum FetchedBlobBuf<T> {
838 Hollow {
839 buf: SegmentedBytes,
840 part: HollowBatchPart<T>,
841 },
842 Inline {
843 desc: Description<T>,
844 updates: LazyInlineBatchPart,
845 ts_rewrite: Option<Antichain<T>>,
846 },
847}
848
849impl<K: Codec, V: Codec, T: Clone, D> Clone for FetchedBlob<K, V, T, D> {
850 fn clone(&self) -> Self {
851 Self {
852 metrics: Arc::clone(&self.metrics),
853 read_metrics: self.read_metrics.clone(),
854 buf: self.buf.clone(),
855 registered_desc: self.registered_desc.clone(),
856 migration: self.migration.clone(),
857 filter: self.filter.clone(),
858 filter_pushdown_audit: self.filter_pushdown_audit.clone(),
859 fetch_permit: self.fetch_permit.clone(),
860 structured_part_audit: self.structured_part_audit.clone(),
861 fetch_config: self.fetch_config.clone(),
862 _phantom: self._phantom.clone(),
863 }
864 }
865}
866
867pub struct ShardSourcePart<K: Codec, V: Codec, T, D> {
870 pub part: FetchedPart<K, V, T, D>,
872 fetch_permit: Option<Arc<MetricsPermits>>,
873}
874
875impl<K, V, T: Debug, D: Debug> Debug for ShardSourcePart<K, V, T, D>
876where
877 K: Codec + Debug,
878 <K as Codec>::Storage: Debug,
879 V: Codec + Debug,
880 <V as Codec>::Storage: Debug,
881{
882 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
883 let ShardSourcePart { part, fetch_permit } = self;
884 f.debug_struct("ShardSourcePart")
885 .field("part", part)
886 .field("fetch_permit", fetch_permit)
887 .finish()
888 }
889}
890
891impl<K: Codec, V: Codec, T: Timestamp + Lattice + Codec64, D> FetchedBlob<K, V, T, D> {
892 pub fn parse(&self) -> ShardSourcePart<K, V, T, D> {
894 self.parse_internal(&self.fetch_config)
895 }
896
897 pub(crate) fn parse_internal(&self, cfg: &FetchConfig) -> ShardSourcePart<K, V, T, D> {
899 let (part, stats) = match &self.buf {
900 FetchedBlobBuf::Hollow { buf, part } => {
901 let source = PartSource::from_hollow_blob(
902 cfg,
903 &self.metrics,
904 &self.read_metrics,
905 self.registered_desc.clone(),
906 part,
907 buf,
908 );
909 (source, part.stats.as_ref())
910 }
911 FetchedBlobBuf::Inline {
912 desc,
913 updates,
914 ts_rewrite,
915 } => {
916 let parsed = EncodedPart::from_inline(
917 cfg,
918 &self.metrics,
919 self.read_metrics.clone(),
920 desc.clone(),
921 updates,
922 ts_rewrite.as_ref(),
923 );
924 (PartSource::Whole(parsed), None)
925 }
926 };
927 let part = FetchedPart::new(
928 Arc::clone(&self.metrics),
929 part,
930 self.migration.clone(),
931 self.filter.clone(),
932 self.filter_pushdown_audit,
933 self.structured_part_audit,
934 stats,
935 );
936 ShardSourcePart {
937 part,
938 fetch_permit: self.fetch_permit.clone(),
939 }
940 }
941
942 pub fn stats(&self) -> Option<PartStats> {
947 match &self.buf {
948 FetchedBlobBuf::Hollow { part, .. } => {
949 part.stats.as_ref().and_then(|x| x.try_decode().ok())
950 }
951 FetchedBlobBuf::Inline { .. } => None,
952 }
953 }
954}
955
956#[derive(Debug)]
961pub struct FetchedPart<K: Codec, V: Codec, T, D> {
962 metrics: Arc<Metrics>,
963 ts_filter: FetchBatchFilter<T>,
964 rows: Option<DecodedRows<K, V>>,
967 batches: Option<EncodedPartBatches<T>>,
969 part_decode_format: PartDecodeFormat,
970 part_rows: usize,
972 migration: PartMigration<K, V>,
973 filter_pushdown_audit: Option<LazyPartStats>,
974 peek_stash: Option<((K, V), T, D)>,
975 key_storage: Option<K::Storage>,
976 val_storage: Option<V::Storage>,
977
978 _phantom: PhantomData<fn() -> D>,
979}
980
981#[derive(Debug)]
983struct DecodedRows<K: Codec, V: Codec> {
984 part: EitherOrBoth<
987 ColumnarRecords,
988 (
989 <K::Schema as Schema<K>>::Decoder,
990 <V::Schema as Schema<V>>::Decoder,
991 ),
992 >,
993 timestamps: Int64Array,
994 diffs: Int64Array,
995 cursor: usize,
996}
997
998impl<K: Codec, V: Codec, T: Timestamp + Lattice + Codec64, D> FetchedPart<K, V, T, D> {
999 pub(crate) fn new(
1000 metrics: Arc<Metrics>,
1001 source: PartSource<T>,
1002 migration: PartMigration<K, V>,
1003 ts_filter: FetchBatchFilter<T>,
1004 filter_pushdown_audit: bool,
1005 part_decode_format: PartDecodeFormat,
1006 stats: Option<&LazyPartStats>,
1007 ) -> Self {
1008 let (updates, batches, part_rows) = match source {
1009 PartSource::Whole(part) => {
1010 let part_rows = part.updates().len();
1011 (Some(part.normalize(&metrics.columnar)), None, part_rows)
1012 }
1013 PartSource::Batched(batches) => {
1014 let part_rows = batches.reader.num_rows();
1015 (None, Some(batches), part_rows)
1016 }
1017 };
1018 let part_len = u64::cast_from(part_rows);
1019 match &migration {
1020 PartMigration::SameSchema { .. } => metrics.schema.migration_count_same.inc(),
1021 PartMigration::Schemaless { .. } => {
1022 metrics.schema.migration_count_codec.inc();
1023 metrics.schema.migration_len_legacy_codec.inc_by(part_len);
1024 }
1025 PartMigration::Either { .. } => {
1026 metrics.schema.migration_count_either.inc();
1027 match part_decode_format {
1028 PartDecodeFormat::Row {
1029 validate_structured: false,
1030 } => metrics.schema.migration_len_either_codec.inc_by(part_len),
1031 PartDecodeFormat::Row {
1032 validate_structured: true,
1033 } => {
1034 metrics.schema.migration_len_either_codec.inc_by(part_len);
1035 metrics.schema.migration_len_either_arrow.inc_by(part_len);
1036 }
1037 PartDecodeFormat::Arrow => {
1038 metrics.schema.migration_len_either_arrow.inc_by(part_len)
1039 }
1040 }
1041 }
1042 }
1043
1044 let filter_pushdown_audit = if filter_pushdown_audit {
1045 stats.cloned()
1046 } else {
1047 None
1048 };
1049
1050 let mut ret = FetchedPart {
1051 metrics,
1052 ts_filter,
1053 rows: None,
1054 peek_stash: None,
1055 batches,
1056 part_decode_format,
1057 part_rows,
1058 migration,
1059 filter_pushdown_audit,
1060 key_storage: None,
1061 val_storage: None,
1062 _phantom: PhantomData,
1063 };
1064 ret.rows = updates.map(|updates| ret.decode_rows(updates));
1065 ret
1066 }
1067
1068 fn next_batch(&mut self) -> bool {
1073 let Some(batches) = self.batches.as_mut() else {
1074 return false;
1075 };
1076 self.rows = None;
1079 match batches.next_updates(&self.metrics) {
1080 Some(updates) => {
1081 self.rows = Some(self.decode_rows(updates));
1082 true
1083 }
1084 None => {
1085 self.batches = None;
1086 false
1087 }
1088 }
1089 }
1090
1091 fn decode_rows(&self, updates: BlobTraceUpdates) -> DecodedRows<K, V> {
1093 let metrics = &*self.metrics;
1094 let migration = &self.migration;
1095 let downcast_structured = |structured: ColumnarRecordsStructuredExt,
1096 structured_only: bool| {
1097 let key_size_before = ArrayOrd::new(&structured.key).goodbytes();
1098
1099 let structured = match migration {
1100 PartMigration::SameSchema { .. } => structured,
1101 PartMigration::Schemaless { read } if structured_only => {
1102 let start = Instant::now();
1104 let read_key = data_type::<K>(&*read.key).ok()?;
1105 let read_val = data_type::<V>(&*read.val).ok()?;
1106 let key_migration = backward_compatible(structured.key.data_type(), &read_key)?;
1107 let val_migration = backward_compatible(structured.val.data_type(), &read_val)?;
1108 let key = key_migration.migrate(structured.key);
1109 let val = val_migration.migrate(structured.val);
1110 metrics
1111 .schema
1112 .migration_migrate_seconds
1113 .inc_by(start.elapsed().as_secs_f64());
1114 ColumnarRecordsStructuredExt { key, val }
1115 }
1116 PartMigration::Schemaless { .. } => return None,
1117 PartMigration::Either {
1118 write: _,
1119 read: _,
1120 key_migration,
1121 val_migration,
1122 } => {
1123 let start = Instant::now();
1124 let key = key_migration.migrate(structured.key);
1125 let val = val_migration.migrate(structured.val);
1126 metrics
1127 .schema
1128 .migration_migrate_seconds
1129 .inc_by(start.elapsed().as_secs_f64());
1130 ColumnarRecordsStructuredExt { key, val }
1131 }
1132 };
1133
1134 let read_schema = migration.codec_read();
1135 let key = K::Schema::decoder_any(&*read_schema.key, &*structured.key);
1136 let val = V::Schema::decoder_any(&*read_schema.val, &*structured.val);
1137
1138 match &key {
1139 Ok(key_decoder) => {
1140 let key_size_after = key_decoder.goodbytes();
1141 let key_diff = key_size_before.saturating_sub(key_size_after);
1142 metrics
1143 .pushdown
1144 .parts_projection_trimmed_bytes
1145 .inc_by(u64::cast_from(key_diff));
1146 }
1147 Err(e) => {
1148 soft_panic_or_log!("failed to create decoder: {e:#?}");
1149 }
1150 }
1151
1152 Some((key.ok()?, val.ok()?))
1153 };
1154
1155 let timestamps = updates.timestamps().clone();
1156 let diffs = updates.diffs().clone();
1157 let part = match updates {
1158 BlobTraceUpdates::Row(records) => EitherOrBoth::Left(records),
1160 BlobTraceUpdates::Structured { key_values, .. } => EitherOrBoth::Right(
1161 downcast_structured(key_values, true).expect("valid schemas for structured data"),
1164 ),
1165 BlobTraceUpdates::Both(records, ext) => match self.part_decode_format {
1167 PartDecodeFormat::Row {
1168 validate_structured: false,
1169 } => EitherOrBoth::Left(records),
1170 PartDecodeFormat::Row {
1171 validate_structured: true,
1172 } => match downcast_structured(ext, false) {
1173 Some(decoders) => EitherOrBoth::Both(records, decoders),
1174 None => EitherOrBoth::Left(records),
1175 },
1176 PartDecodeFormat::Arrow => match downcast_structured(ext, false) {
1177 Some(decoders) => EitherOrBoth::Right(decoders),
1178 None => EitherOrBoth::Left(records),
1179 },
1180 },
1181 };
1182
1183 DecodedRows {
1184 part,
1185 timestamps,
1186 diffs,
1187 cursor: 0,
1188 }
1189 }
1190
1191 pub fn is_filter_pushdown_audit(&self) -> Option<impl std::fmt::Debug + use<K, V, T, D>> {
1197 self.filter_pushdown_audit.clone()
1198 }
1199}
1200
1201#[derive(Debug)]
1204pub(crate) struct EncodedPart<T> {
1205 part: BlobTraceBatchPart<T>,
1206 normalization: PartNormalization<T>,
1207}
1208
1209#[derive(Debug)]
1217pub(crate) enum PartSource<T> {
1218 Whole(EncodedPart<T>),
1219 Batched(EncodedPartBatches<T>),
1220}
1221
1222impl<T: Timestamp + Lattice + Codec64> PartSource<T> {
1223 pub(crate) fn from_hollow_blob(
1226 cfg: &FetchConfig,
1227 metrics: &Metrics,
1228 read_metrics: &ReadMetrics,
1229 registered_desc: Description<T>,
1230 part: &HollowBatchPart<T>,
1231 buf: &SegmentedBytes,
1232 ) -> Self {
1233 match cfg.part_decode_batch_rows {
1234 None => PartSource::Whole(decode_batch_part_blob(
1235 cfg,
1236 metrics,
1237 read_metrics,
1238 registered_desc,
1239 part,
1240 buf,
1241 )),
1242 Some(batch_rows) => PartSource::Batched(EncodedPartBatches::new(
1243 cfg,
1244 batch_rows,
1245 metrics,
1246 read_metrics.clone(),
1247 registered_desc,
1248 part,
1249 buf.clone(),
1250 )),
1251 }
1252 }
1253}
1254
1255#[derive(Debug)]
1258struct PartNormalization<T> {
1259 metrics: ReadMetrics,
1260 registered_desc: Description<T>,
1261 needs_truncation: bool,
1262 ts_rewrite: Option<Antichain<T>>,
1263}
1264
1265#[derive(Debug)]
1270pub(crate) struct EncodedPartBatches<T> {
1271 reader: BlobTraceBatchPartReader<T>,
1272 normalization: PartNormalization<T>,
1273}
1274
1275impl<T: Timestamp + Lattice + Codec64> EncodedPartBatches<T> {
1276 pub(crate) fn new(
1277 cfg: &FetchConfig,
1278 batch_rows: usize,
1279 metrics: &Metrics,
1280 read_metrics: ReadMetrics,
1281 registered_desc: Description<T>,
1282 part: &HollowBatchPart<T>,
1283 buf: SegmentedBytes,
1284 ) -> Self {
1285 let reader = metrics
1286 .codecs
1287 .batch
1288 .decode(|| BlobTraceBatchPartReader::new(buf, batch_rows))
1289 .map_err(|err| anyhow!("couldn't decode batch at key {}: {}", part.key, err))
1290 .expect("internal error: invalid encoded state");
1293 let normalization = PartNormalization::new(
1294 cfg,
1295 read_metrics,
1296 registered_desc,
1297 &part.key.0,
1298 part.ts_rewrite.as_ref(),
1299 reader.desc(),
1300 );
1301 EncodedPartBatches {
1302 reader,
1303 normalization,
1304 }
1305 }
1306
1307 fn next_updates(&mut self, metrics: &Metrics) -> Option<BlobTraceUpdates> {
1310 let start = Instant::now();
1311 let updates = self
1312 .reader
1313 .next_updates(&metrics.columnar)?
1314 .expect("internal error: invalid encoded state");
1315 metrics
1318 .codecs
1319 .batch
1320 .decode_seconds
1321 .inc_by(start.elapsed().as_secs_f64());
1322 self.normalization
1323 .metrics
1324 .part_goodbytes
1325 .inc_by(u64::cast_from(updates.goodbytes()));
1326 Some(self.normalization.normalize(&updates, &metrics.columnar))
1327 }
1328}
1329
1330impl<K, V, T, D> FetchedPart<K, V, T, D>
1331where
1332 K: Debug + Codec,
1333 V: Debug + Codec,
1334 T: Timestamp + Lattice + Codec64,
1335 D: Monoid + Codec64 + Send + Sync,
1336{
1337 pub fn next_with_storage(
1342 &mut self,
1343 key: &mut Option<K>,
1344 val: &mut Option<V>,
1345 ) -> Option<((K, V), T, D)> {
1346 let mut consolidated = self.peek_stash.take();
1347 loop {
1348 let row = match &mut self.rows {
1350 Some(rows) if rows.cursor < rows.timestamps.len() => {
1351 let idx = rows.cursor;
1352 rows.cursor += 1;
1353 Some((idx, rows.timestamps.values()[idx], rows.diffs.values()[idx]))
1354 }
1355 _ => None,
1356 };
1357 let next = if let Some((next_idx, t, d)) = row {
1358 let mut t = T::decode(t.to_le_bytes());
1361 if !self.ts_filter.filter_ts(&mut t) {
1362 continue;
1363 }
1364 let d = D::decode(d.to_le_bytes());
1365 if d.is_zero() {
1366 continue;
1367 }
1368 let kv = self.decode_kv(next_idx, key, val);
1369 (kv, t, d)
1370 } else if self.next_batch() {
1371 continue;
1374 } else {
1375 break;
1376 };
1377
1378 if let Some((kv, t, d)) = &mut consolidated {
1380 let (kv_next, t_next, d_next) = &next;
1381 if kv == kv_next && t == t_next {
1382 d.plus_equals(d_next);
1383 if d.is_zero() {
1384 consolidated = None;
1385 }
1386 } else {
1387 self.peek_stash = Some(next);
1388 break;
1389 }
1390 } else {
1391 consolidated = Some(next);
1392 }
1393 }
1394
1395 let (kv, t, d) = consolidated?;
1396
1397 Some((kv, t, d))
1398 }
1399
1400 fn decode_kv(&mut self, index: usize, key: &mut Option<K>, val: &mut Option<V>) -> (K, V) {
1401 let decoded = self
1402 .rows
1403 .as_ref()
1404 .expect("decoded rows present while the cursor is in bounds")
1405 .part
1406 .as_ref()
1407 .map_left(|codec| {
1408 let ((ck, cv), _, _) = codec.get(index).expect("valid index");
1409 let (k, v) = Self::decode_codec(
1410 &*self.metrics,
1411 self.migration.codec_read(),
1412 ck,
1413 cv,
1414 key,
1415 val,
1416 &mut self.key_storage,
1417 &mut self.val_storage,
1418 );
1419 (k.expect("valid legacy key"), v.expect("valid legacy value"))
1420 })
1421 .map_right(|(structured_key, structured_val)| {
1422 self.decode_structured(index, structured_key, structured_val, key, val)
1423 });
1424
1425 match decoded {
1426 EitherOrBoth::Both((k, v), (k_s, v_s)) => {
1427 let is_valid = self
1429 .metrics
1430 .columnar
1431 .arrow()
1432 .key()
1433 .report_valid(|| k_s == k);
1434 if !is_valid {
1435 soft_panic_no_log!("structured key did not match, {k_s:?} != {k:?}");
1436 }
1437 let is_valid = self
1439 .metrics
1440 .columnar
1441 .arrow()
1442 .val()
1443 .report_valid(|| v_s == v);
1444 if !is_valid {
1445 soft_panic_no_log!("structured val did not match, {v_s:?} != {v:?}");
1446 }
1447
1448 (k, v)
1449 }
1450 EitherOrBoth::Left(kv) => kv,
1451 EitherOrBoth::Right(kv) => kv,
1452 }
1453 }
1454
1455 fn decode_codec(
1456 metrics: &Metrics,
1457 read_schemas: &Schemas<K, V>,
1458 key_buf: &[u8],
1459 val_buf: &[u8],
1460 key: &mut Option<K>,
1461 val: &mut Option<V>,
1462 key_storage: &mut Option<K::Storage>,
1463 val_storage: &mut Option<V::Storage>,
1464 ) -> (Result<K, String>, Result<V, String>) {
1465 let k = metrics.codecs.key.decode(|| match key.take() {
1466 Some(mut key) => {
1467 match K::decode_from(&mut key, key_buf, key_storage, &read_schemas.key) {
1468 Ok(()) => Ok(key),
1469 Err(err) => Err(err),
1470 }
1471 }
1472 None => K::decode(key_buf, &read_schemas.key),
1473 });
1474 let v = metrics.codecs.val.decode(|| match val.take() {
1475 Some(mut val) => {
1476 match V::decode_from(&mut val, val_buf, val_storage, &read_schemas.val) {
1477 Ok(()) => Ok(val),
1478 Err(err) => Err(err),
1479 }
1480 }
1481 None => V::decode(val_buf, &read_schemas.val),
1482 });
1483 (k, v)
1484 }
1485
1486 fn decode_structured(
1487 &self,
1488 idx: usize,
1489 keys: &<K::Schema as Schema<K>>::Decoder,
1490 vals: &<V::Schema as Schema<V>>::Decoder,
1491 key: &mut Option<K>,
1492 val: &mut Option<V>,
1493 ) -> (K, V) {
1494 let mut key = key.take().unwrap_or_default();
1495 keys.decode(idx, &mut key);
1496
1497 let mut val = val.take().unwrap_or_default();
1498 vals.decode(idx, &mut val);
1499
1500 (key, val)
1501 }
1502}
1503
1504impl<K, V, T, D> Iterator for FetchedPart<K, V, T, D>
1505where
1506 K: Debug + Codec,
1507 V: Debug + Codec,
1508 T: Timestamp + Lattice + Codec64,
1509 D: Monoid + Codec64 + Send + Sync,
1510{
1511 type Item = ((K, V), T, D);
1512
1513 fn next(&mut self) -> Option<Self::Item> {
1514 self.next_with_storage(&mut None, &mut None)
1515 }
1516
1517 fn size_hint(&self) -> (usize, Option<usize>) {
1518 let max_len = self.part_rows;
1520 (0, Some(max_len))
1521 }
1522}
1523
1524impl<T> EncodedPart<T>
1525where
1526 T: Timestamp + Lattice + Codec64,
1527{
1528 pub async fn fetch(
1529 cfg: &FetchConfig,
1530 shard_id: &ShardId,
1531 blob: &dyn Blob,
1532 metrics: &Metrics,
1533 shard_metrics: &ShardMetrics,
1534 read_metrics: &ReadMetrics,
1535 registered_desc: &Description<T>,
1536 part: &BatchPart<T>,
1537 ) -> Result<Self, BlobKey> {
1538 match part {
1539 BatchPart::Hollow(x) => {
1540 fetch_batch_part(
1541 cfg,
1542 shard_id,
1543 blob,
1544 metrics,
1545 shard_metrics,
1546 read_metrics,
1547 registered_desc,
1548 x,
1549 )
1550 .await
1551 }
1552 BatchPart::Inline {
1553 updates,
1554 ts_rewrite,
1555 ..
1556 } => Ok(EncodedPart::from_inline(
1557 cfg,
1558 metrics,
1559 read_metrics.clone(),
1560 registered_desc.clone(),
1561 updates,
1562 ts_rewrite.as_ref(),
1563 )),
1564 }
1565 }
1566
1567 pub(crate) fn from_inline(
1568 cfg: &FetchConfig,
1569 metrics: &Metrics,
1570 read_metrics: ReadMetrics,
1571 desc: Description<T>,
1572 x: &LazyInlineBatchPart,
1573 ts_rewrite: Option<&Antichain<T>>,
1574 ) -> Self {
1575 let parsed = x.decode(&metrics.columnar).expect("valid inline part");
1576 Self::new(cfg, read_metrics, desc, "inline", ts_rewrite, parsed)
1577 }
1578
1579 pub(crate) fn from_hollow(
1580 cfg: &FetchConfig,
1581 metrics: ReadMetrics,
1582 registered_desc: Description<T>,
1583 part: &HollowBatchPart<T>,
1584 parsed: BlobTraceBatchPart<T>,
1585 ) -> Self {
1586 Self::new(
1587 cfg,
1588 metrics,
1589 registered_desc,
1590 &part.key.0,
1591 part.ts_rewrite.as_ref(),
1592 parsed,
1593 )
1594 }
1595
1596 pub(crate) fn new(
1597 cfg: &FetchConfig,
1598 metrics: ReadMetrics,
1599 registered_desc: Description<T>,
1600 printable_name: &str,
1601 ts_rewrite: Option<&Antichain<T>>,
1602 parsed: BlobTraceBatchPart<T>,
1603 ) -> Self {
1604 let normalization = PartNormalization::new(
1605 cfg,
1606 metrics,
1607 registered_desc,
1608 printable_name,
1609 ts_rewrite,
1610 &parsed.desc,
1611 );
1612 EncodedPart {
1613 part: parsed,
1614 normalization,
1615 }
1616 }
1617
1618 pub(crate) fn maybe_unconsolidated(&self) -> bool {
1619 self.part.desc.since().borrow() == AntichainRef::new(&[T::minimum()])
1622 }
1623
1624 pub(crate) fn updates(&self) -> &BlobTraceUpdates {
1625 &self.part.updates
1626 }
1627
1628 pub(crate) fn normalize(&self, metrics: &ColumnarMetrics) -> BlobTraceUpdates {
1630 self.normalization.normalize(&self.part.updates, metrics)
1631 }
1632}
1633
1634impl<T: Timestamp + Lattice + Codec64> PartNormalization<T> {
1635 fn new(
1636 cfg: &FetchConfig,
1637 metrics: ReadMetrics,
1638 registered_desc: Description<T>,
1639 printable_name: &str,
1640 ts_rewrite: Option<&Antichain<T>>,
1641 inline_desc: &Description<T>,
1642 ) -> Self {
1643 let needs_truncation = inline_desc.lower() != registered_desc.lower()
1658 || inline_desc.upper() != registered_desc.upper();
1659 if needs_truncation {
1660 if cfg.validate_bounds_on_read {
1661 soft_assert_or_log!(
1662 PartialOrder::less_equal(inline_desc.lower(), registered_desc.lower()),
1663 "key={} inline={:?} registered={:?}",
1664 printable_name,
1665 inline_desc,
1666 registered_desc
1667 );
1668
1669 if ts_rewrite.is_none() {
1670 soft_assert_or_log!(
1675 PartialOrder::less_equal(registered_desc.upper(), inline_desc.upper()),
1676 "key={} inline={:?} registered={:?}",
1677 printable_name,
1678 inline_desc,
1679 registered_desc
1680 );
1681 }
1682 }
1683 assert_eq!(
1688 inline_desc.since(),
1689 &Antichain::from_elem(T::minimum()),
1690 "key={} inline={:?} registered={:?}",
1691 printable_name,
1692 inline_desc,
1693 registered_desc
1694 );
1695 } else {
1696 soft_assert_or_log!(
1697 PartialOrder::less_equal(inline_desc.since(), registered_desc.since()),
1698 "key={} inline={:?} registered={:?}",
1699 printable_name,
1700 inline_desc,
1701 registered_desc
1702 );
1703 assert_eq!(
1704 inline_desc.lower(),
1705 registered_desc.lower(),
1706 "key={} inline={:?} registered={:?}",
1707 printable_name,
1708 inline_desc,
1709 registered_desc
1710 );
1711 assert_eq!(
1712 inline_desc.upper(),
1713 registered_desc.upper(),
1714 "key={} inline={:?} registered={:?}",
1715 printable_name,
1716 inline_desc,
1717 registered_desc
1718 );
1719 }
1720
1721 PartNormalization {
1722 metrics,
1723 registered_desc,
1724 needs_truncation,
1725 ts_rewrite: ts_rewrite.cloned(),
1726 }
1727 }
1728
1729 fn normalize(&self, updates: &BlobTraceUpdates, metrics: &ColumnarMetrics) -> BlobTraceUpdates {
1731 let updates = updates.clone();
1732 if !self.needs_truncation && self.ts_rewrite.is_none() {
1733 return updates;
1734 }
1735
1736 let mut codec = updates
1737 .records()
1738 .map(|r| (r.keys().clone(), r.vals().clone()));
1739 let mut structured = updates.structured().cloned();
1740 let mut timestamps = updates.timestamps().clone();
1741 let mut diffs = updates.diffs().clone();
1742
1743 if let Some(rewrite) = self.ts_rewrite.as_ref() {
1744 timestamps = arrow::compute::unary(×tamps, |i: i64| {
1745 let mut t = T::decode(i.to_le_bytes());
1746 t.advance_by(rewrite.borrow());
1747 i64::from_le_bytes(T::encode(&t))
1748 });
1749 }
1750
1751 let reallocated = if self.needs_truncation {
1752 let filter = BooleanArray::from_unary(×tamps, |i| {
1753 let t = T::decode(i.to_le_bytes());
1754 let truncate_t = {
1755 !self.registered_desc.lower().less_equal(&t)
1756 || self.registered_desc.upper().less_equal(&t)
1757 };
1758 !truncate_t
1759 });
1760 if filter.false_count() == 0 {
1761 false
1763 } else {
1764 let filter = FilterBuilder::new(&filter).optimize().build();
1765 let do_filter = |array: &dyn Array| filter.filter(array).expect("valid filter len");
1766 if let Some((keys, vals)) = codec {
1767 codec = Some((
1768 realloc_array(do_filter(&keys).as_binary(), metrics),
1769 realloc_array(do_filter(&vals).as_binary(), metrics),
1770 ));
1771 }
1772 if let Some(ext) = structured {
1773 structured = Some(ColumnarRecordsStructuredExt {
1774 key: realloc_any(do_filter(&*ext.key), metrics),
1775 val: realloc_any(do_filter(&*ext.val), metrics),
1776 });
1777 }
1778 timestamps = realloc_array(do_filter(×tamps).as_primitive(), metrics);
1779 diffs = realloc_array(do_filter(&diffs).as_primitive(), metrics);
1780 true
1781 }
1782 } else {
1783 false
1784 };
1785
1786 if self.ts_rewrite.is_some() && !reallocated {
1787 timestamps = realloc_array(×tamps, metrics);
1788 }
1789
1790 if self.ts_rewrite.is_some() {
1791 self.metrics
1792 .ts_rewrite
1793 .inc_by(u64::cast_from(timestamps.len()));
1794 }
1795
1796 match (codec, structured) {
1797 (Some((key, value)), None) => {
1798 BlobTraceUpdates::Row(ColumnarRecords::new(key, value, timestamps, diffs))
1799 }
1800 (Some((key, value)), Some(ext)) => {
1801 BlobTraceUpdates::Both(ColumnarRecords::new(key, value, timestamps, diffs), ext)
1802 }
1803 (None, Some(ext)) => BlobTraceUpdates::Structured {
1804 key_values: ext,
1805 timestamps,
1806 diffs,
1807 },
1808 (None, None) => unreachable!(),
1809 }
1810 }
1811}
1812
1813#[derive(Debug, Serialize, Deserialize, Clone)]
1822pub struct ExchangeableBatchPart<T> {
1823 shard_id: ShardId,
1824 encoded_size_bytes: usize,
1826 desc: Description<T>,
1827 filter: FetchBatchFilter<T>,
1828 part: LazyProto<ProtoHollowBatchPart>,
1829 reader_id: LeasedReaderId,
1832 filter_pushdown_audit: bool,
1833}
1834
1835impl<T> ExchangeableBatchPart<T> {
1836 pub fn encoded_size_bytes(&self) -> usize {
1838 self.encoded_size_bytes
1839 }
1840
1841 pub fn reader_id(&self) -> &LeasedReaderId {
1843 &self.reader_id
1844 }
1845}
1846
1847#[derive(Debug, Copy, Clone)]
1851pub enum PartDecodeFormat {
1852 Row {
1854 validate_structured: bool,
1856 },
1857 Arrow,
1859}
1860
1861impl PartDecodeFormat {
1862 pub const fn default() -> Self {
1864 PartDecodeFormat::Arrow
1865 }
1866
1867 pub fn from_str(s: &str) -> Self {
1870 match s {
1871 "row" => PartDecodeFormat::Row {
1872 validate_structured: false,
1873 },
1874 "row_with_validate" => PartDecodeFormat::Row {
1875 validate_structured: true,
1876 },
1877 "arrow" => PartDecodeFormat::Arrow,
1878 x => {
1879 let default = PartDecodeFormat::default();
1880 soft_panic_or_log!("Invalid part decode format: '{x}', falling back to {default}");
1881 default
1882 }
1883 }
1884 }
1885
1886 pub const fn as_str(&self) -> &'static str {
1888 match self {
1889 PartDecodeFormat::Row {
1890 validate_structured: false,
1891 } => "row",
1892 PartDecodeFormat::Row {
1893 validate_structured: true,
1894 } => "row_with_validate",
1895 PartDecodeFormat::Arrow => "arrow",
1896 }
1897 }
1898}
1899
1900impl fmt::Display for PartDecodeFormat {
1901 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1902 f.write_str(self.as_str())
1903 }
1904}
1905
1906#[mz_ore::test]
1907fn client_exchange_data() {
1908 fn is_exchange_data<T: timely::ExchangeData>() {}
1912 is_exchange_data::<ExchangeableBatchPart<u64>>();
1913 is_exchange_data::<ExchangeableBatchPart<u64>>();
1914}
1915
1916#[cfg(test)]
1917mod tests;