1use std::cmp::Ordering;
11use std::collections::BTreeMap;
12use std::fmt::Debug;
13use std::sync::Arc;
14
15use bytes::{Bytes, BytesMut};
16use differential_dataflow::lattice::Lattice;
17use differential_dataflow::trace::Description;
18use mz_ore::assert_none;
19use mz_ore::cast::CastFrom;
20use mz_persist::location::{SeqNo, VersionedData};
21use mz_persist_types::Codec64;
22use mz_persist_types::schema::SchemaId;
23use mz_proto::TryFromProtoError;
24use timely::PartialOrder;
25use timely::progress::{Antichain, Timestamp};
26use tracing::debug;
27
28use crate::critical::CriticalReaderId;
29use crate::internal::paths::PartialRollupKey;
30use crate::internal::state::{
31 CriticalReaderState, EncodedSchemas, HollowBatch, HollowBlobRef, HollowRollup,
32 LeasedReaderState, ProtoStateField, ProtoStateFieldDiffType, ProtoStateFieldDiffs, RunPart,
33 State, StateCollections, WriterState,
34};
35use crate::internal::trace::CompactionInput;
36use crate::internal::trace::{FueledMergeRes, SpineId, ThinMerge, ThinSpineBatch, Trace};
37use crate::read::LeasedReaderId;
38use crate::write::WriterId;
39use crate::{Metrics, PersistConfig, ShardId};
40
41use StateFieldValDiff::*;
42
43use super::state::{ActiveGc, ActiveRollup};
44
45#[derive(Clone, Debug)]
46#[cfg_attr(any(test, debug_assertions), derive(PartialEq))]
47pub enum StateFieldValDiff<V> {
48 Insert(V),
49 Update(V, V),
50 Delete(V),
51}
52
53#[derive(Clone)]
54#[cfg_attr(any(test, debug_assertions), derive(PartialEq))]
55pub struct StateFieldDiff<K, V> {
56 pub key: K,
57 pub val: StateFieldValDiff<V>,
58}
59
60impl<K: Debug, V: Debug> std::fmt::Debug for StateFieldDiff<K, V> {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("StateFieldDiff")
63 .field("val", &self.val)
66 .field("key", &self.key)
67 .finish()
68 }
69}
70
71#[derive(Debug)]
72#[cfg_attr(any(test, debug_assertions), derive(Clone, PartialEq))]
73pub struct StateDiff<T> {
74 pub(crate) applier_version: semver::Version,
75 pub(crate) seqno_from: SeqNo,
76 pub(crate) seqno_to: SeqNo,
77 pub(crate) walltime_ms: u64,
78 pub(crate) latest_rollup_key: PartialRollupKey,
79 pub(crate) rollups: Vec<StateFieldDiff<SeqNo, HollowRollup>>,
80 pub(crate) active_rollup: Vec<StateFieldDiff<(), ActiveRollup>>,
81 pub(crate) active_gc: Vec<StateFieldDiff<(), ActiveGc>>,
82 pub(crate) hostname: Vec<StateFieldDiff<(), String>>,
83 pub(crate) last_gc_req: Vec<StateFieldDiff<(), SeqNo>>,
84 pub(crate) leased_readers: Vec<StateFieldDiff<LeasedReaderId, LeasedReaderState<T>>>,
85 pub(crate) critical_readers: Vec<StateFieldDiff<CriticalReaderId, CriticalReaderState<T>>>,
86 pub(crate) writers: Vec<StateFieldDiff<WriterId, WriterState<T>>>,
87 pub(crate) schemas: Vec<StateFieldDiff<SchemaId, EncodedSchemas>>,
88 pub(crate) since: Vec<StateFieldDiff<(), Antichain<T>>>,
89 pub(crate) legacy_batches: Vec<StateFieldDiff<HollowBatch<T>, ()>>,
90 pub(crate) hollow_batches: Vec<StateFieldDiff<SpineId, Arc<HollowBatch<T>>>>,
91 pub(crate) spine_batches: Vec<StateFieldDiff<SpineId, ThinSpineBatch<T>>>,
92 pub(crate) merges: Vec<StateFieldDiff<SpineId, ThinMerge<T>>>,
93}
94
95impl<T: Timestamp + Codec64> StateDiff<T> {
96 pub fn new(
97 applier_version: semver::Version,
98 seqno_from: SeqNo,
99 seqno_to: SeqNo,
100 walltime_ms: u64,
101 latest_rollup_key: PartialRollupKey,
102 ) -> Self {
103 StateDiff {
104 applier_version,
105 seqno_from,
106 seqno_to,
107 walltime_ms,
108 latest_rollup_key,
109 rollups: Vec::default(),
110 active_rollup: Vec::default(),
111 active_gc: Vec::default(),
112 hostname: Vec::default(),
113 last_gc_req: Vec::default(),
114 leased_readers: Vec::default(),
115 critical_readers: Vec::default(),
116 writers: Vec::default(),
117 schemas: Vec::default(),
118 since: Vec::default(),
119 legacy_batches: Vec::default(),
120 hollow_batches: Vec::default(),
121 spine_batches: Vec::default(),
122 merges: Vec::default(),
123 }
124 }
125
126 pub fn referenced_batches(&self) -> impl Iterator<Item = StateFieldValDiff<&HollowBatch<T>>> {
127 let legacy_batches = self
128 .legacy_batches
129 .iter()
130 .filter_map(|diff| match diff.val {
131 Insert(()) => Some(Insert(&diff.key)),
132 Update((), ()) => None, Delete(()) => Some(Delete(&diff.key)),
134 });
135 let hollow_batches = self.hollow_batches.iter().map(|diff| match &diff.val {
136 Insert(batch) => Insert(&**batch),
137 Update(before, after) => Update(&**before, &**after),
138 Delete(batch) => Delete(&**batch),
139 });
140 legacy_batches.chain(hollow_batches)
141 }
142}
143
144impl<T: Timestamp + Lattice + Codec64> StateDiff<T> {
145 pub fn from_diff(from: &State<T>, to: &State<T>) -> Self {
146 let State {
149 shard_id: from_shard_id,
150 seqno: from_seqno,
151 hostname: from_hostname,
152 walltime_ms: _, collections:
154 StateCollections {
155 version: _,
156 last_gc_req: from_last_gc_req,
157 rollups: from_rollups,
158 active_rollup: from_active_rollup,
159 active_gc: from_active_gc,
160 leased_readers: from_leased_readers,
161 critical_readers: from_critical_readers,
162 writers: from_writers,
163 schemas: from_schemas,
164 trace: from_trace,
165 },
166 } = from;
167 let State {
168 shard_id: to_shard_id,
169 seqno: to_seqno,
170 walltime_ms: to_walltime_ms,
171 hostname: to_hostname,
172 collections:
173 StateCollections {
174 version: to_applier_version,
175 last_gc_req: to_last_gc_req,
176 rollups: to_rollups,
177 active_rollup: to_active_rollup,
178 active_gc: to_active_gc,
179 leased_readers: to_leased_readers,
180 critical_readers: to_critical_readers,
181 writers: to_writers,
182 schemas: to_schemas,
183 trace: to_trace,
184 },
185 } = to;
186 assert_eq!(from_shard_id, to_shard_id);
187
188 let (_, latest_rollup) = to.latest_rollup();
189 let mut diffs = Self::new(
190 to_applier_version.clone(),
191 *from_seqno,
192 *to_seqno,
193 *to_walltime_ms,
194 latest_rollup.key.clone(),
195 );
196 diff_field_single(from_hostname, to_hostname, &mut diffs.hostname);
197 diff_field_single(from_last_gc_req, to_last_gc_req, &mut diffs.last_gc_req);
198 diff_field_sorted_iter(
199 from_active_rollup.iter().map(|r| (&(), r)),
200 to_active_rollup.iter().map(|r| (&(), r)),
201 &mut diffs.active_rollup,
202 );
203 diff_field_sorted_iter(
204 from_active_gc.iter().map(|g| (&(), g)),
205 to_active_gc.iter().map(|g| (&(), g)),
206 &mut diffs.active_gc,
207 );
208 diff_field_sorted_iter(from_rollups.iter(), to_rollups, &mut diffs.rollups);
209 diff_field_sorted_iter(
210 from_leased_readers.iter(),
211 to_leased_readers,
212 &mut diffs.leased_readers,
213 );
214 diff_field_sorted_iter(
215 from_critical_readers.iter(),
216 to_critical_readers,
217 &mut diffs.critical_readers,
218 );
219 diff_field_sorted_iter(from_writers.iter(), to_writers, &mut diffs.writers);
220 diff_field_sorted_iter(from_schemas.iter(), to_schemas, &mut diffs.schemas);
221 diff_field_single(from_trace.since(), to_trace.since(), &mut diffs.since);
222
223 let from_flat = from_trace.flatten();
224 let to_flat = to_trace.flatten();
225 diff_field_sorted_iter(
226 from_flat.legacy_batches.iter().map(|(k, v)| (&**k, v)),
227 to_flat.legacy_batches.iter().map(|(k, v)| (&**k, v)),
228 &mut diffs.legacy_batches,
229 );
230 diff_field_sorted_iter(
231 from_flat.hollow_batches.iter(),
232 to_flat.hollow_batches.iter(),
233 &mut diffs.hollow_batches,
234 );
235 diff_field_sorted_iter(
236 from_flat.spine_batches.iter(),
237 to_flat.spine_batches.iter(),
238 &mut diffs.spine_batches,
239 );
240 diff_field_sorted_iter(
241 from_flat.merges.iter(),
242 to_flat.merges.iter(),
243 &mut diffs.merges,
244 );
245 diffs
246 }
247
248 pub(crate) fn blob_inserts(&self) -> impl Iterator<Item = HollowBlobRef<'_, T>> {
249 let batches = self
250 .referenced_batches()
251 .filter_map(|spine_diff| match spine_diff {
252 Insert(b) | Update(_, b) => Some(HollowBlobRef::Batch(b)),
253 Delete(_) => None, });
255 let rollups = self
256 .rollups
257 .iter()
258 .filter_map(|rollups_diff| match &rollups_diff.val {
259 StateFieldValDiff::Insert(x) | StateFieldValDiff::Update(_, x) => {
260 Some(HollowBlobRef::Rollup(x))
261 }
262 StateFieldValDiff::Delete(_) => None, });
264 batches.chain(rollups)
265 }
266
267 pub(crate) fn part_deletes(&self) -> impl Iterator<Item = &RunPart<T>> {
268 let removed = self
274 .referenced_batches()
275 .filter_map(|spine_diff| match spine_diff {
276 Insert(_) => None,
277 Update(a, _) | Delete(a) => Some(a.parts.iter().collect::<Vec<_>>()),
278 });
279
280 let added: std::collections::BTreeSet<_> = self
281 .referenced_batches()
282 .filter_map(|spine_diff| match spine_diff {
283 Insert(a) | Update(_, a) => Some(a.parts.iter().collect::<Vec<_>>()),
284 Delete(_) => None,
285 })
286 .flatten()
287 .collect();
288
289 removed
290 .into_iter()
291 .flat_map(|x| x)
292 .filter(move |part| !added.contains(part))
293 }
294
295 pub(crate) fn rollup_deletes(&self) -> impl Iterator<Item = &HollowRollup> {
296 self.rollups
297 .iter()
298 .filter_map(|rollups_diff| match &rollups_diff.val {
299 Insert(_) => None,
300 Update(a, _) | Delete(a) => Some(a),
301 })
302 }
303
304 #[cfg(any(test, debug_assertions))]
305 #[allow(dead_code)]
306 pub fn validate_roundtrip<K, V, D>(
307 metrics: &Metrics,
308 from_state: &crate::internal::state::TypedState<K, V, T, D>,
309 diff: &Self,
310 to_state: &crate::internal::state::TypedState<K, V, T, D>,
311 ) -> Result<(), String>
312 where
313 K: mz_persist_types::Codec + std::fmt::Debug,
314 V: mz_persist_types::Codec + std::fmt::Debug,
315 D: differential_dataflow::difference::Monoid + Codec64,
316 {
317 use mz_proto::RustType;
318 use prost::Message;
319
320 use crate::internal::state::ProtoStateDiff;
321
322 let mut roundtrip_state = from_state.clone(from_state.hostname.clone());
323 roundtrip_state.apply_diff(metrics, diff.clone())?;
324
325 if &roundtrip_state != to_state {
326 return Err(format!(
329 "state didn't roundtrip\n from_state {:?}\n to_state {:?}\n rt_state {:?}\n diff {:?}\n",
330 from_state, to_state, roundtrip_state, diff
331 ));
332 }
333
334 let encoded_diff = diff.into_proto().encode_to_vec();
335 let roundtrip_diff = Self::from_proto(
336 ProtoStateDiff::decode(encoded_diff.as_slice()).map_err(|err| err.to_string())?,
337 )
338 .map_err(|err| err.to_string())?;
339
340 if &roundtrip_diff != diff {
341 return Err(format!(
344 "diff didn't roundtrip\n diff {:?}\n rt_diff {:?}",
345 diff, roundtrip_diff
346 ));
347 }
348
349 Ok(())
350 }
351}
352
353impl<T: Timestamp + Lattice + Codec64> State<T> {
354 pub fn apply_encoded_diffs<'a, I: IntoIterator<Item = &'a VersionedData>>(
355 &mut self,
356 cfg: &PersistConfig,
357 metrics: &Metrics,
358 diffs: I,
359 ) {
360 let mut state_seqno = self.seqno;
361 let diffs = diffs.into_iter().filter_map(move |x| {
362 if x.seqno != state_seqno.next() {
363 return None;
365 }
366 let data = x.data.clone();
367 let diff = metrics
368 .codecs
369 .state_diff
370 .decode(|| StateDiff::decode(&cfg.build_version, x.data.clone()));
372 assert_eq!(diff.seqno_from, state_seqno);
373 state_seqno = diff.seqno_to;
374 Some((diff, data))
375 });
376 self.apply_diffs(metrics, diffs);
377 }
378}
379
380impl<T: Timestamp + Lattice + Codec64> State<T> {
381 pub fn apply_diffs<I: IntoIterator<Item = (StateDiff<T>, Bytes)>>(
382 &mut self,
383 metrics: &Metrics,
384 diffs: I,
385 ) {
386 for (diff, data) in diffs {
387 match self.apply_diff(metrics, diff) {
391 Ok(()) => {}
392 Err(err) => {
393 let diff = StateDiff::<T>::decode(&self.collections.version, data);
399 panic!(
400 "state diff should apply cleanly: {} diff {:?} state {:?}",
401 err, diff, self
402 )
403 }
404 }
405 }
406 }
407
408 pub(super) fn apply_diff(
411 &mut self,
412 metrics: &Metrics,
413 diff: StateDiff<T>,
414 ) -> Result<(), String> {
415 let StateDiff {
417 applier_version: diff_applier_version,
418 seqno_from: diff_seqno_from,
419 seqno_to: diff_seqno_to,
420 walltime_ms: diff_walltime_ms,
421 latest_rollup_key: _,
422 rollups: diff_rollups,
423 active_rollup: diff_active_rollup,
424 active_gc: diff_active_gc,
425 hostname: diff_hostname,
426 last_gc_req: diff_last_gc_req,
427 leased_readers: diff_leased_readers,
428 critical_readers: diff_critical_readers,
429 writers: diff_writers,
430 schemas: diff_schemas,
431 since: diff_since,
432 legacy_batches: diff_legacy_batches,
433 hollow_batches: diff_hollow_batches,
434 spine_batches: diff_spine_batches,
435 merges: diff_merges,
436 } = diff;
437 if self.seqno == diff_seqno_to {
438 return Ok(());
439 }
440 if self.seqno != diff_seqno_from {
441 return Err(format!(
442 "could not apply diff {} -> {} to state {}",
443 diff_seqno_from, diff_seqno_to, self.seqno
444 ));
445 }
446 self.seqno = diff_seqno_to;
447 self.walltime_ms = diff_walltime_ms;
448 force_apply_diffs_single(
449 &self.shard_id,
450 diff_seqno_to,
451 "hostname",
452 diff_hostname,
453 &mut self.hostname,
454 metrics,
455 )?;
456
457 let StateCollections {
460 version,
461 last_gc_req,
462 rollups,
463 active_rollup,
464 active_gc,
465 leased_readers,
466 critical_readers,
467 writers,
468 schemas,
469 trace,
470 } = &mut self.collections;
471
472 *version = diff_applier_version;
473 apply_diffs_map("rollups", diff_rollups, rollups)?;
474 apply_diffs_single("last_gc_req", diff_last_gc_req, last_gc_req)?;
475 apply_diffs_single_option("active_rollup", diff_active_rollup, active_rollup)?;
476 apply_diffs_single_option("active_gc", diff_active_gc, active_gc)?;
477 apply_diffs_map("leased_readers", diff_leased_readers, leased_readers)?;
478 apply_diffs_map("critical_readers", diff_critical_readers, critical_readers)?;
479 apply_diffs_map("writers", diff_writers, writers)?;
480 apply_diffs_map("schemas", diff_schemas, schemas)?;
481
482 let structure_unchanged = diff_hollow_batches.is_empty()
483 && diff_spine_batches.is_empty()
484 && diff_merges.is_empty();
485 let spine_unchanged =
486 diff_since.is_empty() && diff_legacy_batches.is_empty() && structure_unchanged;
487
488 if spine_unchanged {
489 return Ok(());
490 }
491
492 let mut flat = if trace.roundtrip_structure {
493 metrics.state.apply_spine_flattened.inc();
494 let mut flat = trace.flatten();
495 apply_diffs_single("since", diff_since, &mut flat.since)?;
496 apply_diffs_map(
497 "legacy_batches",
498 diff_legacy_batches
499 .into_iter()
500 .map(|StateFieldDiff { key, val }| StateFieldDiff {
501 key: Arc::new(key),
502 val,
503 }),
504 &mut flat.legacy_batches,
505 )?;
506 Some(flat)
507 } else {
508 for x in diff_since {
509 match x.val {
510 Update(from, to) => {
511 if trace.since() != &from {
512 return Err(format!(
513 "since update didn't match: {:?} vs {:?}",
514 self.collections.trace.since(),
515 from
516 ));
517 }
518 trace.downgrade_since(&to);
519 }
520 Insert(_) => return Err("cannot insert since field".to_string()),
521 Delete(_) => return Err("cannot delete since field".to_string()),
522 }
523 }
524 if !diff_legacy_batches.is_empty() {
525 apply_diffs_spine(metrics, diff_legacy_batches, trace)?;
526 debug_assert_eq!(trace.validate(), Ok(()), "{:?}", trace);
527 }
528 None
529 };
530
531 if !structure_unchanged {
532 let flat = flat.get_or_insert_with(|| trace.flatten());
533 apply_diffs_map(
534 "hollow_batches",
535 diff_hollow_batches,
536 &mut flat.hollow_batches,
537 )?;
538 apply_diffs_map("spine_batches", diff_spine_batches, &mut flat.spine_batches)?;
539 apply_diffs_map("merges", diff_merges, &mut flat.merges)?;
540 }
541
542 if let Some(flat) = flat {
543 *trace = Trace::unflatten(flat)?;
544 }
545
546 Ok(())
552 }
553}
554
555fn diff_field_single<T: PartialEq + Clone>(
556 from: &T,
557 to: &T,
558 diffs: &mut Vec<StateFieldDiff<(), T>>,
559) {
560 if from != to {
563 diffs.push(StateFieldDiff {
564 key: (),
565 val: Update(from.clone(), to.clone()),
566 })
567 }
568}
569
570fn apply_diffs_single_option<X: PartialEq + Debug>(
571 name: &str,
572 diffs: Vec<StateFieldDiff<(), X>>,
573 single: &mut Option<X>,
574) -> Result<(), String> {
575 for diff in diffs {
576 apply_diff_single_option(name, diff, single)?;
577 }
578 Ok(())
579}
580
581fn apply_diff_single_option<X: PartialEq + Debug>(
582 name: &str,
583 diff: StateFieldDiff<(), X>,
584 single: &mut Option<X>,
585) -> Result<(), String> {
586 match diff.val {
587 Update(from, to) => {
588 if single.as_ref() != Some(&from) {
589 return Err(format!(
590 "{} update didn't match: {:?} vs {:?}",
591 name, single, from
592 ));
593 }
594 *single = Some(to)
595 }
596 Insert(to) => {
597 if single.is_some() {
598 return Err(format!("{} insert found existing value", name));
599 }
600 *single = Some(to)
601 }
602 Delete(from) => {
603 if single.as_ref() != Some(&from) {
604 return Err(format!(
605 "{} delete didn't match: {:?} vs {:?}",
606 name, single, from
607 ));
608 }
609 *single = None
610 }
611 }
612 Ok(())
613}
614
615fn apply_diffs_single<X: PartialEq + Debug>(
616 name: &str,
617 diffs: Vec<StateFieldDiff<(), X>>,
618 single: &mut X,
619) -> Result<(), String> {
620 for diff in diffs {
621 apply_diff_single(name, diff, single)?;
622 }
623 Ok(())
624}
625
626fn apply_diff_single<X: PartialEq + Debug>(
627 name: &str,
628 diff: StateFieldDiff<(), X>,
629 single: &mut X,
630) -> Result<(), String> {
631 match diff.val {
632 Update(from, to) => {
633 if single != &from {
634 return Err(format!(
635 "{} update didn't match: {:?} vs {:?}",
636 name, single, from
637 ));
638 }
639 *single = to
640 }
641 Insert(_) => return Err(format!("cannot insert {} field", name)),
642 Delete(_) => return Err(format!("cannot delete {} field", name)),
643 }
644 Ok(())
645}
646
647fn force_apply_diffs_single<X: PartialEq + Debug>(
654 shard_id: &ShardId,
655 seqno: SeqNo,
656 name: &str,
657 diffs: Vec<StateFieldDiff<(), X>>,
658 single: &mut X,
659 metrics: &Metrics,
660) -> Result<(), String> {
661 for diff in diffs {
662 force_apply_diff_single(shard_id, seqno, name, diff, single, metrics)?;
663 }
664 Ok(())
665}
666
667fn force_apply_diff_single<X: PartialEq + Debug>(
668 shard_id: &ShardId,
669 seqno: SeqNo,
670 name: &str,
671 diff: StateFieldDiff<(), X>,
672 single: &mut X,
673 metrics: &Metrics,
674) -> Result<(), String> {
675 match diff.val {
676 Update(from, to) => {
677 if single != &from {
678 debug!(
679 "{}: update didn't match: {:?} vs {:?}, continuing to force apply diff to {:?} for shard {} and seqno {}",
680 name, single, &from, &to, shard_id, seqno
681 );
682 metrics.state.force_apply_hostname.inc();
683 }
684 *single = to
685 }
686 Insert(_) => return Err(format!("cannot insert {} field", name)),
687 Delete(_) => return Err(format!("cannot delete {} field", name)),
688 }
689 Ok(())
690}
691
692fn diff_field_sorted_iter<'a, K, V, IF, IT>(from: IF, to: IT, diffs: &mut Vec<StateFieldDiff<K, V>>)
693where
694 K: Ord + Clone + 'a,
695 V: PartialEq + Clone + 'a,
696 IF: IntoIterator<Item = (&'a K, &'a V)>,
697 IT: IntoIterator<Item = (&'a K, &'a V)>,
698{
699 let (mut from, mut to) = (from.into_iter(), to.into_iter());
700 let (mut f, mut t) = (from.next(), to.next());
701 loop {
702 match (f, t) {
703 (None, None) => break,
704 (Some((fk, fv)), Some((tk, tv))) => match fk.cmp(tk) {
705 Ordering::Less => {
706 diffs.push(StateFieldDiff {
707 key: fk.clone(),
708 val: Delete(fv.clone()),
709 });
710 let f_next = from.next();
711 mz_ore::soft_assert_no_log!(
712 f_next.as_ref().map_or(true, |(fk_next, _)| fk_next > &fk)
713 );
714 f = f_next;
715 }
716 Ordering::Greater => {
717 diffs.push(StateFieldDiff {
718 key: tk.clone(),
719 val: Insert(tv.clone()),
720 });
721 let t_next = to.next();
722 mz_ore::soft_assert_no_log!(
723 t_next.as_ref().map_or(true, |(tk_next, _)| tk_next > &tk)
724 );
725 t = t_next;
726 }
727 Ordering::Equal => {
728 if fv != tv {
731 diffs.push(StateFieldDiff {
732 key: fk.clone(),
733 val: Update(fv.clone(), tv.clone()),
734 });
735 }
736 let f_next = from.next();
737 mz_ore::soft_assert_no_log!(
738 f_next.as_ref().map_or(true, |(fk_next, _)| fk_next > &fk)
739 );
740 f = f_next;
741 let t_next = to.next();
742 mz_ore::soft_assert_no_log!(
743 t_next.as_ref().map_or(true, |(tk_next, _)| tk_next > &tk)
744 );
745 t = t_next;
746 }
747 },
748 (None, Some((tk, tv))) => {
749 diffs.push(StateFieldDiff {
750 key: tk.clone(),
751 val: Insert(tv.clone()),
752 });
753 let t_next = to.next();
754 mz_ore::soft_assert_no_log!(
755 t_next.as_ref().map_or(true, |(tk_next, _)| tk_next > &tk)
756 );
757 t = t_next;
758 }
759 (Some((fk, fv)), None) => {
760 diffs.push(StateFieldDiff {
761 key: fk.clone(),
762 val: Delete(fv.clone()),
763 });
764 let f_next = from.next();
765 mz_ore::soft_assert_no_log!(
766 f_next.as_ref().map_or(true, |(fk_next, _)| fk_next > &fk)
767 );
768 f = f_next;
769 }
770 }
771 }
772}
773
774fn apply_diffs_map<K: Ord, V: PartialEq + Debug>(
775 name: &str,
776 diffs: impl IntoIterator<Item = StateFieldDiff<K, V>>,
777 map: &mut BTreeMap<K, V>,
778) -> Result<(), String> {
779 for diff in diffs {
780 apply_diff_map(name, diff, map)?;
781 }
782 Ok(())
783}
784
785fn apply_diff_map<K: Ord, V: PartialEq + Debug>(
789 name: &str,
790 diff: StateFieldDiff<K, V>,
791 map: &mut BTreeMap<K, V>,
792) -> Result<(), String> {
793 match diff.val {
794 Insert(to) => {
795 let prev = map.insert(diff.key, to);
796 if prev != None {
797 return Err(format!("{} insert found existing value: {:?}", name, prev));
798 }
799 }
800 Update(from, to) => {
801 let prev = map.insert(diff.key, to);
802 if prev.as_ref() != Some(&from) {
803 return Err(format!(
804 "{} update didn't match: {:?} vs {:?}",
805 name,
806 prev,
807 Some(from),
808 ));
809 }
810 }
811 Delete(from) => {
812 let prev = map.remove(&diff.key);
813 if prev.as_ref() != Some(&from) {
814 return Err(format!(
815 "{} delete didn't match: {:?} vs {:?}",
816 name,
817 prev,
818 Some(from),
819 ));
820 }
821 }
822 };
823 Ok(())
824}
825
826fn apply_diffs_spine<T: Timestamp + Lattice + Codec64>(
830 metrics: &Metrics,
831 mut diffs: Vec<StateFieldDiff<HollowBatch<T>, ()>>,
832 trace: &mut Trace<T>,
833) -> Result<(), String> {
834 if let Some(insert) = sniff_insert(&mut diffs, trace.upper()) {
838 let () = trace.push_batch_no_merge_reqs(insert);
841 if diffs.is_empty() {
845 metrics.state.apply_spine_fast_path.inc();
846 return Ok(());
847 }
848 }
849
850 match &diffs[..] {
851 [] => return Ok(()),
853
854 [
858 StateFieldDiff {
859 key: del,
860 val: StateFieldValDiff::Delete(()),
861 },
862 StateFieldDiff {
863 key: ins,
864 val: StateFieldValDiff::Insert(()),
865 },
866 ] => {
867 if del.is_empty()
868 && ins.is_empty()
869 && del.desc.lower() == ins.desc.lower()
870 && PartialOrder::less_than(del.desc.upper(), ins.desc.upper())
871 {
872 let () = trace.push_batch_no_merge_reqs(HollowBatch::empty(Description::new(
875 del.desc.upper().clone(),
876 ins.desc.upper().clone(),
877 Antichain::from_elem(T::minimum()),
881 )));
882 metrics.state.apply_spine_fast_path.inc();
883 return Ok(());
884 }
885 }
886 _ => {}
888 }
889
890 if let Some((_inputs, output)) = sniff_compaction(&diffs) {
892 let res = FueledMergeRes {
893 output,
894 input: CompactionInput::Legacy,
895 new_active_compaction: None,
896 };
897 if trace.apply_merge_res_unchecked(&res).applied() {
908 metrics.state.apply_spine_fast_path.inc();
911 return Ok(());
912 }
913
914 let mut batches = Vec::new();
916 trace.map_batches(|b| batches.push(b.clone()));
917
918 match apply_compaction_lenient(metrics, batches, &res.output) {
919 Ok(batches) => {
920 let mut new_trace = Trace::default();
921 new_trace.roundtrip_structure = trace.roundtrip_structure;
922 new_trace.downgrade_since(trace.since());
923 for batch in batches {
924 let () = new_trace.push_batch_no_merge_reqs(batch.clone());
927 }
928 *trace = new_trace;
929 metrics.state.apply_spine_slow_path_lenient.inc();
930 return Ok(());
931 }
932 Err(err) => {
933 return Err(format!(
934 "lenient compaction result apply unexpectedly failed: {}",
935 err
936 ));
937 }
938 }
939 }
940
941 metrics.state.apply_spine_slow_path.inc();
943 debug!(
944 "apply_diffs_spine didn't hit a fast-path diffs={:?} trace={:?}",
945 diffs, trace
946 );
947
948 let batches = {
949 let mut batches = BTreeMap::new();
950 trace.map_batches(|b| assert_none!(batches.insert(b.clone(), ())));
951 apply_diffs_map("spine", diffs.clone(), &mut batches).map(|_ok| batches)
952 };
953
954 let batches = match batches {
955 Ok(batches) => batches,
956 Err(err) => {
957 metrics
958 .state
959 .apply_spine_slow_path_with_reconstruction
960 .inc();
961 debug!(
962 "apply_diffs_spines could not apply diffs directly to existing trace batches: {}. diffs={:?} trace={:?}",
963 err, diffs, trace
964 );
965 let mut reconstructed_spine = Trace::default();
971 reconstructed_spine.roundtrip_structure = trace.roundtrip_structure;
972 trace.map_batches(|b| {
973 let () = reconstructed_spine.push_batch_no_merge_reqs(b.clone());
976 });
977
978 let mut batches = BTreeMap::new();
979 reconstructed_spine.map_batches(|b| assert_none!(batches.insert(b.clone(), ())));
980 apply_diffs_map("spine", diffs, &mut batches)?;
981 batches
982 }
983 };
984
985 let mut new_trace = Trace::default();
986 new_trace.roundtrip_structure = trace.roundtrip_structure;
987 new_trace.downgrade_since(trace.since());
988 for (batch, ()) in batches {
989 let () = new_trace.push_batch_no_merge_reqs(batch);
992 }
993 *trace = new_trace;
994 Ok(())
995}
996
997fn sniff_insert<T: Timestamp + Lattice>(
998 diffs: &mut Vec<StateFieldDiff<HollowBatch<T>, ()>>,
999 upper: &Antichain<T>,
1000) -> Option<HollowBatch<T>> {
1001 for idx in 0..diffs.len() {
1002 match &diffs[idx] {
1003 StateFieldDiff {
1004 key,
1005 val: StateFieldValDiff::Insert(()),
1006 } if key.desc.lower() == upper => return Some(diffs.remove(idx).key),
1007 _ => continue,
1008 }
1009 }
1010 None
1011}
1012
1013fn sniff_compaction<'a, T: Timestamp + Lattice>(
1016 diffs: &'a [StateFieldDiff<HollowBatch<T>, ()>],
1017) -> Option<(Vec<&'a HollowBatch<T>>, HollowBatch<T>)> {
1018 let mut inserts = diffs.iter().flat_map(|x| match x.val {
1021 StateFieldValDiff::Insert(()) => Some(&x.key),
1022 _ => None,
1023 });
1024 let compaction_output = match inserts.next() {
1025 Some(x) => x,
1026 None => return None,
1027 };
1028 if let Some(_) = inserts.next() {
1029 return None;
1030 }
1031
1032 let mut compaction_inputs = Vec::with_capacity(diffs.len() - 1);
1034 for diff in diffs.iter() {
1035 match diff.val {
1036 StateFieldValDiff::Delete(()) => {
1037 compaction_inputs.push(&diff.key);
1038 }
1039 StateFieldValDiff::Insert(()) => {}
1040 StateFieldValDiff::Update((), ()) => {
1041 return None;
1044 }
1045 }
1046 }
1047
1048 Some((compaction_inputs, compaction_output.clone()))
1049}
1050
1051fn apply_compaction_lenient<'a, T: Timestamp + Lattice>(
1073 metrics: &Metrics,
1074 mut trace: Vec<HollowBatch<T>>,
1075 replacement: &'a HollowBatch<T>,
1076) -> Result<Vec<HollowBatch<T>>, String> {
1077 let mut overlapping_batches = Vec::new();
1078 trace.retain(|b| {
1079 let before_replacement = PartialOrder::less_equal(b.desc.upper(), replacement.desc.lower());
1080 let after_replacement = PartialOrder::less_equal(replacement.desc.upper(), b.desc.lower());
1081 let overlaps_replacement = !(before_replacement || after_replacement);
1082 if overlaps_replacement {
1083 overlapping_batches.push(b.clone());
1084 false
1085 } else {
1086 true
1087 }
1088 });
1089
1090 {
1091 let first_overlapping_batch = match overlapping_batches.first() {
1092 Some(x) => x,
1093 None => return Err("replacement didn't overlap any batches".into()),
1094 };
1095 if PartialOrder::less_than(
1096 first_overlapping_batch.desc.lower(),
1097 replacement.desc.lower(),
1098 ) {
1099 if first_overlapping_batch.len > 0 {
1100 return Err(format!(
1101 "overlapping batch was unexpectedly non-empty: {:?}",
1102 first_overlapping_batch
1103 ));
1104 }
1105 let desc = Description::new(
1106 first_overlapping_batch.desc.lower().clone(),
1107 replacement.desc.lower().clone(),
1108 first_overlapping_batch.desc.since().clone(),
1109 );
1110 trace.push(HollowBatch::empty(desc));
1111 metrics.state.apply_spine_slow_path_lenient_adjustment.inc();
1112 }
1113 }
1114
1115 {
1116 let last_overlapping_batch = match overlapping_batches.last() {
1117 Some(x) => x,
1118 None => return Err("replacement didn't overlap any batches".into()),
1119 };
1120 if PartialOrder::less_than(
1121 replacement.desc.upper(),
1122 last_overlapping_batch.desc.upper(),
1123 ) {
1124 if last_overlapping_batch.len > 0 {
1125 return Err(format!(
1126 "overlapping batch was unexpectedly non-empty: {:?}",
1127 last_overlapping_batch
1128 ));
1129 }
1130 let desc = Description::new(
1131 replacement.desc.upper().clone(),
1132 last_overlapping_batch.desc.upper().clone(),
1133 last_overlapping_batch.desc.since().clone(),
1134 );
1135 trace.push(HollowBatch::empty(desc));
1136 metrics.state.apply_spine_slow_path_lenient_adjustment.inc();
1137 }
1138 }
1139 trace.push(replacement.clone());
1140
1141 trace.sort_by(|a, b| a.desc.lower().elements().cmp(b.desc.lower().elements()));
1143
1144 let mut expected_lower = &Antichain::from_elem(T::minimum());
1146 for b in trace.iter() {
1147 if b.desc.lower() != expected_lower {
1148 return Err(format!(
1149 "lower {:?} did not match expected {:?}: {:?}",
1150 b.desc.lower(),
1151 expected_lower,
1152 trace
1153 ));
1154 }
1155 expected_lower = b.desc.upper();
1156 }
1157 Ok(trace)
1158}
1159
1160#[derive(Debug)]
1176pub struct ProtoStateFieldDiffsWriter {
1177 data_buf: BytesMut,
1178 proto: ProtoStateFieldDiffs,
1179}
1180
1181impl ProtoStateFieldDiffsWriter {
1182 pub fn push_field(&mut self, field: ProtoStateField) {
1184 self.proto.fields.push(i32::from(field));
1185 }
1186
1187 pub fn push_diff_type(&mut self, diff_type: ProtoStateFieldDiffType) {
1189 self.proto.diff_types.push(i32::from(diff_type));
1190 }
1191
1192 pub fn encode_proto<M: prost::Message>(&mut self, msg: &M) {
1194 let len_before = self.data_buf.len();
1195 self.data_buf.reserve(msg.encoded_len());
1196
1197 msg.encode_raw(&mut self.data_buf);
1202
1203 let written_len = self.data_buf.len() - len_before;
1205 self.proto.data_lens.push(u64::cast_from(written_len));
1206 }
1207
1208 pub fn into_proto(self) -> ProtoStateFieldDiffs {
1209 let ProtoStateFieldDiffsWriter {
1210 data_buf,
1211 mut proto,
1212 } = self;
1213
1214 assert!(proto.data_bytes.is_empty());
1216
1217 let data_bytes = data_buf.freeze();
1219 proto.data_bytes = data_bytes;
1220
1221 proto
1222 }
1223}
1224
1225impl ProtoStateFieldDiffs {
1226 pub fn into_writer(mut self) -> ProtoStateFieldDiffsWriter {
1227 let mut data_buf = BytesMut::with_capacity(self.data_bytes.len());
1229
1230 let existing_data = std::mem::take(&mut self.data_bytes);
1232 data_buf.extend(existing_data);
1233
1234 ProtoStateFieldDiffsWriter {
1235 data_buf,
1236 proto: self,
1237 }
1238 }
1239
1240 pub fn iter<'a>(&'a self) -> ProtoStateFieldDiffsIter<'a> {
1241 let len = self.fields.len();
1242 assert_eq!(self.diff_types.len(), len);
1243
1244 ProtoStateFieldDiffsIter {
1245 len,
1246 diff_idx: 0,
1247 data_idx: 0,
1248 data_offset: 0,
1249 diffs: self,
1250 }
1251 }
1252
1253 pub fn validate(&self) -> Result<(), String> {
1254 if self.fields.len() != self.diff_types.len() {
1255 return Err(format!(
1256 "fields {} and diff_types {} lengths disagree",
1257 self.fields.len(),
1258 self.diff_types.len()
1259 ));
1260 }
1261
1262 let mut expected_data_slices = 0;
1263 for diff_type in self.diff_types.iter() {
1264 expected_data_slices += 1;
1266 match ProtoStateFieldDiffType::try_from(*diff_type) {
1268 Ok(ProtoStateFieldDiffType::Insert) => expected_data_slices += 1,
1269 Ok(ProtoStateFieldDiffType::Update) => expected_data_slices += 2,
1270 Ok(ProtoStateFieldDiffType::Delete) => expected_data_slices += 1,
1271 Err(_) => return Err(format!("unknown diff_type {}", diff_type)),
1272 }
1273 }
1274 if expected_data_slices != self.data_lens.len() {
1275 return Err(format!(
1276 "expected {} data slices got {}",
1277 expected_data_slices,
1278 self.data_lens.len()
1279 ));
1280 }
1281
1282 let Some(expected_data_bytes) = self
1288 .data_lens
1289 .iter()
1290 .copied()
1291 .try_fold(0u64, |acc, len| acc.checked_add(len))
1292 .and_then(|sum| usize::try_from(sum).ok())
1293 else {
1294 return Err(format!(
1295 "data_lens sum overflows, got {} lens over {} data bytes",
1296 self.data_lens.len(),
1297 self.data_bytes.len()
1298 ));
1299 };
1300 if expected_data_bytes != self.data_bytes.len() {
1301 return Err(format!(
1302 "expected {} data bytes got {}",
1303 expected_data_bytes,
1304 self.data_bytes.len()
1305 ));
1306 }
1307
1308 Ok(())
1309 }
1310}
1311
1312#[derive(Debug)]
1313pub struct ProtoStateFieldDiff<'a> {
1314 pub key: &'a [u8],
1315 pub diff_type: ProtoStateFieldDiffType,
1316 pub from: &'a [u8],
1317 pub to: &'a [u8],
1318}
1319
1320pub struct ProtoStateFieldDiffsIter<'a> {
1321 len: usize,
1322 diff_idx: usize,
1323 data_idx: usize,
1324 data_offset: usize,
1325 diffs: &'a ProtoStateFieldDiffs,
1326}
1327
1328impl<'a> Iterator for ProtoStateFieldDiffsIter<'a> {
1329 type Item = Result<(ProtoStateField, ProtoStateFieldDiff<'a>), TryFromProtoError>;
1330
1331 fn next(&mut self) -> Option<Self::Item> {
1332 if self.diff_idx >= self.len {
1333 return None;
1334 }
1335 let mut next_data = || {
1336 let start = self.data_offset;
1337 let end = start + usize::cast_from(self.diffs.data_lens[self.data_idx]);
1338 let data = &self.diffs.data_bytes[start..end];
1339 self.data_idx += 1;
1340 self.data_offset = end;
1341 data
1342 };
1343 let field = match ProtoStateField::try_from(self.diffs.fields[self.diff_idx]) {
1344 Ok(x) => x,
1345 Err(_) => {
1346 return Some(Err(TryFromProtoError::unknown_enum_variant(format!(
1347 "ProtoStateField({})",
1348 self.diffs.fields[self.diff_idx]
1349 ))));
1350 }
1351 };
1352 let diff_type =
1353 match ProtoStateFieldDiffType::try_from(self.diffs.diff_types[self.diff_idx]) {
1354 Ok(x) => x,
1355 Err(_) => {
1356 return Some(Err(TryFromProtoError::unknown_enum_variant(format!(
1357 "ProtoStateFieldDiffType({})",
1358 self.diffs.diff_types[self.diff_idx]
1359 ))));
1360 }
1361 };
1362 let key = next_data();
1363 let (from, to): (&[u8], &[u8]) = match diff_type {
1364 ProtoStateFieldDiffType::Insert => (&[], next_data()),
1365 ProtoStateFieldDiffType::Update => (next_data(), next_data()),
1366 ProtoStateFieldDiffType::Delete => (next_data(), &[]),
1367 };
1368 let diff = ProtoStateFieldDiff {
1369 key,
1370 diff_type,
1371 from,
1372 to,
1373 };
1374 self.diff_idx += 1;
1375 Some(Ok((field, diff)))
1376 }
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381 use semver::Version;
1382 use std::ops::ControlFlow::Continue;
1383
1384 use crate::internal::paths::{PartId, PartialBatchKey, RollupId, WriterKey};
1385 use mz_ore::metrics::MetricsRegistry;
1386
1387 use crate::ShardId;
1388 use crate::internal::state::TypedState;
1389
1390 use super::*;
1391
1392 #[mz_ore::test]
1393 #[cfg_attr(miri, ignore)] fn proto_state_diff_invalid_field_diffs_is_error() {
1395 use crate::internal::state::ProtoStateDiff;
1401 use mz_proto::ProtoType;
1402 use prost::Message;
1403
1404 let bytes: &[u8] = &[0x2a, 0x04, 0x08, 0x00, 0x68, 0x00, 0x40, 0x48];
1405 let proto = ProtoStateDiff::decode(bytes).expect("crash input decodes as a proto");
1406 let result: Result<StateDiff<u64>, _> = proto.into_rust();
1407 assert!(
1408 result.is_err(),
1409 "invalid field diffs must be a decode error"
1410 );
1411 }
1412
1413 #[mz_ore::test]
1414 #[cfg_attr(miri, ignore)] fn proto_state_diff_data_lens_overflow_is_error() {
1416 use crate::internal::state::ProtoStateDiff;
1421 use mz_proto::ProtoType;
1422
1423 let proto = ProtoStateDiff {
1424 applier_version: "0.1.2".into(),
1425 seqno_from: 0,
1426 seqno_to: 1,
1427 walltime_ms: 0,
1428 latest_rollup_key: "rollup".into(),
1429 field_diffs: Some(ProtoStateFieldDiffs {
1430 fields: vec![ProtoStateField::Hostname.into()],
1431 diff_types: vec![ProtoStateFieldDiffType::Insert.into()],
1433 data_lens: vec![u64::MAX, 1],
1434 data_bytes: Bytes::new(),
1435 }),
1436 };
1437 let result: Result<StateDiff<u64>, _> = proto.into_rust();
1438 assert!(
1439 result.is_err(),
1440 "data_lens that overflow must be a decode error"
1441 );
1442 }
1443
1444 #[mz_ore::test]
1447 #[cfg_attr(miri, ignore)] fn test_state_sync() {
1449 use proptest::prelude::*;
1450
1451 #[derive(Debug, Clone)]
1452 enum Action {
1453 Append { empty: bool, time_delta: u64 },
1455 Compact { req: usize },
1457 }
1458
1459 let action_gen: BoxedStrategy<Action> = {
1460 prop::strategy::Union::new([
1461 (any::<bool>(), 1u64..10u64)
1462 .prop_map(|(empty, time_delta)| Action::Append { empty, time_delta })
1463 .boxed(),
1464 (0usize..10usize)
1465 .prop_map(|req| Action::Compact { req })
1466 .boxed(),
1467 ])
1468 .boxed()
1469 };
1470
1471 fn run(actions: Vec<(Action, bool)>, metrics: &Metrics) {
1472 let version = Version::new(0, 100, 0);
1473 let writer_key = WriterKey::Version(version.to_string());
1474 let id = ShardId::new();
1475 let hostname = "computer";
1476 let typed: TypedState<String, (), u64, i64> =
1477 TypedState::new(version, id, hostname.to_string(), 0);
1478 let mut leader = typed.state;
1479
1480 let seqno = SeqNo::minimum();
1481 let mut lower = 0u64;
1482 let mut merge_reqs = vec![];
1483
1484 leader.collections.rollups.insert(
1485 seqno,
1486 HollowRollup {
1487 key: PartialRollupKey::new(seqno, &RollupId::new()),
1488 encoded_size_bytes: None,
1489 },
1490 );
1491 leader.collections.trace.roundtrip_structure = false;
1492 let mut follower = leader.clone();
1493
1494 for (action, roundtrip_structure) in actions {
1495 let mut old_leader = leader.clone();
1497 match action {
1498 Action::Append { empty, time_delta } => {
1499 let upper = lower + time_delta;
1500 let key = if empty {
1501 None
1502 } else {
1503 let id = PartId::new();
1504 Some(PartialBatchKey::new(&writer_key, &id))
1505 };
1506
1507 let keys = key.as_ref().map(|k| k.0.as_str());
1508 let reqs = leader.collections.trace.push_batch(
1509 crate::internal::state::tests::hollow(
1510 lower,
1511 upper,
1512 keys.as_slice(),
1513 if empty { 0 } else { 1 },
1514 ),
1515 );
1516 merge_reqs.extend(reqs);
1517 lower = upper;
1518 }
1519 Action::Compact { req } => {
1520 if !merge_reqs.is_empty() {
1521 let req = merge_reqs.remove(req.min(merge_reqs.len() - 1));
1522 let len = req.inputs.iter().map(|p| p.batch.len).sum();
1523 let parts = req
1524 .inputs
1525 .into_iter()
1526 .flat_map(|p| p.batch.parts.clone())
1527 .collect();
1528 let output = HollowBatch::new_run(req.desc, parts, len);
1529 leader
1530 .collections
1531 .trace
1532 .apply_merge_res_unchecked(&FueledMergeRes {
1533 output,
1534 input: CompactionInput::Legacy,
1535 new_active_compaction: None,
1536 });
1537 }
1538 }
1539 }
1540 leader.collections.trace.roundtrip_structure = roundtrip_structure;
1541 leader.seqno.0 += 1;
1542 let diff = StateDiff::from_diff(&old_leader, &leader);
1543
1544 old_leader
1547 .apply_diff(metrics, diff.clone())
1548 .expect("diff applies to the old version of the leader state");
1549 follower
1550 .apply_diff(metrics, diff.clone())
1551 .expect("diff applies to the synced version of the follower state");
1552
1553 }
1556 }
1557
1558 let config = PersistConfig::new_for_tests();
1559 let metrics_registry = MetricsRegistry::new();
1560 let metrics: Metrics = Metrics::new(&config, &metrics_registry);
1561
1562 proptest!(|(actions in prop::collection::vec((action_gen, any::<bool>()), 1..20))| {
1563 run(actions, &metrics)
1564 })
1565 }
1566
1567 #[mz_ore::test]
1571 fn regression_15493_sniff_insert() {
1572 fn hb(lower: u64, upper: u64, len: usize) -> HollowBatch<u64> {
1573 HollowBatch::new_run(
1574 Description::new(
1575 Antichain::from_elem(lower),
1576 Antichain::from_elem(upper),
1577 Antichain::from_elem(0),
1578 ),
1579 Vec::new(),
1580 len,
1581 )
1582 }
1583
1584 let batches_before = [hb(0, 7094664, 0), hb(7094664, 7185234, 100)];
1601
1602 let diffs = vec![
1603 StateFieldDiff {
1604 key: hb(0, 6805359, 0),
1605 val: StateFieldValDiff::Delete(()),
1606 },
1607 StateFieldDiff {
1608 key: hb(6805359, 7083793, 0),
1609 val: StateFieldValDiff::Delete(()),
1610 },
1611 StateFieldDiff {
1612 key: hb(0, 7083793, 0),
1613 val: StateFieldValDiff::Insert(()),
1614 },
1615 StateFieldDiff {
1616 key: hb(7185234, 7185859, 20),
1617 val: StateFieldValDiff::Insert(()),
1618 },
1619 ];
1620
1621 let batches_after = vec![
1628 hb(0, 7094664, 0),
1629 hb(7094664, 7185234, 100),
1630 hb(7185234, 7185859, 20),
1631 ];
1632
1633 let cfg = PersistConfig::new_for_tests();
1634 let state = TypedState::<(), (), u64, i64>::new(
1635 cfg.build_version.clone(),
1636 ShardId::new(),
1637 cfg.hostname.clone(),
1638 (cfg.now)(),
1639 );
1640 let state = state.clone_apply(&cfg, &mut |_seqno, _cfg, state| {
1641 for b in batches_before.iter() {
1642 let _merge_reqs = state.trace.push_batch(b.clone());
1643 }
1644 Continue::<(), ()>(())
1645 });
1646 let mut state = match state {
1647 Continue((_, x)) => x,
1648 std::ops::ControlFlow::Break(_) => unreachable!(),
1649 };
1650
1651 let metrics = Metrics::new(&PersistConfig::new_for_tests(), &MetricsRegistry::new());
1652 assert_eq!(
1653 apply_diffs_spine(&metrics, diffs, &mut state.collections.trace),
1654 Ok(())
1655 );
1656
1657 let mut actual = Vec::new();
1658 state
1659 .collections
1660 .trace
1661 .map_batches(|b| actual.push(b.clone()));
1662 assert_eq!(actual, batches_after);
1663 }
1664
1665 #[mz_ore::test]
1666 #[cfg_attr(miri, ignore)] fn apply_lenient() {
1668 #[track_caller]
1669 fn testcase(
1670 replacement: (u64, u64, u64, usize),
1671 spine: &[(u64, u64, u64, usize)],
1672 expected: Result<&[(u64, u64, u64, usize)], &str>,
1673 ) {
1674 fn batch(x: &(u64, u64, u64, usize)) -> HollowBatch<u64> {
1675 let (lower, upper, since, len) = x;
1676 let desc = Description::new(
1677 Antichain::from_elem(*lower),
1678 Antichain::from_elem(*upper),
1679 Antichain::from_elem(*since),
1680 );
1681 HollowBatch::new_run(desc, Vec::new(), *len)
1682 }
1683 let replacement = batch(&replacement);
1684 let batches = spine.iter().map(batch).collect::<Vec<_>>();
1685
1686 let metrics = Metrics::new(&PersistConfig::new_for_tests(), &MetricsRegistry::new());
1687 let actual = apply_compaction_lenient(&metrics, batches, &replacement);
1688 let expected = match expected {
1689 Ok(batches) => Ok(batches.iter().map(batch).collect::<Vec<_>>()),
1690 Err(err) => Err(err.to_owned()),
1691 };
1692 assert_eq!(actual, expected);
1693 }
1694
1695 testcase(
1697 (0, 3, 0, 100),
1698 &[(0, 1, 0, 0), (1, 2, 0, 0), (2, 3, 0, 0)],
1699 Ok(&[(0, 3, 0, 100)]),
1700 );
1701
1702 testcase(
1704 (1, 2, 0, 100),
1705 &[(0, 3, 0, 0)],
1706 Ok(&[(0, 1, 0, 0), (1, 2, 0, 100), (2, 3, 0, 0)]),
1707 );
1708
1709 testcase(
1711 (2, 4, 0, 100),
1712 &[(0, 3, 0, 0), (3, 4, 0, 0)],
1713 Ok(&[(0, 2, 0, 0), (2, 4, 0, 100)]),
1714 );
1715
1716 testcase(
1718 (2, 4, 0, 100),
1719 &[(0, 3, 0, 1), (3, 4, 0, 0)],
1720 Err(
1721 "overlapping batch was unexpectedly non-empty: HollowBatch { desc: ([0], [3], [0]), parts: [], len: 1, runs: [], run_meta: [] }",
1722 ),
1723 );
1724
1725 testcase(
1727 (2, 4, 0, 100),
1728 &[(0, 1, 0, 0), (1, 3, 0, 0), (3, 4, 0, 0)],
1729 Ok(&[(0, 1, 0, 0), (1, 2, 0, 0), (2, 4, 0, 100)]),
1730 );
1731
1732 testcase(
1734 (2, 4, 0, 100),
1735 &[(0, 3, 200, 0), (3, 4, 0, 0)],
1736 Ok(&[(0, 2, 200, 0), (2, 4, 0, 100)]),
1737 );
1738
1739 testcase(
1741 (0, 2, 0, 100),
1742 &[(0, 1, 0, 0), (1, 4, 0, 0)],
1743 Ok(&[(0, 2, 0, 100), (2, 4, 0, 0)]),
1744 );
1745
1746 testcase(
1748 (0, 2, 0, 100),
1749 &[(0, 1, 0, 0), (1, 4, 0, 1)],
1750 Err(
1751 "overlapping batch was unexpectedly non-empty: HollowBatch { desc: ([1], [4], [0]), parts: [], len: 1, runs: [], run_meta: [] }",
1752 ),
1753 );
1754
1755 testcase(
1757 (0, 2, 0, 100),
1758 &[(0, 1, 0, 0), (1, 3, 0, 0), (3, 4, 0, 0)],
1759 Ok(&[(0, 2, 0, 100), (2, 3, 0, 0), (3, 4, 0, 0)]),
1760 );
1761
1762 testcase(
1764 (0, 2, 0, 100),
1765 &[(0, 1, 0, 0), (1, 4, 200, 0)],
1766 Ok(&[(0, 2, 0, 100), (2, 4, 200, 0)]),
1767 );
1768
1769 testcase(
1771 (2, 6, 0, 100),
1772 &[(0, 3, 0, 0), (3, 5, 0, 0), (5, 8, 0, 0)],
1773 Ok(&[(0, 2, 0, 0), (2, 6, 0, 100), (6, 8, 0, 0)]),
1774 );
1775
1776 testcase(
1778 (2, 3, 0, 100),
1779 &[(0, 1, 0, 0)],
1780 Err("replacement didn't overlap any batches"),
1781 );
1782
1783 testcase(
1785 (2, 3, 0, 100),
1786 &[(4, 5, 0, 0)],
1787 Err("replacement didn't overlap any batches"),
1788 );
1789 }
1790}