1use std::borrow::Cow;
13use std::collections::{BTreeMap, BTreeSet};
14use std::fmt::Debug;
15use std::marker::PhantomData;
16use std::mem;
17use std::sync::Arc;
18use std::time::Instant;
19
20use arrow::array::{Array, Int64Array};
21use bytes::Bytes;
22use differential_dataflow::difference::Monoid;
23use differential_dataflow::lattice::Lattice;
24use differential_dataflow::trace::Description;
25use futures_util::stream::StreamExt;
26use futures_util::{FutureExt, stream};
27use mz_dyncfg::{Config, ParameterScope};
28use mz_ore::cast::CastFrom;
29use mz_ore::instrument;
30use mz_persist::indexed::encoding::{BatchColumnarFormat, BlobTraceBatchPart, BlobTraceUpdates};
31use mz_persist::location::Blob;
32use mz_persist_types::arrow::{ArrayBound, ArrayOrd};
33use mz_persist_types::columnar::{ColumnDecoder, Schema};
34use mz_persist_types::parquet::{CompressionFormat, EncodingConfig};
35use mz_persist_types::part::{Part, PartBuilder};
36use mz_persist_types::schema::SchemaId;
37use mz_persist_types::stats::{
38 PartStats, TRUNCATE_LEN, TruncateBound, trim_to_budget, truncate_bytes,
39};
40use mz_persist_types::{Codec, Codec64};
41use mz_proto::RustType;
42use mz_timely_util::order::Reverse;
43use proptest_derive::Arbitrary;
44use semver::Version;
45use timely::PartialOrder;
46use timely::order::TotalOrder;
47use timely::progress::{Antichain, Timestamp};
48use tracing::{Instrument, debug_span, trace_span, warn};
49
50use crate::async_runtime::IsolatedRuntime;
51use crate::cfg::{BATCH_BUILDER_MAX_OUTSTANDING_PARTS, MiB};
52use crate::error::InvalidUsage;
53use crate::internal::compact::{CompactConfig, Compactor};
54use crate::internal::encoding::{
55 LazyInlineBatchPart, LazyPartStats, LazyProto, MetadataMap, Schemas,
56};
57use crate::internal::machine::retry_external;
58use crate::internal::merge::{MergeTree, Pending};
59use crate::internal::metrics::{BatchWriteMetrics, Metrics, RetryMetrics, ShardMetrics};
60use crate::internal::paths::{PartId, PartialBatchKey, WriterKey};
61use crate::internal::state::{
62 BatchPart, ENABLE_INCREMENTAL_COMPACTION, HollowBatch, HollowBatchPart, HollowRun,
63 HollowRunRef, ProtoInlineBatchPart, RunId, RunMeta, RunOrder, RunPart,
64};
65use crate::stats::{STATS_BUDGET_BYTES, STATS_COLLECTION_ENABLED, untrimmable_columns};
66use crate::{PersistConfig, ShardId};
67
68include!(concat!(env!("OUT_DIR"), "/mz_persist_client.batch.rs"));
69
70#[derive(Debug)]
76pub struct Batch<K, V, T, D> {
77 pub(crate) batch_delete_enabled: bool,
78 pub(crate) metrics: Arc<Metrics>,
79 pub(crate) shard_metrics: Arc<ShardMetrics>,
80
81 pub(crate) version: Version,
83
84 pub(crate) schemas: (Bytes, Bytes),
86
87 pub(crate) batch: HollowBatch<T>,
89
90 pub(crate) blob: Arc<dyn Blob>,
92
93 pub(crate) _phantom: PhantomData<fn() -> (K, V, T, D)>,
96}
97
98impl<K, V, T, D> Drop for Batch<K, V, T, D> {
99 fn drop(&mut self) {
100 if self.batch.part_count() > 0 {
101 warn!(
102 "un-consumed Batch, with {} parts and dangling blob keys: {:?}",
103 self.batch.part_count(),
104 self.batch
105 .parts
106 .iter()
107 .map(|x| x.printable_name())
108 .collect::<Vec<_>>(),
109 );
110 }
111 }
112}
113
114impl<K, V, T, D> Batch<K, V, T, D>
115where
116 K: Debug + Codec,
117 V: Debug + Codec,
118 T: Timestamp + Lattice + Codec64,
119 D: Monoid + Codec64,
120{
121 pub(crate) fn new(
122 batch_delete_enabled: bool,
123 metrics: Arc<Metrics>,
124 blob: Arc<dyn Blob>,
125 shard_metrics: Arc<ShardMetrics>,
126 version: Version,
127 schemas: (Bytes, Bytes),
128 batch: HollowBatch<T>,
129 ) -> Self {
130 Self {
131 batch_delete_enabled,
132 metrics,
133 shard_metrics,
134 version,
135 schemas,
136 batch,
137 blob,
138 _phantom: PhantomData,
139 }
140 }
141
142 pub fn shard_id(&self) -> ShardId {
144 self.shard_metrics.shard_id
145 }
146
147 pub fn upper(&self) -> &Antichain<T> {
149 self.batch.desc.upper()
150 }
151
152 pub fn lower(&self) -> &Antichain<T> {
154 self.batch.desc.lower()
155 }
156
157 pub(crate) fn mark_consumed(&mut self) {
163 self.batch.parts.clear();
164 }
165
166 #[instrument(level = "debug", fields(shard = %self.shard_id()))]
169 pub async fn delete(mut self) {
170 if !self.batch_delete_enabled {
171 self.mark_consumed();
172 return;
173 }
174 let mut deletes = PartDeletes::default();
175 for part in self.batch.parts.drain(..) {
176 deletes.add(&part);
177 }
178 let () = deletes
179 .delete(
180 &*self.blob,
181 self.shard_id(),
182 usize::MAX,
183 &*self.metrics,
184 &*self.metrics.retries.external.batch_delete,
185 )
186 .await;
187 }
188
189 pub fn schemas(&self) -> impl Iterator<Item = SchemaId> + '_ {
191 self.batch.parts.iter().flat_map(|b| b.schema_id())
192 }
193
194 pub fn into_hollow_batch(mut self) -> HollowBatch<T> {
199 let ret = self.batch.clone();
200 self.mark_consumed();
201 ret
202 }
203
204 pub fn into_transmittable_batch(mut self) -> ProtoBatch {
213 let ret = ProtoBatch {
214 shard_id: self.shard_metrics.shard_id.into_proto(),
215 version: self.version.to_string(),
216 batch: Some(self.batch.into_proto()),
217 key_schema: self.schemas.0.clone(),
218 val_schema: self.schemas.1.clone(),
219 };
220 self.mark_consumed();
221 ret
222 }
223
224 pub(crate) async fn flush_to_blob(
225 &mut self,
226 cfg: &BatchBuilderConfig,
227 batch_metrics: &BatchWriteMetrics,
228 isolated_runtime: &Arc<IsolatedRuntime>,
229 write_schemas: &Schemas<K, V>,
230 ) {
231 let mut parts = Vec::new();
236 for (run_meta, run_parts) in self.batch.runs() {
237 for part in run_parts {
238 let (updates, ts_rewrite, schema_id) = match part {
239 RunPart::Single(BatchPart::Inline {
240 updates,
241 ts_rewrite,
242 schema_id,
243 deprecated_schema_id: _,
244 }) => (updates, ts_rewrite, schema_id),
245 other @ RunPart::Many(_) | other @ RunPart::Single(BatchPart::Hollow(_)) => {
246 parts.push(other.clone());
247 continue;
248 }
249 };
250 let updates = updates
251 .decode::<T>(&self.metrics.columnar)
252 .expect("valid inline part");
253 let diffs_sum = diffs_sum::<D>(updates.updates.diffs());
254 let mut write_schemas = write_schemas.clone();
255 write_schemas.id = *schema_id;
256
257 let write_span =
258 debug_span!("batch::flush_to_blob", shard = %self.shard_metrics.shard_id)
259 .or_current();
260 let handle = mz_ore::task::spawn(
261 || "batch::flush_to_blob",
262 BatchParts::write_hollow_part(
263 cfg.clone(),
264 Arc::clone(&self.blob),
265 Arc::clone(&self.metrics),
266 Arc::clone(&self.shard_metrics),
267 batch_metrics.clone(),
268 Arc::clone(isolated_runtime),
269 updates,
270 run_meta.order.unwrap_or(RunOrder::Unordered),
271 ts_rewrite.clone(),
272 D::encode(&diffs_sum),
273 write_schemas,
274 )
275 .instrument(write_span),
276 );
277 let part = handle.await;
278 parts.push(RunPart::Single(part));
279 }
280 }
281 self.batch.parts = parts;
282 }
283
284 pub fn encoded_size_bytes(&self) -> usize {
286 self.batch.encoded_size_bytes()
287 }
288}
289
290impl<K, V, T, D> Batch<K, V, T, D>
291where
292 K: Debug + Codec,
293 V: Debug + Codec,
294 T: Timestamp + Lattice + Codec64 + TotalOrder,
295 D: Monoid + Codec64,
296{
297 pub fn rewrite_ts(
331 &mut self,
332 frontier: &Antichain<T>,
333 new_upper: Antichain<T>,
334 ) -> Result<(), InvalidUsage<T>> {
335 self.batch
336 .rewrite_ts(frontier, new_upper)
337 .map_err(InvalidUsage::InvalidRewrite)
338 }
339}
340
341#[derive(Debug)]
343pub enum Added {
344 Record,
346 RecordAndParts,
349}
350
351#[derive(Debug, Clone)]
354pub struct BatchBuilderConfig {
355 writer_key: WriterKey,
356 pub(crate) blob_target_size: usize,
357 pub(crate) batch_delete_enabled: bool,
358 pub(crate) batch_builder_max_outstanding_parts: usize,
359 pub(crate) inline_writes_single_max_bytes: usize,
360 pub(crate) stats_collection_enabled: bool,
361 pub(crate) stats_budget: usize,
362 pub(crate) stats_untrimmable_columns: Arc<UntrimmableColumns>,
363 pub(crate) encoding_config: EncodingConfig,
364 pub(crate) preferred_order: RunOrder,
365 pub(crate) structured_key_lower_len: usize,
366 pub(crate) run_length_limit: usize,
367 pub(crate) enable_incremental_compaction: bool,
368 pub(crate) max_runs: Option<usize>,
372}
373
374pub(crate) const BATCH_DELETE_ENABLED: Config<bool> = Config::new(
376 "persist_batch_delete_enabled",
377 true,
378 "Whether to actually delete blobs when batch delete is called (Materialize).",
379 ParameterScope::Environment,
380);
381
382pub(crate) const ENCODING_ENABLE_DICTIONARY: Config<bool> = Config::new(
383 "persist_encoding_enable_dictionary",
384 true,
385 "A feature flag to enable dictionary encoding for Parquet data (Materialize).",
386 ParameterScope::Environment,
387);
388
389pub(crate) const ENCODING_COMPRESSION_FORMAT: Config<&'static str> = Config::new(
390 "persist_encoding_compression_format",
391 "none",
392 "A feature flag to enable compression of Parquet data (Materialize).",
393 ParameterScope::Environment,
394);
395
396pub(crate) const STRUCTURED_KEY_LOWER_LEN: Config<usize> = Config::new(
397 "persist_batch_structured_key_lower_len",
398 256,
399 "The maximum size in proto bytes of any structured key-lower metadata to preserve. \
400 (If we're unable to fit the lower in budget, or the budget is zero, no metadata is kept.)",
401 ParameterScope::Environment,
402);
403
404pub(crate) const MAX_RUN_LEN: Config<usize> = Config::new(
405 "persist_batch_max_run_len",
406 usize::MAX,
407 "The maximum length a run can have before it will be spilled as a hollow run \
408 into the blob store.",
409 ParameterScope::Environment,
410);
411
412pub(crate) const MAX_RUNS: Config<usize> = Config::new(
413 "persist_batch_max_runs",
414 1,
415 "The maximum number of runs a batch builder should generate for user batches. \
416 (Compaction outputs always generate a single run.) \
417 The minimum value is 2; below this, compaction is disabled.",
418 ParameterScope::Environment,
419);
420
421pub(crate) const BLOB_TARGET_SIZE: Config<usize> = Config::new(
428 "persist_blob_target_size",
429 128 * MiB,
430 "A target maximum size of persist blob payloads in bytes (Materialize).",
431 ParameterScope::Environment,
432);
433
434pub(crate) const INLINE_WRITES_SINGLE_MAX_BYTES: Config<usize> = Config::new(
435 "persist_inline_writes_single_max_bytes",
436 4096,
437 "The (exclusive) maximum size of a write that persist will inline in metadata.",
438 ParameterScope::Environment,
439);
440
441pub(crate) const INLINE_WRITES_TOTAL_MAX_BYTES: Config<usize> = Config::new(
442 "persist_inline_writes_total_max_bytes",
443 1 * MiB,
444 "\
445 The (exclusive) maximum total size of inline writes in metadata before \
446 persist will backpressure them by flushing out to s3.",
447 ParameterScope::Environment,
448);
449
450impl BatchBuilderConfig {
451 pub fn new(value: &PersistConfig, _shard_id: ShardId) -> Self {
453 let writer_key = WriterKey::for_version(&value.build_version);
454
455 let preferred_order = RunOrder::Structured;
456
457 BatchBuilderConfig {
458 writer_key,
459 blob_target_size: BLOB_TARGET_SIZE.get(value).clamp(1, usize::MAX),
460 batch_delete_enabled: BATCH_DELETE_ENABLED.get(value),
461 batch_builder_max_outstanding_parts: BATCH_BUILDER_MAX_OUTSTANDING_PARTS.get(value),
462 inline_writes_single_max_bytes: INLINE_WRITES_SINGLE_MAX_BYTES.get(value),
463 stats_collection_enabled: STATS_COLLECTION_ENABLED.get(value),
464 stats_budget: STATS_BUDGET_BYTES.get(value),
465 stats_untrimmable_columns: Arc::new(untrimmable_columns(value)),
466 encoding_config: EncodingConfig {
467 use_dictionary: ENCODING_ENABLE_DICTIONARY.get(value),
468 compression: CompressionFormat::from_str(&ENCODING_COMPRESSION_FORMAT.get(value)),
469 },
470 preferred_order,
471 structured_key_lower_len: STRUCTURED_KEY_LOWER_LEN.get(value),
472 run_length_limit: MAX_RUN_LEN.get(value).clamp(2, usize::MAX),
473 max_runs: match MAX_RUNS.get(value) {
474 limit @ 2.. => Some(limit),
475 _ => None,
476 },
477 enable_incremental_compaction: ENABLE_INCREMENTAL_COMPACTION.get(value),
478 }
479 }
480}
481
482#[derive(
485 Debug,
486 Clone,
487 PartialEq,
488 Eq,
489 serde::Serialize,
490 serde::Deserialize,
491 Arbitrary
492)]
493pub(crate) struct UntrimmableColumns {
494 pub equals: Vec<Cow<'static, str>>,
496 pub prefixes: Vec<Cow<'static, str>>,
498 pub suffixes: Vec<Cow<'static, str>>,
500}
501
502impl UntrimmableColumns {
503 pub(crate) fn should_retain(&self, name: &str) -> bool {
504 let name_lower = name.to_lowercase();
507 for s in &self.equals {
508 if *s == name_lower {
509 return true;
510 }
511 }
512 for s in &self.prefixes {
513 if name_lower.starts_with(s.as_ref()) {
514 return true;
515 }
516 }
517 for s in &self.suffixes {
518 if name_lower.ends_with(s.as_ref()) {
519 return true;
520 }
521 }
522 false
523 }
524}
525
526#[derive(Debug)]
529pub struct BatchBuilder<K, V, T, D>
530where
531 K: Codec,
532 V: Codec,
533 T: Timestamp + Lattice + Codec64,
534{
535 inline_desc: Description<T>,
536 inclusive_upper: Antichain<Reverse<T>>,
537
538 records_builder: PartBuilder<K, K::Schema, V, V::Schema>,
539 pub(crate) builder: BatchBuilderInternal<K, V, T, D>,
540}
541
542impl<K, V, T, D> BatchBuilder<K, V, T, D>
543where
544 K: Debug + Codec,
545 V: Debug + Codec,
546 T: Timestamp + Lattice + Codec64,
547 D: Monoid + Codec64,
548{
549 pub(crate) fn new(
550 builder: BatchBuilderInternal<K, V, T, D>,
551 inline_desc: Description<T>,
552 ) -> Self {
553 let records_builder = PartBuilder::new(
554 builder.write_schemas.key.as_ref(),
555 builder.write_schemas.val.as_ref(),
556 );
557 Self {
558 inline_desc,
559 inclusive_upper: Antichain::new(),
560 records_builder,
561 builder,
562 }
563 }
564
565 pub async fn finish(
570 mut self,
571 registered_upper: Antichain<T>,
572 ) -> Result<Batch<K, V, T, D>, InvalidUsage<T>> {
573 if PartialOrder::less_than(®istered_upper, self.inline_desc.lower()) {
574 return Err(InvalidUsage::InvalidBounds {
575 lower: self.inline_desc.lower().clone(),
576 upper: registered_upper,
577 });
578 }
579
580 if PartialOrder::less_equal(self.inline_desc.since(), self.inline_desc.lower()) {
585 for ts in self.inclusive_upper.iter() {
586 if registered_upper.less_equal(&ts.0) {
587 return Err(InvalidUsage::UpdateBeyondUpper {
588 ts: ts.0.clone(),
589 expected_upper: registered_upper.clone(),
590 });
591 }
592 }
593 }
594
595 let updates = self.records_builder.finish();
596 self.builder
597 .flush_part(self.inline_desc.clone(), updates)
598 .await;
599
600 self.builder
601 .finish(Description::new(
602 self.inline_desc.lower().clone(),
603 registered_upper,
604 self.inline_desc.since().clone(),
605 ))
606 .await
607 }
608
609 pub async fn add(
614 &mut self,
615 key: &K,
616 val: &V,
617 ts: &T,
618 diff: &D,
619 ) -> Result<Added, InvalidUsage<T>> {
620 if !self.inline_desc.lower().less_equal(ts) {
621 return Err(InvalidUsage::UpdateNotBeyondLower {
622 ts: ts.clone(),
623 lower: self.inline_desc.lower().clone(),
624 });
625 }
626 self.inclusive_upper.insert(Reverse(ts.clone()));
627
628 let added = {
629 self.records_builder
630 .push(key, val, ts.clone(), diff.clone());
631 if self.records_builder.goodbytes() >= self.builder.parts.cfg.blob_target_size {
632 let part = self.records_builder.finish_and_replace(
633 self.builder.write_schemas.key.as_ref(),
634 self.builder.write_schemas.val.as_ref(),
635 );
636 Some(part)
637 } else {
638 None
639 }
640 };
641
642 let added = if let Some(full_batch) = added {
643 self.builder
644 .flush_part(self.inline_desc.clone(), full_batch)
645 .await;
646 Added::RecordAndParts
647 } else {
648 Added::Record
649 };
650 Ok(added)
651 }
652}
653
654#[derive(Debug)]
655pub(crate) struct BatchBuilderInternal<K, V, T, D>
656where
657 K: Codec,
658 V: Codec,
659 T: Timestamp + Lattice + Codec64,
660{
661 shard_id: ShardId,
662 version: Version,
663 blob: Arc<dyn Blob>,
664 metrics: Arc<Metrics>,
665
666 write_schemas: Schemas<K, V>,
667 parts: BatchParts<T>,
668
669 _phantom: PhantomData<fn(K, V, T, D)>,
672}
673
674impl<K, V, T, D> BatchBuilderInternal<K, V, T, D>
675where
676 K: Debug + Codec,
677 V: Debug + Codec,
678 T: Timestamp + Lattice + Codec64,
679 D: Monoid + Codec64,
680{
681 pub(crate) fn new(
682 _cfg: BatchBuilderConfig,
683 parts: BatchParts<T>,
684 metrics: Arc<Metrics>,
685 write_schemas: Schemas<K, V>,
686 blob: Arc<dyn Blob>,
687 shard_id: ShardId,
688 version: Version,
689 ) -> Self {
690 Self {
691 blob,
692 metrics,
693 write_schemas,
694 parts,
695 shard_id,
696 version,
697 _phantom: PhantomData,
698 }
699 }
700
701 #[instrument(level = "debug", name = "batch::finish", fields(shard = %self.shard_id))]
706 pub async fn finish(
707 self,
708 registered_desc: Description<T>,
709 ) -> Result<Batch<K, V, T, D>, InvalidUsage<T>> {
710 let write_run_ids = self.parts.cfg.enable_incremental_compaction;
711 let batch_delete_enabled = self.parts.cfg.batch_delete_enabled;
712 let shard_metrics = Arc::clone(&self.parts.shard_metrics);
713 let runs = self.parts.finish().await;
714
715 let mut run_parts = vec![];
716 let mut run_splits = vec![];
717 let mut run_meta = vec![];
718 let total_updates = runs
719 .iter()
720 .map(|(_, _, num_updates)| num_updates)
721 .sum::<usize>();
722 for (order, parts, num_updates) in runs {
723 if parts.is_empty() {
724 continue;
725 }
726 if run_parts.len() != 0 {
727 run_splits.push(run_parts.len());
728 }
729 run_meta.push(RunMeta {
730 order: Some(order),
731 schema: self.write_schemas.id,
732 deprecated_schema: None,
734 id: if write_run_ids {
735 Some(RunId::new())
736 } else {
737 None
738 },
739 len: if write_run_ids {
740 Some(num_updates)
741 } else {
742 None
743 },
744 meta: MetadataMap::default(),
745 });
746 run_parts.extend(parts);
747 }
748 let desc = registered_desc;
749
750 let batch = Batch::new(
751 batch_delete_enabled,
752 Arc::clone(&self.metrics),
753 self.blob,
754 shard_metrics,
755 self.version,
756 (
757 K::encode_schema(&*self.write_schemas.key),
758 V::encode_schema(&*self.write_schemas.val),
759 ),
760 HollowBatch::new(desc, run_parts, total_updates, run_meta, run_splits),
761 );
762
763 Ok(batch)
764 }
765
766 pub async fn flush_part(&mut self, part_desc: Description<T>, columnar: Part) {
772 let num_updates = columnar.len();
773 if num_updates == 0 {
774 return;
775 }
776 let diffs_sum = diffs_sum::<D>(&columnar.diff);
777
778 let start = Instant::now();
779 self.parts
780 .write(&self.write_schemas, part_desc, columnar, diffs_sum)
781 .await;
782 self.metrics
783 .compaction
784 .batch
785 .step_part_writing
786 .inc_by(start.elapsed().as_secs_f64());
787 }
788}
789
790#[derive(Debug, Clone)]
791pub(crate) struct RunWithMeta<T> {
792 pub parts: Vec<RunPart<T>>,
793 pub num_updates: usize,
794}
795
796impl<T> RunWithMeta<T> {
797 pub fn new(parts: Vec<RunPart<T>>, num_updates: usize) -> Self {
798 Self { parts, num_updates }
799 }
800
801 pub fn single(part: RunPart<T>, num_updates: usize) -> Self {
802 Self {
803 parts: vec![part],
804 num_updates,
805 }
806 }
807}
808
809#[derive(Debug)]
810enum WritingRuns<T> {
811 Ordered(RunOrder, MergeTree<Pending<RunWithMeta<T>>>),
815 Compacting(MergeTree<(RunOrder, Pending<RunWithMeta<T>>)>),
818}
819
820#[derive(Debug)]
823pub(crate) struct BatchParts<T> {
824 cfg: BatchBuilderConfig,
825 metrics: Arc<Metrics>,
826 shard_metrics: Arc<ShardMetrics>,
827 shard_id: ShardId,
828 blob: Arc<dyn Blob>,
829 isolated_runtime: Arc<IsolatedRuntime>,
830 next_index: u64,
831 writing_runs: WritingRuns<T>,
832 batch_metrics: BatchWriteMetrics,
833}
834
835impl<T: Timestamp + Codec64> BatchParts<T> {
836 pub(crate) fn new_compacting<K, V, D>(
837 cfg: CompactConfig,
838 desc: Description<T>,
839 runs_per_compaction: usize,
840 metrics: Arc<Metrics>,
841 shard_metrics: Arc<ShardMetrics>,
842 shard_id: ShardId,
843 blob: Arc<dyn Blob>,
844 isolated_runtime: Arc<IsolatedRuntime>,
845 batch_metrics: &BatchWriteMetrics,
846 schemas: Schemas<K, V>,
847 ) -> Self
848 where
849 K: Codec + Debug,
850 V: Codec + Debug,
851 T: Lattice + Send + Sync,
852 D: Monoid + Ord + Codec64 + Send + Sync,
853 {
854 let writing_runs = {
855 let cfg = cfg.clone();
856 let blob = Arc::clone(&blob);
857 let metrics = Arc::clone(&metrics);
858 let shard_metrics = Arc::clone(&shard_metrics);
859 let isolated_runtime = Arc::clone(&isolated_runtime);
860 let runs_per_compaction = runs_per_compaction.clamp(2, 1024);
862
863 let merge_fn = move |parts: Vec<(RunOrder, Pending<RunWithMeta<T>>)>| {
864 let blob = Arc::clone(&blob);
865 let metrics = Arc::clone(&metrics);
866 let shard_metrics = Arc::clone(&shard_metrics);
867 let cfg = cfg.clone();
868 let isolated_runtime = Arc::clone(&isolated_runtime);
869 let write_schemas = schemas.clone();
870 let compact_desc = desc.clone();
871 let handle = mz_ore::task::spawn(
872 || "batch::compact_runs",
873 async move {
874 let runs: Vec<_> = stream::iter(parts)
875 .then(|(order, parts)| async move {
876 let completed_run = parts.into_result().await;
877 (
878 RunMeta {
879 order: Some(order),
880 schema: schemas.id,
881 deprecated_schema: None,
884 id: if cfg.batch.enable_incremental_compaction {
885 Some(RunId::new())
886 } else {
887 None
888 },
889 len: if cfg.batch.enable_incremental_compaction {
890 Some(completed_run.num_updates)
891 } else {
892 None
893 },
894 meta: MetadataMap::default(),
895 },
896 completed_run.parts,
897 )
898 })
899 .collect()
900 .await;
901
902 let run_refs: Vec<_> = runs
903 .iter()
904 .map(|(meta, run)| (&compact_desc, meta, run.as_slice()))
905 .collect();
906
907 let output_batch = Compactor::<K, V, T, D>::compact_runs(
908 &cfg,
909 &shard_id,
910 &compact_desc,
911 run_refs,
912 blob,
913 metrics,
914 shard_metrics,
915 isolated_runtime,
916 write_schemas,
917 )
918 .await
919 .expect("successful compaction");
920
921 assert_eq!(
922 output_batch.run_meta.len(),
923 1,
924 "compaction is guaranteed to emit a single run"
925 );
926 let total_compacted_updates: usize = output_batch.len;
927
928 RunWithMeta::new(output_batch.parts, total_compacted_updates)
929 }
930 .instrument(debug_span!("batch::compact_runs")),
931 );
932 (RunOrder::Structured, Pending::new(handle))
933 };
934 WritingRuns::Compacting(MergeTree::new(runs_per_compaction, merge_fn))
935 };
936 BatchParts {
937 cfg: cfg.batch,
938 metrics,
939 shard_metrics,
940 shard_id,
941 blob,
942 isolated_runtime,
943 next_index: 0,
944 writing_runs,
945 batch_metrics: batch_metrics.clone(),
946 }
947 }
948
949 pub(crate) fn new_ordered<D: Monoid + Codec64>(
950 cfg: BatchBuilderConfig,
951 order: RunOrder,
952 metrics: Arc<Metrics>,
953 shard_metrics: Arc<ShardMetrics>,
954 shard_id: ShardId,
955 blob: Arc<dyn Blob>,
956 isolated_runtime: Arc<IsolatedRuntime>,
957 batch_metrics: &BatchWriteMetrics,
958 ) -> Self {
959 let writing_runs = {
960 let cfg = cfg.clone();
961 let blob = Arc::clone(&blob);
962 let metrics = Arc::clone(&metrics);
963 let writer_key = cfg.writer_key.clone();
964 let run_length_limit = (order == RunOrder::Unordered)
967 .then_some(usize::MAX)
968 .unwrap_or(cfg.run_length_limit);
969 let merge_fn = move |parts: Vec<Pending<RunWithMeta<T>>>| {
970 let blob = Arc::clone(&blob);
971 let writer_key = writer_key.clone();
972 let metrics = Arc::clone(&metrics);
973 let handle = mz_ore::task::spawn(
974 || "batch::spill_run",
975 async move {
976 let completed_runs: Vec<RunWithMeta<T>> = stream::iter(parts)
977 .then(|p| p.into_result())
978 .collect()
979 .await;
980
981 let mut all_run_parts = Vec::new();
982 let mut total_updates = 0;
983
984 for completed_run in completed_runs {
985 all_run_parts.extend(completed_run.parts);
986 total_updates += completed_run.num_updates;
987 }
988
989 let run_ref = HollowRunRef::set::<D>(
990 shard_id,
991 blob.as_ref(),
992 &writer_key,
993 HollowRun {
994 parts: all_run_parts,
995 },
996 &*metrics,
997 )
998 .await;
999
1000 RunWithMeta::single(RunPart::Many(run_ref), total_updates)
1001 }
1002 .instrument(debug_span!("batch::spill_run")),
1003 );
1004 Pending::new(handle)
1005 };
1006 WritingRuns::Ordered(order, MergeTree::new(run_length_limit, merge_fn))
1007 };
1008 BatchParts {
1009 cfg,
1010 metrics,
1011 shard_metrics,
1012 shard_id,
1013 blob,
1014 isolated_runtime,
1015 next_index: 0,
1016 writing_runs,
1017 batch_metrics: batch_metrics.clone(),
1018 }
1019 }
1020
1021 pub(crate) fn expected_order(&self) -> RunOrder {
1022 match self.writing_runs {
1023 WritingRuns::Ordered(order, _) => order,
1024 WritingRuns::Compacting(_) => RunOrder::Unordered,
1025 }
1026 }
1027
1028 pub(crate) async fn write<K: Codec, V: Codec, D: Codec64>(
1029 &mut self,
1030 write_schemas: &Schemas<K, V>,
1031 desc: Description<T>,
1032 updates: Part,
1033 diffs_sum: D,
1034 ) {
1035 let batch_metrics = self.batch_metrics.clone();
1036 let index = self.next_index;
1037 self.next_index += 1;
1038 let num_updates = updates.len();
1039 let ts_rewrite = None;
1040 let schema_id = write_schemas.id;
1041
1042 let inline_threshold = self.cfg.inline_writes_single_max_bytes;
1045
1046 let updates = BlobTraceUpdates::from_part(updates);
1047 let (name, write_future) = if updates.goodbytes() < inline_threshold {
1048 let span = debug_span!("batch::inline_part", shard = %self.shard_id).or_current();
1049 (
1050 "batch::inline_part",
1051 async move {
1052 let start = Instant::now();
1053 let updates = LazyInlineBatchPart::from(&ProtoInlineBatchPart {
1054 desc: Some(desc.into_proto()),
1055 index: index.into_proto(),
1056 updates: Some(updates.into_proto()),
1057 });
1058 batch_metrics
1059 .step_inline
1060 .inc_by(start.elapsed().as_secs_f64());
1061
1062 RunWithMeta::single(
1063 RunPart::Single(BatchPart::Inline {
1064 updates,
1065 ts_rewrite,
1066 schema_id,
1067 deprecated_schema_id: None,
1069 }),
1070 num_updates,
1071 )
1072 }
1073 .instrument(span)
1074 .boxed(),
1075 )
1076 } else {
1077 let part = BlobTraceBatchPart {
1078 desc,
1079 updates,
1080 index,
1081 };
1082 let cfg = self.cfg.clone();
1083 let blob = Arc::clone(&self.blob);
1084 let metrics = Arc::clone(&self.metrics);
1085 let shard_metrics = Arc::clone(&self.shard_metrics);
1086 let isolated_runtime = Arc::clone(&self.isolated_runtime);
1087 let expected_order = self.expected_order();
1088 let encoded_diffs_sum = D::encode(&diffs_sum);
1089 let write_schemas_clone = write_schemas.clone();
1090 let write_span =
1091 debug_span!("batch::write_part", shard = %self.shard_metrics.shard_id).or_current();
1092 (
1093 "batch::write_part",
1094 async move {
1095 let part = BatchParts::write_hollow_part(
1096 cfg,
1097 blob,
1098 metrics,
1099 shard_metrics,
1100 batch_metrics,
1101 isolated_runtime,
1102 part,
1103 expected_order,
1104 ts_rewrite,
1105 encoded_diffs_sum,
1106 write_schemas_clone,
1107 )
1108 .await;
1109 RunWithMeta::single(RunPart::Single(part), num_updates)
1110 }
1111 .instrument(write_span)
1112 .boxed(),
1113 )
1114 };
1115
1116 match &mut self.writing_runs {
1117 WritingRuns::Ordered(_order, run) => {
1118 let part = Pending::new(mz_ore::task::spawn(|| name, write_future));
1119 run.push(part);
1120
1121 for part in run
1124 .iter_mut()
1125 .rev()
1126 .skip(self.cfg.batch_builder_max_outstanding_parts)
1127 .take_while(|p| !p.is_finished())
1128 {
1129 self.batch_metrics.write_stalls.inc();
1130 part.block_until_ready().await;
1131 }
1132 }
1133 WritingRuns::Compacting(batches) => {
1134 let run = Pending::Writing(mz_ore::task::spawn(|| name, write_future));
1135 batches.push((RunOrder::Unordered, run));
1136
1137 let mut part_budget = self.cfg.batch_builder_max_outstanding_parts;
1140 let mut compaction_budget = 1;
1141 for (_, part) in batches
1142 .iter_mut()
1143 .rev()
1144 .skip_while(|(order, _)| match order {
1145 RunOrder::Unordered if part_budget > 0 => {
1146 part_budget -= 1;
1147 true
1148 }
1149 RunOrder::Structured | RunOrder::Codec if compaction_budget > 0 => {
1150 compaction_budget -= 1;
1151 true
1152 }
1153 _ => false,
1154 })
1155 .take_while(|(_, p)| !p.is_finished())
1156 {
1157 self.batch_metrics.write_stalls.inc();
1158 part.block_until_ready().await;
1159 }
1160 }
1161 }
1162 }
1163
1164 async fn write_hollow_part<K: Codec, V: Codec>(
1165 cfg: BatchBuilderConfig,
1166 blob: Arc<dyn Blob>,
1167 metrics: Arc<Metrics>,
1168 shard_metrics: Arc<ShardMetrics>,
1169 batch_metrics: BatchWriteMetrics,
1170 isolated_runtime: Arc<IsolatedRuntime>,
1171 mut updates: BlobTraceBatchPart<T>,
1172 run_order: RunOrder,
1173 ts_rewrite: Option<Antichain<T>>,
1174 diffs_sum: [u8; 8],
1175 write_schemas: Schemas<K, V>,
1176 ) -> BatchPart<T> {
1177 let partial_key = PartialBatchKey::new(&cfg.writer_key, &PartId::new());
1178 let key = partial_key.complete(&shard_metrics.shard_id);
1179 let goodbytes = updates.updates.goodbytes();
1180 let metrics_ = Arc::clone(&metrics);
1181 let schema_id = write_schemas.id;
1182
1183 let (stats, key_lower, structured_key_lower, (buf, encode_time)) = isolated_runtime
1184 .spawn_named(|| "batch::encode_part", async move {
1185 let stats = metrics_.columnar.arrow().measure_part_build(|| {
1187 let stats = if cfg.stats_collection_enabled {
1188 let ext = updates.updates.get_or_make_structured::<K, V>(
1189 write_schemas.key.as_ref(),
1190 write_schemas.val.as_ref(),
1191 );
1192
1193 let key_stats = write_schemas
1194 .key
1195 .decoder_any(ext.key.as_ref())
1196 .expect("decoding just-encoded data")
1197 .stats();
1198
1199 let part_stats = PartStats { key: key_stats };
1200
1201 let trimmed_start = Instant::now();
1203 let mut trimmed_bytes = 0;
1204 let trimmed_stats = LazyPartStats::encode(&part_stats, |s| {
1205 trimmed_bytes = trim_to_budget(s, cfg.stats_budget, |s| {
1206 cfg.stats_untrimmable_columns.should_retain(s)
1207 })
1208 });
1209 let trimmed_duration = trimmed_start.elapsed();
1210 Some((trimmed_stats, trimmed_duration, trimmed_bytes))
1211 } else {
1212 None
1213 };
1214
1215 updates.updates = updates.updates.as_structured::<K, V>(
1217 write_schemas.key.as_ref(),
1218 write_schemas.val.as_ref(),
1219 );
1220
1221 stats
1222 });
1223
1224 let key_lower = if let Some(records) = updates.updates.records() {
1225 let key_bytes = records.keys();
1226 if key_bytes.is_empty() {
1227 &[]
1228 } else if run_order == RunOrder::Codec {
1229 key_bytes.value(0)
1230 } else {
1231 ::arrow::compute::min_binary(key_bytes).expect("min of nonempty array")
1232 }
1233 } else {
1234 &[]
1235 };
1236 let key_lower = truncate_bytes(key_lower, TRUNCATE_LEN, TruncateBound::Lower)
1237 .expect("lower bound always exists");
1238
1239 let structured_key_lower = if cfg.structured_key_lower_len > 0 {
1240 updates.updates.structured().and_then(|ext| {
1241 let min_key = if run_order == RunOrder::Structured {
1242 0
1243 } else {
1244 let ord = ArrayOrd::new(ext.key.as_ref());
1245 (0..ext.key.len())
1246 .min_by_key(|i| ord.at(*i))
1247 .expect("non-empty batch")
1248 };
1249 let lower = ArrayBound::new(Arc::clone(&ext.key), min_key)
1250 .to_proto_lower(cfg.structured_key_lower_len);
1251 if lower.is_none() {
1252 batch_metrics.key_lower_too_big.inc()
1253 }
1254 lower.map(|proto| LazyProto::from(&proto))
1255 })
1256 } else {
1257 None
1258 };
1259
1260 let encode_start = Instant::now();
1261 let mut buf = Vec::new();
1262 updates.encode(&mut buf, &metrics_.columnar, &cfg.encoding_config);
1263
1264 drop(updates);
1266 (
1267 stats,
1268 key_lower,
1269 structured_key_lower,
1270 (Bytes::from(buf), encode_start.elapsed()),
1271 )
1272 })
1273 .instrument(debug_span!("batch::encode_part"))
1274 .await;
1275 metrics.codecs.batch.encode_count.inc();
1277 metrics
1278 .codecs
1279 .batch
1280 .encode_seconds
1281 .inc_by(encode_time.as_secs_f64());
1282
1283 let start = Instant::now();
1284 let payload_len = buf.len();
1285 let () = retry_external(&metrics.retries.external.batch_set, || async {
1286 shard_metrics.blob_sets.inc();
1287 blob.set(&key, Bytes::clone(&buf)).await
1288 })
1289 .instrument(trace_span!("batch::set", payload_len))
1290 .await;
1291 batch_metrics.seconds.inc_by(start.elapsed().as_secs_f64());
1292 batch_metrics.bytes.inc_by(u64::cast_from(payload_len));
1293 batch_metrics.goodbytes.inc_by(u64::cast_from(goodbytes));
1294 match run_order {
1295 RunOrder::Unordered => batch_metrics.unordered.inc(),
1296 RunOrder::Codec => batch_metrics.codec_order.inc(),
1297 RunOrder::Structured => batch_metrics.structured_order.inc(),
1298 }
1299 let stats = stats.map(|(stats, stats_step_timing, trimmed_bytes)| {
1300 batch_metrics
1301 .step_stats
1302 .inc_by(stats_step_timing.as_secs_f64());
1303 if trimmed_bytes > 0 {
1304 metrics.pushdown.parts_stats_trimmed_count.inc();
1305 metrics
1306 .pushdown
1307 .parts_stats_trimmed_bytes
1308 .inc_by(u64::cast_from(trimmed_bytes));
1309 }
1310 stats
1311 });
1312
1313 let meta = MetadataMap::default();
1314 BatchPart::Hollow(HollowBatchPart {
1315 key: partial_key,
1316 meta,
1317 encoded_size_bytes: payload_len,
1318 key_lower,
1319 structured_key_lower,
1320 stats,
1321 ts_rewrite,
1322 diffs_sum: Some(diffs_sum),
1323 format: Some(BatchColumnarFormat::Structured),
1324 schema_id,
1325 deprecated_schema_id: None,
1327 })
1328 }
1329
1330 #[instrument(level = "debug", name = "batch::finish_upload", fields(shard = %self.shard_id))]
1331 pub(crate) async fn finish(self) -> Vec<(RunOrder, Vec<RunPart<T>>, usize)> {
1332 match self.writing_runs {
1333 WritingRuns::Ordered(RunOrder::Unordered, run) => {
1334 let completed_runs = run.finish();
1335 let mut output = Vec::with_capacity(completed_runs.len());
1336 for completed_run in completed_runs {
1337 let completed_run = completed_run.into_result().await;
1338 for part in completed_run.parts {
1340 output.push((RunOrder::Unordered, vec![part], completed_run.num_updates));
1341 }
1342 }
1343 output
1344 }
1345 WritingRuns::Ordered(order, run) => {
1346 let completed_runs = run.finish();
1347 let mut all_parts = Vec::new();
1348 let mut all_update_counts = 0;
1349 for completed_run in completed_runs {
1350 let completed_run = completed_run.into_result().await;
1351 all_parts.extend(completed_run.parts);
1352 all_update_counts += completed_run.num_updates;
1353 }
1354 vec![(order, all_parts, all_update_counts)]
1355 }
1356 WritingRuns::Compacting(batches) => {
1357 let runs = batches.finish();
1358 let mut output = Vec::new();
1359 for (order, run) in runs {
1360 let completed_run = run.into_result().await;
1361 output.push((order, completed_run.parts, completed_run.num_updates));
1362 }
1363 output
1364 }
1365 }
1366 }
1367}
1368
1369pub(crate) fn validate_truncate_batch<T: Timestamp>(
1370 batch: &HollowBatch<T>,
1371 truncate: &Description<T>,
1372 any_batch_rewrite: bool,
1373 validate_part_bounds_on_write: bool,
1374) -> Result<(), InvalidUsage<T>> {
1375 if any_batch_rewrite {
1378 if truncate.upper() != batch.desc.upper() {
1383 return Err(InvalidUsage::InvalidRewrite(format!(
1384 "rewritten batch might have data past {:?} up to {:?}",
1385 truncate.upper().elements(),
1386 batch.desc.upper().elements(),
1387 )));
1388 }
1389 for part in batch.parts.iter() {
1392 let part_lower_bound = part.ts_rewrite().unwrap_or_else(|| batch.desc.lower());
1393 if !PartialOrder::less_equal(truncate.lower(), part_lower_bound) {
1394 return Err(InvalidUsage::InvalidRewrite(format!(
1395 "rewritten batch might have data below {:?} at {:?}",
1396 truncate.lower().elements(),
1397 part_lower_bound.elements(),
1398 )));
1399 }
1400 }
1401 }
1402
1403 if !validate_part_bounds_on_write {
1404 return Ok(());
1405 }
1406
1407 let batch = &batch.desc;
1408 if !PartialOrder::less_equal(batch.lower(), truncate.lower())
1409 || PartialOrder::less_than(batch.upper(), truncate.upper())
1410 {
1411 return Err(InvalidUsage::InvalidBatchBounds {
1412 batch_lower: batch.lower().clone(),
1413 batch_upper: batch.upper().clone(),
1414 append_lower: truncate.lower().clone(),
1415 append_upper: truncate.upper().clone(),
1416 });
1417 }
1418
1419 Ok(())
1420}
1421
1422#[derive(Debug)]
1423pub(crate) struct PartDeletes<T> {
1424 blob_keys: BTreeSet<PartialBatchKey>,
1426 hollow_runs: BTreeMap<PartialBatchKey, HollowRunRef<T>>,
1428}
1429
1430impl<T> Default for PartDeletes<T> {
1431 fn default() -> Self {
1432 Self {
1433 blob_keys: Default::default(),
1434 hollow_runs: Default::default(),
1435 }
1436 }
1437}
1438
1439impl<T: Timestamp> PartDeletes<T> {
1440 pub fn add(&mut self, part: &RunPart<T>) -> bool {
1443 match part {
1444 RunPart::Many(r) => self.hollow_runs.insert(r.key.clone(), r.clone()).is_none(),
1445 RunPart::Single(BatchPart::Hollow(x)) => self.blob_keys.insert(x.key.clone()),
1446 RunPart::Single(BatchPart::Inline { .. }) => {
1447 true
1449 }
1450 }
1451 }
1452
1453 pub fn contains(&self, part: &RunPart<T>) -> bool {
1454 match part {
1455 RunPart::Many(r) => self.hollow_runs.contains_key(&r.key),
1456 RunPart::Single(BatchPart::Hollow(x)) => self.blob_keys.contains(&x.key),
1457 RunPart::Single(BatchPart::Inline { .. }) => false,
1458 }
1459 }
1460
1461 pub fn is_empty(&self) -> bool {
1462 self.len() == 0
1463 }
1464
1465 pub fn len(&self) -> usize {
1466 match self {
1467 Self {
1468 blob_keys,
1469 hollow_runs,
1470 } => blob_keys.len() + hollow_runs.len(),
1471 }
1472 }
1473
1474 pub async fn delete(
1475 mut self,
1476 blob: &dyn Blob,
1477 shard_id: ShardId,
1478 concurrency: usize,
1479 metrics: &Metrics,
1480 delete_metrics: &RetryMetrics,
1481 ) where
1482 T: Codec64,
1483 {
1484 loop {
1485 let () = stream::iter(mem::take(&mut self.blob_keys))
1486 .map(|key| {
1487 let key = key.complete(&shard_id);
1488 async move {
1489 retry_external(delete_metrics, || blob.delete(&key)).await;
1490 }
1491 })
1492 .buffer_unordered(concurrency)
1493 .collect()
1494 .await;
1495
1496 let Some((run_key, run_ref)) = self.hollow_runs.pop_first() else {
1497 break;
1498 };
1499
1500 if let Some(run) = run_ref.get(shard_id, blob, metrics).await {
1501 for part in &run.parts {
1503 self.add(part);
1504 }
1505 self.blob_keys.insert(run_key);
1506 };
1507 }
1508 }
1509}
1510
1511fn diffs_sum<D: Monoid + Codec64>(updates: &Int64Array) -> D {
1513 let mut sum = D::zero();
1514 for d in updates.values().iter() {
1515 let d = D::decode(d.to_le_bytes());
1516 sum.plus_equals(&d);
1517 }
1518 sum
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523 use mz_dyncfg::ConfigUpdates;
1524
1525 use super::*;
1526 use crate::PersistLocation;
1527 use crate::cache::PersistClientCache;
1528 use crate::cfg::BATCH_BUILDER_MAX_OUTSTANDING_PARTS;
1529 use crate::internal::paths::{BlobKey, PartialBlobKey};
1530 use crate::tests::{all_ok, new_test_client};
1531
1532 #[mz_ore::test(tokio::test)]
1533 #[cfg_attr(miri, ignore)] async fn batch_builder_flushing() {
1535 let data = vec![
1536 (("1".to_owned(), "one".to_owned()), 1, 1),
1537 (("2".to_owned(), "two".to_owned()), 2, 1),
1538 (("3".to_owned(), "three".to_owned()), 3, 1),
1539 (("4".to_owned(), "four".to_owned()), 4, 1),
1540 ];
1541
1542 let cache = PersistClientCache::new_no_metrics();
1543
1544 cache.cfg.set_config(&BLOB_TARGET_SIZE, 0);
1548 cache.cfg.set_config(&MAX_RUNS, 3);
1549 cache
1550 .cfg
1551 .set_config(&BATCH_BUILDER_MAX_OUTSTANDING_PARTS, 2);
1552
1553 let client = cache
1554 .open(PersistLocation::new_in_mem())
1555 .await
1556 .expect("client construction failed");
1557 let (mut write, mut read) = client
1558 .expect_open::<String, String, u64, i64>(ShardId::new())
1559 .await;
1560
1561 let mut builder = write.builder(Antichain::from_elem(0));
1563
1564 fn assert_writing(
1565 builder: &BatchBuilder<String, String, u64, i64>,
1566 expected_finished: &[bool],
1567 ) {
1568 let WritingRuns::Compacting(run) = &builder.builder.parts.writing_runs else {
1569 unreachable!("ordered run!")
1570 };
1571
1572 let actual: Vec<_> = run.iter().map(|(_, p)| p.is_finished()).collect();
1573 assert_eq!(*expected_finished, actual);
1574 }
1575
1576 assert_writing(&builder, &[]);
1577
1578 let ((k, v), t, d) = &data[0];
1581 builder.add(k, v, t, d).await.expect("invalid usage");
1582 assert_writing(&builder, &[false]);
1583
1584 let ((k, v), t, d) = &data[1];
1587 builder.add(k, v, t, d).await.expect("invalid usage");
1588 assert_writing(&builder, &[false, false]);
1589
1590 let ((k, v), t, d) = &data[2];
1593 builder.add(k, v, t, d).await.expect("invalid usage");
1594 assert_writing(&builder, &[true, false, false]);
1595
1596 let ((k, v), t, d) = &data[3];
1599 builder.add(k, v, t, d).await.expect("invalid usage");
1600 assert_writing(&builder, &[false, false]);
1601
1602 let batch = builder
1605 .finish(Antichain::from_elem(5))
1606 .await
1607 .expect("invalid usage");
1608 assert_eq!(batch.batch.runs().count(), 2);
1609 assert_eq!(batch.batch.part_count(), 4);
1610 write
1611 .append_batch(batch, Antichain::from_elem(0), Antichain::from_elem(5))
1612 .await
1613 .expect("invalid usage")
1614 .expect("unexpected upper");
1615 assert_eq!(read.expect_snapshot_and_fetch(4).await, all_ok(&data, 4));
1616 }
1617
1618 #[mz_ore::test(tokio::test)]
1619 #[cfg_attr(miri, ignore)] async fn batch_builder_keys() {
1621 let cache = PersistClientCache::new_no_metrics();
1622 cache.cfg.set_config(&BLOB_TARGET_SIZE, 0);
1624 cache.cfg.set_config(&STRUCTURED_KEY_LOWER_LEN, 0);
1626 cache.cfg.set_config(&INLINE_WRITES_SINGLE_MAX_BYTES, 0);
1627 cache.cfg.set_config(&INLINE_WRITES_TOTAL_MAX_BYTES, 0);
1628 let client = cache
1629 .open(PersistLocation::new_in_mem())
1630 .await
1631 .expect("client construction failed");
1632 let shard_id = ShardId::new();
1633 let (mut write, _) = client
1634 .expect_open::<String, String, u64, i64>(shard_id)
1635 .await;
1636
1637 let batch = write
1638 .expect_batch(
1639 &[
1640 (("1".into(), "one".into()), 1, 1),
1641 (("2".into(), "two".into()), 2, 1),
1642 (("3".into(), "three".into()), 3, 1),
1643 ],
1644 0,
1645 4,
1646 )
1647 .await;
1648
1649 assert_eq!(batch.batch.part_count(), 3);
1650 for part in &batch.batch.parts {
1651 let part = part.expect_hollow_part();
1652 match BlobKey::parse_ids(&part.key.complete(&shard_id)) {
1653 Ok((shard, PartialBlobKey::Batch(writer, _))) => {
1654 assert_eq!(shard.to_string(), shard_id.to_string());
1655 assert_eq!(writer, WriterKey::for_version(&cache.cfg.build_version));
1656 }
1657 _ => panic!("unparseable blob key"),
1658 }
1659 }
1660 }
1661
1662 #[mz_ore::test(tokio::test)]
1663 #[cfg_attr(miri, ignore)] async fn batch_delete() {
1665 let cache = PersistClientCache::new_no_metrics();
1666 cache.cfg.set_config(&INLINE_WRITES_SINGLE_MAX_BYTES, 0);
1667 cache.cfg.set_config(&INLINE_WRITES_TOTAL_MAX_BYTES, 0);
1668 cache.cfg.set_config(&BATCH_DELETE_ENABLED, true);
1669 let client = cache
1670 .open(PersistLocation::new_in_mem())
1671 .await
1672 .expect("client construction failed");
1673 let shard_id = ShardId::new();
1674 let (mut write, _) = client
1675 .expect_open::<String, String, u64, i64>(shard_id)
1676 .await;
1677
1678 let batch = write
1679 .expect_batch(
1680 &[
1681 (("1".into(), "one".into()), 1, 1),
1682 (("2".into(), "two".into()), 2, 1),
1683 (("3".into(), "three".into()), 3, 1),
1684 ],
1685 0,
1686 4,
1687 )
1688 .await;
1689
1690 assert_eq!(batch.batch.part_count(), 1);
1691 let part_key = batch.batch.parts[0]
1692 .expect_hollow_part()
1693 .key
1694 .complete(&shard_id);
1695
1696 let part_bytes = client.blob.get(&part_key).await.expect("invalid usage");
1697 assert!(part_bytes.is_some());
1698
1699 batch.delete().await;
1700
1701 let part_bytes = client.blob.get(&part_key).await.expect("invalid usage");
1702 assert!(part_bytes.is_none());
1703 }
1704
1705 #[mz_ore::test]
1706 fn untrimmable_columns() {
1707 let untrimmable = UntrimmableColumns {
1708 equals: vec!["abc".into(), "def".into()],
1709 prefixes: vec!["123".into(), "234".into()],
1710 suffixes: vec!["xyz".into()],
1711 };
1712
1713 assert!(untrimmable.should_retain("abc"));
1715 assert!(untrimmable.should_retain("ABC"));
1716 assert!(untrimmable.should_retain("aBc"));
1717 assert!(!untrimmable.should_retain("abcd"));
1718 assert!(untrimmable.should_retain("deF"));
1719 assert!(!untrimmable.should_retain("defg"));
1720
1721 assert!(untrimmable.should_retain("123"));
1723 assert!(untrimmable.should_retain("123-4"));
1724 assert!(untrimmable.should_retain("1234"));
1725 assert!(untrimmable.should_retain("234"));
1726 assert!(!untrimmable.should_retain("345"));
1727
1728 assert!(untrimmable.should_retain("ijk_xyZ"));
1730 assert!(untrimmable.should_retain("ww-XYZ"));
1731 assert!(!untrimmable.should_retain("xya"));
1732 }
1733
1734 #[mz_persist_proc::test(tokio::test)]
1736 #[cfg_attr(miri, ignore)] async fn rewrite_ts_example(dyncfgs: ConfigUpdates) {
1738 let client = new_test_client(&dyncfgs).await;
1739 let (mut write, read) = client
1740 .expect_open::<String, (), u64, i64>(ShardId::new())
1741 .await;
1742
1743 let mut batch = write.builder(Antichain::from_elem(0));
1744 batch.add(&"foo".to_owned(), &(), &0, &1).await.unwrap();
1745 let batch = batch.finish(Antichain::from_elem(1)).await.unwrap();
1746
1747 let batch = batch.into_transmittable_batch();
1749 let mut batch = write.batch_from_transmittable_batch(batch);
1750 batch
1751 .rewrite_ts(&Antichain::from_elem(2), Antichain::from_elem(3))
1752 .unwrap();
1753 write
1754 .expect_compare_and_append_batch(&mut [&mut batch], 0, 3)
1755 .await;
1756
1757 let (actual, _) = read.expect_listen(0).await.read_until(&3).await;
1758 let expected = vec![((("foo".to_owned()), ()), 2, 1)];
1759 assert_eq!(actual, expected);
1760 }
1761
1762 #[mz_ore::test(tokio::test)]
1763 #[cfg_attr(miri, ignore)] async fn structured_lowers() {
1765 let cache = PersistClientCache::new_no_metrics();
1766 cache.cfg().set_config(&STRUCTURED_KEY_LOWER_LEN, 1024);
1768 cache.cfg().set_config(&INLINE_WRITES_SINGLE_MAX_BYTES, 0);
1770 cache.cfg().set_config(&INLINE_WRITES_TOTAL_MAX_BYTES, 0);
1771 let client = cache
1772 .open(PersistLocation::new_in_mem())
1773 .await
1774 .expect("client construction failed");
1775 let shard_id = ShardId::new();
1776 let (mut write, _) = client
1777 .expect_open::<String, String, u64, i64>(shard_id)
1778 .await;
1779
1780 let batch = write
1781 .expect_batch(
1782 &[
1783 (("1".into(), "one".into()), 1, 1),
1784 (("2".into(), "two".into()), 2, 1),
1785 (("3".into(), "three".into()), 3, 1),
1786 ],
1787 0,
1788 4,
1789 )
1790 .await;
1791
1792 assert_eq!(batch.batch.part_count(), 1);
1793 let [part] = batch.batch.parts.as_slice() else {
1794 panic!("expected single part")
1795 };
1796 assert!(part.structured_key_lower().is_some());
1798 }
1799}