1#[cfg(debug_assertions)]
13use std::collections::BTreeSet;
14use std::fmt::Debug;
15use std::ops::ControlFlow::{Break, Continue};
16use std::sync::Arc;
17use std::sync::atomic::Ordering;
18use std::time::SystemTime;
19
20use bytes::Bytes;
21use differential_dataflow::difference::Monoid;
22use differential_dataflow::lattice::Lattice;
23use differential_dataflow::trace::Description;
24use mz_ore::cast::CastFrom;
25use mz_persist::location::{
26 Blob, CaSResult, Consensus, Indeterminate, SCAN_ALL, SeqNo, VersionedData,
27};
28use mz_persist::retry::Retry;
29use mz_persist_types::{Codec, Codec64};
30use mz_proto::RustType;
31use prost::Message;
32use timely::progress::Timestamp;
33use tracing::{Instrument, debug, debug_span, trace, warn};
34
35use crate::cfg::STATE_VERSIONS_RECENT_LIVE_DIFFS_LIMIT;
36use crate::error::{CodecMismatch, CodecMismatchT};
37use crate::internal::encoding::{Rollup, UntypedState};
38use crate::internal::machine::{retry_determinate, retry_external};
39use crate::internal::metrics::ShardMetrics;
40use crate::internal::paths::{BlobKey, PartialBlobKey, PartialRollupKey, RollupId};
41#[cfg(debug_assertions)]
42use crate::internal::state::HollowBatch;
43use crate::internal::state::{
44 BatchPart, HollowBlobRef, HollowRollup, NoOpStateTransition, RunPart, State, TypedState,
45};
46use crate::internal::state_diff::{StateDiff, StateFieldValDiff};
47use crate::{Metrics, PersistConfig, ShardId};
48
49#[derive(Debug)]
100pub struct StateVersions {
101 pub(crate) cfg: PersistConfig,
102 pub(crate) consensus: Arc<dyn Consensus>,
103 pub(crate) blob: Arc<dyn Blob>,
104 pub(crate) metrics: Arc<Metrics>,
105}
106
107#[derive(Debug, Clone)]
108pub struct RecentLiveDiffs(pub Vec<VersionedData>);
109
110#[derive(Debug, Clone)]
111pub struct EncodedRollup {
112 pub(crate) shard_id: ShardId,
113 pub(crate) seqno: SeqNo,
114 pub(crate) key: PartialRollupKey,
115 pub(crate) _desc: Description<SeqNo>,
116 buf: Bytes,
117}
118
119impl EncodedRollup {
120 pub fn to_hollow(&self) -> HollowRollup {
121 HollowRollup {
122 key: self.key.clone(),
123 encoded_size_bytes: Some(self.buf.len()),
124 }
125 }
126}
127
128impl StateVersions {
129 pub fn new(
130 cfg: PersistConfig,
131 consensus: Arc<dyn Consensus>,
132 blob: Arc<dyn Blob>,
133 metrics: Arc<Metrics>,
134 ) -> Self {
135 StateVersions {
136 cfg,
137 consensus,
138 blob,
139 metrics,
140 }
141 }
142
143 pub async fn maybe_init_shard<K, V, T, D>(
146 &self,
147 shard_metrics: &ShardMetrics,
148 ) -> Result<TypedState<K, V, T, D>, Box<CodecMismatch>>
149 where
150 K: Debug + Codec,
151 V: Debug + Codec,
152 T: Timestamp + Lattice + Codec64,
153 D: Monoid + Codec64,
154 {
155 let shard_id = shard_metrics.shard_id;
156
157 let recent_live_diffs = self.fetch_recent_live_diffs::<T>(&shard_id).await;
159 if !recent_live_diffs.0.is_empty() {
160 return self
161 .fetch_current_state(&shard_id, recent_live_diffs.0)
162 .await
163 .check_codecs(&shard_id);
164 }
165
166 let (initial_state, initial_diff) = self.write_initial_rollup(shard_metrics).await;
168 assert_eq!(
169 initial_state.seqno(),
170 SeqNo::minimum(),
171 "initial state should have the initial seqno"
172 );
173 let (cas_res, _diff) =
174 retry_external(&self.metrics.retries.external.maybe_init_cas, || async {
175 self.try_compare_and_set_current(
176 "maybe_init_shard",
177 shard_metrics,
178 &initial_state,
179 &initial_diff,
180 )
181 .await
182 .map_err(|err| err.into())
183 })
184 .await;
185 match cas_res {
186 CaSResult::Committed => Ok(initial_state),
187 CaSResult::ExpectationMismatch => {
188 let recent_live_diffs = self.fetch_recent_live_diffs::<T>(&shard_id).await;
189 let state = self
190 .fetch_current_state(&shard_id, recent_live_diffs.0)
191 .await
192 .check_codecs(&shard_id);
193
194 let (_, rollup) = initial_state.latest_rollup();
202 let should_delete_rollup = match state.as_ref() {
203 Ok(state) => !state
204 .collections
205 .rollups
206 .values()
207 .any(|x| &x.key == &rollup.key),
208 Err(_codec_mismatch) => true,
211 };
212 if should_delete_rollup {
213 self.delete_rollup(&shard_id, &rollup.key).await;
214 }
215
216 state
217 }
218 }
219 }
220
221 pub async fn try_compare_and_set_current<K, V, T, D>(
226 &self,
227 cmd_name: &str,
228 shard_metrics: &ShardMetrics,
229 new_state: &TypedState<K, V, T, D>,
230 diff: &StateDiff<T>,
231 ) -> Result<(CaSResult, VersionedData), Indeterminate>
232 where
233 K: Debug + Codec,
234 V: Debug + Codec,
235 T: Timestamp + Lattice + Codec64,
236 D: Monoid + Codec64,
237 {
238 assert_eq!(shard_metrics.shard_id, new_state.shard_id);
239 let path = new_state.shard_id.to_string();
240
241 trace!(
242 "apply_unbatched_cmd {} attempting {}\n new_state={:?}",
243 cmd_name,
244 new_state.seqno(),
245 new_state
246 );
247 let new = self.metrics.codecs.state_diff.encode(|| {
248 let mut buf = Vec::new();
249 diff.encode(&mut buf);
250 VersionedData {
251 seqno: new_state.seqno(),
252 data: Bytes::from(buf),
253 }
254 });
255 assert_eq!(new.seqno, diff.seqno_to);
256
257 let payload_len = new.data.len();
258 let cas_res = retry_determinate(
259 &self.metrics.retries.determinate.apply_unbatched_cmd_cas,
260 || async { self.consensus.compare_and_set(&path, new.clone()).await },
261 )
262 .instrument(debug_span!("apply_unbatched_cmd::cas", payload_len))
263 .await
264 .map_err(|err| {
265 debug!("apply_unbatched_cmd {} errored: {}", cmd_name, err);
266 err
267 })?;
268
269 match cas_res {
270 CaSResult::Committed => {
271 trace!(
272 "apply_unbatched_cmd {} succeeded {}\n new_state={:?}",
273 cmd_name,
274 new_state.seqno(),
275 new_state
276 );
277
278 shard_metrics.seqnos_since_last_rollup.set(
279 new_state
280 .seqno
281 .0
282 .saturating_sub(new_state.latest_rollup().0.0),
283 );
284 shard_metrics
285 .spine_batch_count
286 .set(u64::cast_from(new_state.spine_batch_count()));
287 let size_metrics = new_state.size_metrics();
288 shard_metrics
289 .hollow_batch_count
290 .set(u64::cast_from(size_metrics.hollow_batch_count));
291 shard_metrics
292 .batch_part_count
293 .set(u64::cast_from(size_metrics.batch_part_count));
294 shard_metrics
295 .update_count
296 .set(u64::cast_from(size_metrics.num_updates));
297 shard_metrics
298 .rollup_count
299 .set(u64::cast_from(size_metrics.state_rollup_count));
300 shard_metrics
301 .largest_batch_size
302 .set(u64::cast_from(size_metrics.largest_batch_bytes));
303 shard_metrics
304 .usage_current_state_batches_bytes
305 .set(u64::cast_from(size_metrics.state_batches_bytes));
306 shard_metrics
307 .usage_current_state_rollups_bytes
308 .set(u64::cast_from(size_metrics.state_rollups_bytes));
309 shard_metrics
310 .seqnos_held
311 .set(u64::cast_from(new_state.seqnos_held()));
312 shard_metrics
313 .encoded_diff_size
314 .inc_by(u64::cast_from(payload_len));
315 shard_metrics
316 .inline_part_count
317 .set(u64::cast_from(size_metrics.inline_part_count));
318 shard_metrics.stale.store(
319 new_state
320 .state
321 .collections
322 .version
323 .cmp_precedence(&self.cfg.build_version)
324 .is_lt(),
325 Ordering::Relaxed,
326 );
327
328 let spine_metrics = new_state.collections.trace.spine_metrics();
329 shard_metrics
330 .compact_batches
331 .set(spine_metrics.compact_batches);
332 shard_metrics
333 .compacting_batches
334 .set(spine_metrics.compacting_batches);
335 shard_metrics
336 .noncompact_batches
337 .set(spine_metrics.noncompact_batches);
338
339 let batch_parts_by_version = new_state
340 .collections
341 .trace
342 .batches()
343 .flat_map(|x| x.parts.iter())
344 .flat_map(|part| {
345 let key = match part {
346 RunPart::Many(x) => Some(&x.key),
347 RunPart::Single(BatchPart::Hollow(x)) => Some(&x.key),
348 RunPart::Single(BatchPart::Inline { .. }) => None,
350 }?;
351 let (writer_key, _) = key.0.split_once('/')?;
353 match &writer_key[..1] {
354 "w" => Some("old"),
355 "n" => Some(&writer_key[1..]),
356 _ => None,
357 }
358 });
359 shard_metrics.set_batch_part_versions(batch_parts_by_version);
360
361 Ok((CaSResult::Committed, new))
362 }
363 CaSResult::ExpectationMismatch => {
364 debug!(
365 "apply_unbatched_cmd {} {} lost the CaS race, retrying: {:?}",
366 new_state.shard_id(),
367 cmd_name,
368 new_state.seqno.previous(),
369 );
370 Ok((CaSResult::ExpectationMismatch, new))
371 }
372 }
373 }
374
375 pub async fn fetch_current_state<T>(
382 &self,
383 shard_id: &ShardId,
384 mut live_diffs: Vec<VersionedData>,
385 ) -> UntypedState<T>
386 where
387 T: Timestamp + Lattice + Codec64,
388 {
389 let retry = self
390 .metrics
391 .retries
392 .fetch_latest_state
393 .stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
394 loop {
395 let latest_diff = live_diffs
396 .last()
397 .expect("initialized shard should have at least one diff");
398 let latest_diff = self
399 .metrics
400 .codecs
401 .state_diff
402 .decode(|| {
404 StateDiff::<T>::decode(&self.cfg.build_version, latest_diff.data.clone())
405 });
406 let mut state = match self
407 .fetch_rollup_at_key(shard_id, &latest_diff.latest_rollup_key)
408 .await
409 {
410 Some(x) => x,
411 None => {
412 retry.retries.inc();
415 let earliest_before_refetch = live_diffs
416 .first()
417 .expect("initialized shard should have at least one diff")
418 .seqno;
419 live_diffs = self.fetch_recent_live_diffs::<T>(shard_id).await.0;
420
421 let earliest_after_refetch = live_diffs
428 .first()
429 .expect("initialized shard should have at least one diff")
430 .seqno;
431 if earliest_before_refetch >= earliest_after_refetch {
432 warn!(
433 concat!(
434 "fetch_current_state refetch expects earliest live diff to advance: {} vs {}. ",
435 "In dev and testing, this happens when persist's Blob (files in mzdata) ",
436 "is deleted out from under it or when two processes are talking to ",
437 "different Blobs (e.g. docker containers without it shared)."
438 ),
439 earliest_before_refetch, earliest_after_refetch
440 )
441 }
442 continue;
443 }
444 };
445
446 state.apply_encoded_diffs(&self.cfg, &self.metrics, &live_diffs);
447 return state;
448 }
449 }
450
451 pub async fn fetch_all_live_states<T>(
455 &self,
456 shard_id: ShardId,
457 ) -> Option<UntypedStateVersionsIter<T>>
458 where
459 T: Timestamp + Lattice + Codec64,
460 {
461 let retry = self
462 .metrics
463 .retries
464 .fetch_live_states
465 .stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
466 let mut all_live_diffs = self.fetch_all_live_diffs(&shard_id).await;
467 loop {
468 let earliest_live_diff = match all_live_diffs.first() {
469 Some(x) => x,
470 None => return None,
471 };
472 let state = match self
473 .fetch_rollup_at_seqno(&shard_id, all_live_diffs.clone(), earliest_live_diff.seqno)
474 .await
475 {
476 Some(x) => x,
477 None => {
478 retry.retries.inc();
485 let earliest_before_refetch = earliest_live_diff.seqno;
486 all_live_diffs = self.fetch_all_live_diffs(&shard_id).await;
487
488 let earliest_after_refetch = all_live_diffs
495 .first()
496 .expect("initialized shard should have at least one diff")
497 .seqno;
498 if earliest_before_refetch >= earliest_after_refetch {
499 warn!(
500 concat!(
501 "fetch_all_live_states refetch expects earliest live diff to advance: {} vs {}. ",
502 "In dev and testing, this happens when persist's Blob (files in mzdata) ",
503 "is deleted out from under it or when two processes are talking to ",
504 "different Blobs (e.g. docker containers without it shared)."
505 ),
506 earliest_before_refetch, earliest_after_refetch
507 )
508 }
509 continue;
510 }
511 };
512 assert_eq!(earliest_live_diff.seqno, state.seqno());
513 return Some(UntypedStateVersionsIter {
514 shard_id,
515 cfg: self.cfg.clone(),
516 metrics: Arc::clone(&self.metrics),
517 state,
518 diffs: all_live_diffs,
519 });
520 }
521 }
522
523 pub async fn fetch_all_live_diffs(&self, shard_id: &ShardId) -> Vec<VersionedData> {
529 let path = shard_id.to_string();
530 let diffs = retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
531 self.consensus.scan(&path, SeqNo::minimum(), SCAN_ALL).await
532 })
533 .instrument(debug_span!("fetch_state::scan"))
534 .await;
535 diffs
536 }
537
538 async fn fetch_live_diffs(
541 &self,
542 shard_id: &ShardId,
543 from: SeqNo,
544 limit: usize,
545 ) -> Vec<VersionedData> {
546 let path = shard_id.to_string();
547 retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
548 self.consensus.scan(&path, from, limit).await
549 })
550 .instrument(debug_span!("fetch_state::scan"))
551 .await
552 }
553
554 pub async fn fetch_live_diffs_through(
557 &self,
558 shard_id: &ShardId,
559 through: SeqNo,
560 ) -> Vec<VersionedData> {
561 let scan_limit = STATE_VERSIONS_RECENT_LIVE_DIFFS_LIMIT.get(&self.cfg);
563 let mut versions = self
564 .fetch_live_diffs(shard_id, SeqNo::minimum(), scan_limit)
565 .await;
566
567 if versions.len() == scan_limit {
568 loop {
570 let Some(last_seqno) = versions.last().map(|v| v.seqno) else {
571 break;
572 };
573 if through <= last_seqno {
574 break;
575 }
576 let from = last_seqno.next();
577 let limit = usize::cast_from(through.0 - last_seqno.0).clamp(1, 10 * scan_limit);
578 let more_versions = self.fetch_live_diffs(shard_id, from, limit).await;
579 let more_versions_len = more_versions.len();
580 if let Some(first) = more_versions.first() {
581 assert!(last_seqno < first.seqno);
582 }
583 versions.extend(more_versions);
584 if more_versions_len < limit {
585 break;
586 }
587 }
588 }
589 let partition_index = versions.partition_point(|v| v.seqno <= through);
592 versions.truncate(partition_index);
593 versions
594 }
595
596 pub async fn fetch_recent_live_diffs<T>(&self, shard_id: &ShardId) -> RecentLiveDiffs
603 where
604 T: Timestamp + Lattice + Codec64,
605 {
606 let path = shard_id.to_string();
607 let scan_limit = STATE_VERSIONS_RECENT_LIVE_DIFFS_LIMIT.get(&self.cfg);
608 let oldest_diffs =
609 retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
610 self.consensus
611 .scan(&path, SeqNo::minimum(), scan_limit)
612 .await
613 })
614 .instrument(debug_span!("fetch_state::scan"))
615 .await;
616
617 if oldest_diffs.len() < scan_limit {
620 self.metrics.state.fetch_recent_live_diffs_fast_path.inc();
621 return RecentLiveDiffs(oldest_diffs);
622 }
623
624 let head = retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
635 self.consensus.head(&path).await
636 })
637 .instrument(debug_span!("fetch_state::slow_path::head"))
638 .await
639 .expect("initialized shard should have at least 1 diff");
640
641 let latest_diff = self
642 .metrics
643 .codecs
644 .state_diff
645 .decode(|| StateDiff::<T>::decode(&self.cfg.build_version, head.data));
646
647 match BlobKey::parse_ids(&latest_diff.latest_rollup_key.complete(shard_id)) {
648 Ok((_shard_id, PartialBlobKey::Rollup(seqno, _rollup))) => {
649 self.metrics.state.fetch_recent_live_diffs_slow_path.inc();
650 let diffs =
651 retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
652 self.consensus.scan(&path, seqno, SCAN_ALL).await
656 })
657 .instrument(debug_span!("fetch_state::slow_path::scan"))
658 .await;
659 RecentLiveDiffs(diffs)
660 }
661 Ok(_) => panic!(
662 "invalid state diff rollup key: {}",
663 latest_diff.latest_rollup_key
664 ),
665 Err(err) => panic!("unparseable state diff rollup key: {}", err),
666 }
667 }
668
669 pub async fn fetch_all_live_diffs_gt_seqno<K, V, T, D>(
674 &self,
675 shard_id: &ShardId,
676 seqno: SeqNo,
677 ) -> Vec<VersionedData> {
678 let path = shard_id.to_string();
679 retry_external(&self.metrics.retries.external.fetch_state_scan, || async {
680 self.consensus.scan(&path, seqno.next(), SCAN_ALL).await
681 })
682 .instrument(debug_span!("fetch_state::scan"))
683 .await
684 }
685
686 pub async fn truncate_diffs(&self, shard_id: &ShardId, seqno: SeqNo) {
688 let path = shard_id.to_string();
689 let _deleted_count = retry_external(&self.metrics.retries.external.gc_truncate, || async {
690 self.consensus.truncate(&path, seqno).await
691 })
692 .instrument(debug_span!("gc::truncate"))
693 .await;
694 }
695
696 async fn write_initial_rollup<K, V, T, D>(
700 &self,
701 shard_metrics: &ShardMetrics,
702 ) -> (TypedState<K, V, T, D>, StateDiff<T>)
703 where
704 K: Debug + Codec,
705 V: Debug + Codec,
706 T: Timestamp + Lattice + Codec64,
707 D: Monoid + Codec64,
708 {
709 let empty_state = TypedState::new(
710 self.cfg.build_version.clone(),
711 shard_metrics.shard_id,
712 self.cfg.hostname.clone(),
713 (self.cfg.now)(),
714 );
715 let mut initial_state = empty_state.clone_for_rollup();
716 let rollup_seqno = initial_state.seqno();
717 let rollup = HollowRollup {
718 key: PartialRollupKey::new(rollup_seqno, &RollupId::new()),
719 encoded_size_bytes: None,
723 };
724 let applied = match initial_state
725 .collections
726 .add_rollup((rollup_seqno, &rollup))
727 {
728 Continue(x) => x,
729 Break(NoOpStateTransition(_)) => {
730 panic!("initial state transition should not be a no-op")
731 }
732 };
733 assert!(
734 applied,
735 "add_and_remove_rollups should apply to the empty state"
736 );
737
738 let rollup = self.encode_rollup_blob(
739 shard_metrics,
740 initial_state.clone_for_rollup(),
741 vec![],
742 rollup.key,
743 );
744 let () = self.write_rollup_blob(&rollup).await;
745 assert_eq!(initial_state.seqno, rollup.seqno);
746
747 let diff = StateDiff::from_diff(&empty_state.state, &initial_state.state);
748 (initial_state, diff)
749 }
750
751 pub async fn write_rollup_for_state<K, V, T, D>(
752 &self,
753 shard_metrics: &ShardMetrics,
754 state: TypedState<K, V, T, D>,
755 rollup_id: &RollupId,
756 ) -> Option<EncodedRollup>
757 where
758 K: Debug + Codec,
759 V: Debug + Codec,
760 T: Timestamp + Lattice + Codec64,
761 D: Monoid + Codec64,
762 {
763 let (latest_rollup_seqno, _rollup) = state.latest_rollup();
764 let seqno = state.seqno();
765
766 let diffs: Vec<_> = self
770 .fetch_all_live_diffs_gt_seqno::<K, V, T, D>(&state.shard_id, *latest_rollup_seqno)
771 .await;
772
773 match diffs.first() {
774 None => {
775 self.metrics.state.rollup_write_noop_latest.inc();
781 assert_eq!(seqno, *latest_rollup_seqno);
782 return None;
783 }
784 Some(first) => {
785 self.metrics.state.rollup_write_noop_truncated.inc();
793 if first.seqno != latest_rollup_seqno.next() {
794 assert!(
795 first.seqno > latest_rollup_seqno.next(),
796 "diff: {}, rollup: {}",
797 first.seqno,
798 latest_rollup_seqno,
799 );
800 return None;
801 }
802 }
803 }
804
805 let diffs: Vec<_> = diffs.into_iter().filter(|x| x.seqno <= seqno).collect();
807
808 assert_eq!(
811 diffs.first().map(|x| x.seqno),
812 Some(latest_rollup_seqno.next())
813 );
814 assert_eq!(diffs.last().map(|x| x.seqno), Some(state.seqno));
815
816 let key = PartialRollupKey::new(state.seqno, rollup_id);
817 let rollup = self.encode_rollup_blob(shard_metrics, state, diffs, key);
818 let () = self.write_rollup_blob(&rollup).await;
819
820 self.metrics.state.rollup_write_success.inc();
821
822 Some(rollup)
823 }
824
825 pub fn encode_rollup_blob<K, V, T, D>(
829 &self,
830 shard_metrics: &ShardMetrics,
831 state: TypedState<K, V, T, D>,
832 diffs: Vec<VersionedData>,
833 key: PartialRollupKey,
834 ) -> EncodedRollup
835 where
836 K: Debug + Codec,
837 V: Debug + Codec,
838 T: Timestamp + Lattice + Codec64,
839 D: Monoid + Codec64,
840 {
841 let shard_id = state.shard_id;
842 let rollup_seqno = state.seqno;
843
844 let rollup = Rollup::from(state.into(), diffs);
845 let desc = rollup.diffs.as_ref().expect("inlined diffs").description();
846
847 let buf = self.metrics.codecs.state.encode(|| {
848 let mut buf = Vec::new();
849 rollup
850 .into_proto()
851 .encode(&mut buf)
852 .expect("no required fields means no initialization errors");
853 Bytes::from(buf)
854 });
855 shard_metrics
856 .latest_rollup_size
857 .set(u64::cast_from(buf.len()));
858 EncodedRollup {
859 shard_id,
860 seqno: rollup_seqno,
861 key,
862 buf,
863 _desc: desc,
864 }
865 }
866
867 pub async fn write_rollup_blob(&self, rollup: &EncodedRollup) {
869 let payload_len = rollup.buf.len();
870 retry_external(&self.metrics.retries.external.rollup_set, || async {
871 self.blob
872 .set(
873 &rollup.key.complete(&rollup.shard_id),
874 Bytes::clone(&rollup.buf),
875 )
876 .await
877 })
878 .instrument(debug_span!("rollup::set", payload_len))
879 .await;
880 }
881
882 async fn fetch_rollup_at_seqno<T>(
889 &self,
890 shard_id: &ShardId,
891 live_diffs: Vec<VersionedData>,
892 seqno: SeqNo,
893 ) -> Option<UntypedState<T>>
894 where
895 T: Timestamp + Lattice + Codec64,
896 {
897 let rollup_key_for_migration = live_diffs.iter().find_map(|x| {
898 let diff = self
899 .metrics
900 .codecs
901 .state_diff
902 .decode(|| StateDiff::<T>::decode(&self.cfg.build_version, x.data.clone()));
904 diff.rollups
905 .iter()
906 .find(|x| x.key == seqno)
907 .map(|x| match &x.val {
908 StateFieldValDiff::Insert(x) => x.clone(),
909 StateFieldValDiff::Update(_, x) => x.clone(),
910 StateFieldValDiff::Delete(x) => x.clone(),
911 })
912 });
913
914 let state = self.fetch_current_state::<T>(shard_id, live_diffs).await;
915 if let Some(rollup) = state.rollups().get(&seqno) {
916 return self.fetch_rollup_at_key(shard_id, &rollup.key).await;
917 }
918
919 let rollup = rollup_key_for_migration.expect("someone should have a key for this rollup");
952 tracing::info!("only found rollup for {} {} via migration", shard_id, seqno);
953 self.metrics.state.rollup_at_seqno_migration.inc();
954 self.fetch_rollup_at_key(shard_id, &rollup.key).await
955 }
956
957 pub async fn fetch_rollup_at_key<T>(
959 &self,
960 shard_id: &ShardId,
961 rollup_key: &PartialRollupKey,
962 ) -> Option<UntypedState<T>>
963 where
964 T: Timestamp + Lattice + Codec64,
965 {
966 retry_external(&self.metrics.retries.external.rollup_get, || async {
967 self.blob.get(&rollup_key.complete(shard_id)).await
968 })
969 .instrument(debug_span!("rollup::get"))
970 .await
971 .map(|buf| {
972 self.metrics
973 .codecs
974 .state
975 .decode(|| UntypedState::decode(&self.cfg.build_version, buf))
976 })
977 }
978
979 pub async fn delete_rollup(&self, shard_id: &ShardId, key: &PartialRollupKey) {
981 let _ = retry_external(&self.metrics.retries.external.rollup_delete, || async {
982 self.blob.delete(&key.complete(shard_id)).await
983 })
984 .await
985 .instrument(debug_span!("rollup::delete"));
986 }
987}
988
989pub struct UntypedStateVersionsIter<T> {
990 shard_id: ShardId,
991 cfg: PersistConfig,
992 metrics: Arc<Metrics>,
993 state: UntypedState<T>,
994 diffs: Vec<VersionedData>,
995}
996
997impl<T: Timestamp + Lattice + Codec64> UntypedStateVersionsIter<T> {
998 pub(crate) fn new(
999 shard_id: ShardId,
1000 cfg: PersistConfig,
1001 metrics: Arc<Metrics>,
1002 state: UntypedState<T>,
1003 diffs: Vec<VersionedData>,
1004 ) -> Self {
1005 Self {
1006 shard_id,
1007 cfg,
1008 metrics,
1009 state,
1010 diffs,
1011 }
1012 }
1013
1014 pub(crate) fn check_ts_codec(self) -> Result<StateVersionsIter<T>, CodecMismatchT> {
1015 let key_codec = self.state.key_codec.clone();
1016 let val_codec = self.state.val_codec.clone();
1017 let diff_codec = self.state.diff_codec.clone();
1018 let state = self.state.check_ts_codec(&self.shard_id)?;
1019 Ok(StateVersionsIter::new(
1020 self.cfg,
1021 self.metrics,
1022 state,
1023 self.diffs,
1024 key_codec,
1025 val_codec,
1026 diff_codec,
1027 ))
1028 }
1029}
1030
1031pub struct StateVersionsIter<T> {
1033 cfg: PersistConfig,
1034 metrics: Arc<Metrics>,
1035 state: State<T>,
1036 diffs: Vec<VersionedData>,
1037 key_codec: String,
1038 val_codec: String,
1039 diff_codec: String,
1040 #[cfg(debug_assertions)]
1041 validator: ReferencedBlobValidator<T>,
1042}
1043
1044impl<T: Timestamp + Lattice + Codec64> StateVersionsIter<T> {
1045 fn new(
1046 cfg: PersistConfig,
1047 metrics: Arc<Metrics>,
1048 state: State<T>,
1049 mut diffs: Vec<VersionedData>,
1051 key_codec: String,
1052 val_codec: String,
1053 diff_codec: String,
1054 ) -> Self {
1055 assert!(diffs.first().map_or(true, |x| x.seqno == state.seqno));
1056 diffs.reverse();
1057 StateVersionsIter {
1058 cfg,
1059 metrics,
1060 state,
1061 diffs,
1062 key_codec,
1063 val_codec,
1064 diff_codec,
1065 #[cfg(debug_assertions)]
1066 validator: ReferencedBlobValidator::default(),
1067 }
1068 }
1069
1070 pub fn len(&self) -> usize {
1071 self.diffs.len()
1072 }
1073
1074 pub fn next<F: for<'a> FnMut(InspectDiff<'a, T>)>(
1082 &mut self,
1083 mut inspect_diff_fn: F,
1084 ) -> Option<&State<T>> {
1085 let diff = match self.diffs.pop() {
1086 Some(x) => x,
1087 None => return None,
1088 };
1089 let data = diff.data.clone();
1090 let diff = self
1091 .metrics
1092 .codecs
1093 .state_diff
1094 .decode(|| StateDiff::decode(&self.cfg.build_version, diff.data));
1095
1096 if diff.seqno_to == self.state.seqno {
1099 let inspect = InspectDiff::FromInitial(&self.state);
1100 #[cfg(debug_assertions)]
1101 {
1102 inspect
1103 .referenced_blobs()
1104 .for_each(|x| self.validator.add_inc_blob(x));
1105 }
1106 inspect_diff_fn(inspect);
1107 } else {
1108 let inspect = InspectDiff::Diff(&diff);
1109 #[cfg(debug_assertions)]
1110 {
1111 inspect
1112 .referenced_blobs()
1113 .for_each(|x| self.validator.add_inc_blob(x));
1114 }
1115 inspect_diff_fn(inspect);
1116 }
1117
1118 let diff_seqno_to = diff.seqno_to;
1119 self.state
1120 .apply_diffs(&self.metrics, std::iter::once((diff, data)));
1121 assert_eq!(self.state.seqno, diff_seqno_to);
1122 #[cfg(debug_assertions)]
1123 {
1124 self.validator.validate_against_state(&self.state);
1125 }
1126 Some(&self.state)
1127 }
1128
1129 pub fn state(&self) -> &State<T> {
1130 &self.state
1131 }
1132
1133 pub fn into_rollup_proto_without_diffs(&self) -> impl serde::Serialize + use<T> {
1134 Rollup::from_state_without_diffs(
1135 State {
1136 shard_id: self.state.shard_id.clone(),
1137 seqno: self.state.seqno.clone(),
1138 walltime_ms: self.state.walltime_ms.clone(),
1139 hostname: self.state.hostname.clone(),
1140 collections: self.state.collections.clone(),
1141 },
1142 self.key_codec.clone(),
1143 self.val_codec.clone(),
1144 T::codec_name(),
1145 self.diff_codec.clone(),
1146 )
1147 .into_proto()
1148 }
1149}
1150
1151#[derive(Debug)]
1156pub enum InspectDiff<'a, T> {
1157 FromInitial(&'a State<T>),
1158 Diff(&'a StateDiff<T>),
1159}
1160
1161impl<T: Timestamp + Lattice + Codec64> InspectDiff<'_, T> {
1162 pub fn referenced_blobs(&self) -> impl Iterator<Item = HollowBlobRef<'_, T>> {
1166 let (state, diff) = match self {
1167 InspectDiff::FromInitial(x) => (Some(x), None),
1168 InspectDiff::Diff(x) => (None, Some(x)),
1169 };
1170 let state_blobs = state.into_iter().flat_map(|s| s.blobs());
1171 let diff_blobs = diff.into_iter().flat_map(|d| d.blob_inserts());
1172 state_blobs.chain(diff_blobs)
1173 }
1174}
1175
1176#[cfg(debug_assertions)]
1177struct ReferencedBlobValidator<T> {
1178 full_batches: BTreeSet<HollowBatch<T>>,
1181 full_rollups: BTreeSet<HollowRollup>,
1182 inc_batches: BTreeSet<HollowBatch<T>>,
1185 inc_rollups: BTreeSet<HollowRollup>,
1186}
1187
1188#[cfg(debug_assertions)]
1189impl<T> Default for ReferencedBlobValidator<T> {
1190 fn default() -> Self {
1191 Self {
1192 full_batches: Default::default(),
1193 full_rollups: Default::default(),
1194 inc_batches: Default::default(),
1195 inc_rollups: Default::default(),
1196 }
1197 }
1198}
1199
1200#[cfg(debug_assertions)]
1201impl<T: Timestamp + Lattice + Codec64> ReferencedBlobValidator<T> {
1202 fn add_inc_blob(&mut self, x: HollowBlobRef<'_, T>) {
1203 match x {
1204 HollowBlobRef::Batch(x) => assert!(
1205 self.inc_batches.insert(x.clone()) || x.desc.lower() == x.desc.upper(),
1206 "non-empty batches should only be appended once; duplicate: {x:?}"
1207 ),
1208 HollowBlobRef::Rollup(x) => assert!(self.inc_rollups.insert(x.clone())),
1209 }
1210 }
1211 fn validate_against_state(&mut self, x: &State<T>) {
1212 use std::hash::{DefaultHasher, Hash, Hasher};
1213
1214 use mz_ore::collections::HashSet;
1215 use timely::progress::Antichain;
1216
1217 use crate::internal::state::BatchPart;
1218
1219 x.blobs().for_each(|x| match x {
1220 HollowBlobRef::Batch(x) => {
1221 self.full_batches.insert(x.clone());
1222 }
1223 HollowBlobRef::Rollup(x) => {
1224 self.full_rollups.insert(x.clone());
1225 }
1226 });
1227
1228 fn overall_desc<'a, T: Timestamp + Lattice>(
1232 iter: impl Iterator<Item = &'a Description<T>>,
1233 ) -> (Antichain<T>, Antichain<T>) {
1234 let mut lower = Antichain::new();
1235 let mut upper = Antichain::from_elem(T::minimum());
1236 for desc in iter {
1237 lower.meet_assign(desc.lower());
1238 upper.join_assign(desc.upper());
1239 }
1240 (lower, upper)
1241 }
1242 let (inc_lower, inc_upper) = overall_desc(self.inc_batches.iter().map(|a| &a.desc));
1243 let (full_lower, full_upper) = overall_desc(self.full_batches.iter().map(|a| &a.desc));
1244 assert_eq!(inc_lower, full_lower);
1245 assert_eq!(inc_upper, full_upper);
1246
1247 fn part_unique<T: Codec64>(x: &RunPart<T>) -> String {
1248 match x {
1249 RunPart::Single(BatchPart::Inline {
1250 updates,
1251 ts_rewrite,
1252 ..
1253 }) => {
1254 let mut h = DefaultHasher::new();
1255 updates.hash(&mut h);
1256 if let Some(frontier) = &ts_rewrite {
1257 h.write_usize(frontier.len());
1258 frontier.iter().for_each(|t| t.encode().hash(&mut h));
1259 }
1260 h.finish().to_string()
1261 }
1262 other => other.printable_name().to_string(),
1263 }
1264 }
1265
1266 let inc_parts: HashSet<_> = self
1268 .inc_batches
1269 .iter()
1270 .flat_map(|x| x.parts.iter())
1271 .map(part_unique)
1272 .collect();
1273 let full_parts = self
1274 .full_batches
1275 .iter()
1276 .flat_map(|x| x.parts.iter())
1277 .map(part_unique)
1278 .collect();
1279 assert_eq!(inc_parts, full_parts);
1280
1281 assert_eq!(self.inc_rollups, self.full_rollups);
1283 }
1284}
1285
1286#[cfg(test)]
1287mod tests {
1288 use mz_dyncfg::ConfigUpdates;
1289
1290 use crate::tests::new_test_client;
1291
1292 use super::*;
1293
1294 #[mz_persist_proc::test(tokio::test)]
1297 #[cfg_attr(miri, ignore)] async fn fetch_all_live_states_regression_uninitialized(dyncfgs: ConfigUpdates) {
1299 let client = new_test_client(&dyncfgs).await;
1300 let state_versions = StateVersions::new(
1301 client.cfg.clone(),
1302 Arc::clone(&client.consensus),
1303 Arc::clone(&client.blob),
1304 Arc::clone(&client.metrics),
1305 );
1306 assert!(
1307 state_versions
1308 .fetch_all_live_states::<u64>(ShardId::new())
1309 .await
1310 .is_none()
1311 );
1312 }
1313}