1#[cfg(test)]
11mod tests;
12
13use std::cmp::max;
14use std::collections::{BTreeMap, VecDeque};
15use std::fmt::Debug;
16use std::str::FromStr;
17use std::sync::{Arc, LazyLock};
18use std::time::{Duration, Instant};
19
20use async_trait::async_trait;
21use differential_dataflow::lattice::Lattice;
22use futures::{FutureExt, StreamExt};
23use itertools::Itertools;
24use mz_audit_log::VersionedEvent;
25use mz_ore::cast::CastFrom;
26use mz_ore::metrics::MetricsFutureExt;
27use mz_ore::now::EpochMillis;
28use mz_ore::{
29 soft_assert_eq_no_log, soft_assert_eq_or_log, soft_assert_ne_or_log, soft_assert_no_log,
30 soft_assert_or_log, soft_panic_or_log,
31};
32use mz_persist_client::cfg::USE_CRITICAL_SINCE_CATALOG;
33use mz_persist_client::cli::admin::{CATALOG_FORCE_COMPACTION_FUEL, CATALOG_FORCE_COMPACTION_WAIT};
34use mz_persist_client::critical::{CriticalReaderId, Opaque, SinceHandle};
35use mz_persist_client::error::UpperMismatch;
36use mz_persist_client::read::{Listen, ListenEvent, ReadHandle};
37use mz_persist_client::write::WriteHandle;
38use mz_persist_client::{Diagnostics, PersistClient, ShardId};
39use mz_persist_types::codec_impls::UnitSchema;
40use mz_proto::TryFromProtoError;
41use mz_repr::Diff;
42use mz_storage_client::controller::PersistEpoch;
43use mz_storage_types::StorageDiff;
44use mz_storage_types::sources::SourceData;
45use sha2::Digest;
46use timely::progress::{Antichain, Timestamp as TimelyTimestamp};
47use tracing::{debug, info, warn};
48use uuid::Uuid;
49
50use crate::durable::debug::{Collection, CollectionType, DebugCatalogState, Trace};
51use crate::durable::error::FenceError;
52use crate::durable::initialize::{
53 ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT, SYSTEM_CONFIG_SYNCED_KEY, USER_VERSION_KEY,
54 WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL, WITH_0DT_DEPLOYMENT_MAX_WAIT,
55};
56use crate::durable::metrics::Metrics;
57use crate::durable::objects::serialization::RustType;
58use crate::durable::objects::state_update::{
59 IntoStateUpdateKindJson, StateUpdate, StateUpdateKind, StateUpdateKindJson,
60 TryIntoStateUpdateKind,
61};
62use crate::durable::objects::{AuditLogKey, FenceToken, Snapshot};
63use crate::durable::transaction::TransactionBatch;
64use crate::durable::upgrade::upgrade;
65use crate::durable::{
66 BootstrapArgs, CATALOG_CONTENT_VERSION_KEY, CatalogError, DryRunTransaction,
67 DurableCatalogError, DurableCatalogState, Epoch, OpenableDurableCatalogState,
68 ReadOnlyDurableCatalogState, Transaction, initialize, persist_desc,
69};
70use crate::memory;
71
72pub(crate) type Timestamp = mz_repr::Timestamp;
74
75const MIN_EPOCH: Epoch = Epoch::new(1).expect("1 is non-zero");
77
78const CATALOG_SHARD_NAME: &str = "catalog";
80
81static CATALOG_CRITICAL_SINCE: LazyLock<CriticalReaderId> = LazyLock::new(|| {
84 "c55555555-6666-7777-8888-999999999999"
85 .parse()
86 .expect("valid CriticalReaderId")
87});
88
89const CATALOG_SEED: usize = 1;
91const _UPGRADE_SEED: usize = 2;
93pub const _BUILTIN_MIGRATION_SEED: usize = 3;
95pub const _EXPRESSION_CACHE_SEED: usize = 4;
97
98#[derive(Debug, Copy, Clone, Eq, PartialEq)]
100pub(crate) enum Mode {
101 Readonly,
103 Savepoint,
105 Writable,
107}
108
109#[derive(Debug)]
111pub(crate) enum FenceableToken {
112 Initializing {
115 durable_token: Option<FenceToken>,
117 current_deploy_generation: Option<u64>,
119 },
120 Unfenced { current_token: FenceToken },
122 Fenced {
124 current_token: FenceToken,
125 fence_token: FenceToken,
126 },
127}
128
129impl FenceableToken {
130 fn new(current_deploy_generation: Option<u64>) -> Self {
132 Self::Initializing {
133 durable_token: None,
134 current_deploy_generation,
135 }
136 }
137
138 fn validate(&self) -> Result<Option<FenceToken>, FenceError> {
140 match self {
141 FenceableToken::Initializing { durable_token, .. } => Ok(durable_token.clone()),
142 FenceableToken::Unfenced { current_token, .. } => Ok(Some(current_token.clone())),
143 FenceableToken::Fenced {
144 current_token,
145 fence_token,
146 } => {
147 assert!(
148 fence_token > current_token,
149 "must be fenced by higher token; current={current_token:?}, fence={fence_token:?}"
150 );
151 if fence_token.deploy_generation > current_token.deploy_generation {
152 Err(FenceError::DeployGeneration {
153 current_generation: current_token.deploy_generation,
154 fence_generation: fence_token.deploy_generation,
155 })
156 } else {
157 assert!(
158 fence_token.epoch > current_token.epoch,
159 "must be fenced by higher token; current={current_token:?}, fence={fence_token:?}"
160 );
161 Err(FenceError::Epoch {
162 current_epoch: current_token.epoch,
163 fence_epoch: fence_token.epoch,
164 })
165 }
166 }
167 }
168 }
169
170 fn token(&self) -> Option<FenceToken> {
172 match self {
173 FenceableToken::Initializing { durable_token, .. } => durable_token.clone(),
174 FenceableToken::Unfenced { current_token, .. } => Some(current_token.clone()),
175 FenceableToken::Fenced { current_token, .. } => Some(current_token.clone()),
176 }
177 }
178
179 fn maybe_fence(&mut self, token: FenceToken) -> Result<(), FenceError> {
181 match self {
182 FenceableToken::Initializing {
183 durable_token,
184 current_deploy_generation,
185 ..
186 } => {
187 match durable_token {
188 Some(durable_token) => {
189 *durable_token = max(durable_token.clone(), token.clone());
190 }
191 None => {
192 *durable_token = Some(token.clone());
193 }
194 }
195 if let Some(current_deploy_generation) = current_deploy_generation {
196 if *current_deploy_generation < token.deploy_generation {
197 *self = FenceableToken::Fenced {
198 current_token: FenceToken {
199 deploy_generation: *current_deploy_generation,
200 epoch: token.epoch,
201 },
202 fence_token: token,
203 };
204 self.validate()?;
205 }
206 }
207 }
208 FenceableToken::Unfenced { current_token } => {
209 if *current_token < token {
210 *self = FenceableToken::Fenced {
211 current_token: current_token.clone(),
212 fence_token: token,
213 };
214 self.validate()?;
215 }
216 }
217 FenceableToken::Fenced { .. } => {
218 self.validate()?;
219 }
220 }
221
222 Ok(())
223 }
224
225 fn generate_unfenced_token(
229 &self,
230 mode: Mode,
231 ) -> Result<Option<(Vec<(StateUpdateKind, Diff)>, FenceableToken)>, DurableCatalogError> {
232 let (durable_token, current_deploy_generation) = match self {
233 FenceableToken::Initializing {
234 durable_token,
235 current_deploy_generation,
236 } => (durable_token.clone(), current_deploy_generation.clone()),
237 FenceableToken::Unfenced { .. } | FenceableToken::Fenced { .. } => return Ok(None),
238 };
239
240 let mut fence_updates = Vec::with_capacity(2);
241
242 if let Some(durable_token) = &durable_token {
243 fence_updates.push((
244 StateUpdateKind::FenceToken(durable_token.clone()),
245 Diff::MINUS_ONE,
246 ));
247 }
248
249 let current_deploy_generation = current_deploy_generation
250 .or_else(|| durable_token.as_ref().map(|token| token.deploy_generation))
251 .ok_or(DurableCatalogError::Uninitialized)?;
253 let mut current_epoch = durable_token
254 .map(|token| token.epoch)
255 .unwrap_or(MIN_EPOCH)
256 .get();
257 if matches!(mode, Mode::Writable) {
259 current_epoch = current_epoch + 1;
260 }
261 let current_epoch = Epoch::new(current_epoch).expect("known to be non-zero");
262 let current_token = FenceToken {
263 deploy_generation: current_deploy_generation,
264 epoch: current_epoch,
265 };
266
267 fence_updates.push((
268 StateUpdateKind::FenceToken(current_token.clone()),
269 Diff::ONE,
270 ));
271
272 let current_fenceable_token = FenceableToken::Unfenced { current_token };
273
274 Ok(Some((fence_updates, current_fenceable_token)))
275 }
276}
277
278#[derive(Debug, thiserror::Error)]
280pub(crate) enum CompareAndAppendError {
281 #[error(transparent)]
282 Fence(#[from] FenceError),
283 #[error(
288 "expected catalog upper {expected_upper:?} did not match actual catalog upper {actual_upper:?}"
289 )]
290 UpperMismatch {
291 expected_upper: Timestamp,
292 actual_upper: Timestamp,
293 },
294}
295
296impl CompareAndAppendError {
297 pub(crate) fn unwrap_fence_error(self) -> FenceError {
298 match self {
299 CompareAndAppendError::Fence(e) => e,
300 e @ CompareAndAppendError::UpperMismatch { .. } => {
301 panic!("unexpected upper mismatch: {e:?}")
302 }
303 }
304 }
305}
306
307impl From<UpperMismatch<Timestamp>> for CompareAndAppendError {
308 fn from(upper_mismatch: UpperMismatch<Timestamp>) -> Self {
309 Self::UpperMismatch {
310 expected_upper: antichain_to_timestamp(upper_mismatch.expected),
311 actual_upper: antichain_to_timestamp(upper_mismatch.current),
312 }
313 }
314}
315
316pub(crate) trait ApplyUpdate<T: IntoStateUpdateKindJson> {
317 fn apply_update(
321 &mut self,
322 update: StateUpdate<T>,
323 current_fence_token: &mut FenceableToken,
324 metrics: &Arc<Metrics>,
325 ) -> Result<Option<StateUpdate<T>>, FenceError>;
326}
327
328#[derive(Debug)]
342pub(crate) struct PersistHandle<T: TryIntoStateUpdateKind, U: ApplyUpdate<T>> {
343 pub(crate) mode: Mode,
345 since_handle: SinceHandle<SourceData, (), Timestamp, StorageDiff>,
347 write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff>,
349 listen: Listen<SourceData, (), Timestamp, StorageDiff>,
351 persist_client: PersistClient,
353 shard_id: ShardId,
355 pub(crate) snapshot: Vec<(T, Timestamp, Diff)>,
359 update_applier: U,
361 pub(crate) upper: Timestamp,
363 fenceable_token: FenceableToken,
365 catalog_content_version: semver::Version,
367 bootstrap_complete: bool,
369 metrics: Arc<Metrics>,
371 size_at_last_consolidation: Option<usize>,
375 updates_applied: u64,
380}
381
382impl<T: TryIntoStateUpdateKind, U: ApplyUpdate<T>> PersistHandle<T, U> {
383 #[mz_ore::instrument]
385 async fn current_upper(&mut self) -> Timestamp {
386 match self.mode {
387 Mode::Writable | Mode::Readonly => {
388 let upper = self.write_handle.fetch_recent_upper().await;
389 antichain_to_timestamp(upper.clone())
390 }
391 Mode::Savepoint => self.upper,
392 }
393 }
394
395 #[mz_ore::instrument]
399 pub(crate) async fn compare_and_append<S: IntoStateUpdateKindJson>(
400 &mut self,
401 updates: Vec<(S, Diff)>,
402 commit_ts: Timestamp,
403 ) -> Result<Timestamp, CompareAndAppendError> {
404 let updates = updates.into_iter().map(|(kind, diff)| {
405 let kind: StateUpdateKindJson = kind.into();
406 (
407 (Into::<SourceData>::into(kind), ()),
408 commit_ts,
409 diff.into_inner(),
410 )
411 });
412 let next_upper = commit_ts.step_forward();
413 self.compare_and_append_inner(updates, next_upper).await?;
415
416 self.sync(next_upper).await?;
417 Ok(next_upper)
418 }
419
420 async fn compare_and_append_inner(
430 &mut self,
431 updates: impl IntoIterator<Item = ((SourceData, ()), Timestamp, StorageDiff)>,
432 next_upper: Timestamp,
433 ) -> Result<(), CompareAndAppendError> {
434 assert_eq!(self.mode, Mode::Writable);
435 assert!(
436 next_upper > self.upper,
437 "next_upper ({next_upper}) not greater than current upper ({})",
438 self.upper,
439 );
440
441 let res = self
442 .write_handle
443 .compare_and_append(
444 updates,
445 Antichain::from_elem(self.upper),
446 Antichain::from_elem(next_upper),
447 )
448 .await
449 .expect("invalid usage");
450
451 if let Err(e @ UpperMismatch { .. }) = res {
452 self.sync_to_current_upper().await?;
455 return Err(e.into());
456 }
457
458 let downgrade_to = Antichain::from_elem(next_upper.saturating_sub(1));
460
461 let opaque = self.since_handle.opaque().clone();
466 let downgrade = self
467 .since_handle
468 .maybe_compare_and_downgrade_since(&opaque, (&opaque, &downgrade_to))
469 .await;
470 if let Some(Err(e)) = downgrade {
471 soft_panic_or_log!("found opaque value {e:?}, but expected {opaque:?}");
472 }
473
474 Ok(())
475 }
476
477 fn classify_upper_mismatch(
482 &self,
483 updates_applied_before: u64,
484 actual_upper: Timestamp,
485 ) -> Result<(), DurableCatalogError> {
486 if self.updates_applied != updates_applied_before {
487 Err(DurableCatalogError::CatalogOutOfSync {
488 update_count: usize::cast_from(self.updates_applied - updates_applied_before),
489 upper: actual_upper,
490 })
491 } else {
492 Ok(())
493 }
494 }
495
496 #[mz_ore::instrument]
499 async fn snapshot_unconsolidated(&mut self) -> Vec<StateUpdate<StateUpdateKind>> {
500 let current_upper = self.current_upper().await;
501
502 let mut snapshot = Vec::new();
503 let mut read_handle = self.read_handle().await;
504 let as_of = as_of(&read_handle, current_upper);
505 let mut stream = Box::pin(
506 read_handle
508 .snapshot_and_stream(Antichain::from_elem(as_of))
509 .await
510 .expect("we have advanced the restart_as_of by the since"),
511 );
512 while let Some(update) = stream.next().await {
513 snapshot.push(update)
514 }
515 read_handle.expire().await;
516 snapshot
517 .into_iter()
518 .map(Into::<StateUpdate<StateUpdateKindJson>>::into)
519 .map(|state_update| state_update.try_into().expect("kind decoding error"))
520 .collect()
521 }
522
523 #[mz_ore::instrument]
527 pub(crate) async fn sync_to_current_upper(&mut self) -> Result<(), FenceError> {
528 let upper = self.current_upper().await;
529 self.sync(upper).await
530 }
531
532 #[mz_ore::instrument(level = "debug")]
536 pub(crate) async fn sync(&mut self, target_upper: Timestamp) -> Result<(), FenceError> {
537 self.metrics.syncs.inc();
538 let histogram = self.metrics.sync_latency_seconds.clone();
539 self.sync_inner(target_upper)
540 .wall_time()
541 .observe(histogram)
542 .await
543 }
544
545 #[mz_ore::instrument(level = "debug")]
546 async fn sync_inner(&mut self, target_upper: Timestamp) -> Result<(), FenceError> {
547 self.fenceable_token.validate()?;
548
549 if self.mode == Mode::Savepoint {
552 self.upper = max(self.upper, target_upper);
553 return Ok(());
554 }
555
556 let mut updates: BTreeMap<_, Vec<_>> = BTreeMap::new();
557 let updates_applied_before = self.updates_applied;
558
559 self.size_at_last_consolidation = None;
562
563 while self.upper < target_upper {
564 let listen_events = self.listen.fetch_next().await;
565 for listen_event in listen_events {
566 match listen_event {
567 ListenEvent::Progress(upper) => {
568 debug!("synced up to {upper:?}");
569 self.upper = antichain_to_timestamp(upper);
570 while let Some((ts, updates)) = updates.pop_first() {
575 assert!(ts < self.upper, "expected {} < {}", ts, self.upper);
576 let updates = updates.into_iter().map(
577 |update: StateUpdate<StateUpdateKindJson>| {
578 let kind =
579 T::try_from(update.kind).expect("kind decoding error");
580 StateUpdate {
581 kind,
582 ts: update.ts,
583 diff: update.diff,
584 }
585 },
586 );
587 self.apply_updates(updates)?;
588 self.maybe_consolidate();
589 }
590 }
591 ListenEvent::Updates(batch_updates) => {
592 for update in batch_updates {
593 let update: StateUpdate<StateUpdateKindJson> = update.into();
594 updates.entry(update.ts).or_default().push(update);
595 }
596 }
597 }
598 }
599 }
600 assert_eq!(updates, BTreeMap::new(), "all updates should be applied");
601 if self.updates_applied != updates_applied_before {
603 self.consolidate();
604 }
605 Ok(())
606 }
607
608 pub(crate) fn apply_updates_and_consolidate(
615 &mut self,
616 updates: impl IntoIterator<Item = StateUpdate<T>>,
617 ) -> Result<(), FenceError> {
618 self.apply_updates(updates)?;
619 self.consolidate();
620 Ok(())
621 }
622
623 #[mz_ore::instrument(level = "debug")]
631 fn apply_updates(
632 &mut self,
633 updates: impl IntoIterator<Item = StateUpdate<T>>,
634 ) -> Result<(), FenceError> {
635 let mut updates: Vec<_> = updates
636 .into_iter()
637 .map(|StateUpdate { kind, ts, diff }| (kind, ts, diff))
638 .collect();
639
640 differential_dataflow::consolidation::consolidate_updates(&mut updates);
644
645 updates.sort_by(|(_, ts1, diff1), (_, ts2, diff2)| ts1.cmp(ts2).then(diff1.cmp(diff2)));
648
649 let mut errors = Vec::new();
650
651 for (kind, ts, diff) in updates {
652 if diff != Diff::ONE && diff != Diff::MINUS_ONE {
653 panic!("invalid update in consolidated trace: ({kind:?}, {ts:?}, {diff:?})");
654 }
655 self.updates_applied += 1;
656
657 match self.update_applier.apply_update(
658 StateUpdate { kind, ts, diff },
659 &mut self.fenceable_token,
660 &self.metrics,
661 ) {
662 Ok(Some(StateUpdate { kind, ts, diff })) => self.snapshot.push((kind, ts, diff)),
663 Ok(None) => {}
664 Err(err) => errors.push(err),
667 }
668 }
669
670 let len = i64::try_from(self.snapshot.len()).unwrap_or(i64::MAX);
672 if len > self.metrics.snapshot_max_entries.get() {
673 self.metrics.snapshot_max_entries.set(len);
674 }
675
676 errors.sort();
677 if let Some(err) = errors.into_iter().next() {
678 return Err(err);
679 }
680
681 Ok(())
682 }
683
684 fn maybe_consolidate(&mut self) {
689 let threshold = *self
690 .size_at_last_consolidation
691 .get_or_insert_with(|| max(self.snapshot.len(), 8));
694 if self.snapshot.len() >= threshold * 2 {
695 self.consolidate();
696 self.size_at_last_consolidation = Some(self.snapshot.len());
697 }
698 }
699
700 #[mz_ore::instrument]
701 pub(crate) fn consolidate(&mut self) {
702 self.metrics.snapshot_consolidations.inc();
703 soft_assert_no_log!(
704 self.snapshot
705 .windows(2)
706 .all(|updates| updates[0].1 <= updates[1].1),
707 "snapshot should be sorted by timestamp, {:#?}",
708 self.snapshot
709 );
710
711 let new_ts = self
712 .snapshot
713 .last()
714 .map(|(_, ts, _)| *ts)
715 .unwrap_or_else(Timestamp::minimum);
716 for (_, ts, _) in &mut self.snapshot {
717 *ts = new_ts;
718 }
719 differential_dataflow::consolidation::consolidate_updates(&mut self.snapshot);
720 }
721
722 async fn with_trace<R>(
726 &mut self,
727 f: impl FnOnce(&Vec<(T, Timestamp, Diff)>) -> Result<R, CatalogError>,
728 ) -> Result<R, CatalogError> {
729 self.sync_to_current_upper().await?;
730 f(&self.snapshot)
731 }
732
733 async fn read_handle(&self) -> ReadHandle<SourceData, (), Timestamp, StorageDiff> {
735 self.persist_client
736 .open_leased_reader(
737 self.shard_id,
738 Arc::new(persist_desc()),
739 Arc::new(UnitSchema::default()),
740 Diagnostics {
741 shard_name: CATALOG_SHARD_NAME.to_string(),
742 handle_purpose: "openable durable catalog state temporary reader".to_string(),
743 },
744 USE_CRITICAL_SINCE_CATALOG.get(self.persist_client.dyncfgs()),
745 )
746 .await
747 .expect("invalid usage")
748 }
749
750 async fn expire(self: Box<Self>) {
752 self.write_handle.expire().await;
753 self.listen.expire().await;
754 }
755}
756
757impl<U: ApplyUpdate<StateUpdateKind>> PersistHandle<StateUpdateKind, U> {
758 async fn with_snapshot<T>(
762 &mut self,
763 f: impl FnOnce(Snapshot) -> Result<T, CatalogError>,
764 ) -> Result<T, CatalogError> {
765 fn apply<K, V>(map: &mut BTreeMap<K, V>, key: &K, value: &V, diff: Diff)
766 where
767 K: Ord + Clone,
768 V: Ord + Clone + Debug,
769 {
770 let key = key.clone();
771 let value = value.clone();
772 if diff == Diff::ONE {
773 let prev = map.insert(key, value);
774 assert_eq!(
775 prev, None,
776 "values must be explicitly retracted before inserting a new value"
777 );
778 } else if diff == Diff::MINUS_ONE {
779 let prev = map.remove(&key);
780 assert_eq!(
781 prev,
782 Some(value),
783 "retraction does not match existing value"
784 );
785 }
786 }
787
788 self.with_trace(|trace| {
789 let mut snapshot = Snapshot::empty();
790 for (kind, ts, diff) in trace {
791 let diff = *diff;
792 if diff != Diff::ONE && diff != Diff::MINUS_ONE {
793 panic!("invalid update in consolidated trace: ({kind:?}, {ts:?}, {diff:?})");
794 }
795
796 match kind {
797 StateUpdateKind::AuditLog(_key, ()) => {
798 }
800 StateUpdateKind::Cluster(key, value) => {
801 apply(&mut snapshot.clusters, key, value, diff);
802 }
803 StateUpdateKind::ClusterReplica(key, value) => {
804 apply(&mut snapshot.cluster_replicas, key, value, diff);
805 }
806 StateUpdateKind::ClusterReplicaSize(key, value) => {
807 apply(&mut snapshot.cluster_replica_sizes, key, value, diff);
808 }
809 StateUpdateKind::Comment(key, value) => {
810 apply(&mut snapshot.comments, key, value, diff);
811 }
812 StateUpdateKind::IndexColumns(key, value) => {
813 apply(&mut snapshot.index_columns, key, value, diff);
814 }
815 StateUpdateKind::ItemColumns(key, value) => {
816 apply(&mut snapshot.item_columns, key, value, diff);
817 }
818 StateUpdateKind::Config(key, value) => {
819 apply(&mut snapshot.configs, key, value, diff);
820 }
821 StateUpdateKind::Database(key, value) => {
822 apply(&mut snapshot.databases, key, value, diff);
823 }
824 StateUpdateKind::DefaultPrivilege(key, value) => {
825 apply(&mut snapshot.default_privileges, key, value, diff);
826 }
827 StateUpdateKind::FenceToken(_token) => {
828 }
830 StateUpdateKind::IdAllocator(key, value) => {
831 apply(&mut snapshot.id_allocator, key, value, diff);
832 }
833 StateUpdateKind::IntrospectionSourceIndex(key, value) => {
834 apply(&mut snapshot.introspection_sources, key, value, diff);
835 }
836 StateUpdateKind::Item(key, value) => {
837 apply(&mut snapshot.items, key, value, diff);
838 }
839 StateUpdateKind::NetworkPolicy(key, value) => {
840 apply(&mut snapshot.network_policies, key, value, diff);
841 }
842 StateUpdateKind::Role(key, value) => {
843 apply(&mut snapshot.roles, key, value, diff);
844 }
845 StateUpdateKind::Schema(key, value) => {
846 apply(&mut snapshot.schemas, key, value, diff);
847 }
848 StateUpdateKind::Setting(key, value) => {
849 apply(&mut snapshot.settings, key, value, diff);
850 }
851 StateUpdateKind::SourceReferences(key, value) => {
852 apply(&mut snapshot.source_references, key, value, diff);
853 }
854 StateUpdateKind::SystemConfiguration(key, value) => {
855 apply(&mut snapshot.system_configurations, key, value, diff);
856 }
857 StateUpdateKind::ClusterSystemConfiguration(key, value) => {
858 apply(
859 &mut snapshot.cluster_system_configurations,
860 key,
861 value,
862 diff,
863 );
864 }
865 StateUpdateKind::ReplicaSystemConfiguration(key, value) => {
866 apply(
867 &mut snapshot.replica_system_configurations,
868 key,
869 value,
870 diff,
871 );
872 }
873 StateUpdateKind::SystemObjectMapping(key, value) => {
874 apply(&mut snapshot.system_object_mappings, key, value, diff);
875 }
876 StateUpdateKind::SystemPrivilege(key, value) => {
877 apply(&mut snapshot.system_privileges, key, value, diff);
878 }
879 StateUpdateKind::StorageCollectionMetadata(key, value) => {
880 apply(&mut snapshot.storage_collection_metadata, key, value, diff);
881 }
882 StateUpdateKind::UnfinalizedShard(key, ()) => {
883 apply(&mut snapshot.unfinalized_shards, key, &(), diff);
884 }
885 StateUpdateKind::TxnWalShard((), value) => {
886 apply(&mut snapshot.txn_wal_shard, &(), value, diff);
887 }
888 StateUpdateKind::RoleAuth(key, value) => {
889 apply(&mut snapshot.role_auth, key, value, diff);
890 }
891 }
892 }
893 f(snapshot)
894 })
895 .await
896 }
897
898 #[mz_ore::instrument(level = "debug")]
905 async fn persist_snapshot(&self) -> impl Iterator<Item = StateUpdate> + DoubleEndedIterator {
906 let mut read_handle = self.read_handle().await;
907 let as_of = as_of(&read_handle, self.upper);
908 let snapshot = snapshot_binary(&mut read_handle, as_of, &self.metrics)
909 .await
910 .map(|update| update.try_into().expect("kind decoding error"));
911 read_handle.expire().await;
912 snapshot
913 }
914}
915
916#[derive(Debug)]
918pub(crate) struct UnopenedCatalogStateInner {
919 configs: BTreeMap<String, u64>,
921 settings: BTreeMap<String, String>,
923}
924
925impl UnopenedCatalogStateInner {
926 fn new() -> UnopenedCatalogStateInner {
927 UnopenedCatalogStateInner {
928 configs: BTreeMap::new(),
929 settings: BTreeMap::new(),
930 }
931 }
932}
933
934impl ApplyUpdate<StateUpdateKindJson> for UnopenedCatalogStateInner {
935 fn apply_update(
936 &mut self,
937 update: StateUpdate<StateUpdateKindJson>,
938 current_fence_token: &mut FenceableToken,
939 _metrics: &Arc<Metrics>,
940 ) -> Result<Option<StateUpdate<StateUpdateKindJson>>, FenceError> {
941 if !update.kind.is_audit_log() && update.kind.is_always_deserializable() {
942 let kind = TryInto::try_into(&update.kind).expect("kind is known to be deserializable");
943 match (kind, update.diff) {
944 (StateUpdateKind::Config(key, value), Diff::ONE) => {
945 let prev = self.configs.insert(key.key, value.value);
946 assert_eq!(
947 prev, None,
948 "values must be explicitly retracted before inserting a new value"
949 );
950 }
951 (StateUpdateKind::Config(key, value), Diff::MINUS_ONE) => {
952 let prev = self.configs.remove(&key.key);
953 assert_eq!(
954 prev,
955 Some(value.value),
956 "retraction does not match existing value"
957 );
958 }
959 (StateUpdateKind::Setting(key, value), Diff::ONE) => {
960 let prev = self.settings.insert(key.name, value.value);
961 assert_eq!(
962 prev, None,
963 "values must be explicitly retracted before inserting a new value"
964 );
965 }
966 (StateUpdateKind::Setting(key, value), Diff::MINUS_ONE) => {
967 let prev = self.settings.remove(&key.name);
968 assert_eq!(
969 prev,
970 Some(value.value),
971 "retraction does not match existing value"
972 );
973 }
974 (StateUpdateKind::FenceToken(fence_token), Diff::ONE) => {
975 current_fence_token.maybe_fence(fence_token)?;
976 }
977 _ => {}
978 }
979 }
980
981 Ok(Some(update))
982 }
983}
984
985pub(crate) type UnopenedPersistCatalogState =
993 PersistHandle<StateUpdateKindJson, UnopenedCatalogStateInner>;
994
995impl UnopenedPersistCatalogState {
996 #[mz_ore::instrument]
1002 pub(crate) async fn new(
1003 persist_client: PersistClient,
1004 organization_id: Uuid,
1005 version: semver::Version,
1006 deploy_generation: Option<u64>,
1007 metrics: Arc<Metrics>,
1008 ) -> Result<UnopenedPersistCatalogState, DurableCatalogError> {
1009 let catalog_shard_id = shard_id(organization_id, CATALOG_SEED);
1010 debug!(?catalog_shard_id, "new persist backed catalog state");
1011
1012 let version_in_catalog_shard =
1016 fetch_catalog_shard_version(&persist_client, catalog_shard_id).await;
1017 if let Some(version_in_catalog_shard) = version_in_catalog_shard {
1018 if !mz_persist_client::cfg::code_can_write_data(&version, &version_in_catalog_shard) {
1019 return Err(DurableCatalogError::IncompatiblePersistVersion {
1020 found_version: version_in_catalog_shard,
1021 catalog_version: version,
1022 });
1023 }
1024 }
1025
1026 let open_handles_start = Instant::now();
1027 info!("startup: envd serve: catalog init: open handles beginning");
1028 let since_handle = persist_client
1029 .open_critical_since(
1030 catalog_shard_id,
1031 CATALOG_CRITICAL_SINCE.clone(),
1032 Opaque::encode(&i64::MIN),
1033 Diagnostics {
1034 shard_name: CATALOG_SHARD_NAME.to_string(),
1035 handle_purpose: "durable catalog state critical since".to_string(),
1036 },
1037 )
1038 .await
1039 .expect("invalid usage");
1040
1041 let (mut write_handle, mut read_handle) = persist_client
1042 .open(
1043 catalog_shard_id,
1044 Arc::new(persist_desc()),
1045 Arc::new(UnitSchema::default()),
1046 Diagnostics {
1047 shard_name: CATALOG_SHARD_NAME.to_string(),
1048 handle_purpose: "durable catalog state handles".to_string(),
1049 },
1050 USE_CRITICAL_SINCE_CATALOG.get(persist_client.dyncfgs()),
1051 )
1052 .await
1053 .expect("invalid usage");
1054 info!(
1055 "startup: envd serve: catalog init: open handles complete in {:?}",
1056 open_handles_start.elapsed()
1057 );
1058
1059 let upper = {
1061 const EMPTY_UPDATES: &[((SourceData, ()), Timestamp, StorageDiff)] = &[];
1062 let upper = Antichain::from_elem(Timestamp::minimum());
1063 let next_upper = Timestamp::minimum().step_forward();
1064 match write_handle
1065 .compare_and_append(EMPTY_UPDATES, upper, Antichain::from_elem(next_upper))
1066 .await
1067 .expect("invalid usage")
1068 {
1069 Ok(()) => next_upper,
1070 Err(mismatch) => antichain_to_timestamp(mismatch.current),
1071 }
1072 };
1073
1074 let snapshot_start = Instant::now();
1075 info!("startup: envd serve: catalog init: snapshot beginning");
1076 let as_of = as_of(&read_handle, upper);
1077 let snapshot: Vec<_> = snapshot_binary(&mut read_handle, as_of, &metrics)
1078 .await
1079 .map(|StateUpdate { kind, ts, diff }| (kind, ts, diff))
1080 .collect();
1081 let listen = read_handle
1082 .listen(Antichain::from_elem(as_of))
1083 .await
1084 .expect("invalid usage");
1085 info!(
1086 "startup: envd serve: catalog init: snapshot complete in {:?}",
1087 snapshot_start.elapsed()
1088 );
1089
1090 let mut handle = UnopenedPersistCatalogState {
1091 mode: Mode::Writable,
1093 since_handle,
1094 write_handle,
1095 listen,
1096 persist_client,
1097 shard_id: catalog_shard_id,
1098 snapshot: Vec::new(),
1100 update_applier: UnopenedCatalogStateInner::new(),
1101 upper,
1102 fenceable_token: FenceableToken::new(deploy_generation),
1103 catalog_content_version: version,
1104 bootstrap_complete: false,
1105 metrics,
1106 size_at_last_consolidation: None,
1107 updates_applied: 0,
1108 };
1109 soft_assert_no_log!(
1112 snapshot.iter().all(|(_, _, diff)| *diff == Diff::ONE),
1113 "snapshot should be consolidated: {snapshot:#?}"
1114 );
1115
1116 let apply_start = Instant::now();
1117 info!("startup: envd serve: catalog init: apply updates beginning");
1118 let updates = snapshot
1119 .into_iter()
1120 .map(|(kind, ts, diff)| StateUpdate { kind, ts, diff });
1121 handle.apply_updates_and_consolidate(updates)?;
1122 info!(
1123 "startup: envd serve: catalog init: apply updates complete in {:?}",
1124 apply_start.elapsed()
1125 );
1126
1127 if let Some(found_version) = handle.get_catalog_content_version().await? {
1133 if handle
1135 .catalog_content_version
1136 .cmp_precedence(&found_version)
1137 == std::cmp::Ordering::Less
1138 {
1139 return Err(DurableCatalogError::IncompatiblePersistVersion {
1140 found_version,
1141 catalog_version: handle.catalog_content_version,
1142 });
1143 }
1144 }
1145
1146 Ok(handle)
1147 }
1148
1149 #[mz_ore::instrument]
1150 async fn open_inner(
1151 mut self,
1152 mode: Mode,
1153 initial_ts: Timestamp,
1154 bootstrap_args: &BootstrapArgs,
1155 ) -> Result<Box<dyn DurableCatalogState>, CatalogError> {
1156 let mut commit_ts = self.upper;
1159 self.mode = mode;
1160
1161 match (&self.mode, &self.fenceable_token) {
1163 (_, FenceableToken::Unfenced { .. } | FenceableToken::Fenced { .. }) => {
1164 return Err(DurableCatalogError::Internal(
1165 "catalog should not have fenced before opening".to_string(),
1166 )
1167 .into());
1168 }
1169 (
1170 Mode::Writable | Mode::Savepoint,
1171 FenceableToken::Initializing {
1172 current_deploy_generation: None,
1173 ..
1174 },
1175 ) => {
1176 return Err(DurableCatalogError::Internal(format!(
1177 "cannot open in mode '{:?}' without a deploy generation",
1178 self.mode,
1179 ))
1180 .into());
1181 }
1182 _ => {}
1183 }
1184
1185 let read_only = matches!(self.mode, Mode::Readonly);
1186
1187 loop {
1189 self.sync_to_current_upper().await?;
1190 commit_ts = max(commit_ts, self.upper);
1191 let (fence_updates, current_fenceable_token) = self
1192 .fenceable_token
1193 .generate_unfenced_token(self.mode)?
1194 .ok_or_else(|| {
1195 DurableCatalogError::Internal(
1196 "catalog should not have fenced before opening".to_string(),
1197 )
1198 })?;
1199 debug!(
1200 ?self.upper,
1201 ?self.fenceable_token,
1202 ?current_fenceable_token,
1203 "fencing previous catalogs"
1204 );
1205 if matches!(self.mode, Mode::Writable) {
1206 match self
1207 .compare_and_append(fence_updates.clone(), commit_ts)
1208 .await
1209 {
1210 Ok(upper) => {
1211 commit_ts = upper;
1212 }
1213 Err(CompareAndAppendError::Fence(e)) => return Err(e.into()),
1214 Err(e @ CompareAndAppendError::UpperMismatch { .. }) => {
1215 warn!("catalog write failed due to upper mismatch, retrying: {e:?}");
1216 continue;
1217 }
1218 }
1219 }
1220 self.fenceable_token = current_fenceable_token;
1221 break;
1222 }
1223
1224 if matches!(self.mode, Mode::Writable) {
1225 let mut controller_handle = self
1232 .persist_client
1233 .open_critical_since::<SourceData, (), Timestamp, StorageDiff>(
1234 self.shard_id,
1235 PersistClient::CONTROLLER_CRITICAL_SINCE,
1236 Opaque::encode(&i64::MIN),
1237 Diagnostics {
1238 shard_name: CATALOG_SHARD_NAME.to_string(),
1239 handle_purpose: "durable catalog state critical since (migration)"
1240 .to_string(),
1241 },
1242 )
1243 .await
1244 .expect("invalid usage");
1245
1246 let since = controller_handle.since().clone();
1247 let res = controller_handle
1248 .compare_and_downgrade_since(
1249 &Opaque::encode(&i64::MIN),
1250 (&Opaque::encode(&PersistEpoch::default()), &since),
1251 )
1252 .await;
1253 match res {
1254 Ok(_) => info!("migrated Opaque of catalog since handle"),
1255 Err(_) => { }
1256 }
1257 }
1258
1259 let is_initialized = self.is_initialized_inner();
1260 if !matches!(self.mode, Mode::Writable) && !is_initialized {
1261 return Err(CatalogError::Durable(DurableCatalogError::NotWritable(
1262 format!(
1263 "catalog tables do not exist; will not create in {:?} mode",
1264 self.mode
1265 ),
1266 )));
1267 }
1268 soft_assert_ne_or_log!(self.upper, Timestamp::minimum());
1269
1270 let (audit_logs, snapshot): (Vec<_>, Vec<_>) = self
1275 .snapshot
1276 .into_iter()
1277 .partition(|(update, _, _)| update.is_audit_log());
1278 self.snapshot = snapshot;
1279 let audit_log_count = audit_logs.iter().map(|(_, _, diff)| diff).sum::<Diff>();
1280 drop(audit_logs);
1281
1282 if is_initialized && !read_only {
1284 commit_ts =
1285 upgrade(&mut self, commit_ts, bootstrap_args.force_catalog_migration).await?;
1286 }
1287
1288 debug!(
1289 ?is_initialized,
1290 ?self.upper,
1291 "initializing catalog state"
1292 );
1293 let mut catalog = PersistCatalogState {
1294 mode: self.mode,
1295 since_handle: self.since_handle,
1296 write_handle: self.write_handle,
1297 listen: self.listen,
1298 persist_client: self.persist_client,
1299 shard_id: self.shard_id,
1300 upper: self.upper,
1301 fenceable_token: self.fenceable_token,
1302 snapshot: Vec::new(),
1304 update_applier: CatalogStateInner::new(),
1305 catalog_content_version: self.catalog_content_version,
1306 bootstrap_complete: false,
1307 metrics: self.metrics,
1308 size_at_last_consolidation: None,
1309 updates_applied: 0,
1310 };
1311 catalog.metrics.collection_entries.reset();
1312 catalog
1315 .metrics
1316 .collection_entries
1317 .with_label_values(&[&CollectionType::AuditLog.to_string()])
1318 .add(audit_log_count.into_inner());
1319 let updates = self.snapshot.into_iter().map(|(kind, ts, diff)| {
1320 let kind = TryIntoStateUpdateKind::try_into(kind).expect("kind decoding error");
1321 StateUpdate { kind, ts, diff }
1322 });
1323 catalog.apply_updates_and_consolidate(updates)?;
1324
1325 let catalog_content_version = catalog.catalog_content_version.to_string();
1326 let txn = if is_initialized {
1327 let mut txn = catalog.transaction_unchecked().await?;
1328
1329 if txn.get_setting("migration_version".into()).is_none() && mode != Mode::Readonly {
1336 let old_version = txn.get_catalog_content_version();
1337 txn.set_setting("migration_version".into(), old_version.map(Into::into))?;
1338 }
1339
1340 if mode != Mode::Readonly {
1353 txn.remove_ephemeral_items();
1354 }
1355
1356 txn.set_catalog_content_version(catalog_content_version)?;
1357 txn
1358 } else {
1359 soft_assert_eq_no_log!(
1360 catalog
1361 .snapshot
1362 .iter()
1363 .filter(|(kind, _, _)| !matches!(kind, StateUpdateKind::FenceToken(_)))
1364 .count(),
1365 0,
1366 "trace should not contain any updates for an uninitialized catalog: {:#?}",
1367 catalog.snapshot
1368 );
1369
1370 let mut txn = catalog.transaction_unchecked().await?;
1371 initialize::initialize(
1372 &mut txn,
1373 bootstrap_args,
1374 initial_ts.into(),
1375 catalog_content_version,
1376 )
1377 .await?;
1378 txn
1379 };
1380
1381 if read_only {
1382 let (txn_batch, _) = txn.into_parts()?;
1383 let updates = StateUpdate::from_txn_batch_ts(txn_batch, catalog.upper);
1385 catalog.apply_updates_and_consolidate(updates)?;
1386 } else {
1387 txn.commit_internal(commit_ts).await?;
1388 }
1389
1390 if matches!(catalog.mode, Mode::Writable) {
1391 let write_handle = catalog
1392 .persist_client
1393 .open_writer::<SourceData, (), Timestamp, i64>(
1394 catalog.write_handle.shard_id(),
1395 Arc::new(persist_desc()),
1396 Arc::new(UnitSchema::default()),
1397 Diagnostics {
1398 shard_name: CATALOG_SHARD_NAME.to_string(),
1399 handle_purpose: "compact catalog".to_string(),
1400 },
1401 )
1402 .await
1403 .expect("invalid usage");
1404 let fuel = CATALOG_FORCE_COMPACTION_FUEL.handle(catalog.persist_client.dyncfgs());
1405 let wait = CATALOG_FORCE_COMPACTION_WAIT.handle(catalog.persist_client.dyncfgs());
1406 let _task = mz_ore::task::spawn(|| "catalog::force_shard_compaction", async move {
1409 let () =
1410 mz_persist_client::cli::admin::dangerous_force_compaction_and_break_pushdown(
1411 &write_handle,
1412 || fuel.get(),
1413 || wait.get(),
1414 )
1415 .await;
1416 });
1417 }
1418
1419 Ok(Box::new(catalog))
1420 }
1421
1422 #[mz_ore::instrument]
1427 fn is_initialized_inner(&self) -> bool {
1428 !self.update_applier.configs.is_empty()
1429 }
1430
1431 #[mz_ore::instrument]
1435 async fn get_current_config(&mut self, key: &str) -> Result<Option<u64>, DurableCatalogError> {
1436 self.sync_to_current_upper().await?;
1437 Ok(self.update_applier.configs.get(key).cloned())
1438 }
1439
1440 #[mz_ore::instrument]
1444 pub(crate) async fn get_user_version(&mut self) -> Result<Option<u64>, DurableCatalogError> {
1445 self.get_current_config(USER_VERSION_KEY).await
1446 }
1447
1448 #[mz_ore::instrument]
1452 async fn get_current_setting(
1453 &mut self,
1454 name: &str,
1455 ) -> Result<Option<String>, DurableCatalogError> {
1456 self.sync_to_current_upper().await?;
1457 Ok(self.update_applier.settings.get(name).cloned())
1458 }
1459
1460 #[mz_ore::instrument]
1465 async fn get_catalog_content_version(
1466 &mut self,
1467 ) -> Result<Option<semver::Version>, DurableCatalogError> {
1468 let version = self
1469 .get_current_setting(CATALOG_CONTENT_VERSION_KEY)
1470 .await?;
1471 let version = version.map(|version| version.parse().expect("invalid version persisted"));
1472 Ok(version)
1473 }
1474}
1475
1476#[async_trait]
1477impl OpenableDurableCatalogState for UnopenedPersistCatalogState {
1478 #[mz_ore::instrument]
1479 async fn open_savepoint(
1480 mut self: Box<Self>,
1481 initial_ts: Timestamp,
1482 bootstrap_args: &BootstrapArgs,
1483 ) -> Result<Box<dyn DurableCatalogState>, CatalogError> {
1484 self.open_inner(Mode::Savepoint, initial_ts, bootstrap_args)
1485 .boxed()
1486 .await
1487 }
1488
1489 #[mz_ore::instrument]
1490 async fn open_read_only(
1491 mut self: Box<Self>,
1492 bootstrap_args: &BootstrapArgs,
1493 ) -> Result<Box<dyn DurableCatalogState>, CatalogError> {
1494 self.open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args)
1495 .boxed()
1496 .await
1497 }
1498
1499 #[mz_ore::instrument]
1500 async fn open(
1501 mut self: Box<Self>,
1502 initial_ts: Timestamp,
1503 bootstrap_args: &BootstrapArgs,
1504 ) -> Result<Box<dyn DurableCatalogState>, CatalogError> {
1505 self.open_inner(Mode::Writable, initial_ts, bootstrap_args)
1506 .boxed()
1507 .await
1508 }
1509
1510 #[mz_ore::instrument(level = "debug")]
1511 async fn open_debug(mut self: Box<Self>) -> Result<DebugCatalogState, CatalogError> {
1512 Ok(DebugCatalogState(*self))
1513 }
1514
1515 #[mz_ore::instrument]
1516 async fn is_initialized(&mut self) -> Result<bool, CatalogError> {
1517 self.sync_to_current_upper().await?;
1518 Ok(self.is_initialized_inner())
1519 }
1520
1521 #[mz_ore::instrument]
1522 async fn epoch(&mut self) -> Result<Epoch, CatalogError> {
1523 self.sync_to_current_upper().await?;
1524 self.fenceable_token
1525 .validate()?
1526 .map(|token| token.epoch)
1527 .ok_or(CatalogError::Durable(DurableCatalogError::Uninitialized))
1528 }
1529
1530 #[mz_ore::instrument]
1531 async fn get_deployment_generation(&mut self) -> Result<u64, CatalogError> {
1532 self.sync_to_current_upper().await?;
1533 self.fenceable_token
1534 .token()
1535 .map(|token| token.deploy_generation)
1536 .ok_or(CatalogError::Durable(DurableCatalogError::Uninitialized))
1537 }
1538
1539 #[mz_ore::instrument(level = "debug")]
1540 async fn get_0dt_deployment_max_wait(&mut self) -> Result<Option<Duration>, CatalogError> {
1541 let value = self
1542 .get_current_config(WITH_0DT_DEPLOYMENT_MAX_WAIT)
1543 .await?;
1544 match value {
1545 None => Ok(None),
1546 Some(millis) => Ok(Some(Duration::from_millis(millis))),
1547 }
1548 }
1549
1550 #[mz_ore::instrument(level = "debug")]
1551 async fn get_0dt_deployment_ddl_check_interval(
1552 &mut self,
1553 ) -> Result<Option<Duration>, CatalogError> {
1554 let value = self
1555 .get_current_config(WITH_0DT_DEPLOYMENT_DDL_CHECK_INTERVAL)
1556 .await?;
1557 match value {
1558 None => Ok(None),
1559 Some(millis) => Ok(Some(Duration::from_millis(millis))),
1560 }
1561 }
1562
1563 #[mz_ore::instrument(level = "debug")]
1564 async fn get_enable_0dt_deployment_panic_after_timeout(
1565 &mut self,
1566 ) -> Result<Option<bool>, CatalogError> {
1567 let value = self
1568 .get_current_config(ENABLE_0DT_DEPLOYMENT_PANIC_AFTER_TIMEOUT)
1569 .await?;
1570 match value {
1571 None => Ok(None),
1572 Some(0) => Ok(Some(false)),
1573 Some(1) => Ok(Some(true)),
1574 Some(v) => Err(
1575 DurableCatalogError::from(TryFromProtoError::UnknownEnumVariant(format!(
1576 "{v} is not a valid boolean value"
1577 )))
1578 .into(),
1579 ),
1580 }
1581 }
1582
1583 #[mz_ore::instrument]
1584 async fn has_system_config_synced_once(&mut self) -> Result<bool, DurableCatalogError> {
1585 self.get_current_config(SYSTEM_CONFIG_SYNCED_KEY)
1586 .await
1587 .map(|value| value.map(|value| value > 0).unwrap_or(false))
1588 }
1589
1590 #[mz_ore::instrument]
1591 async fn trace_unconsolidated(&mut self) -> Result<Trace, CatalogError> {
1592 self.sync_to_current_upper().await?;
1593 if self.is_initialized_inner() {
1594 let snapshot = self.snapshot_unconsolidated().await;
1595 Ok(Trace::from_snapshot(snapshot))
1596 } else {
1597 Err(CatalogError::Durable(DurableCatalogError::Uninitialized))
1598 }
1599 }
1600
1601 #[mz_ore::instrument]
1602 async fn trace_consolidated(&mut self) -> Result<Trace, CatalogError> {
1603 self.sync_to_current_upper().await?;
1604 if self.is_initialized_inner() {
1605 let snapshot = self.current_snapshot().await?;
1606 Ok(Trace::from_snapshot(snapshot))
1607 } else {
1608 Err(CatalogError::Durable(DurableCatalogError::Uninitialized))
1609 }
1610 }
1611
1612 #[mz_ore::instrument(level = "debug")]
1613 async fn expire(self: Box<Self>) {
1614 self.expire().await
1615 }
1616}
1617
1618#[derive(Debug)]
1620struct CatalogStateInner {
1621 updates: VecDeque<memory::objects::StateUpdate>,
1623}
1624
1625impl CatalogStateInner {
1626 fn new() -> CatalogStateInner {
1627 CatalogStateInner {
1628 updates: VecDeque::new(),
1629 }
1630 }
1631}
1632
1633impl ApplyUpdate<StateUpdateKind> for CatalogStateInner {
1634 fn apply_update(
1635 &mut self,
1636 update: StateUpdate<StateUpdateKind>,
1637 current_fence_token: &mut FenceableToken,
1638 metrics: &Arc<Metrics>,
1639 ) -> Result<Option<StateUpdate<StateUpdateKind>>, FenceError> {
1640 if let Some(collection_type) = update.kind.collection_type() {
1641 metrics
1642 .collection_entries
1643 .with_label_values(&[&collection_type.to_string()])
1644 .add(update.diff.into_inner());
1645 }
1646
1647 {
1648 let update: Option<memory::objects::StateUpdate> = (&update)
1649 .try_into()
1650 .expect("invalid persisted update: {update:#?}");
1651 if let Some(update) = update {
1652 self.updates.push_back(update);
1653 }
1654 }
1655
1656 match (update.kind, update.diff) {
1657 (StateUpdateKind::AuditLog(_, ()), _) => Ok(None),
1658 (StateUpdateKind::FenceToken(_), Diff::MINUS_ONE) => Ok(None),
1660 (StateUpdateKind::FenceToken(token), Diff::ONE) => {
1661 current_fence_token.maybe_fence(token)?;
1662 Ok(None)
1663 }
1664 (kind, diff) => Ok(Some(StateUpdate {
1665 kind,
1666 ts: update.ts,
1667 diff,
1668 })),
1669 }
1670 }
1671}
1672
1673type PersistCatalogState = PersistHandle<StateUpdateKind, CatalogStateInner>;
1679
1680impl PersistHandle<StateUpdateKind, CatalogStateInner> {
1681 async fn transaction_unchecked(&mut self) -> Result<Transaction<'_>, CatalogError> {
1683 self.metrics.transactions_started.inc();
1684 let snapshot = self.snapshot().await?;
1685 let commit_ts = self.upper;
1686 Transaction::new(self, snapshot, commit_ts)
1687 }
1688}
1689
1690#[async_trait]
1691impl ReadOnlyDurableCatalogState for PersistCatalogState {
1692 fn epoch(&self) -> Epoch {
1693 self.fenceable_token
1694 .token()
1695 .expect("opened catalog state must have an epoch")
1696 .epoch
1697 }
1698
1699 fn metrics(&self) -> &Metrics {
1700 &self.metrics
1701 }
1702
1703 #[mz_ore::instrument(level = "debug")]
1704 async fn expire(self: Box<Self>) {
1705 self.expire().await
1706 }
1707
1708 fn is_bootstrap_complete(&self) -> bool {
1709 self.bootstrap_complete
1710 }
1711
1712 async fn get_audit_logs(&mut self) -> Result<Vec<VersionedEvent>, CatalogError> {
1713 self.sync_to_current_upper().await?;
1714 let audit_logs: Vec<_> = self
1715 .persist_snapshot()
1716 .await
1717 .filter_map(
1718 |StateUpdate {
1719 kind,
1720 ts: _,
1721 diff: _,
1722 }| match kind {
1723 StateUpdateKind::AuditLog(key, ()) => Some(key),
1724 _ => None,
1725 },
1726 )
1727 .collect();
1728 let mut audit_logs: Vec<_> = audit_logs
1729 .into_iter()
1730 .map(RustType::from_proto)
1731 .map_ok(|key: AuditLogKey| key.event)
1732 .collect::<Result<_, _>>()?;
1733 audit_logs.sort_by(|a, b| a.sortable_id().cmp(&b.sortable_id()));
1734 Ok(audit_logs)
1735 }
1736
1737 #[mz_ore::instrument(level = "debug")]
1738 async fn get_next_id(&mut self, id_type: &str) -> Result<u64, CatalogError> {
1739 self.with_trace(|trace| {
1740 Ok(trace
1741 .into_iter()
1742 .rev()
1743 .filter_map(|(kind, _, _)| match kind {
1744 StateUpdateKind::IdAllocator(key, value) if key.name == id_type => {
1745 Some(value.next_id)
1746 }
1747 _ => None,
1748 })
1749 .next()
1750 .expect("must exist"))
1751 })
1752 .await
1753 }
1754
1755 #[mz_ore::instrument(level = "debug")]
1756 async fn get_deployment_generation(&mut self) -> Result<u64, CatalogError> {
1757 self.sync_to_current_upper().await?;
1758 Ok(self
1759 .fenceable_token
1760 .token()
1761 .expect("opened catalogs must have a token")
1762 .deploy_generation)
1763 }
1764
1765 #[mz_ore::instrument(level = "debug")]
1766 async fn snapshot(&mut self) -> Result<Snapshot, CatalogError> {
1767 self.with_snapshot(Ok).await
1768 }
1769
1770 #[mz_ore::instrument(level = "debug")]
1771 async fn sync_to_current_updates(
1772 &mut self,
1773 ) -> Result<Vec<memory::objects::StateUpdate>, CatalogError> {
1774 let upper = self.current_upper().await;
1775 self.sync_updates(upper).await
1776 }
1777
1778 #[mz_ore::instrument(level = "debug")]
1779 async fn sync_updates(
1780 &mut self,
1781 target_upper: mz_repr::Timestamp,
1782 ) -> Result<Vec<memory::objects::StateUpdate>, CatalogError> {
1783 self.sync(target_upper).await?;
1784 let mut updates = Vec::new();
1785 while let Some(update) = self.update_applier.updates.front() {
1786 if update.ts >= target_upper {
1787 break;
1788 }
1789
1790 let update = self
1791 .update_applier
1792 .updates
1793 .pop_front()
1794 .expect("peeked above");
1795 updates.push(update);
1796 }
1797 Ok(updates)
1798 }
1799
1800 #[mz_ore::instrument(level = "debug")]
1801 async fn ensure_not_out_of_sync(
1802 &mut self,
1803 target_upper: Timestamp,
1804 ) -> Result<(), CatalogError> {
1805 self.sync(target_upper).await?;
1806 let update_count = self
1807 .update_applier
1808 .updates
1809 .iter()
1810 .take_while(|update| update.ts < target_upper)
1811 .count();
1812 if update_count == 0 {
1813 Ok(())
1814 } else {
1815 Err(DurableCatalogError::CatalogOutOfSync {
1816 update_count,
1817 upper: target_upper,
1818 }
1819 .into())
1820 }
1821 }
1822
1823 async fn current_upper(&mut self) -> Timestamp {
1824 self.current_upper().await
1825 }
1826}
1827
1828#[async_trait]
1829#[allow(mismatched_lifetime_syntaxes)]
1830impl DurableCatalogState for PersistCatalogState {
1831 fn is_read_only(&self) -> bool {
1832 matches!(self.mode, Mode::Readonly)
1833 }
1834
1835 fn is_savepoint(&self) -> bool {
1836 matches!(self.mode, Mode::Savepoint)
1837 }
1838
1839 async fn mark_bootstrap_complete(&mut self) {
1840 self.bootstrap_complete = true;
1841 if matches!(self.mode, Mode::Writable) {
1842 self.since_handle
1843 .upgrade_version()
1844 .await
1845 .expect("invalid usage")
1846 }
1847 }
1848
1849 #[mz_ore::instrument(level = "debug")]
1850 async fn transaction(&mut self) -> Result<Transaction, CatalogError> {
1851 let mut txn = self.transaction_unchecked().await?;
1852 txn.ensure_not_out_of_sync().await?;
1853 Ok(txn)
1854 }
1855
1856 fn transaction_from_snapshot(
1857 &mut self,
1858 snapshot: Snapshot,
1859 ) -> Result<DryRunTransaction, CatalogError> {
1860 let commit_ts = self.upper;
1861 Transaction::new(self, snapshot, commit_ts).map(DryRunTransaction::new)
1862 }
1863
1864 #[mz_ore::instrument(level = "debug")]
1865 async fn allocate_id(
1866 &mut self,
1867 id_type: &str,
1868 amount: u64,
1869 commit_ts: Timestamp,
1870 ) -> Result<Vec<u64>, CatalogError> {
1871 let start = Instant::now();
1872 if amount == 0 {
1873 return Ok(Vec::new());
1874 }
1875 let mut txn = self.transaction_unchecked().await?;
1876 let ids = txn.get_and_increment_id_by(id_type.to_string(), amount)?;
1877 txn.commit_internal(commit_ts).await?;
1878 self.metrics
1879 .allocate_id_seconds
1880 .observe(start.elapsed().as_secs_f64());
1881 Ok(ids)
1882 }
1883
1884 #[mz_ore::instrument(level = "debug")]
1885 async fn commit_transaction(
1886 &mut self,
1887 txn_batch: TransactionBatch,
1888 commit_ts: Timestamp,
1889 ) -> Result<Timestamp, CatalogError> {
1890 async fn commit_transaction_inner(
1891 catalog: &mut PersistCatalogState,
1892 txn_batch: TransactionBatch,
1893 commit_ts: Timestamp,
1894 ) -> Result<Timestamp, CatalogError> {
1895 if catalog.mode == Mode::Readonly {
1899 let updates: Vec<_> = StateUpdate::from_txn_batch(txn_batch).collect();
1900 if !updates.is_empty() {
1901 let collection_types: Vec<_> = updates
1902 .iter()
1903 .filter_map(|u| u.0.collection_type())
1904 .collect();
1905 return Err(DurableCatalogError::NotWritable(format!(
1906 "cannot commit a transaction in a read-only catalog: \
1907 {} updates across collections: {collection_types:?}",
1908 updates.len(),
1909 ))
1910 .into());
1911 }
1912 return Ok(catalog.upper);
1913 }
1914
1915 assert_eq!(
1918 catalog.upper, txn_batch.upper,
1919 "the handle was mutated mid-transaction"
1920 );
1921
1922 let updates: Vec<_> = StateUpdate::from_txn_batch(txn_batch).collect();
1923 debug!("committing updates: {updates:?}");
1924
1925 let mut commit_ts = max(commit_ts, catalog.upper);
1927
1928 let next_upper = match catalog.mode {
1929 Mode::Writable => loop {
1930 let updates_applied_before = catalog.updates_applied;
1931 match catalog.compare_and_append(updates.clone(), commit_ts).await {
1932 Ok(next_upper) => break next_upper,
1933 Err(CompareAndAppendError::Fence(e)) => return Err(e.into()),
1934 Err(CompareAndAppendError::UpperMismatch { actual_upper, .. }) => {
1935 catalog
1938 .classify_upper_mismatch(updates_applied_before, actual_upper)?;
1939 commit_ts = max(commit_ts, catalog.upper);
1940 }
1941 }
1942 },
1943 Mode::Savepoint => {
1944 let updates = updates.into_iter().map(|(kind, diff)| StateUpdate {
1945 kind,
1946 ts: commit_ts,
1947 diff,
1948 });
1949 catalog.apply_updates_and_consolidate(updates)?;
1950 catalog.upper = commit_ts.step_forward();
1951 catalog.upper
1952 }
1953 Mode::Readonly => unreachable!("handled above"),
1954 };
1955
1956 Ok(next_upper)
1957 }
1958 self.metrics.transaction_commits.inc();
1959 let histogram = self.metrics.transaction_commit_latency_seconds.clone();
1960 commit_transaction_inner(self, txn_batch, commit_ts)
1961 .wall_time()
1962 .observe(histogram)
1963 .await
1964 }
1965
1966 #[mz_ore::instrument(level = "debug")]
1967 async fn advance_upper(&mut self, new_upper: Timestamp) -> Result<(), CatalogError> {
1968 loop {
1969 if self.upper >= new_upper {
1970 self.fenceable_token.validate()?;
1973 return Ok(());
1974 }
1975
1976 match self.mode {
1977 Mode::Writable => {}
1978 Mode::Savepoint => {
1979 self.upper = new_upper;
1980 return Ok(());
1981 }
1982 Mode::Readonly => {
1983 return Err(DurableCatalogError::NotWritable(
1984 "cannot advance upper of a read-only catalog".into(),
1985 )
1986 .into());
1987 }
1988 }
1989
1990 let updates_applied_before = self.updates_applied;
1991 match self.compare_and_append_inner([], new_upper).await {
1992 Ok(()) => {
1993 self.upper = new_upper;
1994 return Ok(());
1996 }
1997 Err(CompareAndAppendError::Fence(e)) => return Err(e.into()),
1998 Err(CompareAndAppendError::UpperMismatch { actual_upper, .. }) => {
1999 self.classify_upper_mismatch(updates_applied_before, actual_upper)?;
2001 }
2002 }
2003 }
2004 }
2005
2006 fn shard_id(&self) -> ShardId {
2007 self.shard_id
2008 }
2009}
2010
2011pub fn shard_id(organization_id: Uuid, seed: usize) -> ShardId {
2013 let hash = sha2::Sha256::digest(format!("{organization_id}{seed}")).to_vec();
2014 soft_assert_eq_or_log!(hash.len(), 32, "SHA256 returns 32 bytes (256 bits)");
2015 let uuid = Uuid::from_slice(&hash[0..16]).expect("from_slice accepts exactly 16 bytes");
2016 ShardId::from_str(&format!("s{uuid}")).expect("known to be valid")
2017}
2018
2019fn as_of(
2022 read_handle: &ReadHandle<SourceData, (), Timestamp, StorageDiff>,
2023 upper: Timestamp,
2024) -> Timestamp {
2025 let since = read_handle.since().clone();
2026 let mut as_of = upper.checked_sub(1).unwrap_or_else(|| {
2027 panic!("catalog persist shard should be initialize, found upper: {upper:?}")
2028 });
2029 soft_assert_or_log!(
2032 since.less_equal(&as_of),
2033 "since={since:?}, as_of={as_of:?}; since must be less than or equal to as_of"
2034 );
2035 as_of.advance_by(since.borrow());
2038 as_of
2039}
2040
2041async fn fetch_catalog_shard_version(
2044 persist_client: &PersistClient,
2045 catalog_shard_id: ShardId,
2046) -> Option<semver::Version> {
2047 let shard_state = persist_client
2048 .inspect_shard::<Timestamp>(&catalog_shard_id)
2049 .await
2050 .ok()?;
2051 let json_state = serde_json::to_value(shard_state).expect("state serialization error");
2052 let json_version = json_state
2053 .get("applier_version")
2054 .cloned()
2055 .expect("missing applier_version");
2056 let version = serde_json::from_value(json_version).expect("version deserialization error");
2057 Some(version)
2058}
2059
2060#[mz_ore::instrument(level = "debug")]
2065async fn snapshot_binary(
2066 read_handle: &mut ReadHandle<SourceData, (), Timestamp, StorageDiff>,
2067 as_of: Timestamp,
2068 metrics: &Arc<Metrics>,
2069) -> impl Iterator<Item = StateUpdate<StateUpdateKindJson>> + DoubleEndedIterator + use<> {
2070 metrics.snapshots_taken.inc();
2071 let histogram = metrics.snapshot_latency_seconds.clone();
2072 snapshot_binary_inner(read_handle, as_of)
2073 .wall_time()
2074 .observe(histogram)
2075 .await
2076}
2077
2078#[mz_ore::instrument(level = "debug")]
2083async fn snapshot_binary_inner(
2084 read_handle: &mut ReadHandle<SourceData, (), Timestamp, StorageDiff>,
2085 as_of: Timestamp,
2086) -> impl Iterator<Item = StateUpdate<StateUpdateKindJson>> + DoubleEndedIterator + use<> {
2087 let snapshot = read_handle
2088 .snapshot_and_fetch(Antichain::from_elem(as_of))
2089 .await
2090 .expect("we have advanced the restart_as_of by the since");
2091 soft_assert_no_log!(
2092 snapshot.iter().all(|(_, _, diff)| *diff == 1),
2093 "snapshot_and_fetch guarantees a consolidated result: {snapshot:#?}"
2094 );
2095 snapshot
2096 .into_iter()
2097 .map(Into::<StateUpdate<StateUpdateKindJson>>::into)
2098 .sorted_by(|a, b| Ord::cmp(&b.ts, &a.ts))
2099}
2100
2101pub(crate) fn antichain_to_timestamp(antichain: Antichain<Timestamp>) -> Timestamp {
2106 antichain
2107 .into_option()
2108 .expect("we use a totally ordered time and never finalize the shard")
2109}
2110
2111impl Trace {
2114 fn from_snapshot(snapshot: impl IntoIterator<Item = StateUpdate>) -> Trace {
2116 let mut trace = Trace::new();
2117 for StateUpdate { kind, ts, diff } in snapshot {
2118 match kind {
2119 StateUpdateKind::AuditLog(k, v) => trace.audit_log.values.push(((k, v), ts, diff)),
2120 StateUpdateKind::Cluster(k, v) => trace.clusters.values.push(((k, v), ts, diff)),
2121 StateUpdateKind::ClusterReplica(k, v) => {
2122 trace.cluster_replicas.values.push(((k, v), ts, diff))
2123 }
2124 StateUpdateKind::ClusterReplicaSize(k, v) => {
2125 trace.cluster_replica_sizes.values.push(((k, v), ts, diff))
2126 }
2127 StateUpdateKind::Comment(k, v) => trace.comments.values.push(((k, v), ts, diff)),
2128 StateUpdateKind::IndexColumns(k, v) => {
2129 trace.index_columns.values.push(((k, v), ts, diff))
2130 }
2131 StateUpdateKind::ItemColumns(k, v) => {
2132 trace.item_columns.values.push(((k, v), ts, diff))
2133 }
2134 StateUpdateKind::Config(k, v) => trace.configs.values.push(((k, v), ts, diff)),
2135 StateUpdateKind::Database(k, v) => trace.databases.values.push(((k, v), ts, diff)),
2136 StateUpdateKind::DefaultPrivilege(k, v) => {
2137 trace.default_privileges.values.push(((k, v), ts, diff))
2138 }
2139 StateUpdateKind::FenceToken(_) => {
2140 }
2142 StateUpdateKind::IdAllocator(k, v) => {
2143 trace.id_allocator.values.push(((k, v), ts, diff))
2144 }
2145 StateUpdateKind::IntrospectionSourceIndex(k, v) => {
2146 trace.introspection_sources.values.push(((k, v), ts, diff))
2147 }
2148 StateUpdateKind::Item(k, v) => trace.items.values.push(((k, v), ts, diff)),
2149 StateUpdateKind::NetworkPolicy(k, v) => {
2150 trace.network_policies.values.push(((k, v), ts, diff))
2151 }
2152 StateUpdateKind::Role(k, v) => trace.roles.values.push(((k, v), ts, diff)),
2153 StateUpdateKind::Schema(k, v) => trace.schemas.values.push(((k, v), ts, diff)),
2154 StateUpdateKind::Setting(k, v) => trace.settings.values.push(((k, v), ts, diff)),
2155 StateUpdateKind::SourceReferences(k, v) => {
2156 trace.source_references.values.push(((k, v), ts, diff))
2157 }
2158 StateUpdateKind::SystemConfiguration(k, v) => {
2159 trace.system_configurations.values.push(((k, v), ts, diff))
2160 }
2161 StateUpdateKind::ClusterSystemConfiguration(k, v) => trace
2162 .cluster_system_configurations
2163 .values
2164 .push(((k, v), ts, diff)),
2165 StateUpdateKind::ReplicaSystemConfiguration(k, v) => trace
2166 .replica_system_configurations
2167 .values
2168 .push(((k, v), ts, diff)),
2169 StateUpdateKind::SystemObjectMapping(k, v) => {
2170 trace.system_object_mappings.values.push(((k, v), ts, diff))
2171 }
2172 StateUpdateKind::SystemPrivilege(k, v) => {
2173 trace.system_privileges.values.push(((k, v), ts, diff))
2174 }
2175 StateUpdateKind::StorageCollectionMetadata(k, v) => trace
2176 .storage_collection_metadata
2177 .values
2178 .push(((k, v), ts, diff)),
2179 StateUpdateKind::UnfinalizedShard(k, ()) => {
2180 trace.unfinalized_shards.values.push(((k, ()), ts, diff))
2181 }
2182 StateUpdateKind::TxnWalShard((), v) => {
2183 trace.txn_wal_shard.values.push((((), v), ts, diff))
2184 }
2185 StateUpdateKind::RoleAuth(k, v) => trace.role_auth.values.push(((k, v), ts, diff)),
2186 }
2187 }
2188 trace
2189 }
2190}
2191
2192impl UnopenedPersistCatalogState {
2193 #[mz_ore::instrument]
2195 pub(crate) async fn debug_edit<T: Collection>(
2196 &mut self,
2197 key: T::Key,
2198 value: T::Value,
2199 ) -> Result<Option<T::Value>, CatalogError>
2200 where
2201 T::Key: PartialEq + Eq + Debug + Clone,
2202 T::Value: Debug + Clone,
2203 {
2204 let prev_value = loop {
2205 let key = key.clone();
2206 let value = value.clone();
2207 let snapshot = self.current_snapshot().await?;
2208 let trace = Trace::from_snapshot(snapshot);
2209 let collection_trace = T::collection_trace(trace);
2210 let prev_values: Vec<_> = collection_trace
2211 .values
2212 .into_iter()
2213 .filter(|((k, _), _, diff)| {
2214 soft_assert_eq_or_log!(*diff, Diff::ONE, "trace is consolidated");
2215 &key == k
2216 })
2217 .collect();
2218
2219 let prev_value = match &prev_values[..] {
2220 [] => None,
2221 [((_, v), _, _)] => Some(v.clone()),
2222 prev_values => panic!("multiple values found for key {key:?}: {prev_values:?}"),
2223 };
2224
2225 let mut updates: Vec<_> = prev_values
2226 .into_iter()
2227 .map(|((k, v), _, _)| (T::update(k, v), Diff::MINUS_ONE))
2228 .collect();
2229 updates.push((T::update(key, value), Diff::ONE));
2230 match self.fenceable_token.generate_unfenced_token(self.mode)? {
2232 Some((fence_updates, current_fenceable_token)) => {
2233 updates.extend(fence_updates.clone());
2234 match self.compare_and_append(updates, self.upper).await {
2235 Ok(_) => {
2236 self.fenceable_token = current_fenceable_token;
2237 break prev_value;
2238 }
2239 Err(CompareAndAppendError::Fence(e)) => return Err(e.into()),
2240 Err(e @ CompareAndAppendError::UpperMismatch { .. }) => {
2241 warn!("catalog write failed due to upper mismatch, retrying: {e:?}");
2242 continue;
2243 }
2244 }
2245 }
2246 None => {
2247 self.compare_and_append(updates, self.upper)
2248 .await
2249 .map_err(|e| e.unwrap_fence_error())?;
2250 break prev_value;
2251 }
2252 }
2253 };
2254 Ok(prev_value)
2255 }
2256
2257 #[mz_ore::instrument]
2259 pub(crate) async fn debug_delete<T: Collection>(
2260 &mut self,
2261 key: T::Key,
2262 ) -> Result<(), CatalogError>
2263 where
2264 T::Key: PartialEq + Eq + Debug + Clone,
2265 T::Value: Debug,
2266 {
2267 loop {
2268 let key = key.clone();
2269 let snapshot = self.current_snapshot().await?;
2270 let trace = Trace::from_snapshot(snapshot);
2271 let collection_trace = T::collection_trace(trace);
2272 let mut retractions: Vec<_> = collection_trace
2273 .values
2274 .into_iter()
2275 .filter(|((k, _), _, diff)| {
2276 soft_assert_eq_or_log!(*diff, Diff::ONE, "trace is consolidated");
2277 &key == k
2278 })
2279 .map(|((k, v), _, _)| (T::update(k, v), Diff::MINUS_ONE))
2280 .collect();
2281
2282 match self.fenceable_token.generate_unfenced_token(self.mode)? {
2284 Some((fence_updates, current_fenceable_token)) => {
2285 retractions.extend(fence_updates.clone());
2286 match self.compare_and_append(retractions, self.upper).await {
2287 Ok(_) => {
2288 self.fenceable_token = current_fenceable_token;
2289 break;
2290 }
2291 Err(CompareAndAppendError::Fence(e)) => return Err(e.into()),
2292 Err(e @ CompareAndAppendError::UpperMismatch { .. }) => {
2293 warn!("catalog write failed due to upper mismatch, retrying: {e:?}");
2294 continue;
2295 }
2296 }
2297 }
2298 None => {
2299 self.compare_and_append(retractions, self.upper)
2300 .await
2301 .map_err(|e| e.unwrap_fence_error())?;
2302 break;
2303 }
2304 }
2305 }
2306 Ok(())
2307 }
2308
2309 async fn current_snapshot(
2314 &mut self,
2315 ) -> Result<impl IntoIterator<Item = StateUpdate> + '_, CatalogError> {
2316 self.sync_to_current_upper().await?;
2317 self.consolidate();
2318 Ok(self.snapshot.iter().cloned().map(|(kind, ts, diff)| {
2319 let kind = TryIntoStateUpdateKind::try_into(kind).expect("kind decoding error");
2320 StateUpdate { kind, ts, diff }
2321 }))
2322 }
2323}