1use anyhow::ensure;
11use async_stream::{stream, try_stream};
12use differential_dataflow::difference::Monoid;
13use mz_persist::metrics::ColumnarMetrics;
14use proptest::prelude::{Arbitrary, Strategy};
15use std::borrow::Cow;
16use std::cmp::Ordering;
17use std::collections::BTreeMap;
18use std::fmt::{Debug, Formatter};
19use std::marker::PhantomData;
20use std::ops::ControlFlow::{self, Break, Continue};
21use std::ops::{Deref, DerefMut};
22use std::time::Duration;
23
24use arrow::array::{Array, ArrayData, make_array};
25use arrow::datatypes::DataType;
26use bytes::Bytes;
27use differential_dataflow::Hashable;
28use differential_dataflow::lattice::Lattice;
29use differential_dataflow::trace::Description;
30use differential_dataflow::trace::implementations::BatchContainer;
31use futures::Stream;
32use futures_util::StreamExt;
33use itertools::Itertools;
34use mz_dyncfg::Config;
35use mz_ore::cast::CastFrom;
36use mz_ore::now::EpochMillis;
37use mz_ore::soft_panic_or_log;
38use mz_ore::vec::PartialOrdVecExt;
39use mz_persist::indexed::encoding::{BatchColumnarFormat, BlobTraceUpdates};
40use mz_persist::location::{Blob, SeqNo};
41use mz_persist_types::arrow::{ArrayBound, ProtoArrayData};
42use mz_persist_types::columnar::{ColumnEncoder, Schema};
43use mz_persist_types::schema::{SchemaId, backward_compatible};
44use mz_persist_types::{Codec, Codec64};
45use mz_proto::ProtoType;
46use mz_proto::RustType;
47use proptest_derive::Arbitrary;
48use semver::Version;
49use serde::ser::SerializeStruct;
50use serde::{Serialize, Serializer};
51use timely::PartialOrder;
52use timely::order::TotalOrder;
53use timely::progress::{Antichain, Timestamp};
54use tracing::info;
55use uuid::Uuid;
56
57use crate::critical::{CriticalReaderId, Opaque};
58use crate::error::InvalidUsage;
59use crate::internal::encoding::{
60 LazyInlineBatchPart, LazyPartStats, LazyProto, MetadataMap, parse_id,
61};
62use crate::internal::gc::GcReq;
63use crate::internal::machine::retry_external;
64use crate::internal::paths::{BlobKey, PartId, PartialBatchKey, PartialRollupKey, WriterKey};
65use crate::internal::trace::{
66 ActiveCompaction, ApplyMergeResult, FueledMergeReq, FueledMergeRes, Trace,
67};
68use crate::metrics::Metrics;
69use crate::read::LeasedReaderId;
70use crate::schema::CaESchema;
71use crate::write::WriterId;
72use crate::{PersistConfig, ShardId};
73
74include!(concat!(
75 env!("OUT_DIR"),
76 "/mz_persist_client.internal.state.rs"
77));
78
79include!(concat!(
80 env!("OUT_DIR"),
81 "/mz_persist_client.internal.diff.rs"
82));
83
84pub(crate) const ROLLUP_THRESHOLD: Config<usize> = Config::new(
92 "persist_rollup_threshold",
93 128,
94 "The number of seqnos between rollups.",
95);
96
97pub(crate) const ROLLUP_FALLBACK_THRESHOLD_MS: Config<usize> = Config::new(
100 "persist_rollup_fallback_threshold_ms",
101 5000,
102 "The number of milliseconds before a worker claims an already claimed rollup.",
103);
104
105pub(crate) const ROLLUP_USE_ACTIVE_ROLLUP: Config<bool> = Config::new(
108 "persist_rollup_use_active_rollup",
109 true,
110 "Whether to use the new active rollup tracking mechanism.",
111);
112
113pub(crate) const GC_FALLBACK_THRESHOLD_MS: Config<usize> = Config::new(
116 "persist_gc_fallback_threshold_ms",
117 900000,
118 "The number of milliseconds before a worker claims an already claimed GC.",
119);
120
121pub(crate) const GC_MIN_VERSIONS: Config<usize> = Config::new(
123 "persist_gc_min_versions",
124 32,
125 "The number of un-GCd versions that may exist in state before we'll trigger a GC.",
126);
127
128pub(crate) const GC_MAX_VERSIONS: Config<usize> = Config::new(
130 "persist_gc_max_versions",
131 128_000,
132 "The maximum number of versions to GC in a single GC run.",
133);
134
135pub(crate) const GC_USE_ACTIVE_GC: Config<bool> = Config::new(
138 "persist_gc_use_active_gc",
139 false,
140 "Whether to use the new active GC tracking mechanism.",
141);
142
143pub(crate) const ENABLE_INCREMENTAL_COMPACTION: Config<bool> = Config::new(
144 "persist_enable_incremental_compaction",
145 false,
146 "Whether to enable incremental compaction.",
147);
148
149#[derive(Arbitrary, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
152#[serde(into = "String")]
153pub struct IdempotencyToken(pub(crate) [u8; 16]);
154
155impl std::fmt::Display for IdempotencyToken {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "i{}", Uuid::from_bytes(self.0))
158 }
159}
160
161impl std::fmt::Debug for IdempotencyToken {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 write!(f, "IdempotencyToken({})", Uuid::from_bytes(self.0))
164 }
165}
166
167impl std::str::FromStr for IdempotencyToken {
168 type Err = String;
169
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 parse_id("i", "IdempotencyToken", s).map(IdempotencyToken)
172 }
173}
174
175impl From<IdempotencyToken> for String {
176 fn from(x: IdempotencyToken) -> Self {
177 x.to_string()
178 }
179}
180
181impl IdempotencyToken {
182 pub(crate) fn new() -> Self {
183 IdempotencyToken(*Uuid::new_v4().as_bytes())
184 }
185 pub(crate) const SENTINEL: IdempotencyToken = IdempotencyToken([17u8; 16]);
186}
187
188#[derive(Clone, Debug, PartialEq, Serialize)]
189pub struct LeasedReaderState<T> {
190 pub seqno: SeqNo,
192 pub since: Antichain<T>,
194 pub last_heartbeat_timestamp_ms: u64,
196 pub lease_duration_ms: u64,
199 pub debug: HandleDebugState,
201}
202
203#[derive(Clone, Debug, PartialEq, Serialize)]
204pub struct CriticalReaderState<T> {
205 pub since: Antichain<T>,
207 pub opaque: Opaque,
209 pub debug: HandleDebugState,
211}
212
213#[derive(Clone, Debug, PartialEq, Serialize)]
214pub struct WriterState<T> {
215 pub last_heartbeat_timestamp_ms: u64,
217 pub lease_duration_ms: u64,
220 pub most_recent_write_token: IdempotencyToken,
223 pub most_recent_write_upper: Antichain<T>,
226 pub debug: HandleDebugState,
228}
229
230#[derive(Arbitrary, Clone, Debug, Default, PartialEq, Serialize)]
232pub struct HandleDebugState {
233 pub hostname: String,
236 pub purpose: String,
238}
239
240#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
244#[serde(tag = "type")]
245pub enum BatchPart<T> {
246 Hollow(HollowBatchPart<T>),
247 Inline {
248 updates: LazyInlineBatchPart,
249 ts_rewrite: Option<Antichain<T>>,
250 schema_id: Option<SchemaId>,
251
252 deprecated_schema_id: Option<SchemaId>,
254 },
255}
256
257fn decode_structured_lower(lower: &LazyProto<ProtoArrayData>) -> Option<ArrayBound> {
258 let try_decode = |lower: &LazyProto<ProtoArrayData>| {
259 let proto = lower.decode()?;
260 let data = ArrayData::from_proto(proto)?;
261 ensure!(data.len() == 1);
262 Ok(ArrayBound::new(make_array(data), 0))
263 };
264
265 let decoded: anyhow::Result<ArrayBound> = try_decode(lower);
266
267 match decoded {
268 Ok(bound) => Some(bound),
269 Err(e) => {
270 soft_panic_or_log!("failed to decode bound: {e:#?}");
271 None
272 }
273 }
274}
275
276impl<T> BatchPart<T> {
277 pub fn hollow_bytes(&self) -> usize {
278 match self {
279 BatchPart::Hollow(x) => x.encoded_size_bytes,
280 BatchPart::Inline { .. } => 0,
281 }
282 }
283
284 pub fn is_inline(&self) -> bool {
285 matches!(self, BatchPart::Inline { .. })
286 }
287
288 pub fn inline_bytes(&self) -> usize {
289 match self {
290 BatchPart::Hollow(_) => 0,
291 BatchPart::Inline { updates, .. } => updates.encoded_size_bytes(),
292 }
293 }
294
295 pub fn writer_key(&self) -> Option<WriterKey> {
296 match self {
297 BatchPart::Hollow(x) => x.key.split().map(|(writer, _part)| writer),
298 BatchPart::Inline { .. } => None,
299 }
300 }
301
302 pub fn encoded_size_bytes(&self) -> usize {
303 match self {
304 BatchPart::Hollow(x) => x.encoded_size_bytes,
305 BatchPart::Inline { updates, .. } => updates.encoded_size_bytes(),
306 }
307 }
308
309 pub fn printable_name(&self) -> &str {
312 match self {
313 BatchPart::Hollow(x) => x.key.0.as_str(),
314 BatchPart::Inline { .. } => "<inline>",
315 }
316 }
317
318 pub fn stats(&self) -> Option<&LazyPartStats> {
319 match self {
320 BatchPart::Hollow(x) => x.stats.as_ref(),
321 BatchPart::Inline { .. } => None,
322 }
323 }
324
325 pub fn key_lower(&self) -> &[u8] {
326 match self {
327 BatchPart::Hollow(x) => x.key_lower.as_slice(),
328 BatchPart::Inline { .. } => &[],
335 }
336 }
337
338 pub fn structured_key_lower(&self) -> Option<ArrayBound> {
339 let part = match self {
340 BatchPart::Hollow(part) => part,
341 BatchPart::Inline { .. } => return None,
342 };
343
344 decode_structured_lower(part.structured_key_lower.as_ref()?)
345 }
346
347 pub fn ts_rewrite(&self) -> Option<&Antichain<T>> {
348 match self {
349 BatchPart::Hollow(x) => x.ts_rewrite.as_ref(),
350 BatchPart::Inline { ts_rewrite, .. } => ts_rewrite.as_ref(),
351 }
352 }
353
354 pub fn schema_id(&self) -> Option<SchemaId> {
355 match self {
356 BatchPart::Hollow(x) => x.schema_id,
357 BatchPart::Inline { schema_id, .. } => *schema_id,
358 }
359 }
360
361 pub fn deprecated_schema_id(&self) -> Option<SchemaId> {
362 match self {
363 BatchPart::Hollow(x) => x.deprecated_schema_id,
364 BatchPart::Inline {
365 deprecated_schema_id,
366 ..
367 } => *deprecated_schema_id,
368 }
369 }
370}
371
372impl<T: Timestamp + Codec64> BatchPart<T> {
373 pub fn is_structured_only(&self, metrics: &ColumnarMetrics) -> bool {
374 match self {
375 BatchPart::Hollow(x) => matches!(x.format, Some(BatchColumnarFormat::Structured)),
376 BatchPart::Inline { updates, .. } => {
377 let inline_part = updates.decode::<T>(metrics).expect("valid inline part");
378 matches!(inline_part.updates, BlobTraceUpdates::Structured { .. })
379 }
380 }
381 }
382
383 pub fn diffs_sum<D: Codec64 + Monoid>(&self, metrics: &ColumnarMetrics) -> Option<D> {
384 match self {
385 BatchPart::Hollow(x) => x.diffs_sum.map(D::decode),
386 BatchPart::Inline { updates, .. } => Some(
387 updates
388 .decode::<T>(metrics)
389 .expect("valid inline part")
390 .updates
391 .diffs_sum(),
392 ),
393 }
394 }
395}
396
397#[derive(Debug, Clone)]
399pub struct HollowRun<T> {
400 pub(crate) parts: Vec<RunPart<T>>,
402}
403
404#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
407pub struct HollowRunRef<T> {
408 pub key: PartialBatchKey,
409
410 pub hollow_bytes: usize,
412
413 pub max_part_bytes: usize,
415
416 pub key_lower: Vec<u8>,
418
419 pub structured_key_lower: Option<LazyProto<ProtoArrayData>>,
421
422 pub diffs_sum: Option<[u8; 8]>,
423
424 pub(crate) _phantom_data: PhantomData<T>,
425}
426impl<T: Eq> PartialOrd<Self> for HollowRunRef<T> {
427 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
428 Some(self.cmp(other))
429 }
430}
431
432impl<T: Eq> Ord for HollowRunRef<T> {
433 fn cmp(&self, other: &Self) -> Ordering {
434 self.key.cmp(&other.key)
435 }
436}
437
438impl<T> HollowRunRef<T> {
439 pub fn writer_key(&self) -> Option<WriterKey> {
440 Some(self.key.split()?.0)
441 }
442}
443
444impl<T: Timestamp + Codec64> HollowRunRef<T> {
445 pub async fn set<D: Codec64 + Monoid>(
447 shard_id: ShardId,
448 blob: &dyn Blob,
449 writer: &WriterKey,
450 data: HollowRun<T>,
451 metrics: &Metrics,
452 ) -> Self {
453 let hollow_bytes = data.parts.iter().map(|p| p.hollow_bytes()).sum();
454 let max_part_bytes = data
455 .parts
456 .iter()
457 .map(|p| p.max_part_bytes())
458 .max()
459 .unwrap_or(0);
460 let key_lower = data
461 .parts
462 .first()
463 .map_or(vec![], |p| p.key_lower().to_vec());
464 let structured_key_lower = match data.parts.first() {
465 Some(RunPart::Many(r)) => r.structured_key_lower.clone(),
466 Some(RunPart::Single(BatchPart::Hollow(p))) => p.structured_key_lower.clone(),
467 Some(RunPart::Single(BatchPart::Inline { .. })) | None => None,
468 };
469 let diffs_sum = data
470 .parts
471 .iter()
472 .map(|p| {
473 p.diffs_sum::<D>(&metrics.columnar)
474 .expect("valid diffs sum")
475 })
476 .reduce(|mut a, b| {
477 a.plus_equals(&b);
478 a
479 })
480 .expect("valid diffs sum")
481 .encode();
482
483 let key = PartialBatchKey::new(writer, &PartId::new());
484 let blob_key = key.complete(&shard_id);
485 let bytes = Bytes::from(prost::Message::encode_to_vec(&data.into_proto()));
486 let () = retry_external(&metrics.retries.external.hollow_run_set, || {
487 blob.set(&blob_key, bytes.clone())
488 })
489 .await;
490 Self {
491 key,
492 hollow_bytes,
493 max_part_bytes,
494 key_lower,
495 structured_key_lower,
496 diffs_sum: Some(diffs_sum),
497 _phantom_data: Default::default(),
498 }
499 }
500
501 pub async fn get(
505 &self,
506 shard_id: ShardId,
507 blob: &dyn Blob,
508 metrics: &Metrics,
509 ) -> Option<HollowRun<T>> {
510 let blob_key = self.key.complete(&shard_id);
511 let mut bytes = retry_external(&metrics.retries.external.hollow_run_get, || {
512 blob.get(&blob_key)
513 })
514 .await?;
515 let proto_runs: ProtoHollowRun =
516 prost::Message::decode(&mut bytes).expect("illegal state: invalid proto bytes");
517 let runs = proto_runs
518 .into_rust()
519 .expect("illegal state: invalid encoded runs proto");
520 Some(runs)
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
528#[serde(untagged)]
529pub enum RunPart<T> {
530 Single(BatchPart<T>),
531 Many(HollowRunRef<T>),
532}
533
534impl<T: Ord> PartialOrd<Self> for RunPart<T> {
535 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
536 Some(self.cmp(other))
537 }
538}
539
540impl<T: Ord> Ord for RunPart<T> {
541 fn cmp(&self, other: &Self) -> Ordering {
542 match (self, other) {
543 (RunPart::Single(a), RunPart::Single(b)) => a.cmp(b),
544 (RunPart::Single(_), RunPart::Many(_)) => Ordering::Less,
545 (RunPart::Many(_), RunPart::Single(_)) => Ordering::Greater,
546 (RunPart::Many(a), RunPart::Many(b)) => a.cmp(b),
547 }
548 }
549}
550
551impl<T> RunPart<T> {
552 #[cfg(test)]
553 pub fn expect_hollow_part(&self) -> &HollowBatchPart<T> {
554 match self {
555 RunPart::Single(BatchPart::Hollow(hollow)) => hollow,
556 _ => panic!("expected hollow part!"),
557 }
558 }
559
560 pub fn hollow_bytes(&self) -> usize {
561 match self {
562 Self::Single(p) => p.hollow_bytes(),
563 Self::Many(r) => r.hollow_bytes,
564 }
565 }
566
567 pub fn is_inline(&self) -> bool {
568 match self {
569 Self::Single(p) => p.is_inline(),
570 Self::Many(_) => false,
571 }
572 }
573
574 pub fn inline_bytes(&self) -> usize {
575 match self {
576 Self::Single(p) => p.inline_bytes(),
577 Self::Many(_) => 0,
578 }
579 }
580
581 pub fn max_part_bytes(&self) -> usize {
582 match self {
583 Self::Single(p) => p.encoded_size_bytes(),
584 Self::Many(r) => r.max_part_bytes,
585 }
586 }
587
588 pub fn writer_key(&self) -> Option<WriterKey> {
589 match self {
590 Self::Single(p) => p.writer_key(),
591 Self::Many(r) => r.writer_key(),
592 }
593 }
594
595 pub fn encoded_size_bytes(&self) -> usize {
596 match self {
597 Self::Single(p) => p.encoded_size_bytes(),
598 Self::Many(r) => r.hollow_bytes,
599 }
600 }
601
602 pub fn schema_id(&self) -> Option<SchemaId> {
603 match self {
604 Self::Single(p) => p.schema_id(),
605 Self::Many(_) => None,
606 }
607 }
608
609 pub fn printable_name(&self) -> &str {
612 match self {
613 Self::Single(p) => p.printable_name(),
614 Self::Many(r) => r.key.0.as_str(),
615 }
616 }
617
618 pub fn stats(&self) -> Option<&LazyPartStats> {
619 match self {
620 Self::Single(p) => p.stats(),
621 Self::Many(_) => None,
623 }
624 }
625
626 pub fn key_lower(&self) -> &[u8] {
627 match self {
628 Self::Single(p) => p.key_lower(),
629 Self::Many(r) => r.key_lower.as_slice(),
630 }
631 }
632
633 pub fn structured_key_lower(&self) -> Option<ArrayBound> {
634 match self {
635 Self::Single(p) => p.structured_key_lower(),
636 Self::Many(_) => None,
637 }
638 }
639
640 pub fn ts_rewrite(&self) -> Option<&Antichain<T>> {
641 match self {
642 Self::Single(p) => p.ts_rewrite(),
643 Self::Many(_) => None,
644 }
645 }
646}
647
648impl<T> RunPart<T>
649where
650 T: Timestamp + Codec64,
651{
652 pub fn diffs_sum<D: Codec64 + Monoid>(&self, metrics: &ColumnarMetrics) -> Option<D> {
653 match self {
654 Self::Single(p) => p.diffs_sum(metrics),
655 Self::Many(hollow_run) => hollow_run.diffs_sum.map(D::decode),
656 }
657 }
658}
659
660#[derive(Clone, Debug)]
662pub struct MissingBlob(BlobKey);
663
664impl std::fmt::Display for MissingBlob {
665 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
666 write!(f, "unexpectedly missing key: {}", self.0)
667 }
668}
669
670impl std::error::Error for MissingBlob {}
671
672impl<T: Timestamp + Codec64 + Sync> RunPart<T> {
673 pub fn part_stream<'a>(
674 &'a self,
675 shard_id: ShardId,
676 blob: &'a dyn Blob,
677 metrics: &'a Metrics,
678 ) -> impl Stream<Item = Result<Cow<'a, BatchPart<T>>, MissingBlob>> + Send + 'a {
679 try_stream! {
680 match self {
681 RunPart::Single(p) => {
682 yield Cow::Borrowed(p);
683 }
684 RunPart::Many(r) => {
685 let fetched = r.get(shard_id, blob, metrics).await
686 .ok_or_else(|| MissingBlob(r.key.complete(&shard_id)))?;
687 for run_part in fetched.parts {
688 for await batch_part in
689 run_part.part_stream(shard_id, blob, metrics).boxed()
690 {
691 yield Cow::Owned(batch_part?.into_owned());
692 }
693 }
694 }
695 }
696 }
697 }
698}
699
700impl<T: Ord> PartialOrd for BatchPart<T> {
701 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
702 Some(self.cmp(other))
703 }
704}
705
706impl<T: Ord> Ord for BatchPart<T> {
707 fn cmp(&self, other: &Self) -> Ordering {
708 match (self, other) {
709 (BatchPart::Hollow(s), BatchPart::Hollow(o)) => s.cmp(o),
710 (
711 BatchPart::Inline {
712 updates: s_updates,
713 ts_rewrite: s_ts_rewrite,
714 schema_id: s_schema_id,
715 deprecated_schema_id: s_deprecated_schema_id,
716 },
717 BatchPart::Inline {
718 updates: o_updates,
719 ts_rewrite: o_ts_rewrite,
720 schema_id: o_schema_id,
721 deprecated_schema_id: o_deprecated_schema_id,
722 },
723 ) => (
724 s_updates,
725 s_ts_rewrite.as_ref().map(|x| x.elements()),
726 s_schema_id,
727 s_deprecated_schema_id,
728 )
729 .cmp(&(
730 o_updates,
731 o_ts_rewrite.as_ref().map(|x| x.elements()),
732 o_schema_id,
733 o_deprecated_schema_id,
734 )),
735 (BatchPart::Hollow(_), BatchPart::Inline { .. }) => Ordering::Less,
736 (BatchPart::Inline { .. }, BatchPart::Hollow(_)) => Ordering::Greater,
737 }
738 }
739}
740
741#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Serialize)]
743pub(crate) enum RunOrder {
744 Unordered,
746 Codec,
748 Structured,
750}
751
752#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Copy, Hash)]
753pub struct RunId(pub(crate) [u8; 16]);
754
755impl std::fmt::Display for RunId {
756 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
757 write!(f, "ri{}", Uuid::from_bytes(self.0))
758 }
759}
760
761impl std::fmt::Debug for RunId {
762 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
763 write!(f, "RunId({})", Uuid::from_bytes(self.0))
764 }
765}
766
767impl std::str::FromStr for RunId {
768 type Err = String;
769
770 fn from_str(s: &str) -> Result<Self, Self::Err> {
771 parse_id("ri", "RunId", s).map(RunId)
772 }
773}
774
775impl From<RunId> for String {
776 fn from(x: RunId) -> Self {
777 x.to_string()
778 }
779}
780
781impl RunId {
782 pub(crate) fn new() -> Self {
783 RunId(*Uuid::new_v4().as_bytes())
784 }
785}
786
787impl Arbitrary for RunId {
788 type Parameters = ();
789 type Strategy = proptest::strategy::BoxedStrategy<Self>;
790 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
791 Strategy::prop_map(proptest::prelude::any::<u128>(), |n| {
792 RunId(*Uuid::from_u128(n).as_bytes())
793 })
794 .boxed()
795 }
796}
797
798#[derive(Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Serialize)]
800pub struct RunMeta {
801 pub(crate) order: Option<RunOrder>,
803 pub(crate) schema: Option<SchemaId>,
805
806 pub(crate) deprecated_schema: Option<SchemaId>,
808
809 pub(crate) id: Option<RunId>,
811
812 pub(crate) len: Option<usize>,
814
815 #[serde(skip_serializing_if = "MetadataMap::is_empty")]
817 pub(crate) meta: MetadataMap,
818}
819
820#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
822pub struct HollowBatchPart<T> {
823 pub key: PartialBatchKey,
825 #[serde(skip_serializing_if = "MetadataMap::is_empty")]
827 pub meta: MetadataMap,
828 pub encoded_size_bytes: usize,
830 #[serde(serialize_with = "serialize_part_bytes")]
833 pub key_lower: Vec<u8>,
834 #[serde(serialize_with = "serialize_lazy_proto")]
836 pub structured_key_lower: Option<LazyProto<ProtoArrayData>>,
837 #[serde(serialize_with = "serialize_part_stats")]
839 pub stats: Option<LazyPartStats>,
840 pub ts_rewrite: Option<Antichain<T>>,
848 #[serde(serialize_with = "serialize_diffs_sum")]
856 pub diffs_sum: Option<[u8; 8]>,
857 pub format: Option<BatchColumnarFormat>,
862 pub schema_id: Option<SchemaId>,
867
868 pub deprecated_schema_id: Option<SchemaId>,
870}
871
872#[derive(Clone, PartialEq, Eq)]
876pub struct HollowBatch<T> {
877 pub desc: Description<T>,
879 pub len: usize,
881 pub(crate) parts: Vec<RunPart<T>>,
883 pub(crate) run_splits: Vec<usize>,
891 pub(crate) run_meta: Vec<RunMeta>,
894}
895
896impl<T: Debug> Debug for HollowBatch<T> {
897 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
898 let HollowBatch {
899 desc,
900 parts,
901 len,
902 run_splits: runs,
903 run_meta,
904 } = self;
905 f.debug_struct("HollowBatch")
906 .field(
907 "desc",
908 &(
909 desc.lower().elements(),
910 desc.upper().elements(),
911 desc.since().elements(),
912 ),
913 )
914 .field("parts", &parts)
915 .field("len", &len)
916 .field("runs", &runs)
917 .field("run_meta", &run_meta)
918 .finish()
919 }
920}
921
922impl<T: Serialize> serde::Serialize for HollowBatch<T> {
923 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
924 let HollowBatch {
925 desc,
926 len,
927 parts: _,
929 run_splits: _,
930 run_meta: _,
931 } = self;
932 let mut s = s.serialize_struct("HollowBatch", 5)?;
933 let () = s.serialize_field("lower", &desc.lower().elements())?;
934 let () = s.serialize_field("upper", &desc.upper().elements())?;
935 let () = s.serialize_field("since", &desc.since().elements())?;
936 let () = s.serialize_field("len", len)?;
937 let () = s.serialize_field("part_runs", &self.runs().collect::<Vec<_>>())?;
938 s.end()
939 }
940}
941
942impl<T: Ord> PartialOrd for HollowBatch<T> {
943 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
944 Some(self.cmp(other))
945 }
946}
947
948impl<T: Ord> Ord for HollowBatch<T> {
949 fn cmp(&self, other: &Self) -> Ordering {
950 let HollowBatch {
953 desc: self_desc,
954 parts: self_parts,
955 len: self_len,
956 run_splits: self_runs,
957 run_meta: self_run_meta,
958 } = self;
959 let HollowBatch {
960 desc: other_desc,
961 parts: other_parts,
962 len: other_len,
963 run_splits: other_runs,
964 run_meta: other_run_meta,
965 } = other;
966 (
967 self_desc.lower().elements(),
968 self_desc.upper().elements(),
969 self_desc.since().elements(),
970 self_parts,
971 self_len,
972 self_runs,
973 self_run_meta,
974 )
975 .cmp(&(
976 other_desc.lower().elements(),
977 other_desc.upper().elements(),
978 other_desc.since().elements(),
979 other_parts,
980 other_len,
981 other_runs,
982 other_run_meta,
983 ))
984 }
985}
986
987impl<T: Timestamp + Codec64 + Sync> HollowBatch<T> {
988 pub(crate) fn part_stream<'a>(
989 &'a self,
990 shard_id: ShardId,
991 blob: &'a dyn Blob,
992 metrics: &'a Metrics,
993 ) -> impl Stream<Item = Result<Cow<'a, BatchPart<T>>, MissingBlob>> + 'a {
994 stream! {
995 for part in &self.parts {
996 for await part in part.part_stream(shard_id, blob, metrics) {
997 yield part;
998 }
999 }
1000 }
1001 }
1002}
1003impl<T> HollowBatch<T> {
1004 pub(crate) fn new(
1011 desc: Description<T>,
1012 parts: Vec<RunPart<T>>,
1013 len: usize,
1014 run_meta: Vec<RunMeta>,
1015 run_splits: Vec<usize>,
1016 ) -> Self {
1017 debug_assert!(
1018 run_splits.is_strictly_sorted(),
1019 "run indices should be strictly increasing"
1020 );
1021 mz_ore::soft_assert_no_log!(
1022 run_splits.first().map_or(true, |i| *i > 0),
1023 "run indices should be positive"
1024 );
1025 mz_ore::soft_assert_no_log!(
1026 run_splits.last().map_or(true, |i| *i < parts.len()),
1027 "run indices should be valid indices into parts"
1028 );
1029 mz_ore::soft_assert_no_log!(
1030 parts.is_empty() || run_meta.len() == run_splits.len() + 1,
1031 "all metadata should correspond to a run"
1032 );
1033
1034 Self {
1035 desc,
1036 len,
1037 parts,
1038 run_splits,
1039 run_meta,
1040 }
1041 }
1042
1043 pub(crate) fn new_run(desc: Description<T>, parts: Vec<RunPart<T>>, len: usize) -> Self {
1045 let run_meta = if parts.is_empty() {
1046 vec![]
1047 } else {
1048 vec![RunMeta::default()]
1049 };
1050 Self {
1051 desc,
1052 len,
1053 parts,
1054 run_splits: vec![],
1055 run_meta,
1056 }
1057 }
1058
1059 #[cfg(test)]
1060 pub(crate) fn new_run_for_test(
1061 desc: Description<T>,
1062 parts: Vec<RunPart<T>>,
1063 len: usize,
1064 run_id: RunId,
1065 ) -> Self {
1066 let run_meta = if parts.is_empty() {
1067 vec![]
1068 } else {
1069 let mut meta = RunMeta::default();
1070 meta.id = Some(run_id);
1071 vec![meta]
1072 };
1073 Self {
1074 desc,
1075 len,
1076 parts,
1077 run_splits: vec![],
1078 run_meta,
1079 }
1080 }
1081
1082 pub(crate) fn empty(desc: Description<T>) -> Self {
1084 Self {
1085 desc,
1086 len: 0,
1087 parts: vec![],
1088 run_splits: vec![],
1089 run_meta: vec![],
1090 }
1091 }
1092
1093 pub(crate) fn runs(&self) -> impl Iterator<Item = (&RunMeta, &[RunPart<T>])> {
1094 let run_ends = self
1095 .run_splits
1096 .iter()
1097 .copied()
1098 .chain(std::iter::once(self.parts.len()));
1099 let run_metas = self.run_meta.iter();
1100 let run_parts = run_ends
1101 .scan(0, |start, end| {
1102 let range = *start..end;
1103 *start = end;
1104 Some(range)
1105 })
1106 .filter(|range| !range.is_empty())
1107 .map(|range| &self.parts[range]);
1108 run_metas.zip_eq(run_parts)
1109 }
1110
1111 pub(crate) fn inline_bytes(&self) -> usize {
1112 self.parts.iter().map(|x| x.inline_bytes()).sum()
1113 }
1114
1115 pub(crate) fn is_empty(&self) -> bool {
1116 self.parts.is_empty()
1117 }
1118
1119 pub(crate) fn part_count(&self) -> usize {
1120 self.parts.len()
1121 }
1122
1123 pub fn encoded_size_bytes(&self) -> usize {
1125 self.parts.iter().map(|p| p.encoded_size_bytes()).sum()
1126 }
1127}
1128
1129impl<T: Timestamp + TotalOrder> HollowBatch<T> {
1131 pub(crate) fn rewrite_ts(
1132 &mut self,
1133 frontier: &Antichain<T>,
1134 new_upper: Antichain<T>,
1135 ) -> Result<(), String> {
1136 if !PartialOrder::less_than(frontier, &new_upper) {
1137 return Err(format!(
1138 "rewrite frontier {:?} !< rewrite upper {:?}",
1139 frontier.elements(),
1140 new_upper.elements(),
1141 ));
1142 }
1143 if PartialOrder::less_than(&new_upper, self.desc.upper()) {
1144 return Err(format!(
1145 "rewrite upper {:?} < batch upper {:?}",
1146 new_upper.elements(),
1147 self.desc.upper().elements(),
1148 ));
1149 }
1150
1151 if PartialOrder::less_than(frontier, self.desc.lower()) {
1154 return Err(format!(
1155 "rewrite frontier {:?} < batch lower {:?}",
1156 frontier.elements(),
1157 self.desc.lower().elements(),
1158 ));
1159 }
1160 if self.desc.since() != &Antichain::from_elem(T::minimum()) {
1161 return Err(format!(
1162 "batch since {:?} != minimum antichain {:?}",
1163 self.desc.since().elements(),
1164 [T::minimum()],
1165 ));
1166 }
1167 for part in self.parts.iter() {
1168 let Some(ts_rewrite) = part.ts_rewrite() else {
1169 continue;
1170 };
1171 if PartialOrder::less_than(frontier, ts_rewrite) {
1172 return Err(format!(
1173 "rewrite frontier {:?} < batch rewrite {:?}",
1174 frontier.elements(),
1175 ts_rewrite.elements(),
1176 ));
1177 }
1178 }
1179
1180 self.desc = Description::new(
1181 self.desc.lower().clone(),
1182 new_upper,
1183 self.desc.since().clone(),
1184 );
1185 for part in &mut self.parts {
1186 match part {
1187 RunPart::Single(BatchPart::Hollow(part)) => {
1188 part.ts_rewrite = Some(frontier.clone())
1189 }
1190 RunPart::Single(BatchPart::Inline { ts_rewrite, .. }) => {
1191 *ts_rewrite = Some(frontier.clone())
1192 }
1193 RunPart::Many(runs) => {
1194 panic!("unexpected rewrite of a hollow runs ref: {runs:?}");
1197 }
1198 }
1199 }
1200 Ok(())
1201 }
1202}
1203
1204impl<T: Ord> PartialOrd for HollowBatchPart<T> {
1205 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1206 Some(self.cmp(other))
1207 }
1208}
1209
1210impl<T: Ord> Ord for HollowBatchPart<T> {
1211 fn cmp(&self, other: &Self) -> Ordering {
1212 let HollowBatchPart {
1215 key: self_key,
1216 meta: self_meta,
1217 encoded_size_bytes: self_encoded_size_bytes,
1218 key_lower: self_key_lower,
1219 structured_key_lower: self_structured_key_lower,
1220 stats: self_stats,
1221 ts_rewrite: self_ts_rewrite,
1222 diffs_sum: self_diffs_sum,
1223 format: self_format,
1224 schema_id: self_schema_id,
1225 deprecated_schema_id: self_deprecated_schema_id,
1226 } = self;
1227 let HollowBatchPart {
1228 key: other_key,
1229 meta: other_meta,
1230 encoded_size_bytes: other_encoded_size_bytes,
1231 key_lower: other_key_lower,
1232 structured_key_lower: other_structured_key_lower,
1233 stats: other_stats,
1234 ts_rewrite: other_ts_rewrite,
1235 diffs_sum: other_diffs_sum,
1236 format: other_format,
1237 schema_id: other_schema_id,
1238 deprecated_schema_id: other_deprecated_schema_id,
1239 } = other;
1240 (
1241 self_key,
1242 self_meta,
1243 self_encoded_size_bytes,
1244 self_key_lower,
1245 self_structured_key_lower,
1246 self_stats,
1247 self_ts_rewrite.as_ref().map(|x| x.elements()),
1248 self_diffs_sum,
1249 self_format,
1250 self_schema_id,
1251 self_deprecated_schema_id,
1252 )
1253 .cmp(&(
1254 other_key,
1255 other_meta,
1256 other_encoded_size_bytes,
1257 other_key_lower,
1258 other_structured_key_lower,
1259 other_stats,
1260 other_ts_rewrite.as_ref().map(|x| x.elements()),
1261 other_diffs_sum,
1262 other_format,
1263 other_schema_id,
1264 other_deprecated_schema_id,
1265 ))
1266 }
1267}
1268
1269#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1271pub struct HollowRollup {
1272 pub key: PartialRollupKey,
1274 pub encoded_size_bytes: Option<usize>,
1276}
1277
1278#[derive(Debug)]
1280pub enum HollowBlobRef<'a, T> {
1281 Batch(&'a HollowBatch<T>),
1282 Rollup(&'a HollowRollup),
1283}
1284
1285#[derive(
1287 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Arbitrary, Serialize
1288)]
1289pub struct ActiveRollup {
1290 pub seqno: SeqNo,
1291 pub start_ms: u64,
1292}
1293
1294#[derive(
1296 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Arbitrary, Serialize
1297)]
1298pub struct ActiveGc {
1299 pub seqno: SeqNo,
1300 pub start_ms: u64,
1301}
1302
1303#[derive(Debug)]
1308#[cfg_attr(any(test, debug_assertions), derive(PartialEq))]
1309pub struct NoOpStateTransition<T>(pub T);
1310
1311#[derive(Debug, Clone)]
1313#[cfg_attr(any(test, debug_assertions), derive(PartialEq))]
1314pub struct StateCollections<T> {
1315 pub(crate) version: Version,
1319
1320 pub(crate) last_gc_req: SeqNo,
1323
1324 pub(crate) rollups: BTreeMap<SeqNo, HollowRollup>,
1326
1327 pub(crate) active_rollup: Option<ActiveRollup>,
1329 pub(crate) active_gc: Option<ActiveGc>,
1331
1332 pub(crate) leased_readers: BTreeMap<LeasedReaderId, LeasedReaderState<T>>,
1333 pub(crate) critical_readers: BTreeMap<CriticalReaderId, CriticalReaderState<T>>,
1334 pub(crate) writers: BTreeMap<WriterId, WriterState<T>>,
1335 pub(crate) schemas: BTreeMap<SchemaId, EncodedSchemas>,
1336
1337 pub(crate) trace: Trace<T>,
1342}
1343
1344#[derive(Debug, Clone, Serialize, PartialEq)]
1360pub struct EncodedSchemas {
1361 pub key: Bytes,
1363 pub key_data_type: Bytes,
1366 pub val: Bytes,
1368 pub val_data_type: Bytes,
1371}
1372
1373impl EncodedSchemas {
1374 pub(crate) fn decode_data_type(buf: &[u8]) -> DataType {
1375 let proto = prost::Message::decode(buf).expect("valid ProtoDataType");
1376 DataType::from_proto(proto).expect("valid DataType")
1377 }
1378}
1379
1380#[derive(Debug)]
1381#[cfg_attr(test, derive(PartialEq))]
1382pub enum CompareAndAppendBreak<T> {
1383 AlreadyCommitted,
1384 Upper {
1385 shard_upper: Antichain<T>,
1386 writer_upper: Antichain<T>,
1387 },
1388 InvalidUsage(InvalidUsage<T>),
1389 InlineBackpressure,
1390}
1391
1392#[derive(Debug)]
1393#[cfg_attr(test, derive(PartialEq))]
1394pub enum SnapshotErr<T> {
1395 AsOfNotYetAvailable(SeqNo, Upper<T>),
1396 AsOfHistoricalDistinctionsLost(Since<T>),
1397}
1398
1399impl<T> StateCollections<T>
1400where
1401 T: Timestamp + Lattice + Codec64,
1402{
1403 pub fn add_rollup(
1404 &mut self,
1405 add_rollup: (SeqNo, &HollowRollup),
1406 ) -> ControlFlow<NoOpStateTransition<bool>, bool> {
1407 let (rollup_seqno, rollup) = add_rollup;
1408 let applied = match self.rollups.get(&rollup_seqno) {
1409 Some(x) => x.key == rollup.key,
1410 None => {
1411 if let Some(min_kept) = self.rollups.keys().next() {
1432 if rollup_seqno < *min_kept {
1433 return Continue(false);
1434 }
1435 }
1436 self.active_rollup = None;
1437 self.rollups.insert(rollup_seqno, rollup.to_owned());
1438 true
1439 }
1440 };
1441 Continue(applied)
1445 }
1446
1447 pub fn remove_rollups(
1448 &mut self,
1449 remove_rollups: &[(SeqNo, PartialRollupKey)],
1450 ) -> ControlFlow<NoOpStateTransition<Vec<SeqNo>>, Vec<SeqNo>> {
1451 if self.is_tombstone() {
1452 return Break(NoOpStateTransition(vec![]));
1453 }
1454
1455 let active_gc_was_set = self.active_gc.take().is_some();
1458
1459 if remove_rollups.is_empty() {
1460 return if active_gc_was_set {
1461 Continue(vec![])
1462 } else {
1463 Break(NoOpStateTransition(vec![]))
1464 };
1465 }
1466
1467 let mut removed = vec![];
1468 for (seqno, key) in remove_rollups {
1469 let removed_key = self.rollups.remove(seqno);
1470 mz_ore::soft_assert_no_log!(
1471 removed_key.as_ref().map_or(true, |x| &x.key == key),
1472 "rollup at {} to be removed has key {:?} in state, but GC asked to remove {}",
1473 seqno,
1474 removed_key,
1475 key
1476 );
1477
1478 if removed_key.is_some() {
1479 removed.push(*seqno);
1480 }
1481 }
1482
1483 Continue(removed)
1484 }
1485
1486 pub fn register_leased_reader(
1487 &mut self,
1488 hostname: &str,
1489 reader_id: &LeasedReaderId,
1490 purpose: &str,
1491 seqno: SeqNo,
1492 lease_duration: Duration,
1493 heartbeat_timestamp_ms: u64,
1494 use_critical_since: bool,
1495 ) -> ControlFlow<
1496 NoOpStateTransition<(LeasedReaderState<T>, SeqNo)>,
1497 (LeasedReaderState<T>, SeqNo),
1498 > {
1499 let since = if use_critical_since {
1500 self.critical_since()
1501 .unwrap_or_else(|| self.trace.since().clone())
1502 } else {
1503 self.trace.since().clone()
1504 };
1505 let reader_state = LeasedReaderState {
1506 debug: HandleDebugState {
1507 hostname: hostname.to_owned(),
1508 purpose: purpose.to_owned(),
1509 },
1510 seqno,
1511 since,
1512 last_heartbeat_timestamp_ms: heartbeat_timestamp_ms,
1513 lease_duration_ms: u64::try_from(lease_duration.as_millis())
1514 .expect("lease duration as millis should fit within u64"),
1515 };
1516
1517 if self.is_tombstone() {
1522 return Break(NoOpStateTransition((reader_state, self.seqno_since(seqno))));
1523 }
1524
1525 self.leased_readers
1527 .insert(reader_id.clone(), reader_state.clone());
1528 Continue((reader_state, self.seqno_since(seqno)))
1529 }
1530
1531 pub fn register_critical_reader(
1532 &mut self,
1533 hostname: &str,
1534 reader_id: &CriticalReaderId,
1535 opaque: Opaque,
1536 purpose: &str,
1537 ) -> ControlFlow<NoOpStateTransition<CriticalReaderState<T>>, CriticalReaderState<T>> {
1538 let state = CriticalReaderState {
1539 debug: HandleDebugState {
1540 hostname: hostname.to_owned(),
1541 purpose: purpose.to_owned(),
1542 },
1543 since: self.trace.since().clone(),
1544 opaque,
1545 };
1546
1547 if self.is_tombstone() {
1552 return Break(NoOpStateTransition(state));
1553 }
1554
1555 let state = match self.critical_readers.get_mut(reader_id) {
1556 Some(existing_state) => {
1557 existing_state.debug = state.debug;
1558 existing_state.clone()
1559 }
1560 None => {
1561 self.critical_readers
1562 .insert(reader_id.clone(), state.clone());
1563 state
1564 }
1565 };
1566 Continue(state)
1567 }
1568
1569 pub fn register_schema<K: Codec, V: Codec>(
1570 &mut self,
1571 key_schema: &K::Schema,
1572 val_schema: &V::Schema,
1573 ) -> ControlFlow<NoOpStateTransition<Option<SchemaId>>, Option<SchemaId>> {
1574 fn encode_data_type(data_type: &DataType) -> Bytes {
1575 let proto = data_type.into_proto();
1576 prost::Message::encode_to_vec(&proto).into()
1577 }
1578
1579 let existing_id = self.schemas.iter().rev().find(|(_, x)| {
1591 K::decode_schema(&x.key) == *key_schema && V::decode_schema(&x.val) == *val_schema
1592 });
1593 match existing_id {
1594 Some((schema_id, _)) => {
1595 Break(NoOpStateTransition(Some(*schema_id)))
1600 }
1601 None if self.is_tombstone() => {
1602 Break(NoOpStateTransition(None))
1604 }
1605 None if self.schemas.is_empty() => {
1606 let id = SchemaId(self.schemas.len());
1610 let key_data_type = mz_persist_types::columnar::data_type::<K>(key_schema)
1611 .expect("valid key schema");
1612 let val_data_type = mz_persist_types::columnar::data_type::<V>(val_schema)
1613 .expect("valid val schema");
1614 let prev = self.schemas.insert(
1615 id,
1616 EncodedSchemas {
1617 key: K::encode_schema(key_schema),
1618 key_data_type: encode_data_type(&key_data_type),
1619 val: V::encode_schema(val_schema),
1620 val_data_type: encode_data_type(&val_data_type),
1621 },
1622 );
1623 assert_eq!(prev, None);
1624 Continue(Some(id))
1625 }
1626 None => {
1627 info!(
1628 "register_schemas got {:?} expected {:?}",
1629 key_schema,
1630 self.schemas
1631 .iter()
1632 .map(|(id, x)| (id, K::decode_schema(&x.key)))
1633 .collect::<Vec<_>>()
1634 );
1635 Break(NoOpStateTransition(None))
1638 }
1639 }
1640 }
1641
1642 pub fn compare_and_evolve_schema<K: Codec, V: Codec>(
1643 &mut self,
1644 expected: SchemaId,
1645 key_schema: &K::Schema,
1646 val_schema: &V::Schema,
1647 ) -> ControlFlow<NoOpStateTransition<CaESchema<K, V>>, CaESchema<K, V>> {
1648 fn data_type<T>(schema: &impl Schema<T>) -> DataType {
1649 let array = Schema::encoder(schema).expect("valid schema").finish();
1653 Array::data_type(&array).clone()
1654 }
1655
1656 let (current_id, current) = self
1657 .schemas
1658 .last_key_value()
1659 .expect("all shards have a schema");
1660 if *current_id != expected {
1661 return Break(NoOpStateTransition(CaESchema::ExpectedMismatch {
1662 schema_id: *current_id,
1663 key: K::decode_schema(¤t.key),
1664 val: V::decode_schema(¤t.val),
1665 }));
1666 }
1667
1668 let current_key = K::decode_schema(¤t.key);
1669 let current_key_dt = EncodedSchemas::decode_data_type(¤t.key_data_type);
1670 let current_val = V::decode_schema(¤t.val);
1671 let current_val_dt = EncodedSchemas::decode_data_type(¤t.val_data_type);
1672
1673 let key_dt = data_type(key_schema);
1674 let val_dt = data_type(val_schema);
1675
1676 if current_key == *key_schema
1678 && current_key_dt == key_dt
1679 && current_val == *val_schema
1680 && current_val_dt == val_dt
1681 {
1682 return Break(NoOpStateTransition(CaESchema::Ok(*current_id)));
1683 }
1684
1685 let key_fn = backward_compatible(¤t_key_dt, &key_dt);
1686 let val_fn = backward_compatible(¤t_val_dt, &val_dt);
1687 let (Some(key_fn), Some(val_fn)) = (key_fn, val_fn) else {
1688 return Break(NoOpStateTransition(CaESchema::Incompatible));
1689 };
1690 if key_fn.contains_drop() || val_fn.contains_drop() {
1694 return Break(NoOpStateTransition(CaESchema::Incompatible));
1695 }
1696
1697 let id = SchemaId(self.schemas.len());
1701 self.schemas.insert(
1702 id,
1703 EncodedSchemas {
1704 key: K::encode_schema(key_schema),
1705 key_data_type: prost::Message::encode_to_vec(&key_dt.into_proto()).into(),
1706 val: V::encode_schema(val_schema),
1707 val_data_type: prost::Message::encode_to_vec(&val_dt.into_proto()).into(),
1708 },
1709 );
1710 Continue(CaESchema::Ok(id))
1711 }
1712
1713 pub fn compare_and_append(
1714 &mut self,
1715 batch: &HollowBatch<T>,
1716 writer_id: &WriterId,
1717 heartbeat_timestamp_ms: u64,
1718 lease_duration_ms: u64,
1719 idempotency_token: &IdempotencyToken,
1720 debug_info: &HandleDebugState,
1721 inline_writes_total_max_bytes: usize,
1722 claim_compaction_percent: usize,
1723 claim_compaction_min_version: Option<&Version>,
1724 ) -> ControlFlow<CompareAndAppendBreak<T>, Vec<FueledMergeReq<T>>> {
1725 if self.is_tombstone() {
1730 assert_eq!(self.trace.upper(), &Antichain::new());
1731 return Break(CompareAndAppendBreak::Upper {
1732 shard_upper: Antichain::new(),
1733 writer_upper: Antichain::new(),
1738 });
1739 }
1740
1741 let writer_state = self
1742 .writers
1743 .entry(writer_id.clone())
1744 .or_insert_with(|| WriterState {
1745 last_heartbeat_timestamp_ms: heartbeat_timestamp_ms,
1746 lease_duration_ms,
1747 most_recent_write_token: IdempotencyToken::SENTINEL,
1748 most_recent_write_upper: Antichain::from_elem(T::minimum()),
1749 debug: debug_info.clone(),
1750 });
1751
1752 if PartialOrder::less_than(batch.desc.upper(), batch.desc.lower()) {
1753 return Break(CompareAndAppendBreak::InvalidUsage(
1754 InvalidUsage::InvalidBounds {
1755 lower: batch.desc.lower().clone(),
1756 upper: batch.desc.upper().clone(),
1757 },
1758 ));
1759 }
1760
1761 if batch.desc.upper() == batch.desc.lower() && !batch.is_empty() {
1764 return Break(CompareAndAppendBreak::InvalidUsage(
1765 InvalidUsage::InvalidEmptyTimeInterval {
1766 lower: batch.desc.lower().clone(),
1767 upper: batch.desc.upper().clone(),
1768 keys: batch
1769 .parts
1770 .iter()
1771 .map(|x| x.printable_name().to_owned())
1772 .collect(),
1773 },
1774 ));
1775 }
1776
1777 if idempotency_token == &writer_state.most_recent_write_token {
1778 assert_eq!(batch.desc.upper(), &writer_state.most_recent_write_upper);
1783 assert!(
1784 PartialOrder::less_equal(batch.desc.upper(), self.trace.upper()),
1785 "{:?} vs {:?}",
1786 batch.desc.upper(),
1787 self.trace.upper()
1788 );
1789 return Break(CompareAndAppendBreak::AlreadyCommitted);
1790 }
1791
1792 let shard_upper = self.trace.upper();
1793 if shard_upper != batch.desc.lower() {
1794 return Break(CompareAndAppendBreak::Upper {
1795 shard_upper: shard_upper.clone(),
1796 writer_upper: writer_state.most_recent_write_upper.clone(),
1797 });
1798 }
1799
1800 let new_inline_bytes = batch.inline_bytes();
1801 if new_inline_bytes > 0 {
1802 let mut existing_inline_bytes = 0;
1803 self.trace
1804 .map_batches(|x| existing_inline_bytes += x.inline_bytes());
1805 if existing_inline_bytes + new_inline_bytes >= inline_writes_total_max_bytes {
1809 return Break(CompareAndAppendBreak::InlineBackpressure);
1810 }
1811 }
1812
1813 let mut merge_reqs = if batch.desc.upper() != batch.desc.lower() {
1814 self.trace.push_batch(batch.clone())
1815 } else {
1816 Vec::new()
1817 };
1818
1819 let all_empty_reqs = merge_reqs
1822 .iter()
1823 .all(|req| req.inputs.iter().all(|b| b.batch.is_empty()));
1824 if all_empty_reqs && !batch.is_empty() {
1825 let mut reqs_to_take = claim_compaction_percent / 100;
1826 if (usize::cast_from(idempotency_token.hashed()) % 100)
1827 < (claim_compaction_percent % 100)
1828 {
1829 reqs_to_take += 1;
1830 }
1831 let threshold_ms = heartbeat_timestamp_ms.saturating_sub(lease_duration_ms);
1832 let min_writer = claim_compaction_min_version.map(WriterKey::for_version);
1833 merge_reqs.extend(
1834 self.trace
1837 .fueled_merge_reqs_before_ms(threshold_ms, min_writer)
1838 .take(reqs_to_take),
1839 )
1840 }
1841
1842 for req in &merge_reqs {
1843 self.trace.claim_compaction(
1844 req.id,
1845 ActiveCompaction {
1846 start_ms: heartbeat_timestamp_ms,
1847 },
1848 )
1849 }
1850
1851 mz_ore::soft_assert_eq_no_log!(self.trace.upper(), batch.desc.upper());
1852 writer_state.most_recent_write_token = idempotency_token.clone();
1853 assert!(
1855 PartialOrder::less_equal(&writer_state.most_recent_write_upper, batch.desc.upper()),
1856 "{:?} vs {:?}",
1857 writer_state.most_recent_write_upper,
1858 batch.desc.upper()
1859 );
1860 writer_state
1861 .most_recent_write_upper
1862 .clone_from(batch.desc.upper());
1863
1864 writer_state.last_heartbeat_timestamp_ms = std::cmp::max(
1866 heartbeat_timestamp_ms,
1867 writer_state.last_heartbeat_timestamp_ms,
1868 );
1869
1870 Continue(merge_reqs)
1871 }
1872
1873 pub fn apply_merge_res<D: Codec64 + Monoid + PartialEq>(
1874 &mut self,
1875 res: &FueledMergeRes<T>,
1876 metrics: &ColumnarMetrics,
1877 ) -> ControlFlow<NoOpStateTransition<ApplyMergeResult>, ApplyMergeResult> {
1878 if self.is_tombstone() {
1883 return Break(NoOpStateTransition(ApplyMergeResult::NotAppliedNoMatch));
1884 }
1885
1886 let apply_merge_result = self.trace.apply_merge_res_checked::<D>(res, metrics);
1887 Continue(apply_merge_result)
1888 }
1889
1890 pub fn spine_exert(
1891 &mut self,
1892 fuel: usize,
1893 ) -> ControlFlow<NoOpStateTransition<Vec<FueledMergeReq<T>>>, Vec<FueledMergeReq<T>>> {
1894 let (merge_reqs, did_work) = self.trace.exert(fuel);
1895 if did_work {
1896 Continue(merge_reqs)
1897 } else {
1898 assert!(merge_reqs.is_empty());
1899 Break(NoOpStateTransition(Vec::new()))
1902 }
1903 }
1904
1905 pub fn downgrade_since(
1906 &mut self,
1907 reader_id: &LeasedReaderId,
1908 seqno: SeqNo,
1909 outstanding_seqno: SeqNo,
1910 new_since: &Antichain<T>,
1911 heartbeat_timestamp_ms: u64,
1912 ) -> ControlFlow<NoOpStateTransition<Since<T>>, Since<T>> {
1913 if self.is_tombstone() {
1918 return Break(NoOpStateTransition(Since(Antichain::new())));
1919 }
1920
1921 let Some(reader_state) = self.leased_reader(reader_id) else {
1924 tracing::warn!(
1925 "Leased reader {reader_id} was expired due to inactivity. Did the machine go to sleep?",
1926 );
1927 return Break(NoOpStateTransition(Since(Antichain::new())));
1928 };
1929
1930 reader_state.last_heartbeat_timestamp_ms = std::cmp::max(
1933 heartbeat_timestamp_ms,
1934 reader_state.last_heartbeat_timestamp_ms,
1935 );
1936
1937 let seqno = {
1938 assert!(
1939 outstanding_seqno >= reader_state.seqno,
1940 "SeqNos cannot go backward; however, oldest leased SeqNo ({:?}) \
1941 is behind current reader_state ({:?})",
1942 outstanding_seqno,
1943 reader_state.seqno,
1944 );
1945 std::cmp::min(outstanding_seqno, seqno)
1946 };
1947
1948 reader_state.seqno = seqno;
1949
1950 let reader_current_since = if PartialOrder::less_than(&reader_state.since, new_since) {
1951 reader_state.since.clone_from(new_since);
1952 self.update_since();
1953 new_since.clone()
1954 } else {
1955 reader_state.since.clone()
1958 };
1959
1960 Continue(Since(reader_current_since))
1961 }
1962
1963 pub fn compare_and_downgrade_since(
1964 &mut self,
1965 reader_id: &CriticalReaderId,
1966 expected_opaque: &Opaque,
1967 (new_opaque, new_since): (&Opaque, &Antichain<T>),
1968 ) -> ControlFlow<
1969 NoOpStateTransition<Result<Since<T>, (Opaque, Since<T>)>>,
1970 Result<Since<T>, (Opaque, Since<T>)>,
1971 > {
1972 if self.is_tombstone() {
1977 return Break(NoOpStateTransition(Ok(Since(Antichain::new()))));
1981 }
1982
1983 let reader_state = self.critical_reader(reader_id);
1984
1985 if reader_state.opaque != *expected_opaque {
1986 return Continue(Err((
1989 reader_state.opaque.clone(),
1990 Since(reader_state.since.clone()),
1991 )));
1992 }
1993
1994 reader_state.opaque = new_opaque.clone();
1995 if PartialOrder::less_equal(&reader_state.since, new_since) {
1996 reader_state.since.clone_from(new_since);
1997 self.update_since();
1998 Continue(Ok(Since(new_since.clone())))
1999 } else {
2000 Continue(Ok(Since(reader_state.since.clone())))
2004 }
2005 }
2006
2007 pub fn expire_leased_reader(
2008 &mut self,
2009 reader_id: &LeasedReaderId,
2010 ) -> ControlFlow<NoOpStateTransition<bool>, bool> {
2011 if self.is_tombstone() {
2016 return Break(NoOpStateTransition(false));
2017 }
2018
2019 let existed = self.leased_readers.remove(reader_id).is_some();
2020 if existed {
2021 }
2035 Continue(existed)
2038 }
2039
2040 pub fn expire_critical_reader(
2041 &mut self,
2042 reader_id: &CriticalReaderId,
2043 ) -> ControlFlow<NoOpStateTransition<bool>, bool> {
2044 if self.is_tombstone() {
2049 return Break(NoOpStateTransition(false));
2050 }
2051
2052 let existed = self.critical_readers.remove(reader_id).is_some();
2053 if existed {
2054 }
2068 Continue(existed)
2072 }
2073
2074 pub fn expire_writer(
2075 &mut self,
2076 writer_id: &WriterId,
2077 ) -> ControlFlow<NoOpStateTransition<bool>, bool> {
2078 if self.is_tombstone() {
2083 return Break(NoOpStateTransition(false));
2084 }
2085
2086 let existed = self.writers.remove(writer_id).is_some();
2087 Continue(existed)
2091 }
2092
2093 fn leased_reader(&mut self, id: &LeasedReaderId) -> Option<&mut LeasedReaderState<T>> {
2094 self.leased_readers.get_mut(id)
2095 }
2096
2097 fn critical_reader(&mut self, id: &CriticalReaderId) -> &mut CriticalReaderState<T> {
2098 self.critical_readers
2099 .get_mut(id)
2100 .unwrap_or_else(|| {
2101 panic!(
2102 "Unknown CriticalReaderId({}). It was either never registered, or has been manually expired.",
2103 id
2104 )
2105 })
2106 }
2107
2108 fn critical_since(&self) -> Option<Antichain<T>> {
2109 let mut critical_sinces = self.critical_readers.values().map(|r| &r.since);
2110 let mut since = critical_sinces.next().cloned()?;
2111 for s in critical_sinces {
2112 since.meet_assign(s);
2113 }
2114 Some(since)
2115 }
2116
2117 fn update_since(&mut self) {
2118 let mut sinces_iter = self
2119 .leased_readers
2120 .values()
2121 .map(|x| &x.since)
2122 .chain(self.critical_readers.values().map(|x| &x.since));
2123 let mut since = match sinces_iter.next() {
2124 Some(since) => since.clone(),
2125 None => {
2126 return;
2129 }
2130 };
2131 while let Some(s) = sinces_iter.next() {
2132 since.meet_assign(s);
2133 }
2134 self.trace.downgrade_since(&since);
2135 }
2136
2137 fn seqno_since(&self, seqno: SeqNo) -> SeqNo {
2138 let mut seqno_since = seqno;
2139 for cap in self.leased_readers.values() {
2140 seqno_since = std::cmp::min(seqno_since, cap.seqno);
2141 }
2142 seqno_since
2144 }
2145
2146 fn tombstone_batch() -> HollowBatch<T> {
2147 HollowBatch::empty(Description::new(
2148 Antichain::from_elem(T::minimum()),
2149 Antichain::new(),
2150 Antichain::new(),
2151 ))
2152 }
2153
2154 pub(crate) fn is_tombstone(&self) -> bool {
2155 self.trace.upper().is_empty()
2156 && self.trace.since().is_empty()
2157 && self.writers.is_empty()
2158 && self.leased_readers.is_empty()
2159 && self.critical_readers.is_empty()
2160 }
2161
2162 pub(crate) fn is_single_empty_batch(&self) -> bool {
2163 let mut batch_count = 0;
2164 let mut is_empty = true;
2165 self.trace.map_batches(|b| {
2166 batch_count += 1;
2167 is_empty &= b.is_empty()
2168 });
2169 batch_count <= 1 && is_empty
2170 }
2171
2172 pub fn become_tombstone_and_shrink(&mut self) -> ControlFlow<NoOpStateTransition<()>, ()> {
2173 assert_eq!(self.trace.upper(), &Antichain::new());
2174 assert_eq!(self.trace.since(), &Antichain::new());
2175
2176 let was_tombstone = self.is_tombstone();
2179
2180 self.writers.clear();
2182 self.leased_readers.clear();
2183 self.critical_readers.clear();
2184
2185 mz_ore::soft_assert_no_log!(self.is_tombstone());
2186
2187 let mut to_replace = None;
2196 let mut batch_count = 0;
2197 self.trace.map_batches(|b| {
2198 batch_count += 1;
2199 if !b.is_empty() && to_replace.is_none() {
2200 to_replace = Some(b.desc.clone());
2201 }
2202 });
2203 if let Some(desc) = to_replace {
2204 let result = self.trace.apply_tombstone_merge(&desc);
2208 assert!(
2209 result.matched(),
2210 "merge with a matching desc should always match"
2211 );
2212 Continue(())
2213 } else if batch_count > 1 {
2214 let mut new_trace = Trace::default();
2219 new_trace.downgrade_since(&Antichain::new());
2220 let merge_reqs = new_trace.push_batch(Self::tombstone_batch());
2221 assert_eq!(merge_reqs, Vec::new());
2222 self.trace = new_trace;
2223 Continue(())
2224 } else if !was_tombstone {
2225 Continue(())
2228 } else {
2229 Break(NoOpStateTransition(()))
2232 }
2233 }
2234}
2235
2236#[derive(Debug)]
2238#[cfg_attr(any(test, debug_assertions), derive(Clone, PartialEq))]
2239pub struct State<T> {
2240 pub(crate) shard_id: ShardId,
2241
2242 pub(crate) seqno: SeqNo,
2243 pub(crate) walltime_ms: u64,
2246 pub(crate) hostname: String,
2249 pub(crate) collections: StateCollections<T>,
2250}
2251
2252pub struct TypedState<K, V, T, D> {
2255 pub(crate) state: State<T>,
2256
2257 pub(crate) _phantom: PhantomData<fn() -> (K, V, D)>,
2265}
2266
2267impl<K, V, T: Clone, D> TypedState<K, V, T, D> {
2268 #[cfg(any(test, debug_assertions))]
2269 pub(crate) fn clone(&self, hostname: String) -> Self {
2270 TypedState {
2271 state: State {
2272 shard_id: self.shard_id.clone(),
2273 seqno: self.seqno.clone(),
2274 walltime_ms: self.walltime_ms,
2275 hostname,
2276 collections: self.collections.clone(),
2277 },
2278 _phantom: PhantomData,
2279 }
2280 }
2281
2282 pub(crate) fn clone_for_rollup(&self) -> Self {
2283 TypedState {
2284 state: State {
2285 shard_id: self.shard_id.clone(),
2286 seqno: self.seqno.clone(),
2287 walltime_ms: self.walltime_ms,
2288 hostname: self.hostname.clone(),
2289 collections: self.collections.clone(),
2290 },
2291 _phantom: PhantomData,
2292 }
2293 }
2294}
2295
2296impl<K, V, T: Debug, D> Debug for TypedState<K, V, T, D> {
2297 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2298 let TypedState { state, _phantom } = self;
2301 f.debug_struct("TypedState").field("state", state).finish()
2302 }
2303}
2304
2305#[cfg(any(test, debug_assertions))]
2307impl<K, V, T: PartialEq, D> PartialEq for TypedState<K, V, T, D> {
2308 fn eq(&self, other: &Self) -> bool {
2309 let TypedState {
2312 state: self_state,
2313 _phantom,
2314 } = self;
2315 let TypedState {
2316 state: other_state,
2317 _phantom,
2318 } = other;
2319 self_state == other_state
2320 }
2321}
2322
2323impl<K, V, T, D> Deref for TypedState<K, V, T, D> {
2324 type Target = State<T>;
2325
2326 fn deref(&self) -> &Self::Target {
2327 &self.state
2328 }
2329}
2330
2331impl<K, V, T, D> DerefMut for TypedState<K, V, T, D> {
2332 fn deref_mut(&mut self) -> &mut Self::Target {
2333 &mut self.state
2334 }
2335}
2336
2337impl<K, V, T, D> TypedState<K, V, T, D>
2338where
2339 K: Codec,
2340 V: Codec,
2341 T: Timestamp + Lattice + Codec64,
2342 D: Codec64,
2343{
2344 pub fn new(
2345 applier_version: Version,
2346 shard_id: ShardId,
2347 hostname: String,
2348 walltime_ms: u64,
2349 ) -> Self {
2350 let state = State {
2351 shard_id,
2352 seqno: SeqNo::minimum(),
2353 walltime_ms,
2354 hostname,
2355 collections: StateCollections {
2356 version: applier_version,
2357 last_gc_req: SeqNo::minimum(),
2358 rollups: BTreeMap::new(),
2359 active_rollup: None,
2360 active_gc: None,
2361 leased_readers: BTreeMap::new(),
2362 critical_readers: BTreeMap::new(),
2363 writers: BTreeMap::new(),
2364 schemas: BTreeMap::new(),
2365 trace: Trace::default(),
2366 },
2367 };
2368 TypedState {
2369 state,
2370 _phantom: PhantomData,
2371 }
2372 }
2373
2374 pub fn clone_apply<R, E, WorkFn>(
2375 &self,
2376 cfg: &PersistConfig,
2377 work_fn: &mut WorkFn,
2378 ) -> ControlFlow<E, (R, Self)>
2379 where
2380 WorkFn: FnMut(SeqNo, &PersistConfig, &mut StateCollections<T>) -> ControlFlow<E, R>,
2381 {
2382 let mut new_state = State {
2384 shard_id: self.shard_id,
2385 seqno: self.seqno.next(),
2386 walltime_ms: (cfg.now)(),
2387 hostname: cfg.hostname.clone(),
2388 collections: self.collections.clone(),
2389 };
2390
2391 if new_state.walltime_ms <= self.walltime_ms {
2394 new_state.walltime_ms = self.walltime_ms + 1;
2395 }
2396
2397 let work_ret = work_fn(new_state.seqno, cfg, &mut new_state.collections)?;
2398 let new_state = TypedState {
2399 state: new_state,
2400 _phantom: PhantomData,
2401 };
2402 Continue((work_ret, new_state))
2403 }
2404}
2405
2406#[derive(Copy, Clone, Debug)]
2407pub struct GcConfig {
2408 pub use_active_gc: bool,
2409 pub fallback_threshold_ms: u64,
2410 pub min_versions: usize,
2411 pub max_versions: usize,
2412}
2413
2414impl<T> State<T>
2415where
2416 T: Timestamp + Lattice + Codec64,
2417{
2418 pub fn shard_id(&self) -> ShardId {
2419 self.shard_id
2420 }
2421
2422 pub fn seqno(&self) -> SeqNo {
2423 self.seqno
2424 }
2425
2426 pub fn since(&self) -> &Antichain<T> {
2427 self.collections.trace.since()
2428 }
2429
2430 pub fn upper(&self) -> &Antichain<T> {
2431 self.collections.trace.upper()
2432 }
2433
2434 pub fn spine_batch_count(&self) -> usize {
2435 self.collections.trace.num_spine_batches()
2436 }
2437
2438 pub fn size_metrics(&self) -> StateSizeMetrics {
2439 let mut ret = StateSizeMetrics::default();
2440 self.blobs().for_each(|x| match x {
2441 HollowBlobRef::Batch(x) => {
2442 ret.hollow_batch_count += 1;
2443 ret.batch_part_count += x.part_count();
2444 ret.num_updates += x.len;
2445
2446 let batch_size = x.encoded_size_bytes();
2447 for x in x.parts.iter() {
2448 if x.ts_rewrite().is_some() {
2449 ret.rewrite_part_count += 1;
2450 }
2451 if x.is_inline() {
2452 ret.inline_part_count += 1;
2453 ret.inline_part_bytes += x.inline_bytes();
2454 }
2455 }
2456 ret.largest_batch_bytes = std::cmp::max(ret.largest_batch_bytes, batch_size);
2457 ret.state_batches_bytes += batch_size;
2458 }
2459 HollowBlobRef::Rollup(x) => {
2460 ret.state_rollup_count += 1;
2461 ret.state_rollups_bytes += x.encoded_size_bytes.unwrap_or_default()
2462 }
2463 });
2464 ret
2465 }
2466
2467 pub fn latest_rollup(&self) -> (&SeqNo, &HollowRollup) {
2468 self.collections
2471 .rollups
2472 .iter()
2473 .rev()
2474 .next()
2475 .expect("State should have at least one rollup if seqno > minimum")
2476 }
2477
2478 pub(crate) fn seqno_since(&self) -> SeqNo {
2479 self.collections.seqno_since(self.seqno)
2480 }
2481
2482 pub fn maybe_gc(&mut self, is_write: bool, now: u64, cfg: GcConfig) -> Option<GcReq> {
2494 let GcConfig {
2495 use_active_gc,
2496 fallback_threshold_ms,
2497 min_versions,
2498 max_versions,
2499 } = cfg;
2500 let gc_threshold = if use_active_gc {
2504 u64::cast_from(min_versions)
2505 } else {
2506 std::cmp::max(
2507 1,
2508 u64::cast_from(self.seqno.0.next_power_of_two().trailing_zeros()),
2509 )
2510 };
2511 let new_seqno_since = self.seqno_since();
2512 let gc_until_seqno = new_seqno_since.min(SeqNo(
2515 self.collections
2516 .last_gc_req
2517 .0
2518 .saturating_add(u64::cast_from(max_versions)),
2519 ));
2520 let should_gc = new_seqno_since
2521 .0
2522 .saturating_sub(self.collections.last_gc_req.0)
2523 >= gc_threshold;
2524
2525 let should_gc = if use_active_gc && !should_gc {
2528 match self.collections.active_gc {
2529 Some(active_gc) => now.saturating_sub(active_gc.start_ms) > fallback_threshold_ms,
2530 None => false,
2531 }
2532 } else {
2533 should_gc
2534 };
2535 let should_gc = should_gc && (is_write || self.collections.writers.is_empty());
2538 let tombstone_needs_gc = self.collections.is_tombstone();
2543 let should_gc = should_gc || tombstone_needs_gc;
2544 let should_gc = if use_active_gc {
2545 should_gc
2549 && match self.collections.active_gc {
2550 Some(active) => now.saturating_sub(active.start_ms) > fallback_threshold_ms,
2551 None => true,
2552 }
2553 } else {
2554 should_gc
2555 };
2556 if should_gc {
2557 self.collections.last_gc_req = gc_until_seqno;
2558 Some(GcReq {
2559 shard_id: self.shard_id,
2560 new_seqno_since: gc_until_seqno,
2561 })
2562 } else {
2563 None
2564 }
2565 }
2566
2567 pub fn seqnos_held(&self) -> usize {
2569 usize::cast_from(self.seqno.0.saturating_sub(self.seqno_since().0))
2570 }
2571
2572 pub fn expire_at(&mut self, walltime_ms: EpochMillis) -> ExpiryMetrics {
2574 let mut metrics = ExpiryMetrics::default();
2575 let shard_id = self.shard_id();
2576 self.collections.leased_readers.retain(|id, state| {
2577 let retain = state.last_heartbeat_timestamp_ms + state.lease_duration_ms >= walltime_ms;
2578 if !retain {
2579 info!(
2580 "Force expiring reader {id} ({}) of shard {shard_id} due to inactivity",
2581 state.debug.purpose
2582 );
2583 metrics.readers_expired += 1;
2584 }
2585 retain
2586 });
2587 self.collections.writers.retain(|id, state| {
2589 let retain =
2590 (state.last_heartbeat_timestamp_ms + state.lease_duration_ms) >= walltime_ms;
2591 if !retain {
2592 info!(
2593 "Force expiring writer {id} ({}) of shard {shard_id} due to inactivity",
2594 state.debug.purpose
2595 );
2596 metrics.writers_expired += 1;
2597 }
2598 retain
2599 });
2600 metrics
2601 }
2602
2603 pub fn snapshot(&self, as_of: &Antichain<T>) -> Result<Vec<HollowBatch<T>>, SnapshotErr<T>> {
2607 if PartialOrder::less_than(as_of, self.collections.trace.since()) {
2608 return Err(SnapshotErr::AsOfHistoricalDistinctionsLost(Since(
2609 self.collections.trace.since().clone(),
2610 )));
2611 }
2612 let upper = self.collections.trace.upper();
2613 if PartialOrder::less_equal(upper, as_of) {
2614 return Err(SnapshotErr::AsOfNotYetAvailable(
2615 self.seqno,
2616 Upper(upper.clone()),
2617 ));
2618 }
2619
2620 let batches = self
2621 .collections
2622 .trace
2623 .batches()
2624 .filter(|b| !PartialOrder::less_than(as_of, b.desc.lower()))
2625 .cloned()
2626 .collect();
2627 Ok(batches)
2628 }
2629
2630 pub fn verify_listen(&self, as_of: &Antichain<T>) -> Result<(), Since<T>> {
2632 if PartialOrder::less_than(as_of, self.collections.trace.since()) {
2633 return Err(Since(self.collections.trace.since().clone()));
2634 }
2635 Ok(())
2636 }
2637
2638 pub fn next_listen_batch(&self, frontier: &Antichain<T>) -> Result<HollowBatch<T>, SeqNo> {
2639 self.collections
2642 .trace
2643 .batches()
2644 .find(|b| {
2645 PartialOrder::less_equal(b.desc.lower(), frontier)
2646 && PartialOrder::less_than(frontier, b.desc.upper())
2647 })
2648 .cloned()
2649 .ok_or(self.seqno)
2650 }
2651
2652 pub fn active_rollup(&self) -> Option<ActiveRollup> {
2653 self.collections.active_rollup
2654 }
2655
2656 pub fn need_rollup(
2657 &self,
2658 threshold: usize,
2659 use_active_rollup: bool,
2660 fallback_threshold_ms: u64,
2661 now: u64,
2662 ) -> Option<SeqNo> {
2663 let (latest_rollup_seqno, _) = self.latest_rollup();
2664
2665 if self.collections.is_tombstone() && latest_rollup_seqno.next() < self.seqno {
2671 return Some(self.seqno);
2672 }
2673
2674 let seqnos_since_last_rollup = self.seqno.0.saturating_sub(latest_rollup_seqno.0);
2675
2676 if use_active_rollup {
2677 if seqnos_since_last_rollup > u64::cast_from(threshold) {
2683 match self.active_rollup() {
2684 Some(active_rollup) => {
2685 if now.saturating_sub(active_rollup.start_ms) > fallback_threshold_ms {
2686 return Some(self.seqno);
2687 }
2688 }
2689 None => {
2690 return Some(self.seqno);
2691 }
2692 }
2693 }
2694 } else {
2695 if seqnos_since_last_rollup > 0
2699 && seqnos_since_last_rollup % u64::cast_from(threshold) == 0
2700 {
2701 return Some(self.seqno);
2702 }
2703
2704 if seqnos_since_last_rollup
2707 > u64::cast_from(
2708 threshold * PersistConfig::DEFAULT_FALLBACK_ROLLUP_THRESHOLD_MULTIPLIER,
2709 )
2710 {
2711 return Some(self.seqno);
2712 }
2713 }
2714
2715 None
2716 }
2717
2718 pub(crate) fn blobs(&self) -> impl Iterator<Item = HollowBlobRef<'_, T>> {
2719 let batches = self.collections.trace.batches().map(HollowBlobRef::Batch);
2720 let rollups = self.collections.rollups.values().map(HollowBlobRef::Rollup);
2721 batches.chain(rollups)
2722 }
2723}
2724
2725fn serialize_part_bytes<S: Serializer>(val: &[u8], s: S) -> Result<S::Ok, S::Error> {
2726 let val = hex::encode(val);
2727 val.serialize(s)
2728}
2729
2730fn serialize_lazy_proto<S: Serializer, T: prost::Message + Default>(
2731 val: &Option<LazyProto<T>>,
2732 s: S,
2733) -> Result<S::Ok, S::Error> {
2734 val.as_ref()
2735 .map(|lazy| hex::encode(&lazy.into_proto()))
2736 .serialize(s)
2737}
2738
2739fn serialize_part_stats<S: Serializer>(
2740 val: &Option<LazyPartStats>,
2741 s: S,
2742) -> Result<S::Ok, S::Error> {
2743 let stats = val.as_ref().and_then(|x| match x.try_decode() {
2749 Ok(stats) => Some(stats.key),
2750 Err(err) => {
2751 tracing::warn!("undecodable part stats, reporting as absent: {err}");
2752 None
2753 }
2754 });
2755 stats.serialize(s)
2756}
2757
2758fn serialize_diffs_sum<S: Serializer>(val: &Option<[u8; 8]>, s: S) -> Result<S::Ok, S::Error> {
2759 let val = val.map(i64::decode);
2761 val.serialize(s)
2762}
2763
2764impl<T: Serialize + Timestamp + Lattice> Serialize for State<T> {
2770 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
2771 let State {
2772 shard_id,
2773 seqno,
2774 walltime_ms,
2775 hostname,
2776 collections:
2777 StateCollections {
2778 version: applier_version,
2779 last_gc_req,
2780 rollups,
2781 active_rollup,
2782 active_gc,
2783 leased_readers,
2784 critical_readers,
2785 writers,
2786 schemas,
2787 trace,
2788 },
2789 } = self;
2790 let mut s = s.serialize_struct("State", 13)?;
2791 let () = s.serialize_field("applier_version", &applier_version.to_string())?;
2792 let () = s.serialize_field("shard_id", shard_id)?;
2793 let () = s.serialize_field("seqno", seqno)?;
2794 let () = s.serialize_field("walltime_ms", walltime_ms)?;
2795 let () = s.serialize_field("hostname", hostname)?;
2796 let () = s.serialize_field("last_gc_req", last_gc_req)?;
2797 let () = s.serialize_field("rollups", rollups)?;
2798 let () = s.serialize_field("active_rollup", active_rollup)?;
2799 let () = s.serialize_field("active_gc", active_gc)?;
2800 let () = s.serialize_field("leased_readers", leased_readers)?;
2801 let () = s.serialize_field("critical_readers", critical_readers)?;
2802 let () = s.serialize_field("writers", writers)?;
2803 let () = s.serialize_field("schemas", schemas)?;
2804 let () = s.serialize_field("since", &trace.since().elements())?;
2805 let () = s.serialize_field("upper", &trace.upper().elements())?;
2806 let trace = trace.flatten();
2807 let () = s.serialize_field("batches", &trace.legacy_batches.keys().collect::<Vec<_>>())?;
2808 let () = s.serialize_field("hollow_batches", &trace.hollow_batches)?;
2809 let () = s.serialize_field("spine_batches", &trace.spine_batches)?;
2810 let () = s.serialize_field("merges", &trace.merges)?;
2811 s.end()
2812 }
2813}
2814
2815#[derive(Debug, Default)]
2816pub struct StateSizeMetrics {
2817 pub hollow_batch_count: usize,
2818 pub batch_part_count: usize,
2819 pub rewrite_part_count: usize,
2820 pub num_updates: usize,
2821 pub largest_batch_bytes: usize,
2822 pub state_batches_bytes: usize,
2823 pub state_rollups_bytes: usize,
2824 pub state_rollup_count: usize,
2825 pub inline_part_count: usize,
2826 pub inline_part_bytes: usize,
2827}
2828
2829#[derive(Default)]
2830pub struct ExpiryMetrics {
2831 pub(crate) readers_expired: usize,
2832 pub(crate) writers_expired: usize,
2833}
2834
2835#[derive(Debug, Clone, PartialEq)]
2837pub struct Since<T>(pub Antichain<T>);
2838
2839#[derive(Debug, PartialEq)]
2841pub struct Upper<T>(pub Antichain<T>);
2842
2843#[cfg(test)]
2844pub(crate) mod tests {
2845 use std::ops::Range;
2846 use std::str::FromStr;
2847
2848 use bytes::Bytes;
2849 use mz_build_info::DUMMY_BUILD_INFO;
2850 use mz_dyncfg::ConfigUpdates;
2851 use mz_ore::now::SYSTEM_TIME;
2852 use mz_ore::{assert_none, assert_ok};
2853 use mz_proto::RustType;
2854 use proptest::prelude::*;
2855 use proptest::strategy::ValueTree;
2856
2857 use crate::InvalidUsage::{InvalidBounds, InvalidEmptyTimeInterval};
2858 use crate::cache::PersistClientCache;
2859 use crate::internal::encoding::any_some_lazy_part_stats;
2860 use crate::internal::paths::RollupId;
2861 use crate::internal::trace::tests::any_trace;
2862 use crate::tests::new_test_client_cache;
2863 use crate::{Diagnostics, PersistLocation};
2864
2865 use super::*;
2866
2867 const LEASE_DURATION_MS: u64 = 900 * 1000;
2868 fn debug_state() -> HandleDebugState {
2869 HandleDebugState {
2870 hostname: "debug".to_owned(),
2871 purpose: "finding the bugs".to_owned(),
2872 }
2873 }
2874
2875 pub fn any_hollow_batch_with_exact_runs<T: Arbitrary + Timestamp>(
2876 num_runs: usize,
2877 ) -> impl Strategy<Value = HollowBatch<T>> {
2878 (
2879 any::<T>(),
2880 any::<T>(),
2881 any::<T>(),
2882 proptest::collection::vec(any_run_part::<T>(), num_runs + 1..20),
2883 any::<usize>(),
2884 )
2885 .prop_map(move |(t0, t1, since, parts, len)| {
2886 let (lower, upper) = if t0 <= t1 {
2887 (Antichain::from_elem(t0), Antichain::from_elem(t1))
2888 } else {
2889 (Antichain::from_elem(t1), Antichain::from_elem(t0))
2890 };
2891 let since = Antichain::from_elem(since);
2892
2893 let run_splits = (1..num_runs)
2894 .map(|i| i * parts.len() / num_runs)
2895 .collect::<Vec<_>>();
2896
2897 let run_meta = (0..num_runs)
2898 .map(|_| {
2899 let mut meta = RunMeta::default();
2900 meta.id = Some(RunId::new());
2901 meta
2902 })
2903 .collect::<Vec<_>>();
2904
2905 HollowBatch::new(
2906 Description::new(lower, upper, since),
2907 parts,
2908 len % 10,
2909 run_meta,
2910 run_splits,
2911 )
2912 })
2913 }
2914
2915 pub fn any_hollow_batch<T: Arbitrary + Timestamp>() -> impl Strategy<Value = HollowBatch<T>> {
2916 Strategy::prop_map(
2917 (
2918 any::<T>(),
2919 any::<T>(),
2920 any::<T>(),
2921 proptest::collection::vec(any_run_part::<T>(), 0..20),
2922 any::<usize>(),
2923 0..=10usize,
2924 proptest::collection::vec(any::<RunId>(), 10),
2925 ),
2926 |(t0, t1, since, parts, len, num_runs, run_ids)| {
2927 let (lower, upper) = if t0 <= t1 {
2928 (Antichain::from_elem(t0), Antichain::from_elem(t1))
2929 } else {
2930 (Antichain::from_elem(t1), Antichain::from_elem(t0))
2931 };
2932 let since = Antichain::from_elem(since);
2933 if num_runs > 0 && parts.len() > 2 && num_runs < parts.len() {
2934 let run_splits = (1..num_runs)
2935 .map(|i| i * parts.len() / num_runs)
2936 .collect::<Vec<_>>();
2937
2938 let run_meta = (0..num_runs)
2939 .enumerate()
2940 .map(|(i, _)| {
2941 let mut meta = RunMeta::default();
2942 meta.id = Some(run_ids[i]);
2943 meta
2944 })
2945 .collect::<Vec<_>>();
2946
2947 HollowBatch::new(
2948 Description::new(lower, upper, since),
2949 parts,
2950 len % 10,
2951 run_meta,
2952 run_splits,
2953 )
2954 } else {
2955 HollowBatch::new_run_for_test(
2956 Description::new(lower, upper, since),
2957 parts,
2958 len % 10,
2959 run_ids[0],
2960 )
2961 }
2962 },
2963 )
2964 }
2965
2966 pub fn any_batch_part<T: Arbitrary + Timestamp>() -> impl Strategy<Value = BatchPart<T>> {
2967 Strategy::prop_map(
2968 (
2969 any::<bool>(),
2970 any_hollow_batch_part(),
2971 any::<Option<T>>(),
2972 any::<Option<SchemaId>>(),
2973 any::<Option<SchemaId>>(),
2974 ),
2975 |(is_hollow, hollow, ts_rewrite, schema_id, deprecated_schema_id)| {
2976 if is_hollow {
2977 BatchPart::Hollow(hollow)
2978 } else {
2979 let updates = LazyInlineBatchPart::from_proto(Bytes::new()).unwrap();
2980 let ts_rewrite = ts_rewrite.map(Antichain::from_elem);
2981 BatchPart::Inline {
2982 updates,
2983 ts_rewrite,
2984 schema_id,
2985 deprecated_schema_id,
2986 }
2987 }
2988 },
2989 )
2990 }
2991
2992 pub fn any_run_part<T: Arbitrary + Timestamp>() -> impl Strategy<Value = RunPart<T>> {
2993 Strategy::prop_map(any_batch_part(), |part| RunPart::Single(part))
2994 }
2995
2996 pub fn any_hollow_batch_part<T: Arbitrary + Timestamp>()
2997 -> impl Strategy<Value = HollowBatchPart<T>> {
2998 Strategy::prop_map(
2999 (
3000 any::<PartialBatchKey>(),
3001 any::<usize>(),
3002 any::<Vec<u8>>(),
3003 any_some_lazy_part_stats(),
3004 any::<Option<T>>(),
3005 any::<[u8; 8]>(),
3006 any::<Option<BatchColumnarFormat>>(),
3007 any::<Option<SchemaId>>(),
3008 any::<Option<SchemaId>>(),
3009 ),
3010 |(
3011 key,
3012 encoded_size_bytes,
3013 key_lower,
3014 stats,
3015 ts_rewrite,
3016 diffs_sum,
3017 format,
3018 schema_id,
3019 deprecated_schema_id,
3020 )| {
3021 HollowBatchPart {
3022 key,
3023 meta: Default::default(),
3024 encoded_size_bytes,
3025 key_lower,
3026 structured_key_lower: None,
3027 stats,
3028 ts_rewrite: ts_rewrite.map(Antichain::from_elem),
3029 diffs_sum: Some(diffs_sum),
3030 format,
3031 schema_id,
3032 deprecated_schema_id,
3033 }
3034 },
3035 )
3036 }
3037
3038 pub fn any_leased_reader_state<T: Arbitrary>() -> impl Strategy<Value = LeasedReaderState<T>> {
3039 Strategy::prop_map(
3040 (
3041 any::<SeqNo>(),
3042 any::<Option<T>>(),
3043 any::<u64>(),
3044 any::<u64>(),
3045 any::<HandleDebugState>(),
3046 ),
3047 |(seqno, since, last_heartbeat_timestamp_ms, mut lease_duration_ms, debug)| {
3048 if lease_duration_ms == 0 {
3052 lease_duration_ms += 1;
3053 }
3054 LeasedReaderState {
3055 seqno,
3056 since: since.map_or_else(Antichain::new, Antichain::from_elem),
3057 last_heartbeat_timestamp_ms,
3058 lease_duration_ms,
3059 debug,
3060 }
3061 },
3062 )
3063 }
3064
3065 pub fn any_critical_reader_state<T>() -> impl Strategy<Value = CriticalReaderState<T>>
3066 where
3067 T: Arbitrary,
3068 {
3069 Strategy::prop_map(
3070 (
3071 any::<Option<T>>(),
3072 any::<Opaque>(),
3073 any::<HandleDebugState>(),
3074 ),
3075 |(since, opaque, debug)| CriticalReaderState {
3076 since: since.map_or_else(Antichain::new, Antichain::from_elem),
3077 opaque,
3078 debug,
3079 },
3080 )
3081 }
3082
3083 pub fn any_writer_state<T: Arbitrary>() -> impl Strategy<Value = WriterState<T>> {
3084 Strategy::prop_map(
3085 (
3086 any::<u64>(),
3087 any::<u64>(),
3088 any::<IdempotencyToken>(),
3089 any::<Option<T>>(),
3090 any::<HandleDebugState>(),
3091 ),
3092 |(
3093 last_heartbeat_timestamp_ms,
3094 lease_duration_ms,
3095 most_recent_write_token,
3096 most_recent_write_upper,
3097 debug,
3098 )| WriterState {
3099 last_heartbeat_timestamp_ms,
3100 lease_duration_ms,
3101 most_recent_write_token,
3102 most_recent_write_upper: most_recent_write_upper
3103 .map_or_else(Antichain::new, Antichain::from_elem),
3104 debug,
3105 },
3106 )
3107 }
3108
3109 pub fn any_encoded_schemas() -> impl Strategy<Value = EncodedSchemas> {
3110 Strategy::prop_map(
3111 (
3112 any::<Vec<u8>>(),
3113 any::<Vec<u8>>(),
3114 any::<Vec<u8>>(),
3115 any::<Vec<u8>>(),
3116 ),
3117 |(key, key_data_type, val, val_data_type)| EncodedSchemas {
3118 key: Bytes::from(key),
3119 key_data_type: Bytes::from(key_data_type),
3120 val: Bytes::from(val),
3121 val_data_type: Bytes::from(val_data_type),
3122 },
3123 )
3124 }
3125
3126 pub fn any_state<T: Arbitrary + Timestamp + Lattice>(
3127 num_trace_batches: Range<usize>,
3128 ) -> impl Strategy<Value = State<T>> {
3129 let part1 = (
3130 any::<ShardId>(),
3131 any::<SeqNo>(),
3132 any::<u64>(),
3133 any::<String>(),
3134 any::<SeqNo>(),
3135 proptest::collection::btree_map(any::<SeqNo>(), any::<HollowRollup>(), 1..3),
3136 proptest::option::of(any::<ActiveRollup>()),
3137 );
3138
3139 let part2 = (
3140 proptest::option::of(any::<ActiveGc>()),
3141 proptest::collection::btree_map(
3142 any::<LeasedReaderId>(),
3143 any_leased_reader_state::<T>(),
3144 1..3,
3145 ),
3146 proptest::collection::btree_map(
3147 any::<CriticalReaderId>(),
3148 any_critical_reader_state::<T>(),
3149 1..3,
3150 ),
3151 proptest::collection::btree_map(any::<WriterId>(), any_writer_state::<T>(), 0..3),
3152 proptest::collection::btree_map(any::<SchemaId>(), any_encoded_schemas(), 0..3),
3153 any_trace::<T>(num_trace_batches),
3154 );
3155
3156 (part1, part2).prop_map(
3157 |(
3158 (shard_id, seqno, walltime_ms, hostname, last_gc_req, rollups, active_rollup),
3159 (active_gc, leased_readers, critical_readers, writers, schemas, trace),
3160 )| State {
3161 shard_id,
3162 seqno,
3163 walltime_ms,
3164 hostname,
3165 collections: StateCollections {
3166 version: Version::new(1, 2, 3),
3167 last_gc_req,
3168 rollups,
3169 active_rollup,
3170 active_gc,
3171 leased_readers,
3172 critical_readers,
3173 writers,
3174 schemas,
3175 trace,
3176 },
3177 },
3178 )
3179 }
3180
3181 pub(crate) fn hollow<T: Timestamp>(
3182 lower: T,
3183 upper: T,
3184 keys: &[&str],
3185 len: usize,
3186 ) -> HollowBatch<T> {
3187 HollowBatch::new_run(
3188 Description::new(
3189 Antichain::from_elem(lower),
3190 Antichain::from_elem(upper),
3191 Antichain::from_elem(T::minimum()),
3192 ),
3193 keys.iter()
3194 .map(|x| {
3195 RunPart::Single(BatchPart::Hollow(HollowBatchPart {
3196 key: PartialBatchKey((*x).to_owned()),
3197 meta: Default::default(),
3198 encoded_size_bytes: 0,
3199 key_lower: vec![],
3200 structured_key_lower: None,
3201 stats: None,
3202 ts_rewrite: None,
3203 diffs_sum: None,
3204 format: None,
3205 schema_id: None,
3206 deprecated_schema_id: None,
3207 }))
3208 })
3209 .collect(),
3210 len,
3211 )
3212 }
3213
3214 #[mz_ore::test]
3215 fn downgrade_since() {
3216 let mut state = TypedState::<(), (), u64, i64>::new(
3217 DUMMY_BUILD_INFO.semver_version(),
3218 ShardId::new(),
3219 "".to_owned(),
3220 0,
3221 );
3222 let reader = LeasedReaderId::new();
3223 let seqno = SeqNo::minimum();
3224 let now = SYSTEM_TIME.clone();
3225 let _ = state.collections.register_leased_reader(
3226 "",
3227 &reader,
3228 "",
3229 seqno,
3230 Duration::from_secs(10),
3231 now(),
3232 false,
3233 );
3234
3235 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(0));
3237
3238 assert_eq!(
3240 state.collections.downgrade_since(
3241 &reader,
3242 seqno,
3243 seqno,
3244 &Antichain::from_elem(2),
3245 now()
3246 ),
3247 Continue(Since(Antichain::from_elem(2)))
3248 );
3249 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3250 assert_eq!(
3252 state.collections.downgrade_since(
3253 &reader,
3254 seqno,
3255 seqno,
3256 &Antichain::from_elem(2),
3257 now()
3258 ),
3259 Continue(Since(Antichain::from_elem(2)))
3260 );
3261 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3262 assert_eq!(
3264 state.collections.downgrade_since(
3265 &reader,
3266 seqno,
3267 seqno,
3268 &Antichain::from_elem(1),
3269 now()
3270 ),
3271 Continue(Since(Antichain::from_elem(2)))
3272 );
3273 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3274
3275 let reader2 = LeasedReaderId::new();
3277 let _ = state.collections.register_leased_reader(
3278 "",
3279 &reader2,
3280 "",
3281 seqno,
3282 Duration::from_secs(10),
3283 now(),
3284 false,
3285 );
3286
3287 assert_eq!(
3289 state.collections.downgrade_since(
3290 &reader2,
3291 seqno,
3292 seqno,
3293 &Antichain::from_elem(3),
3294 now()
3295 ),
3296 Continue(Since(Antichain::from_elem(3)))
3297 );
3298 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3299 assert_eq!(
3301 state.collections.downgrade_since(
3302 &reader,
3303 seqno,
3304 seqno,
3305 &Antichain::from_elem(5),
3306 now()
3307 ),
3308 Continue(Since(Antichain::from_elem(5)))
3309 );
3310 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(3));
3311
3312 assert_eq!(
3314 state.collections.expire_leased_reader(&reader),
3315 Continue(true)
3316 );
3317 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(3));
3318
3319 let reader3 = LeasedReaderId::new();
3321 let _ = state.collections.register_leased_reader(
3322 "",
3323 &reader3,
3324 "",
3325 seqno,
3326 Duration::from_secs(10),
3327 now(),
3328 false,
3329 );
3330
3331 assert_eq!(
3333 state.collections.downgrade_since(
3334 &reader3,
3335 seqno,
3336 seqno,
3337 &Antichain::from_elem(10),
3338 now()
3339 ),
3340 Continue(Since(Antichain::from_elem(10)))
3341 );
3342 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(3));
3343
3344 assert_eq!(
3346 state.collections.expire_leased_reader(&reader2),
3347 Continue(true)
3348 );
3349 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(3));
3354
3355 assert_eq!(
3357 state.collections.expire_leased_reader(&reader3),
3358 Continue(true)
3359 );
3360 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(3));
3365 }
3366
3367 #[mz_ore::test]
3368 fn compare_and_downgrade_since() {
3369 let mut state = TypedState::<(), (), u64, i64>::new(
3370 DUMMY_BUILD_INFO.semver_version(),
3371 ShardId::new(),
3372 "".to_owned(),
3373 0,
3374 );
3375 let reader = CriticalReaderId::new();
3376 let _ = state
3377 .collections
3378 .register_critical_reader("", &reader, Opaque::encode(&0u64), "");
3379
3380 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(0));
3382 assert_eq!(
3384 state
3385 .collections
3386 .critical_reader(&reader)
3387 .opaque
3388 .decode::<u64>(),
3389 u64::MIN
3390 );
3391
3392 assert_eq!(
3394 state.collections.compare_and_downgrade_since(
3395 &reader,
3396 &Opaque::encode(&0u64),
3397 (&Opaque::encode(&1u64), &Antichain::from_elem(2)),
3398 ),
3399 Continue(Ok(Since(Antichain::from_elem(2))))
3400 );
3401 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3402 assert_eq!(
3403 state
3404 .collections
3405 .critical_reader(&reader)
3406 .opaque
3407 .decode::<u64>(),
3408 1
3409 );
3410 assert_eq!(
3412 state.collections.compare_and_downgrade_since(
3413 &reader,
3414 &Opaque::encode(&1u64),
3415 (&Opaque::encode(&2u64), &Antichain::from_elem(2)),
3416 ),
3417 Continue(Ok(Since(Antichain::from_elem(2))))
3418 );
3419 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3420 assert_eq!(
3421 state
3422 .collections
3423 .critical_reader(&reader)
3424 .opaque
3425 .decode::<u64>(),
3426 2
3427 );
3428 assert_eq!(
3430 state.collections.compare_and_downgrade_since(
3431 &reader,
3432 &Opaque::encode(&2u64),
3433 (&Opaque::encode(&3u64), &Antichain::from_elem(1)),
3434 ),
3435 Continue(Ok(Since(Antichain::from_elem(2))))
3436 );
3437 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3438 assert_eq!(
3439 state
3440 .collections
3441 .critical_reader(&reader)
3442 .opaque
3443 .decode::<u64>(),
3444 3
3445 );
3446 }
3447
3448 #[mz_ore::test]
3449 fn compare_and_append() {
3450 let state = &mut TypedState::<String, String, u64, i64>::new(
3451 DUMMY_BUILD_INFO.semver_version(),
3452 ShardId::new(),
3453 "".to_owned(),
3454 0,
3455 )
3456 .collections;
3457
3458 let writer_id = WriterId::new();
3459 let now = SYSTEM_TIME.clone();
3460
3461 assert_eq!(state.trace.num_spine_batches(), 0);
3463 assert_eq!(state.trace.num_hollow_batches(), 0);
3464 assert_eq!(state.trace.num_updates(), 0);
3465
3466 assert_eq!(
3468 state.compare_and_append(
3469 &hollow(1, 2, &["key1"], 1),
3470 &writer_id,
3471 now(),
3472 LEASE_DURATION_MS,
3473 &IdempotencyToken::new(),
3474 &debug_state(),
3475 0,
3476 100,
3477 None
3478 ),
3479 Break(CompareAndAppendBreak::Upper {
3480 shard_upper: Antichain::from_elem(0),
3481 writer_upper: Antichain::from_elem(0)
3482 })
3483 );
3484
3485 assert!(
3487 state
3488 .compare_and_append(
3489 &hollow(0, 5, &[], 0),
3490 &writer_id,
3491 now(),
3492 LEASE_DURATION_MS,
3493 &IdempotencyToken::new(),
3494 &debug_state(),
3495 0,
3496 100,
3497 None
3498 )
3499 .is_continue()
3500 );
3501
3502 assert_eq!(
3504 state.compare_and_append(
3505 &hollow(5, 4, &["key1"], 1),
3506 &writer_id,
3507 now(),
3508 LEASE_DURATION_MS,
3509 &IdempotencyToken::new(),
3510 &debug_state(),
3511 0,
3512 100,
3513 None
3514 ),
3515 Break(CompareAndAppendBreak::InvalidUsage(InvalidBounds {
3516 lower: Antichain::from_elem(5),
3517 upper: Antichain::from_elem(4)
3518 }))
3519 );
3520
3521 assert_eq!(
3523 state.compare_and_append(
3524 &hollow(5, 5, &["key1"], 1),
3525 &writer_id,
3526 now(),
3527 LEASE_DURATION_MS,
3528 &IdempotencyToken::new(),
3529 &debug_state(),
3530 0,
3531 100,
3532 None
3533 ),
3534 Break(CompareAndAppendBreak::InvalidUsage(
3535 InvalidEmptyTimeInterval {
3536 lower: Antichain::from_elem(5),
3537 upper: Antichain::from_elem(5),
3538 keys: vec!["key1".to_owned()],
3539 }
3540 ))
3541 );
3542
3543 assert!(
3545 state
3546 .compare_and_append(
3547 &hollow(5, 5, &[], 0),
3548 &writer_id,
3549 now(),
3550 LEASE_DURATION_MS,
3551 &IdempotencyToken::new(),
3552 &debug_state(),
3553 0,
3554 100,
3555 None
3556 )
3557 .is_continue()
3558 );
3559 }
3560
3561 #[mz_ore::test]
3562 fn snapshot() {
3563 let now = SYSTEM_TIME.clone();
3564
3565 let mut state = TypedState::<String, String, u64, i64>::new(
3566 DUMMY_BUILD_INFO.semver_version(),
3567 ShardId::new(),
3568 "".to_owned(),
3569 0,
3570 );
3571 assert_eq!(
3573 state.snapshot(&Antichain::from_elem(0)),
3574 Err(SnapshotErr::AsOfNotYetAvailable(
3575 SeqNo(0),
3576 Upper(Antichain::from_elem(0))
3577 ))
3578 );
3579
3580 assert_eq!(
3582 state.snapshot(&Antichain::from_elem(5)),
3583 Err(SnapshotErr::AsOfNotYetAvailable(
3584 SeqNo(0),
3585 Upper(Antichain::from_elem(0))
3586 ))
3587 );
3588
3589 let writer_id = WriterId::new();
3590
3591 assert!(
3593 state
3594 .collections
3595 .compare_and_append(
3596 &hollow(0, 5, &["key1"], 1),
3597 &writer_id,
3598 now(),
3599 LEASE_DURATION_MS,
3600 &IdempotencyToken::new(),
3601 &debug_state(),
3602 0,
3603 100,
3604 None
3605 )
3606 .is_continue()
3607 );
3608
3609 assert_eq!(
3611 state.snapshot(&Antichain::from_elem(0)),
3612 Ok(vec![hollow(0, 5, &["key1"], 1)])
3613 );
3614
3615 assert_eq!(
3617 state.snapshot(&Antichain::from_elem(4)),
3618 Ok(vec![hollow(0, 5, &["key1"], 1)])
3619 );
3620
3621 assert_eq!(
3623 state.snapshot(&Antichain::from_elem(5)),
3624 Err(SnapshotErr::AsOfNotYetAvailable(
3625 SeqNo(0),
3626 Upper(Antichain::from_elem(5))
3627 ))
3628 );
3629 assert_eq!(
3630 state.snapshot(&Antichain::from_elem(6)),
3631 Err(SnapshotErr::AsOfNotYetAvailable(
3632 SeqNo(0),
3633 Upper(Antichain::from_elem(5))
3634 ))
3635 );
3636
3637 let reader = LeasedReaderId::new();
3638 let _ = state.collections.register_leased_reader(
3640 "",
3641 &reader,
3642 "",
3643 SeqNo::minimum(),
3644 Duration::from_secs(10),
3645 now(),
3646 false,
3647 );
3648 assert_eq!(
3649 state.collections.downgrade_since(
3650 &reader,
3651 SeqNo::minimum(),
3652 SeqNo::minimum(),
3653 &Antichain::from_elem(2),
3654 now()
3655 ),
3656 Continue(Since(Antichain::from_elem(2)))
3657 );
3658 assert_eq!(state.collections.trace.since(), &Antichain::from_elem(2));
3659 assert_eq!(
3661 state.snapshot(&Antichain::from_elem(1)),
3662 Err(SnapshotErr::AsOfHistoricalDistinctionsLost(Since(
3663 Antichain::from_elem(2)
3664 )))
3665 );
3666
3667 assert!(
3669 state
3670 .collections
3671 .compare_and_append(
3672 &hollow(5, 10, &[], 0),
3673 &writer_id,
3674 now(),
3675 LEASE_DURATION_MS,
3676 &IdempotencyToken::new(),
3677 &debug_state(),
3678 0,
3679 100,
3680 None
3681 )
3682 .is_continue()
3683 );
3684
3685 assert_eq!(
3687 state.snapshot(&Antichain::from_elem(7)),
3688 Ok(vec![hollow(0, 5, &["key1"], 1), hollow(5, 10, &[], 0)])
3689 );
3690
3691 assert_eq!(
3693 state.snapshot(&Antichain::from_elem(10)),
3694 Err(SnapshotErr::AsOfNotYetAvailable(
3695 SeqNo(0),
3696 Upper(Antichain::from_elem(10))
3697 ))
3698 );
3699
3700 assert!(
3702 state
3703 .collections
3704 .compare_and_append(
3705 &hollow(10, 15, &["key2"], 1),
3706 &writer_id,
3707 now(),
3708 LEASE_DURATION_MS,
3709 &IdempotencyToken::new(),
3710 &debug_state(),
3711 0,
3712 100,
3713 None
3714 )
3715 .is_continue()
3716 );
3717
3718 assert_eq!(
3721 state.snapshot(&Antichain::from_elem(9)),
3722 Ok(vec![hollow(0, 5, &["key1"], 1), hollow(5, 10, &[], 0)])
3723 );
3724
3725 assert_eq!(
3727 state.snapshot(&Antichain::from_elem(10)),
3728 Ok(vec![
3729 hollow(0, 5, &["key1"], 1),
3730 hollow(5, 10, &[], 0),
3731 hollow(10, 15, &["key2"], 1)
3732 ])
3733 );
3734
3735 assert_eq!(
3736 state.snapshot(&Antichain::from_elem(11)),
3737 Ok(vec![
3738 hollow(0, 5, &["key1"], 1),
3739 hollow(5, 10, &[], 0),
3740 hollow(10, 15, &["key2"], 1)
3741 ])
3742 );
3743 }
3744
3745 #[mz_ore::test]
3746 fn next_listen_batch() {
3747 let mut state = TypedState::<String, String, u64, i64>::new(
3748 DUMMY_BUILD_INFO.semver_version(),
3749 ShardId::new(),
3750 "".to_owned(),
3751 0,
3752 );
3753
3754 assert_eq!(
3757 state.next_listen_batch(&Antichain::from_elem(0)),
3758 Err(SeqNo(0))
3759 );
3760 assert_eq!(state.next_listen_batch(&Antichain::new()), Err(SeqNo(0)));
3761
3762 let writer_id = WriterId::new();
3763 let now = SYSTEM_TIME.clone();
3764
3765 assert!(
3767 state
3768 .collections
3769 .compare_and_append(
3770 &hollow(0, 5, &["key1"], 1),
3771 &writer_id,
3772 now(),
3773 LEASE_DURATION_MS,
3774 &IdempotencyToken::new(),
3775 &debug_state(),
3776 0,
3777 100,
3778 None
3779 )
3780 .is_continue()
3781 );
3782 assert!(
3783 state
3784 .collections
3785 .compare_and_append(
3786 &hollow(5, 10, &["key2"], 1),
3787 &writer_id,
3788 now(),
3789 LEASE_DURATION_MS,
3790 &IdempotencyToken::new(),
3791 &debug_state(),
3792 0,
3793 100,
3794 None
3795 )
3796 .is_continue()
3797 );
3798
3799 for t in 0..=4 {
3801 assert_eq!(
3802 state.next_listen_batch(&Antichain::from_elem(t)),
3803 Ok(hollow(0, 5, &["key1"], 1))
3804 );
3805 }
3806
3807 for t in 5..=9 {
3809 assert_eq!(
3810 state.next_listen_batch(&Antichain::from_elem(t)),
3811 Ok(hollow(5, 10, &["key2"], 1))
3812 );
3813 }
3814
3815 assert_eq!(
3817 state.next_listen_batch(&Antichain::from_elem(10)),
3818 Err(SeqNo(0))
3819 );
3820
3821 assert_eq!(state.next_listen_batch(&Antichain::new()), Err(SeqNo(0)));
3824 }
3825
3826 #[mz_ore::test]
3827 fn expire_writer() {
3828 let mut state = TypedState::<String, String, u64, i64>::new(
3829 DUMMY_BUILD_INFO.semver_version(),
3830 ShardId::new(),
3831 "".to_owned(),
3832 0,
3833 );
3834 let now = SYSTEM_TIME.clone();
3835
3836 let writer_id_one = WriterId::new();
3837
3838 let writer_id_two = WriterId::new();
3839
3840 assert!(
3842 state
3843 .collections
3844 .compare_and_append(
3845 &hollow(0, 2, &["key1"], 1),
3846 &writer_id_one,
3847 now(),
3848 LEASE_DURATION_MS,
3849 &IdempotencyToken::new(),
3850 &debug_state(),
3851 0,
3852 100,
3853 None
3854 )
3855 .is_continue()
3856 );
3857
3858 assert!(
3859 state
3860 .collections
3861 .expire_writer(&writer_id_one)
3862 .is_continue()
3863 );
3864
3865 assert!(
3867 state
3868 .collections
3869 .compare_and_append(
3870 &hollow(2, 5, &["key2"], 1),
3871 &writer_id_two,
3872 now(),
3873 LEASE_DURATION_MS,
3874 &IdempotencyToken::new(),
3875 &debug_state(),
3876 0,
3877 100,
3878 None
3879 )
3880 .is_continue()
3881 );
3882 }
3883
3884 #[mz_ore::test]
3885 fn maybe_gc_active_gc() {
3886 const GC_CONFIG: GcConfig = GcConfig {
3887 use_active_gc: true,
3888 fallback_threshold_ms: 5000,
3889 min_versions: 99,
3890 max_versions: 500,
3891 };
3892 let now_fn = SYSTEM_TIME.clone();
3893
3894 let mut state = TypedState::<String, String, u64, i64>::new(
3895 DUMMY_BUILD_INFO.semver_version(),
3896 ShardId::new(),
3897 "".to_owned(),
3898 0,
3899 );
3900
3901 let now = now_fn();
3902 assert_eq!(state.maybe_gc(true, now, GC_CONFIG), None);
3904 assert_eq!(state.maybe_gc(false, now, GC_CONFIG), None);
3905
3906 state.seqno = SeqNo(100);
3909 assert_eq!(state.seqno_since(), SeqNo(100));
3910
3911 let writer_id = WriterId::new();
3913 let _ = state.collections.compare_and_append(
3914 &hollow(1, 2, &["key1"], 1),
3915 &writer_id,
3916 now,
3917 LEASE_DURATION_MS,
3918 &IdempotencyToken::new(),
3919 &debug_state(),
3920 0,
3921 100,
3922 None,
3923 );
3924 assert_eq!(state.maybe_gc(false, now, GC_CONFIG), None);
3925
3926 assert_eq!(
3928 state.maybe_gc(true, now, GC_CONFIG),
3929 Some(GcReq {
3930 shard_id: state.shard_id,
3931 new_seqno_since: SeqNo(100)
3932 })
3933 );
3934
3935 state.collections.active_gc = Some(ActiveGc {
3937 seqno: state.seqno,
3938 start_ms: now,
3939 });
3940
3941 state.seqno = SeqNo(200);
3942 assert_eq!(state.seqno_since(), SeqNo(200));
3943
3944 assert_eq!(state.maybe_gc(true, now, GC_CONFIG), None);
3945
3946 state.seqno = SeqNo(300);
3947 assert_eq!(state.seqno_since(), SeqNo(300));
3948 let new_now = now + GC_CONFIG.fallback_threshold_ms + 1;
3950 assert_eq!(
3951 state.maybe_gc(true, new_now, GC_CONFIG),
3952 Some(GcReq {
3953 shard_id: state.shard_id,
3954 new_seqno_since: SeqNo(300)
3955 })
3956 );
3957
3958 state.seqno = SeqNo(301);
3962 assert_eq!(state.seqno_since(), SeqNo(301));
3963 assert_eq!(
3964 state.maybe_gc(true, new_now, GC_CONFIG),
3965 Some(GcReq {
3966 shard_id: state.shard_id,
3967 new_seqno_since: SeqNo(301)
3968 })
3969 );
3970
3971 state.collections.active_gc = None;
3972
3973 state.seqno = SeqNo(400);
3976 assert_eq!(state.seqno_since(), SeqNo(400));
3977
3978 let now = now_fn();
3979
3980 let _ = state.collections.expire_writer(&writer_id);
3982 assert_eq!(
3983 state.maybe_gc(false, now, GC_CONFIG),
3984 Some(GcReq {
3985 shard_id: state.shard_id,
3986 new_seqno_since: SeqNo(400)
3987 })
3988 );
3989
3990 let previous_seqno = state.seqno;
3992 state.seqno = SeqNo(10_000);
3993 assert_eq!(state.seqno_since(), SeqNo(10_000));
3994
3995 let now = now_fn();
3996 assert_eq!(
3997 state.maybe_gc(true, now, GC_CONFIG),
3998 Some(GcReq {
3999 shard_id: state.shard_id,
4000 new_seqno_since: SeqNo(previous_seqno.0 + u64::cast_from(GC_CONFIG.max_versions))
4001 })
4002 );
4003 }
4004
4005 #[mz_ore::test]
4006 fn maybe_gc_classic() {
4007 const GC_CONFIG: GcConfig = GcConfig {
4008 use_active_gc: false,
4009 fallback_threshold_ms: 5000,
4010 min_versions: 16,
4011 max_versions: 128,
4012 };
4013 const NOW_MS: u64 = 0;
4014
4015 let mut state = TypedState::<String, String, u64, i64>::new(
4016 DUMMY_BUILD_INFO.semver_version(),
4017 ShardId::new(),
4018 "".to_owned(),
4019 0,
4020 );
4021
4022 assert_eq!(state.maybe_gc(true, NOW_MS, GC_CONFIG), None);
4024 assert_eq!(state.maybe_gc(false, NOW_MS, GC_CONFIG), None);
4025
4026 state.seqno = SeqNo(100);
4029 assert_eq!(state.seqno_since(), SeqNo(100));
4030
4031 let writer_id = WriterId::new();
4033 let now = SYSTEM_TIME.clone();
4034 let _ = state.collections.compare_and_append(
4035 &hollow(1, 2, &["key1"], 1),
4036 &writer_id,
4037 now(),
4038 LEASE_DURATION_MS,
4039 &IdempotencyToken::new(),
4040 &debug_state(),
4041 0,
4042 100,
4043 None,
4044 );
4045 assert_eq!(state.maybe_gc(false, NOW_MS, GC_CONFIG), None);
4046
4047 assert_eq!(
4049 state.maybe_gc(true, NOW_MS, GC_CONFIG),
4050 Some(GcReq {
4051 shard_id: state.shard_id,
4052 new_seqno_since: SeqNo(100)
4053 })
4054 );
4055
4056 state.seqno = SeqNo(200);
4059 assert_eq!(state.seqno_since(), SeqNo(200));
4060
4061 let _ = state.collections.expire_writer(&writer_id);
4063 assert_eq!(
4064 state.maybe_gc(false, NOW_MS, GC_CONFIG),
4065 Some(GcReq {
4066 shard_id: state.shard_id,
4067 new_seqno_since: SeqNo(200)
4068 })
4069 );
4070 }
4071
4072 #[mz_ore::test]
4073 fn need_rollup_active_rollup() {
4074 const ROLLUP_THRESHOLD: usize = 3;
4075 const ROLLUP_USE_ACTIVE_ROLLUP: bool = true;
4076 const ROLLUP_FALLBACK_THRESHOLD_MS: u64 = 5000;
4077 let now = SYSTEM_TIME.clone();
4078
4079 mz_ore::test::init_logging();
4080 let mut state = TypedState::<String, String, u64, i64>::new(
4081 DUMMY_BUILD_INFO.semver_version(),
4082 ShardId::new(),
4083 "".to_owned(),
4084 0,
4085 );
4086
4087 let rollup_seqno = SeqNo(5);
4088 let rollup = HollowRollup {
4089 key: PartialRollupKey::new(rollup_seqno, &RollupId::new()),
4090 encoded_size_bytes: None,
4091 };
4092
4093 assert!(
4094 state
4095 .collections
4096 .add_rollup((rollup_seqno, &rollup))
4097 .is_continue()
4098 );
4099
4100 state.seqno = SeqNo(5);
4102 assert_none!(state.need_rollup(
4103 ROLLUP_THRESHOLD,
4104 ROLLUP_USE_ACTIVE_ROLLUP,
4105 ROLLUP_FALLBACK_THRESHOLD_MS,
4106 now()
4107 ));
4108
4109 state.seqno = SeqNo(6);
4111 assert_none!(state.need_rollup(
4112 ROLLUP_THRESHOLD,
4113 ROLLUP_USE_ACTIVE_ROLLUP,
4114 ROLLUP_FALLBACK_THRESHOLD_MS,
4115 now()
4116 ));
4117 state.seqno = SeqNo(7);
4118 assert_none!(state.need_rollup(
4119 ROLLUP_THRESHOLD,
4120 ROLLUP_USE_ACTIVE_ROLLUP,
4121 ROLLUP_FALLBACK_THRESHOLD_MS,
4122 now()
4123 ));
4124 state.seqno = SeqNo(8);
4125 assert_none!(state.need_rollup(
4126 ROLLUP_THRESHOLD,
4127 ROLLUP_USE_ACTIVE_ROLLUP,
4128 ROLLUP_FALLBACK_THRESHOLD_MS,
4129 now()
4130 ));
4131
4132 let mut current_time = now();
4133 state.seqno = SeqNo(9);
4135 assert_eq!(
4136 state
4137 .need_rollup(
4138 ROLLUP_THRESHOLD,
4139 ROLLUP_USE_ACTIVE_ROLLUP,
4140 ROLLUP_FALLBACK_THRESHOLD_MS,
4141 current_time
4142 )
4143 .expect("rollup"),
4144 SeqNo(9)
4145 );
4146
4147 state.collections.active_rollup = Some(ActiveRollup {
4148 seqno: SeqNo(9),
4149 start_ms: current_time,
4150 });
4151
4152 assert_none!(state.need_rollup(
4154 ROLLUP_THRESHOLD,
4155 ROLLUP_USE_ACTIVE_ROLLUP,
4156 ROLLUP_FALLBACK_THRESHOLD_MS,
4157 current_time
4158 ));
4159
4160 state.seqno = SeqNo(10);
4161 assert_none!(state.need_rollup(
4164 ROLLUP_THRESHOLD,
4165 ROLLUP_USE_ACTIVE_ROLLUP,
4166 ROLLUP_FALLBACK_THRESHOLD_MS,
4167 current_time
4168 ));
4169
4170 current_time += u64::cast_from(ROLLUP_FALLBACK_THRESHOLD_MS) + 1;
4172 assert_eq!(
4173 state
4174 .need_rollup(
4175 ROLLUP_THRESHOLD,
4176 ROLLUP_USE_ACTIVE_ROLLUP,
4177 ROLLUP_FALLBACK_THRESHOLD_MS,
4178 current_time
4179 )
4180 .expect("rollup"),
4181 SeqNo(10)
4182 );
4183
4184 state.seqno = SeqNo(9);
4185 state.collections.active_rollup = None;
4187 let rollup_seqno = SeqNo(9);
4188 let rollup = HollowRollup {
4189 key: PartialRollupKey::new(rollup_seqno, &RollupId::new()),
4190 encoded_size_bytes: None,
4191 };
4192 assert!(
4193 state
4194 .collections
4195 .add_rollup((rollup_seqno, &rollup))
4196 .is_continue()
4197 );
4198
4199 state.seqno = SeqNo(11);
4200 assert_none!(state.need_rollup(
4202 ROLLUP_THRESHOLD,
4203 ROLLUP_USE_ACTIVE_ROLLUP,
4204 ROLLUP_FALLBACK_THRESHOLD_MS,
4205 current_time
4206 ));
4207 state.seqno = SeqNo(13);
4209 assert_eq!(
4210 state
4211 .need_rollup(
4212 ROLLUP_THRESHOLD,
4213 ROLLUP_USE_ACTIVE_ROLLUP,
4214 ROLLUP_FALLBACK_THRESHOLD_MS,
4215 current_time
4216 )
4217 .expect("rollup"),
4218 SeqNo(13)
4219 );
4220 }
4221
4222 #[mz_ore::test]
4223 fn need_rollup_classic() {
4224 const ROLLUP_THRESHOLD: usize = 3;
4225 const ROLLUP_USE_ACTIVE_ROLLUP: bool = false;
4226 const ROLLUP_FALLBACK_THRESHOLD_MS: u64 = 0;
4227 const NOW: u64 = 0;
4228
4229 mz_ore::test::init_logging();
4230 let mut state = TypedState::<String, String, u64, i64>::new(
4231 DUMMY_BUILD_INFO.semver_version(),
4232 ShardId::new(),
4233 "".to_owned(),
4234 0,
4235 );
4236
4237 let rollup_seqno = SeqNo(5);
4238 let rollup = HollowRollup {
4239 key: PartialRollupKey::new(rollup_seqno, &RollupId::new()),
4240 encoded_size_bytes: None,
4241 };
4242
4243 assert!(
4244 state
4245 .collections
4246 .add_rollup((rollup_seqno, &rollup))
4247 .is_continue()
4248 );
4249
4250 state.seqno = SeqNo(5);
4252 assert_none!(state.need_rollup(
4253 ROLLUP_THRESHOLD,
4254 ROLLUP_USE_ACTIVE_ROLLUP,
4255 ROLLUP_FALLBACK_THRESHOLD_MS,
4256 NOW
4257 ));
4258
4259 state.seqno = SeqNo(6);
4261 assert_none!(state.need_rollup(
4262 ROLLUP_THRESHOLD,
4263 ROLLUP_USE_ACTIVE_ROLLUP,
4264 ROLLUP_FALLBACK_THRESHOLD_MS,
4265 NOW
4266 ));
4267 state.seqno = SeqNo(7);
4268 assert_none!(state.need_rollup(
4269 ROLLUP_THRESHOLD,
4270 ROLLUP_USE_ACTIVE_ROLLUP,
4271 ROLLUP_FALLBACK_THRESHOLD_MS,
4272 NOW
4273 ));
4274
4275 state.seqno = SeqNo(8);
4277 assert_eq!(
4278 state
4279 .need_rollup(
4280 ROLLUP_THRESHOLD,
4281 ROLLUP_USE_ACTIVE_ROLLUP,
4282 ROLLUP_FALLBACK_THRESHOLD_MS,
4283 NOW
4284 )
4285 .expect("rollup"),
4286 SeqNo(8)
4287 );
4288
4289 state.seqno = SeqNo(9);
4291 assert_none!(state.need_rollup(
4292 ROLLUP_THRESHOLD,
4293 ROLLUP_USE_ACTIVE_ROLLUP,
4294 ROLLUP_FALLBACK_THRESHOLD_MS,
4295 NOW
4296 ));
4297
4298 state.seqno = SeqNo(11);
4300 assert_eq!(
4301 state
4302 .need_rollup(
4303 ROLLUP_THRESHOLD,
4304 ROLLUP_USE_ACTIVE_ROLLUP,
4305 ROLLUP_FALLBACK_THRESHOLD_MS,
4306 NOW
4307 )
4308 .expect("rollup"),
4309 SeqNo(11)
4310 );
4311
4312 let rollup_seqno = SeqNo(6);
4314 let rollup = HollowRollup {
4315 key: PartialRollupKey::new(rollup_seqno, &RollupId::new()),
4316 encoded_size_bytes: None,
4317 };
4318 assert!(
4319 state
4320 .collections
4321 .add_rollup((rollup_seqno, &rollup))
4322 .is_continue()
4323 );
4324
4325 state.seqno = SeqNo(8);
4326 assert_none!(state.need_rollup(
4327 ROLLUP_THRESHOLD,
4328 ROLLUP_USE_ACTIVE_ROLLUP,
4329 ROLLUP_FALLBACK_THRESHOLD_MS,
4330 NOW
4331 ));
4332 state.seqno = SeqNo(9);
4333 assert_eq!(
4334 state
4335 .need_rollup(
4336 ROLLUP_THRESHOLD,
4337 ROLLUP_USE_ACTIVE_ROLLUP,
4338 ROLLUP_FALLBACK_THRESHOLD_MS,
4339 NOW
4340 )
4341 .expect("rollup"),
4342 SeqNo(9)
4343 );
4344
4345 let fallback_seqno = SeqNo(
4347 rollup_seqno.0
4348 * u64::cast_from(PersistConfig::DEFAULT_FALLBACK_ROLLUP_THRESHOLD_MULTIPLIER),
4349 );
4350 state.seqno = fallback_seqno;
4351 assert_eq!(
4352 state
4353 .need_rollup(
4354 ROLLUP_THRESHOLD,
4355 ROLLUP_USE_ACTIVE_ROLLUP,
4356 ROLLUP_FALLBACK_THRESHOLD_MS,
4357 NOW
4358 )
4359 .expect("rollup"),
4360 fallback_seqno
4361 );
4362 state.seqno = fallback_seqno.next();
4363 assert_eq!(
4364 state
4365 .need_rollup(
4366 ROLLUP_THRESHOLD,
4367 ROLLUP_USE_ACTIVE_ROLLUP,
4368 ROLLUP_FALLBACK_THRESHOLD_MS,
4369 NOW
4370 )
4371 .expect("rollup"),
4372 fallback_seqno.next()
4373 );
4374 }
4375
4376 #[mz_ore::test]
4377 fn idempotency_token_sentinel() {
4378 assert_eq!(
4379 IdempotencyToken::SENTINEL.to_string(),
4380 "i11111111-1111-1111-1111-111111111111"
4381 );
4382 }
4383
4384 #[mz_ore::test]
4393 #[cfg_attr(miri, ignore)] fn state_inspect_serde_json() {
4395 const STATE_SERDE_JSON: &str = include_str!("state_serde.json");
4396 let mut runner = proptest::test_runner::TestRunner::deterministic();
4397 let tree = any_state::<u64>(6..8).new_tree(&mut runner).unwrap();
4398 let json = serde_json::to_string_pretty(&tree.current()).unwrap();
4399 assert_eq!(
4400 json.trim(),
4401 STATE_SERDE_JSON.trim(),
4402 "\n\nNEW GOLDEN\n{}\n",
4403 json
4404 );
4405 }
4406
4407 #[mz_persist_proc::test(tokio::test)]
4408 #[cfg_attr(miri, ignore)] async fn sneaky_downgrades(dyncfgs: ConfigUpdates) {
4410 let mut clients = new_test_client_cache(&dyncfgs);
4411 let shard_id = ShardId::new();
4412
4413 async fn open_and_write(
4414 clients: &mut PersistClientCache,
4415 version: semver::Version,
4416 shard_id: ShardId,
4417 ) -> Result<(), tokio::task::JoinError> {
4418 clients.cfg.build_version = version.clone();
4419 clients.clear_state_cache();
4420 let client = clients.open(PersistLocation::new_in_mem()).await.unwrap();
4421 mz_ore::task::spawn(|| version.to_string(), async move {
4423 let () = client
4424 .upgrade_version::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
4425 .await
4426 .expect("valid usage");
4427 let (mut write, _) = client.expect_open::<String, (), u64, i64>(shard_id).await;
4428 let current = *write.upper().as_option().unwrap();
4429 write
4431 .expect_compare_and_append_batch(&mut [], current, current + 1)
4432 .await;
4433 })
4434 .into_tokio_handle()
4435 .await
4436 }
4437
4438 let res = open_and_write(&mut clients, Version::new(0, 10, 0), shard_id).await;
4440 assert_ok!(res);
4441
4442 let res = open_and_write(&mut clients, Version::new(0, 11, 0), shard_id).await;
4444 assert_ok!(res);
4445
4446 let res = open_and_write(&mut clients, Version::new(0, 10, 0), shard_id).await;
4448 assert!(res.unwrap_err().is_panic());
4449
4450 let res = open_and_write(&mut clients, Version::new(0, 9, 0), shard_id).await;
4452 assert!(res.unwrap_err().is_panic());
4453 }
4454
4455 #[mz_ore::test]
4456 fn runid_roundtrip() {
4457 proptest!(|(runid: RunId)| {
4458 let runid_str = runid.to_string();
4459 let parsed = RunId::from_str(&runid_str);
4460 prop_assert_eq!(parsed, Ok(runid));
4461 });
4462 }
4463
4464 #[mz_ore::test]
4480 fn add_rollup_idempotent_across_gc_removal() {
4481 let mut state = TypedState::<String, String, u64, i64>::new(
4482 DUMMY_BUILD_INFO.semver_version(),
4483 ShardId::new(),
4484 "".to_owned(),
4485 0,
4486 );
4487
4488 let older_seqno = SeqNo(10);
4489 let older = HollowRollup {
4490 key: PartialRollupKey::new(older_seqno, &RollupId::new()),
4491 encoded_size_bytes: None,
4492 };
4493 let newer_seqno = SeqNo(20);
4494 let newer = HollowRollup {
4495 key: PartialRollupKey::new(newer_seqno, &RollupId::new()),
4496 encoded_size_bytes: None,
4497 };
4498 let add_older = |state: &mut StateCollections<u64>| state.add_rollup((older_seqno, &older));
4499
4500 assert_eq!(add_older(&mut state.collections), Continue(true));
4502 assert_eq!(add_older(&mut state.collections), Continue(true));
4505 assert_eq!(state.collections.rollups.len(), 1);
4506
4507 assert_eq!(
4511 state.collections.add_rollup((newer_seqno, &newer)),
4512 Continue(true),
4513 );
4514
4515 let _ = state
4519 .collections
4520 .remove_rollups(&[(older_seqno, older.key.clone())]);
4521 assert!(!state.collections.rollups.contains_key(&older_seqno));
4522 assert!(state.collections.rollups.contains_key(&newer_seqno));
4523
4524 assert_eq!(add_older(&mut state.collections), Continue(false));
4529 assert!(!state.collections.rollups.contains_key(&older_seqno));
4530 assert_eq!(state.collections.rollups.len(), 1);
4531 }
4532}