1use std::fmt::Debug;
13use std::ops::ControlFlow::{self, Break, Continue};
14use std::sync::Arc;
15use std::time::{Duration, Instant, SystemTime};
16
17use differential_dataflow::difference::Monoid;
18use differential_dataflow::lattice::Lattice;
19use futures::FutureExt;
20use futures::future::{self, BoxFuture};
21use mz_dyncfg::{Config, ConfigSet, ParameterScope};
22use mz_ore::cast::CastFrom;
23use mz_ore::error::ErrorExt;
24#[allow(unused_imports)] use mz_ore::fmt::FormatBuffer;
26use mz_ore::{assert_none, soft_assert_no_log};
27use mz_persist::location::{ExternalError, Indeterminate, SeqNo};
28use mz_persist::retry::Retry;
29use mz_persist_types::schema::SchemaId;
30use mz_persist_types::{Codec, Codec64};
31use semver::Version;
32use timely::PartialOrder;
33use timely::progress::{Antichain, Timestamp};
34use tracing::{Instrument, debug, info, trace_span, warn};
35
36use crate::async_runtime::IsolatedRuntime;
37use crate::batch::INLINE_WRITES_TOTAL_MAX_BYTES;
38use crate::cache::StateCache;
39use crate::cfg::RetryParameters;
40use crate::critical::{CriticalReaderId, Opaque};
41use crate::error::{CodecMismatch, InvalidUsage};
42use crate::internal::apply::Applier;
43use crate::internal::compact::CompactReq;
44use crate::internal::maintenance::{RoutineMaintenance, WriterMaintenance};
45use crate::internal::metrics::{CmdMetrics, Metrics, MetricsRetryStream, RetryMetrics};
46use crate::internal::paths::PartialRollupKey;
47use crate::internal::state::{
48 CompareAndAppendBreak, CriticalReaderState, HandleDebugState, HollowBatch, HollowRollup,
49 IdempotencyToken, LeasedReaderState, NoOpStateTransition, Since, SnapshotErr, StateCollections,
50};
51use crate::internal::state_versions::StateVersions;
52use crate::internal::trace::{ApplyMergeResult, FueledMergeRes};
53use crate::internal::watch::StateWatch;
54use crate::read::LeasedReaderId;
55use crate::rpc::PubSubSender;
56use crate::schema::CaESchema;
57use crate::write::WriterId;
58use crate::{Diagnostics, PersistConfig, ShardId};
59
60#[derive(Debug)]
61pub struct Machine<K, V, T, D> {
62 pub(crate) applier: Applier<K, V, T, D>,
63 pub(crate) isolated_runtime: Arc<IsolatedRuntime>,
64}
65
66impl<K, V, T: Clone, D> Clone for Machine<K, V, T, D> {
68 fn clone(&self) -> Self {
69 Self {
70 applier: self.applier.clone(),
71 isolated_runtime: Arc::clone(&self.isolated_runtime),
72 }
73 }
74}
75
76pub(crate) const CLAIM_UNCLAIMED_COMPACTIONS: Config<bool> = Config::new(
77 "persist_claim_unclaimed_compactions",
78 false,
79 "If an append doesn't result in a compaction request, but there is some uncompacted batch \
80 in state, compact that instead.",
81 ParameterScope::Environment,
82);
83
84pub(crate) const CLAIM_COMPACTION_PERCENT: Config<usize> = Config::new(
85 "persist_claim_compaction_percent",
86 100,
87 "Claim a compaction with the given percent chance, if claiming compactions is enabled. \
88 (If over 100, we'll always claim at least one; for example, if set to 365, we'll claim at least \
89 three and have a 65% chance of claiming a fourth.)",
90 ParameterScope::Environment,
91);
92
93pub(crate) const CLAIM_COMPACTION_MIN_VERSION: Config<String> = Config::new(
94 "persist_claim_compaction_min_version",
95 String::new(),
96 "If set to a valid version string, compact away any earlier versions if possible.",
97 ParameterScope::Environment,
98);
99
100impl<K, V, T, D> Machine<K, V, T, D>
101where
102 K: Debug + Codec,
103 V: Debug + Codec,
104 T: Timestamp + Lattice + Codec64 + Sync,
105 D: Monoid + Codec64,
106{
107 pub async fn new(
108 cfg: PersistConfig,
109 shard_id: ShardId,
110 metrics: Arc<Metrics>,
111 state_versions: Arc<StateVersions>,
112 shared_states: Arc<StateCache>,
113 pubsub_sender: Arc<dyn PubSubSender>,
114 isolated_runtime: Arc<IsolatedRuntime>,
115 diagnostics: Diagnostics,
116 ) -> Result<Self, Box<CodecMismatch>> {
117 let applier = Applier::new(
118 cfg,
119 shard_id,
120 metrics,
121 state_versions,
122 shared_states,
123 pubsub_sender,
124 diagnostics,
125 )
126 .await?;
127 Ok(Machine {
128 applier,
129 isolated_runtime,
130 })
131 }
132
133 pub fn shard_id(&self) -> ShardId {
134 self.applier.shard_id
135 }
136
137 pub fn seqno(&self) -> SeqNo {
138 self.applier.seqno()
139 }
140
141 pub async fn add_rollup_for_current_seqno(&self) -> RoutineMaintenance {
142 let rollup = self.applier.write_rollup_for_state().await;
143 let Some(rollup) = rollup else {
144 return RoutineMaintenance::default();
145 };
146
147 let (applied, maintenance) = self.add_rollup((rollup.seqno, &rollup.to_hollow())).await;
148 if !applied {
149 self.applier
152 .state_versions
153 .delete_rollup(&rollup.shard_id, &rollup.key)
154 .await;
155 }
156 maintenance
157 }
158
159 pub async fn add_rollup(
160 &self,
161 add_rollup: (SeqNo, &HollowRollup),
162 ) -> (bool, RoutineMaintenance) {
163 let mut applied_ever_true = false;
166 let metrics = Arc::clone(&self.applier.metrics);
167 let (_seqno, _applied, maintenance) = self
168 .apply_unbatched_idempotent_cmd(&metrics.cmds.add_rollup, |_, _, state| {
169 let ret = state.add_rollup(add_rollup);
170 if let Continue(applied) = ret {
171 applied_ever_true = applied_ever_true || applied;
172 }
173 ret
174 })
175 .await;
176 (applied_ever_true, maintenance)
177 }
178
179 pub async fn remove_rollups(
180 &self,
181 remove_rollups: &[(SeqNo, PartialRollupKey)],
182 ) -> (Vec<SeqNo>, RoutineMaintenance) {
183 let metrics = Arc::clone(&self.applier.metrics);
184 let (_seqno, removed_rollup_seqnos, maintenance) = self
185 .apply_unbatched_idempotent_cmd(&metrics.cmds.remove_rollups, |_, _, state| {
186 state.remove_rollups(remove_rollups)
187 })
188 .await;
189 (removed_rollup_seqnos, maintenance)
190 }
191
192 pub async fn upgrade_version(&self) -> Result<RoutineMaintenance, Version> {
195 let metrics = Arc::clone(&self.applier.metrics);
196 let (_seqno, upgrade_result, maintenance) = self
197 .apply_unbatched_idempotent_cmd(&metrics.cmds.upgrade_version, |_, cfg, state| {
198 if state.is_tombstone() {
202 return Break(NoOpStateTransition(Ok(())));
203 }
204
205 if state.version <= cfg.build_version {
206 state.version = cfg.build_version.clone();
209 Continue(Ok(()))
210 } else {
211 Break(NoOpStateTransition(Err(state.version.clone())))
212 }
213 })
214 .await;
215
216 match upgrade_result {
217 Ok(()) => Ok(maintenance),
218 Err(version) => {
219 soft_assert_no_log!(
220 maintenance.is_empty(),
221 "should not generate maintenance on failed upgrade"
222 );
223 Err(version)
224 }
225 }
226 }
227
228 pub async fn register_leased_reader(
230 &self,
231 reader_id: &LeasedReaderId,
232 purpose: &str,
233 lease_duration: Duration,
234 use_critical_since: bool,
235 ) -> (LeasedReaderState<T>, RoutineMaintenance) {
236 let metrics = Arc::clone(&self.applier.metrics);
237 let (_seqno, (reader_state, seqno_since), maintenance) = self
238 .apply_unbatched_idempotent_cmd(&metrics.cmds.register, |seqno, cfg, state| {
239 state.register_leased_reader(
240 &cfg.hostname,
241 reader_id,
242 purpose,
243 seqno,
244 lease_duration,
245 (cfg.now)(),
248 use_critical_since,
249 )
250 })
251 .await;
252 mz_ore::soft_assert_no_log!(
261 reader_state.seqno >= seqno_since,
262 "leased reader {} registered with seqno hold {} below the shard's seqno_since {}",
263 reader_id,
264 reader_state.seqno,
265 seqno_since,
266 );
267 (reader_state, maintenance)
268 }
269
270 pub async fn register_critical_reader(
271 &self,
272 reader_id: &CriticalReaderId,
273 default_opaque: Opaque,
274 purpose: &str,
275 ) -> (CriticalReaderState<T>, RoutineMaintenance) {
276 let metrics = Arc::clone(&self.applier.metrics);
277 let (_seqno, state, maintenance) = self
278 .apply_unbatched_idempotent_cmd(&metrics.cmds.register, |_seqno, cfg, state| {
279 state.register_critical_reader(
280 &cfg.hostname,
281 reader_id,
282 default_opaque.clone(),
283 purpose,
284 )
285 })
286 .await;
287 (state, maintenance)
288 }
289
290 pub async fn register_schema(
291 &self,
292 key_schema: &K::Schema,
293 val_schema: &V::Schema,
294 ) -> (Option<SchemaId>, RoutineMaintenance) {
295 let metrics = Arc::clone(&self.applier.metrics);
296 let (_seqno, state, maintenance) = self
297 .apply_unbatched_idempotent_cmd(&metrics.cmds.register, |_seqno, _cfg, state| {
298 state.register_schema::<K, V>(key_schema, val_schema)
299 })
300 .await;
301 (state, maintenance)
302 }
303
304 pub async fn spine_exert(&self, fuel: usize) -> (Vec<CompactReq<T>>, RoutineMaintenance) {
305 if fuel == 0 || self.applier.all_batches().len() < 2 {
307 return (Vec::new(), RoutineMaintenance::default());
308 }
309
310 let metrics = Arc::clone(&self.applier.metrics);
311 let (_seqno, reqs, maintenance) = self
312 .apply_unbatched_idempotent_cmd(&metrics.cmds.spine_exert, |_seqno, _cfg, state| {
313 state.spine_exert(fuel)
314 })
315 .await;
316 let reqs = reqs
317 .into_iter()
318 .map(|req| CompactReq {
319 shard_id: self.shard_id(),
320 desc: req.desc,
321 inputs: req.inputs,
322 })
323 .collect();
324 (reqs, maintenance)
325 }
326
327 pub async fn compare_and_append(
329 &self,
330 batch: &HollowBatch<T>,
331 writer_id: &WriterId,
332 debug_info: &HandleDebugState,
333 ) -> CompareAndAppendRes<T> {
334 let idempotency_token = IdempotencyToken::new();
335 loop {
336 let res = self
337 .compare_and_append_idempotent(
338 batch,
339 writer_id,
340 &idempotency_token,
341 debug_info,
342 None,
343 )
344 .await;
345 match res {
346 CompareAndAppendRes::Success(seqno, maintenance) => {
347 return CompareAndAppendRes::Success(seqno, maintenance);
348 }
349 CompareAndAppendRes::InvalidUsage(x) => {
350 return CompareAndAppendRes::InvalidUsage(x);
351 }
352 CompareAndAppendRes::InlineBackpressure => {
353 return CompareAndAppendRes::InlineBackpressure;
354 }
355 CompareAndAppendRes::UpperMismatch(seqno, _current_upper) => {
356 self.applier.fetch_and_update_state(Some(seqno)).await;
363 let (current_seqno, current_upper) =
364 self.applier.upper(|seqno, upper| (seqno, upper.clone()));
365
366 if ¤t_upper != batch.desc.lower() {
369 return CompareAndAppendRes::UpperMismatch(current_seqno, current_upper);
370 } else {
371 }
374 }
375 }
376 }
377 }
378
379 async fn compare_and_append_idempotent(
380 &self,
381 batch: &HollowBatch<T>,
382 writer_id: &WriterId,
383 idempotency_token: &IdempotencyToken,
384 debug_info: &HandleDebugState,
385 mut indeterminate: Option<Indeterminate>,
389 ) -> CompareAndAppendRes<T> {
390 let metrics = Arc::clone(&self.applier.metrics);
391 let lease_duration_ms = self
392 .applier
393 .cfg
394 .writer_lease_duration
395 .as_millis()
396 .try_into()
397 .expect("reasonable duration");
398 let mut retry = self
481 .applier
482 .metrics
483 .retries
484 .compare_and_append_idempotent
485 .stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
486 let mut writer_was_present = false;
487 loop {
488 let cmd_res = self
489 .applier
490 .apply_unbatched_cmd(&metrics.cmds.compare_and_append, |_, cfg, state| {
491 writer_was_present = state.writers.contains_key(writer_id);
492 state.compare_and_append(
493 batch,
494 writer_id,
495 (cfg.now)(),
498 lease_duration_ms,
499 idempotency_token,
500 debug_info,
501 INLINE_WRITES_TOTAL_MAX_BYTES.get(cfg),
502 if CLAIM_UNCLAIMED_COMPACTIONS.get(cfg) {
503 CLAIM_COMPACTION_PERCENT.get(cfg)
504 } else {
505 0
506 },
507 Version::parse(&CLAIM_COMPACTION_MIN_VERSION.get(cfg))
508 .ok()
509 .as_ref(),
510 )
511 })
512 .await;
513 let (seqno, res, routine) = match cmd_res {
514 Ok(x) => x,
515 Err(err) => {
516 info!(
519 "compare_and_append received an indeterminate error, retrying in {:?}: {}",
520 retry.next_sleep(),
521 err.display_with_causes()
522 );
523 if indeterminate.is_none() {
524 indeterminate = Some(err);
525 }
526 retry = retry.sleep().await;
527 continue;
528 }
529 };
530 match res {
531 Ok(merge_reqs) => {
532 let mut compact_reqs = Vec::with_capacity(merge_reqs.len());
535 for req in merge_reqs {
536 let req = CompactReq {
537 shard_id: self.shard_id(),
538 desc: req.desc,
539 inputs: req.inputs,
540 };
541 compact_reqs.push(req);
542 }
543 let writer_maintenance = WriterMaintenance {
544 routine,
545 compaction: compact_reqs,
546 };
547
548 if !writer_was_present {
549 metrics.state.writer_added.inc();
550 }
551 for part in &batch.parts {
552 if part.is_inline() {
553 let bytes = u64::cast_from(part.inline_bytes());
554 metrics.inline.part_commit_bytes.inc_by(bytes);
555 metrics.inline.part_commit_count.inc();
556 }
557 }
558 return CompareAndAppendRes::Success(seqno, writer_maintenance);
559 }
560 Err(CompareAndAppendBreak::AlreadyCommitted) => {
561 assert!(indeterminate.is_some());
565 self.applier.metrics.cmds.compare_and_append_noop.inc();
566 if !writer_was_present {
567 metrics.state.writer_added.inc();
568 }
569 return CompareAndAppendRes::Success(seqno, WriterMaintenance::default());
570 }
571 Err(CompareAndAppendBreak::InvalidUsage(err)) => {
572 assert_none!(indeterminate);
577 return CompareAndAppendRes::InvalidUsage(err);
578 }
579 Err(CompareAndAppendBreak::InlineBackpressure) => {
580 return CompareAndAppendRes::InlineBackpressure;
583 }
584 Err(CompareAndAppendBreak::Upper {
585 shard_upper,
586 writer_upper,
587 }) => {
588 assert!(
593 PartialOrder::less_equal(&writer_upper, &shard_upper),
594 "{:?} vs {:?}",
595 writer_upper,
596 shard_upper
597 );
598 if PartialOrder::less_than(&writer_upper, batch.desc.upper()) {
599 return CompareAndAppendRes::UpperMismatch(seqno, shard_upper);
603 }
604 if indeterminate.is_none() {
605 return CompareAndAppendRes::UpperMismatch(seqno, shard_upper);
610 }
611 panic!(
621 concat!(
622 "cannot distinguish compare_and_append success or failure ",
623 "caa_lower={:?} caa_upper={:?} writer_upper={:?} shard_upper={:?} err={:?}"
624 ),
625 batch.desc.lower().elements(),
626 batch.desc.upper().elements(),
627 writer_upper.elements(),
628 shard_upper.elements(),
629 indeterminate,
630 );
631 }
632 };
633 }
634 }
635
636 pub async fn downgrade_since(
638 &self,
639 reader_id: &LeasedReaderId,
640 outstanding_seqno: SeqNo,
641 new_since: &Antichain<T>,
642 ) -> (SeqNo, Since<T>, RoutineMaintenance) {
643 let metrics = Arc::clone(&self.applier.metrics);
644 self.apply_unbatched_idempotent_cmd(&metrics.cmds.downgrade_since, |seqno, cfg, state| {
645 state.downgrade_since(reader_id, seqno, outstanding_seqno, new_since, (cfg.now)())
648 })
649 .await
650 }
651
652 pub async fn compare_and_downgrade_since(
653 &self,
654 reader_id: &CriticalReaderId,
655 expected_opaque: &Opaque,
656 (new_opaque, new_since): (&Opaque, &Antichain<T>),
657 ) -> (Result<Since<T>, (Opaque, Since<T>)>, RoutineMaintenance) {
658 let metrics = Arc::clone(&self.applier.metrics);
659 let (_seqno, res, maintenance) = self
660 .apply_unbatched_idempotent_cmd(
661 &metrics.cmds.compare_and_downgrade_since,
662 |_seqno, _cfg, state| {
663 state.compare_and_downgrade_since(
664 reader_id,
665 expected_opaque,
666 (new_opaque, new_since),
667 )
668 },
669 )
670 .await;
671
672 match res {
673 Ok(since) => (Ok(since), maintenance),
674 Err((opaque, since)) => (Err((opaque, since)), maintenance),
675 }
676 }
677
678 pub async fn expire_leased_reader(
679 &self,
680 reader_id: &LeasedReaderId,
681 ) -> (SeqNo, RoutineMaintenance) {
682 let metrics = Arc::clone(&self.applier.metrics);
683 let (seqno, _existed, maintenance) = self
684 .apply_unbatched_idempotent_cmd(&metrics.cmds.expire_reader, |_, _, state| {
685 state.expire_leased_reader(reader_id)
686 })
687 .await;
688 (seqno, maintenance)
689 }
690
691 #[allow(dead_code)] pub async fn expire_critical_reader(
693 &self,
694 reader_id: &CriticalReaderId,
695 ) -> (SeqNo, RoutineMaintenance) {
696 let metrics = Arc::clone(&self.applier.metrics);
697 let (seqno, _existed, maintenance) = self
698 .apply_unbatched_idempotent_cmd(&metrics.cmds.expire_reader, |_, _, state| {
699 state.expire_critical_reader(reader_id)
700 })
701 .await;
702 (seqno, maintenance)
703 }
704
705 pub async fn expire_writer(&self, writer_id: &WriterId) -> (SeqNo, RoutineMaintenance) {
706 let metrics = Arc::clone(&self.applier.metrics);
707 let (seqno, _existed, maintenance) = self
708 .apply_unbatched_idempotent_cmd(&metrics.cmds.expire_writer, |_, _, state| {
709 state.expire_writer(writer_id)
710 })
711 .await;
712 metrics.state.writer_removed.inc();
713 (seqno, maintenance)
714 }
715
716 pub fn is_finalized(&self) -> bool {
717 self.applier.is_finalized()
718 }
719
720 pub fn get_schema(&self, schema_id: SchemaId) -> Option<(K::Schema, V::Schema)> {
722 self.applier.get_schema(schema_id)
723 }
724
725 pub fn latest_schema(&self) -> Option<(SchemaId, K::Schema, V::Schema)> {
727 self.applier.latest_schema()
728 }
729
730 pub fn find_schema(&self, key_schema: &K::Schema, val_schema: &V::Schema) -> Option<SchemaId> {
732 self.applier.find_schema(key_schema, val_schema)
733 }
734
735 pub async fn compare_and_evolve_schema(
739 &self,
740 expected: SchemaId,
741 key_schema: &K::Schema,
742 val_schema: &V::Schema,
743 ) -> (CaESchema<K, V>, RoutineMaintenance) {
744 let metrics = Arc::clone(&self.applier.metrics);
745 let (_seqno, state, maintenance) = self
746 .apply_unbatched_idempotent_cmd(
747 &metrics.cmds.compare_and_evolve_schema,
748 |_seqno, _cfg, state| {
749 state.compare_and_evolve_schema::<K, V>(expected, key_schema, val_schema)
750 },
751 )
752 .await;
753 (state, maintenance)
754 }
755
756 async fn tombstone_step(&self) -> Result<(bool, RoutineMaintenance), InvalidUsage<T>> {
757 let metrics = Arc::clone(&self.applier.metrics);
758 let mut retry = self
759 .applier
760 .metrics
761 .retries
762 .idempotent_cmd
763 .stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
764 loop {
765 let res = self
766 .applier
767 .apply_unbatched_cmd(&metrics.cmds.become_tombstone, |_, _, state| {
768 state.become_tombstone_and_shrink()
769 })
770 .await;
771 let err = match res {
772 Ok((_seqno, Ok(()), maintenance)) => return Ok((true, maintenance)),
773 Ok((_seqno, Err(NoOpStateTransition(())), maintenance)) => {
774 return Ok((false, maintenance));
775 }
776 Err(err) => err,
777 };
778 if retry.attempt() >= INFO_MIN_ATTEMPTS {
779 info!(
780 "become_tombstone received an indeterminate error, retrying in {:?}: {}",
781 retry.next_sleep(),
782 err
783 );
784 } else {
785 debug!(
786 "become_tombstone received an indeterminate error, retrying in {:?}: {}",
787 retry.next_sleep(),
788 err
789 );
790 }
791 retry = retry.sleep().await;
792 }
793 }
794
795 pub async fn become_tombstone(&self) -> Result<RoutineMaintenance, InvalidUsage<T>> {
796 self.applier.check_since_upper_both_empty()?;
797
798 let mut maintenance = RoutineMaintenance::default();
799
800 loop {
801 let (made_progress, more_maintenance) = self.tombstone_step().await?;
802 maintenance.merge(more_maintenance);
803 if !made_progress {
804 break;
805 }
806 }
807
808 Ok(maintenance)
809 }
810
811 pub async fn unleased_snapshot(
815 &self,
816 as_of: &Antichain<T>,
817 ) -> Result<Vec<HollowBatch<T>>, Since<T>> {
818 if let Ok(data) = self.applier.snapshot(as_of) {
819 return Ok(data);
820 }
821 let mut watch = self.applier.watch();
822 self.wait_for_upper_past(
823 as_of,
824 &mut watch,
825 None,
826 &self.applier.metrics.retries.snapshot,
827 RetryParameters::persist_defaults(),
828 )
829 .await;
830 match self.applier.snapshot(as_of) {
831 Ok(data) => Ok(data),
832 Err(SnapshotErr::AsOfHistoricalDistinctionsLost(since)) => Err(since),
833 Err(SnapshotErr::AsOfNotYetAvailable(seqno, upper)) => {
834 panic!(
835 "waited for upper past {as_of:?}, but at latest seqno {seqno:?} the frontier was only {upper:?}",
836 as_of = as_of.elements(),
837 upper = upper.0.elements(),
838 )
839 }
840 }
841 }
842
843 pub fn verify_listen(&self, as_of: &Antichain<T>) -> Result<(), Since<T>> {
845 self.applier.verify_listen(as_of)
846 }
847
848 pub async fn wait_for_upper_past(
849 &self,
850 frontier: &Antichain<T>,
851 watch: &mut StateWatch<K, V, T, D>,
852 reader_id: Option<&LeasedReaderId>,
853 metrics: &RetryMetrics,
854 retry: RetryParameters,
855 ) {
856 let start = Instant::now();
857 let wait_for_seqno_past = self.applier.upper(|seqno, upper| {
858 if PartialOrder::less_than(frontier, upper) {
859 None
860 } else {
861 Some((seqno, upper.clone()))
862 }
863 });
864 let Some((mut seqno, mut upper)) = wait_for_seqno_past else {
865 return;
867 };
868
869 let sleeps = metrics.stream(retry.into_retry(SystemTime::now()).into_retry_stream());
872
873 enum Wake<'a, K, V, T, D> {
874 Watch(&'a mut StateWatch<K, V, T, D>),
875 Sleep(MetricsRetryStream),
876 }
877 let mut watch_fut = std::pin::pin!(
878 watch
879 .wait_for_upper_past(frontier)
880 .map(Wake::Watch)
881 .instrument(trace_span!("snapshot::watch"))
882 );
883 let mut sleep_fut = std::pin::pin!(
884 sleeps
885 .sleep()
886 .map(Wake::Sleep)
887 .instrument(trace_span!("snapshot::sleep"))
888 );
889
890 let mut logged_at_info = false;
894 loop {
895 if !logged_at_info
899 && start.elapsed() >= Duration::from_millis(1024)
900 && metrics.name.as_str() == "snapshot"
901 {
902 logged_at_info = true;
903 info!(
904 shard_id =? self.shard_id(),
905 shard_name =? self.applier.shard_metrics.name,
906 reader_id =? reader_id,
907 wait_frontier =? frontier.elements(),
908 current_upper =? upper.elements(),
909 current_seqno =? seqno,
910 wait_for = &metrics.name,
911 "desired upper not yet available",
912 );
913 } else {
914 debug!(
915 shard_id =? self.shard_id(),
916 shard_name =? self.applier.shard_metrics.name,
917 reader_id =? reader_id,
918 wait_frontier =? frontier.elements(),
919 current_upper =? upper.elements(),
920 current_seqno =? seqno,
921 wait_for = &metrics.name,
922 "desired upper not yet available",
923 );
924 }
925
926 let wake = match future::select(watch_fut.as_mut(), sleep_fut.as_mut()).await {
927 future::Either::Left((wake, _)) => wake,
928 future::Either::Right((wake, _)) => wake,
929 };
930 match &wake {
934 Wake::Watch(_) => self.applier.metrics.watch.wait_woken_via_watch.inc(),
935 Wake::Sleep(_) => {
936 self.applier.metrics.watch.wait_woken_via_sleep.inc();
937 self.applier.fetch_and_update_state(Some(seqno)).await;
938 }
939 }
940
941 let wait_for_seqno_past = self.applier.upper(|seqno, upper| {
942 if PartialOrder::less_than(frontier, upper) {
943 None
944 } else {
945 Some((seqno, upper.clone()))
946 }
947 });
948 match wait_for_seqno_past {
949 None => {
950 match &wake {
951 Wake::Watch(_) => self.applier.metrics.watch.wait_resolved_via_watch.inc(),
952 Wake::Sleep(_) => self.applier.metrics.watch.wait_resolved_via_sleep.inc(),
953 }
954 return;
955 }
956 Some((s, u)) => {
957 seqno = s;
958 upper = u;
959 }
960 };
961
962 match wake {
965 Wake::Watch(watch) => {
966 watch_fut.set(
967 watch
968 .wait_for_upper_past(frontier)
969 .map(Wake::Watch)
970 .instrument(trace_span!("snapshot::watch")),
971 );
972 }
973 Wake::Sleep(sleeps) => {
974 debug!(
975 shard_id =? self.shard_id(),
976 shard_name =? self.applier.shard_metrics.name,
977 reader_id =? reader_id,
978 wait_frontier =? frontier.elements(),
979 current_upper =? upper.elements(),
980 current_seqno =? seqno,
981 wait_for = &metrics.name,
982 "didn't find new data, retrying in {:?}",
983 sleeps.next_sleep(),
984 );
985 sleep_fut.set(
986 sleeps
987 .sleep()
988 .map(Wake::Sleep)
989 .instrument(trace_span!("snapshot::sleep")),
990 );
991 }
992 }
993 }
994 }
995
996 async fn apply_unbatched_idempotent_cmd<
997 R,
998 WorkFn: FnMut(
999 SeqNo,
1000 &PersistConfig,
1001 &mut StateCollections<T>,
1002 ) -> ControlFlow<NoOpStateTransition<R>, R>,
1003 >(
1004 &self,
1005 cmd: &CmdMetrics,
1006 mut work_fn: WorkFn,
1007 ) -> (SeqNo, R, RoutineMaintenance) {
1008 let mut retry = self
1009 .applier
1010 .metrics
1011 .retries
1012 .idempotent_cmd
1013 .stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
1014 loop {
1015 match self.applier.apply_unbatched_cmd(cmd, &mut work_fn).await {
1016 Ok((seqno, x, maintenance)) => match x {
1017 Ok(x) => {
1018 return (seqno, x, maintenance);
1019 }
1020 Err(NoOpStateTransition(x)) => {
1021 return (seqno, x, maintenance);
1022 }
1023 },
1024 Err(err) => {
1025 if retry.attempt() >= INFO_MIN_ATTEMPTS {
1026 info!(
1027 "apply_unbatched_idempotent_cmd {} received an indeterminate error, retrying in {:?}: {}",
1028 cmd.name,
1029 retry.next_sleep(),
1030 err
1031 );
1032 } else {
1033 debug!(
1034 "apply_unbatched_idempotent_cmd {} received an indeterminate error, retrying in {:?}: {}",
1035 cmd.name,
1036 retry.next_sleep(),
1037 err
1038 );
1039 }
1040 retry = retry.sleep().await;
1041 continue;
1042 }
1043 }
1044 }
1045 }
1046}
1047
1048impl<K, V, T, D> Machine<K, V, T, D>
1049where
1050 K: Debug + Codec,
1051 V: Debug + Codec,
1052 T: Timestamp + Lattice + Codec64 + Sync,
1053 D: Monoid + Codec64 + PartialEq,
1054{
1055 pub async fn merge_res(
1056 &self,
1057 res: &FueledMergeRes<T>,
1058 ) -> (ApplyMergeResult, RoutineMaintenance) {
1059 let metrics = Arc::clone(&self.applier.metrics);
1060
1061 let mut merge_result_ever_applied = ApplyMergeResult::NotAppliedNoMatch;
1090 let (_seqno, _apply_merge_result, maintenance) = self
1091 .apply_unbatched_idempotent_cmd(&metrics.cmds.merge_res, |_, _, state| {
1092 let ret = state.apply_merge_res::<D>(res, &Arc::clone(&metrics).columnar);
1093 if let Continue(result) = ret {
1094 if result.applied() {
1096 merge_result_ever_applied = result;
1097 }
1098 if result.matched() && !result.applied() && !merge_result_ever_applied.applied()
1101 {
1102 merge_result_ever_applied = result;
1103 }
1104 }
1105 ret
1106 })
1107 .await;
1108 (merge_result_ever_applied, maintenance)
1109 }
1110}
1111
1112pub(crate) struct ExpireFn(
1113 pub(crate) Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send + Sync + 'static>,
1118);
1119
1120impl Debug for ExpireFn {
1121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1122 f.debug_struct("ExpireFn").finish_non_exhaustive()
1123 }
1124}
1125
1126#[derive(Debug)]
1127pub(crate) enum CompareAndAppendRes<T> {
1128 Success(SeqNo, WriterMaintenance<T>),
1129 InvalidUsage(InvalidUsage<T>),
1130 UpperMismatch(SeqNo, Antichain<T>),
1131 InlineBackpressure,
1132}
1133
1134#[cfg(test)]
1135impl<T: Debug> CompareAndAppendRes<T> {
1136 #[track_caller]
1137 fn unwrap(self) -> (SeqNo, WriterMaintenance<T>) {
1138 match self {
1139 CompareAndAppendRes::Success(seqno, maintenance) => (seqno, maintenance),
1140 x => panic!("{:?}", x),
1141 }
1142 }
1143}
1144
1145pub(crate) const NEXT_LISTEN_BATCH_RETRYER_FIXED_SLEEP: Config<Duration> = Config::new(
1146 "persist_next_listen_batch_retryer_fixed_sleep",
1147 Duration::from_millis(1200), "\
1149 The fixed sleep when polling for new batches from a Listen or Subscribe. Skipped if zero.",
1150 ParameterScope::Environment,
1151);
1152
1153pub(crate) const NEXT_LISTEN_BATCH_RETRYER_INITIAL_BACKOFF: Config<Duration> = Config::new(
1154 "persist_next_listen_batch_retryer_initial_backoff",
1155 Duration::from_millis(100), "The initial backoff when polling for new batches from a Listen or Subscribe.",
1157 ParameterScope::Environment,
1158);
1159
1160pub(crate) const NEXT_LISTEN_BATCH_RETRYER_MULTIPLIER: Config<u32> = Config::new(
1161 "persist_next_listen_batch_retryer_multiplier",
1162 2,
1163 "The backoff multiplier when polling for new batches from a Listen or Subscribe.",
1164 ParameterScope::Environment,
1165);
1166
1167pub(crate) const NEXT_LISTEN_BATCH_RETRYER_CLAMP: Config<Duration> = Config::new(
1168 "persist_next_listen_batch_retryer_clamp",
1169 Duration::from_secs(16), "The backoff clamp duration when polling for new batches from a Listen or Subscribe.",
1171 ParameterScope::Environment,
1172);
1173
1174pub(crate) fn next_listen_batch_retry_params(cfg: &ConfigSet) -> RetryParameters {
1175 RetryParameters {
1176 fixed_sleep: NEXT_LISTEN_BATCH_RETRYER_FIXED_SLEEP.get(cfg),
1177 initial_backoff: NEXT_LISTEN_BATCH_RETRYER_INITIAL_BACKOFF.get(cfg),
1178 multiplier: NEXT_LISTEN_BATCH_RETRYER_MULTIPLIER.get(cfg),
1179 clamp: NEXT_LISTEN_BATCH_RETRYER_CLAMP.get(cfg),
1180 }
1181}
1182
1183pub const INFO_MIN_ATTEMPTS: usize = 3;
1184
1185pub const WARN_MIN_ATTEMPTS: usize = 30;
1190
1191pub async fn retry_external<R, F, WorkFn>(metrics: &RetryMetrics, mut work_fn: WorkFn) -> R
1192where
1193 F: std::future::Future<Output = Result<R, ExternalError>>,
1194 WorkFn: FnMut() -> F,
1195{
1196 let mut retry = metrics.stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
1197 loop {
1198 match work_fn().await {
1199 Ok(x) => {
1200 if retry.attempt() > 0 {
1201 debug!(
1202 "external operation {} succeeded after failing at least once",
1203 metrics.name,
1204 );
1205 }
1206 return x;
1207 }
1208 Err(err) => {
1209 if retry.attempt() >= WARN_MIN_ATTEMPTS {
1210 warn!(
1211 "external operation {} has failed {} times, retrying in {:?}: {}",
1212 metrics.name,
1213 retry.attempt(),
1214 retry.next_sleep(),
1215 err.display_with_causes()
1216 );
1217 } else if retry.attempt() >= INFO_MIN_ATTEMPTS {
1218 info!(
1219 "external operation {} failed, retrying in {:?}: {}",
1220 metrics.name,
1221 retry.next_sleep(),
1222 err.display_with_causes()
1223 );
1224 } else {
1225 debug!(
1226 "external operation {} failed, retrying in {:?}: {}",
1227 metrics.name,
1228 retry.next_sleep(),
1229 err.display_with_causes()
1230 );
1231 }
1232 retry = retry.sleep().await;
1233 }
1234 }
1235 }
1236}
1237
1238pub async fn retry_determinate<R, F, WorkFn>(
1239 metrics: &RetryMetrics,
1240 mut work_fn: WorkFn,
1241) -> Result<R, Indeterminate>
1242where
1243 F: std::future::Future<Output = Result<R, ExternalError>>,
1244 WorkFn: FnMut() -> F,
1245{
1246 let mut retry = metrics.stream(Retry::persist_defaults(SystemTime::now()).into_retry_stream());
1247 loop {
1248 match work_fn().await {
1249 Ok(x) => {
1250 if retry.attempt() > 0 {
1251 debug!(
1252 "external operation {} succeeded after failing at least once",
1253 metrics.name,
1254 );
1255 }
1256 return Ok(x);
1257 }
1258 Err(ExternalError::Determinate(err)) => {
1259 debug!(
1268 "external operation {} failed, retrying in {:?}: {}",
1269 metrics.name,
1270 retry.next_sleep(),
1271 err.display_with_causes()
1272 );
1273 retry = retry.sleep().await;
1274 continue;
1275 }
1276 Err(ExternalError::Indeterminate(x)) => return Err(x),
1277 }
1278 }
1279}
1280
1281#[cfg(test)]
1282pub mod datadriven {
1283 use std::collections::{BTreeMap, BTreeSet};
1284 use std::pin::pin;
1285 use std::sync::{Arc, LazyLock};
1286
1287 use anyhow::anyhow;
1288 use differential_dataflow::consolidation::consolidate_updates;
1289 use differential_dataflow::trace::Description;
1290 use futures::StreamExt;
1291 use mz_dyncfg::{ConfigUpdates, ConfigVal};
1292 use mz_persist::indexed::encoding::BlobTraceBatchPart;
1293 use mz_persist_types::codec_impls::{StringSchema, UnitSchema};
1294
1295 use crate::batch::{
1296 BLOB_TARGET_SIZE, Batch, BatchBuilder, BatchBuilderConfig, BatchBuilderInternal,
1297 BatchParts, validate_truncate_batch,
1298 };
1299 use crate::cfg::COMPACTION_MEMORY_BOUND_BYTES;
1300 use crate::fetch::{EncodedPart, FetchConfig};
1301 use crate::internal::compact::{CompactConfig, CompactReq, Compactor};
1302 use crate::internal::datadriven::DirectiveArgs;
1303 use crate::internal::encoding::Schemas;
1304 use crate::internal::gc::GcReq;
1305 use crate::internal::paths::{BlobKey, BlobKeyPrefix, PartialBlobKey};
1306 use crate::internal::state::{BatchPart, RunOrder, RunPart, Upper};
1307 use crate::internal::state_versions::EncodedRollup;
1308 use crate::internal::trace::{CompactionInput, IdHollowBatch, SpineId};
1309 use crate::read::{Listen, ListenEvent, READER_LEASE_DURATION};
1310 use crate::rpc::NoopPubSubSender;
1311 use crate::tests::new_test_client;
1312 use crate::write::COMBINE_INLINE_WRITES;
1313 use crate::{GarbageCollector, PersistClient};
1314
1315 use super::*;
1316
1317 static SCHEMAS: LazyLock<Schemas<String, ()>> = LazyLock::new(|| Schemas {
1318 id: Some(SchemaId(0)),
1319 key: Arc::new(StringSchema),
1320 val: Arc::new(UnitSchema),
1321 });
1322
1323 #[derive(Debug)]
1325 pub struct MachineState {
1326 pub client: PersistClient,
1327 pub shard_id: ShardId,
1328 pub state_versions: Arc<StateVersions>,
1329 pub machine: Machine<String, (), u64, i64>,
1330 pub gc: GarbageCollector<String, (), u64, i64>,
1331 pub batches: BTreeMap<String, IdHollowBatch<u64>>,
1332 pub next_id: usize,
1333 pub rollups: BTreeMap<String, EncodedRollup>,
1334 pub listens: BTreeMap<String, Listen<String, (), u64, i64>>,
1335 pub routine: Vec<RoutineMaintenance>,
1336 pub compactions: BTreeMap<String, CompactReq<u64>>,
1337 }
1338
1339 impl MachineState {
1340 pub async fn new(dyncfgs: &ConfigUpdates) -> Self {
1341 let shard_id = ShardId::new();
1342 let client = new_test_client(dyncfgs).await;
1343 client
1346 .cfg
1347 .set_config(&BLOB_TARGET_SIZE, *BLOB_TARGET_SIZE.default());
1348 client.cfg.set_config(&COMBINE_INLINE_WRITES, false);
1352 let state_versions = Arc::new(StateVersions::new(
1353 client.cfg.clone(),
1354 Arc::clone(&client.consensus),
1355 Arc::clone(&client.blob),
1356 Arc::clone(&client.metrics),
1357 ));
1358 let machine = Machine::new(
1359 client.cfg.clone(),
1360 shard_id,
1361 Arc::clone(&client.metrics),
1362 Arc::clone(&state_versions),
1363 Arc::clone(&client.shared_states),
1364 Arc::new(NoopPubSubSender),
1365 Arc::clone(&client.isolated_runtime),
1366 Diagnostics::for_tests(),
1367 )
1368 .await
1369 .expect("codecs should match");
1370 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&client.isolated_runtime));
1371 MachineState {
1372 shard_id,
1373 client,
1374 state_versions,
1375 machine,
1376 gc,
1377 batches: BTreeMap::default(),
1378 rollups: BTreeMap::default(),
1379 listens: BTreeMap::default(),
1380 routine: Vec::new(),
1381 compactions: BTreeMap::default(),
1382 next_id: 0,
1383 }
1384 }
1385
1386 fn to_batch(&self, hollow: HollowBatch<u64>) -> Batch<String, (), u64, i64> {
1387 Batch::new(
1388 true,
1389 Arc::clone(&self.client.metrics),
1390 Arc::clone(&self.client.blob),
1391 self.client.metrics.shards.shard(&self.shard_id, "test"),
1392 self.client.cfg.build_version.clone(),
1393 (
1394 <String>::encode_schema(&*SCHEMAS.key),
1395 <()>::encode_schema(&*SCHEMAS.val),
1396 ),
1397 hollow,
1398 )
1399 }
1400 }
1401
1402 pub async fn consensus_scan(
1405 datadriven: &MachineState,
1406 args: DirectiveArgs<'_>,
1407 ) -> Result<String, anyhow::Error> {
1408 let from = args.expect("from_seqno");
1409
1410 let mut states = datadriven
1411 .state_versions
1412 .fetch_all_live_states::<u64>(datadriven.shard_id)
1413 .await
1414 .expect("should only be called on an initialized shard")
1415 .check_ts_codec()
1416 .expect("shard codecs should not change");
1417 let mut s = String::new();
1418 while let Some(x) = states.next(|_| {}) {
1419 if x.seqno < from {
1420 continue;
1421 }
1422 let rollups: Vec<_> = x
1423 .collections
1424 .rollups
1425 .keys()
1426 .map(|seqno| seqno.to_string())
1427 .collect();
1428 let batches: Vec<_> = x
1429 .collections
1430 .trace
1431 .batches()
1432 .filter(|b| !b.is_empty())
1433 .filter_map(|b| {
1434 datadriven
1435 .batches
1436 .iter()
1437 .find(|(_, original_batch)| original_batch.batch.parts == b.parts)
1438 .map(|(batch_name, _)| batch_name.to_owned())
1439 })
1440 .collect();
1441 write!(
1442 s,
1443 "seqno={} batches={} rollups={}\n",
1444 x.seqno,
1445 batches.join(","),
1446 rollups.join(","),
1447 );
1448 }
1449 Ok(s)
1450 }
1451
1452 pub async fn consensus_truncate(
1453 datadriven: &MachineState,
1454 args: DirectiveArgs<'_>,
1455 ) -> Result<String, anyhow::Error> {
1456 let to = args.expect("to_seqno");
1457 let removed = datadriven
1458 .client
1459 .consensus
1460 .truncate(&datadriven.shard_id.to_string(), to)
1461 .await
1462 .expect("valid truncation");
1463 Ok(format!("{:?}\n", removed))
1464 }
1465
1466 pub async fn blob_scan_batches(
1467 datadriven: &MachineState,
1468 _args: DirectiveArgs<'_>,
1469 ) -> Result<String, anyhow::Error> {
1470 let key_prefix = BlobKeyPrefix::Shard(&datadriven.shard_id).to_string();
1471
1472 let mut s = String::new();
1473 let () = datadriven
1474 .state_versions
1475 .blob
1476 .list_keys_and_metadata(&key_prefix, &mut |x| {
1477 let (_, key) = BlobKey::parse_ids(x.key).expect("key should be valid");
1478 if let PartialBlobKey::Batch(_, _) = key {
1479 write!(s, "{}: {}b\n", x.key, x.size_in_bytes);
1480 }
1481 })
1482 .await?;
1483 Ok(s)
1484 }
1485
1486 #[allow(clippy::unused_async)]
1487 pub async fn shard_desc(
1488 datadriven: &MachineState,
1489 _args: DirectiveArgs<'_>,
1490 ) -> Result<String, anyhow::Error> {
1491 Ok(format!(
1492 "since={:?} upper={:?}\n",
1493 datadriven.machine.applier.since().elements(),
1494 datadriven.machine.applier.clone_upper().elements()
1495 ))
1496 }
1497
1498 pub async fn downgrade_since(
1499 datadriven: &mut MachineState,
1500 args: DirectiveArgs<'_>,
1501 ) -> Result<String, anyhow::Error> {
1502 let since = args.expect_antichain("since");
1503 let seqno = args
1504 .optional("seqno")
1505 .unwrap_or_else(|| datadriven.machine.seqno());
1506 let reader_id = args.expect("reader_id");
1507 let (_, since, routine) = datadriven
1508 .machine
1509 .downgrade_since(&reader_id, seqno, &since)
1510 .await;
1511 datadriven.routine.push(routine);
1512 Ok(format!(
1513 "{} {:?}\n",
1514 datadriven.machine.seqno(),
1515 since.0.elements()
1516 ))
1517 }
1518
1519 #[allow(clippy::unused_async)]
1520 pub async fn dyncfg(
1521 datadriven: &MachineState,
1522 args: DirectiveArgs<'_>,
1523 ) -> Result<String, anyhow::Error> {
1524 let mut updates = ConfigUpdates::default();
1525 for x in args.input.trim().split('\n') {
1526 match x.split(' ').collect::<Vec<_>>().as_slice() {
1527 &[name, val] => {
1528 let config = datadriven
1529 .client
1530 .cfg
1531 .entries()
1532 .find(|x| x.name() == name)
1533 .ok_or_else(|| anyhow!("unknown dyncfg: {}", name))?;
1534 match config.val() {
1535 ConfigVal::Usize(_) => {
1536 let val = val.parse().map_err(anyhow::Error::new)?;
1537 updates.add_dynamic(name, ConfigVal::Usize(val));
1538 }
1539 ConfigVal::Bool(_) => {
1540 let val = val.parse().map_err(anyhow::Error::new)?;
1541 updates.add_dynamic(name, ConfigVal::Bool(val));
1542 }
1543 x => unimplemented!("dyncfg type: {:?}", x),
1544 }
1545 }
1546 x => return Err(anyhow!("expected `name val` got: {:?}", x)),
1547 }
1548 }
1549 updates.apply(&datadriven.client.cfg);
1550
1551 Ok("ok\n".to_string())
1552 }
1553
1554 pub async fn compare_and_downgrade_since(
1555 datadriven: &mut MachineState,
1556 args: DirectiveArgs<'_>,
1557 ) -> Result<String, anyhow::Error> {
1558 let expected_opaque: u64 = args.expect("expect_opaque");
1559 let new_opaque: u64 = args.expect("opaque");
1560 let new_since = args.expect_antichain("since");
1561 let reader_id = args.expect("reader_id");
1562 let (res, routine) = datadriven
1563 .machine
1564 .compare_and_downgrade_since(
1565 &reader_id,
1566 &Opaque::encode(&expected_opaque),
1567 (&Opaque::encode(&new_opaque), &new_since),
1568 )
1569 .await;
1570 datadriven.routine.push(routine);
1571 let since = res.map_err(|(opaque, since)| {
1572 anyhow!(
1573 "mismatch: opaque={} since={:?}",
1574 opaque.decode::<u64>(),
1575 since.0.elements()
1576 )
1577 })?;
1578 Ok(format!(
1579 "{} {} {:?}\n",
1580 datadriven.machine.seqno(),
1581 new_opaque,
1582 since.0.elements()
1583 ))
1584 }
1585
1586 pub async fn write_rollup(
1587 datadriven: &mut MachineState,
1588 args: DirectiveArgs<'_>,
1589 ) -> Result<String, anyhow::Error> {
1590 let output = args.expect_str("output");
1591
1592 let rollup = datadriven
1593 .machine
1594 .applier
1595 .write_rollup_for_state()
1596 .await
1597 .expect("rollup");
1598
1599 datadriven
1600 .rollups
1601 .insert(output.to_string(), rollup.clone());
1602
1603 Ok(format!(
1604 "state={} diffs=[{}, {})\n",
1605 rollup.seqno,
1606 rollup._desc.lower().first().expect("seqno"),
1607 rollup._desc.upper().first().expect("seqno"),
1608 ))
1609 }
1610
1611 pub async fn add_rollup(
1612 datadriven: &mut MachineState,
1613 args: DirectiveArgs<'_>,
1614 ) -> Result<String, anyhow::Error> {
1615 let input = args.expect_str("input");
1616 let rollup = datadriven
1617 .rollups
1618 .get(input)
1619 .expect("unknown batch")
1620 .clone();
1621
1622 let (applied, maintenance) = datadriven
1623 .machine
1624 .add_rollup((rollup.seqno, &rollup.to_hollow()))
1625 .await;
1626
1627 if !applied {
1628 return Err(anyhow!("failed to apply rollup for: {}", rollup.seqno));
1629 }
1630
1631 datadriven.routine.push(maintenance);
1632 Ok(format!("{}\n", datadriven.machine.seqno()))
1633 }
1634
1635 pub async fn write_batch(
1636 datadriven: &mut MachineState,
1637 args: DirectiveArgs<'_>,
1638 ) -> Result<String, anyhow::Error> {
1639 let output = args.expect_str("output");
1640 let lower = args.expect_antichain("lower");
1641 let upper = args.expect_antichain("upper");
1642 assert!(PartialOrder::less_than(&lower, &upper));
1643 let since = args
1644 .optional_antichain("since")
1645 .unwrap_or_else(|| Antichain::from_elem(0));
1646 let target_size = args.optional("target_size");
1647 let parts_size_override = args.optional("parts_size_override");
1648 let consolidate = args.optional("consolidate").unwrap_or(true);
1649 let mut updates: Vec<_> = args
1650 .input
1651 .split('\n')
1652 .flat_map(DirectiveArgs::parse_update)
1653 .collect();
1654
1655 let mut cfg = BatchBuilderConfig::new(&datadriven.client.cfg, datadriven.shard_id);
1656 if let Some(target_size) = target_size {
1657 cfg.blob_target_size = target_size;
1658 };
1659 if consolidate {
1660 consolidate_updates(&mut updates);
1661 }
1662 let run_order = if consolidate {
1663 cfg.preferred_order
1664 } else {
1665 RunOrder::Unordered
1666 };
1667 let parts = BatchParts::new_ordered::<i64>(
1668 cfg.clone(),
1669 run_order,
1670 Arc::clone(&datadriven.client.metrics),
1671 Arc::clone(&datadriven.machine.applier.shard_metrics),
1672 datadriven.shard_id,
1673 Arc::clone(&datadriven.client.blob),
1674 Arc::clone(&datadriven.client.isolated_runtime),
1675 &datadriven.client.metrics.user,
1676 );
1677 let builder = BatchBuilderInternal::new(
1678 cfg.clone(),
1679 parts,
1680 Arc::clone(&datadriven.client.metrics),
1681 SCHEMAS.clone(),
1682 Arc::clone(&datadriven.client.blob),
1683 datadriven.shard_id.clone(),
1684 datadriven.client.cfg.build_version.clone(),
1685 );
1686 let mut builder = BatchBuilder::new(builder, Description::new(lower, upper.clone(), since));
1687 for ((k, ()), t, d) in updates {
1688 builder.add(&k, &(), &t, &d).await.expect("invalid batch");
1689 }
1690 let mut batch = builder.finish(upper).await?;
1691 if parts_size_override.is_some() {
1694 batch
1695 .flush_to_blob(
1696 &cfg,
1697 &datadriven.client.metrics.user,
1698 &datadriven.client.isolated_runtime,
1699 &SCHEMAS,
1700 )
1701 .await;
1702 }
1703 let batch = batch.into_hollow_batch();
1704 let batch = IdHollowBatch {
1705 batch: Arc::new(batch),
1706 id: SpineId(datadriven.next_id, datadriven.next_id + 1),
1707 };
1708 datadriven.next_id += 1;
1709
1710 if let Some(size) = parts_size_override {
1711 let mut batch = batch.clone();
1712 let mut hollow_batch = (*batch.batch).clone();
1713 for part in hollow_batch.parts.iter_mut() {
1714 match part {
1715 RunPart::Many(run) => run.max_part_bytes = size,
1716 RunPart::Single(BatchPart::Hollow(part)) => part.encoded_size_bytes = size,
1717 RunPart::Single(BatchPart::Inline { .. }) => unreachable!("flushed out above"),
1718 }
1719 }
1720 batch.batch = Arc::new(hollow_batch);
1721 datadriven.batches.insert(output.to_owned(), batch);
1722 } else {
1723 datadriven.batches.insert(output.to_owned(), batch.clone());
1724 }
1725 Ok(format!(
1726 "parts={} len={}\n",
1727 batch.batch.part_count(),
1728 batch.batch.len
1729 ))
1730 }
1731
1732 pub async fn fetch_batch(
1733 datadriven: &MachineState,
1734 args: DirectiveArgs<'_>,
1735 ) -> Result<String, anyhow::Error> {
1736 let input = args.expect_str("input");
1737 let stats = args.optional_str("stats");
1738 let batch = datadriven.batches.get(input).expect("unknown batch");
1739
1740 let mut s = String::new();
1741 let mut stream = pin!(
1742 batch
1743 .batch
1744 .part_stream(
1745 datadriven.shard_id,
1746 &*datadriven.state_versions.blob,
1747 &*datadriven.state_versions.metrics
1748 )
1749 .enumerate()
1750 );
1751 while let Some((idx, part)) = stream.next().await {
1752 let part = &*part?;
1753 write!(s, "<part {idx}>\n");
1754
1755 let lower = match part {
1756 BatchPart::Inline { updates, .. } => {
1757 let updates: BlobTraceBatchPart<u64> =
1758 updates.decode(&datadriven.client.metrics.columnar)?;
1759 updates.structured_key_lower()
1760 }
1761 other @ BatchPart::Hollow(_) => other.structured_key_lower(),
1762 };
1763
1764 if let Some(lower) = lower {
1765 if stats == Some("lower") {
1766 writeln!(s, "<key lower={}>", lower.get())
1767 }
1768 }
1769
1770 match part {
1771 BatchPart::Hollow(part) => {
1772 let blob_batch = datadriven
1773 .client
1774 .blob
1775 .get(&part.key.complete(&datadriven.shard_id))
1776 .await;
1777 match blob_batch {
1778 Ok(Some(_)) | Err(_) => {}
1779 Ok(None) => {
1782 s.push_str("<empty>\n");
1783 continue;
1784 }
1785 };
1786 }
1787 BatchPart::Inline { .. } => {}
1788 };
1789 let part = EncodedPart::fetch(
1790 &FetchConfig::from_persist_config(&datadriven.client.cfg),
1791 &datadriven.shard_id,
1792 datadriven.client.blob.as_ref(),
1793 datadriven.client.metrics.as_ref(),
1794 datadriven.machine.applier.shard_metrics.as_ref(),
1795 &datadriven.client.metrics.read.batch_fetcher,
1796 &batch.batch.desc,
1797 part,
1798 )
1799 .await
1800 .expect("invalid batch part");
1801 let part = part
1802 .normalize(&datadriven.client.metrics.columnar)
1803 .into_part::<String, ()>(&*SCHEMAS.key, &*SCHEMAS.val);
1804
1805 for ((k, _v), t, d) in part
1806 .decode_iter::<_, _, u64, i64>(&*SCHEMAS.key, &*SCHEMAS.val)
1807 .expect("valid schemas")
1808 {
1809 writeln!(s, "{k} {t} {d}");
1810 }
1811 }
1812 if !s.is_empty() {
1813 for (idx, (_meta, run)) in batch.batch.runs().enumerate() {
1814 write!(s, "<run {idx}>\n");
1815 for part in run {
1816 let part_idx = batch
1817 .batch
1818 .parts
1819 .iter()
1820 .position(|p| p == part)
1821 .expect("part should exist");
1822 write!(s, "part {part_idx}\n");
1823 }
1824 }
1825 }
1826 Ok(s)
1827 }
1828
1829 #[allow(clippy::unused_async)]
1830 pub async fn truncate_batch_desc(
1831 datadriven: &mut MachineState,
1832 args: DirectiveArgs<'_>,
1833 ) -> Result<String, anyhow::Error> {
1834 let input = args.expect_str("input");
1835 let output = args.expect_str("output");
1836 let lower = args.expect_antichain("lower");
1837 let upper = args.expect_antichain("upper");
1838
1839 let batch = datadriven
1840 .batches
1841 .get(input)
1842 .expect("unknown batch")
1843 .clone();
1844 let truncated_desc = Description::new(lower, upper, batch.batch.desc.since().clone());
1845 let () = validate_truncate_batch(&batch.batch, &truncated_desc, false, true)?;
1846 let mut new_hollow_batch = (*batch.batch).clone();
1847 new_hollow_batch.desc = truncated_desc;
1848 let new_batch = IdHollowBatch {
1849 batch: Arc::new(new_hollow_batch),
1850 id: batch.id,
1851 };
1852 datadriven
1853 .batches
1854 .insert(output.to_owned(), new_batch.clone());
1855 Ok(format!(
1856 "parts={} len={}\n",
1857 batch.batch.part_count(),
1858 batch.batch.len
1859 ))
1860 }
1861
1862 #[allow(clippy::unused_async)]
1863 pub async fn set_batch_parts_size(
1864 datadriven: &mut MachineState,
1865 args: DirectiveArgs<'_>,
1866 ) -> Result<String, anyhow::Error> {
1867 let input = args.expect_str("input");
1868 let size = args.expect("size");
1869 let batch = datadriven.batches.get_mut(input).expect("unknown batch");
1870 let mut hollow_batch = (*batch.batch).clone();
1871 for part in hollow_batch.parts.iter_mut() {
1872 match part {
1873 RunPart::Single(BatchPart::Hollow(x)) => x.encoded_size_bytes = size,
1874 _ => {
1875 panic!("set_batch_parts_size only supports hollow parts")
1876 }
1877 }
1878 }
1879 batch.batch = Arc::new(hollow_batch);
1880 Ok("ok\n".to_string())
1881 }
1882
1883 pub async fn compact(
1884 datadriven: &mut MachineState,
1885 args: DirectiveArgs<'_>,
1886 ) -> Result<String, anyhow::Error> {
1887 let output = args.expect_str("output");
1888 let lower = args.expect_antichain("lower");
1889 let upper = args.expect_antichain("upper");
1890 let since = args.expect_antichain("since");
1891 let target_size = args.optional("target_size");
1892 let memory_bound = args.optional("memory_bound");
1893
1894 let mut inputs = Vec::new();
1895 for input in args.args.get("inputs").expect("missing inputs") {
1896 inputs.push(
1897 datadriven
1898 .batches
1899 .get(input)
1900 .expect("unknown batch")
1901 .clone(),
1902 );
1903 }
1904
1905 let cfg = datadriven.client.cfg.clone();
1906 if let Some(target_size) = target_size {
1907 cfg.set_config(&BLOB_TARGET_SIZE, target_size);
1908 };
1909 if let Some(memory_bound) = memory_bound {
1910 cfg.set_config(&COMPACTION_MEMORY_BOUND_BYTES, memory_bound);
1911 }
1912 let req = CompactReq {
1913 shard_id: datadriven.shard_id,
1914 desc: Description::new(lower, upper, since),
1915 inputs: inputs.clone(),
1916 };
1917 datadriven
1918 .compactions
1919 .insert(output.to_owned(), req.clone());
1920 let spine_lower = inputs
1921 .first()
1922 .map_or_else(|| datadriven.next_id, |x| x.id.0);
1923 let spine_upper = inputs.last().map_or_else(
1924 || {
1925 datadriven.next_id += 1;
1926 datadriven.next_id
1927 },
1928 |x| x.id.1,
1929 );
1930 let new_spine_id = SpineId(spine_lower, spine_upper);
1931 let res = Compactor::<String, (), u64, i64>::compact(
1932 CompactConfig::new(&cfg, datadriven.shard_id),
1933 Arc::clone(&datadriven.client.blob),
1934 Arc::clone(&datadriven.client.metrics),
1935 Arc::clone(&datadriven.machine.applier.shard_metrics),
1936 Arc::clone(&datadriven.client.isolated_runtime),
1937 req,
1938 SCHEMAS.clone(),
1939 )
1940 .await?;
1941
1942 let batch = IdHollowBatch {
1943 batch: Arc::new(res.output.clone()),
1944 id: new_spine_id,
1945 };
1946
1947 datadriven.batches.insert(output.to_owned(), batch.clone());
1948 Ok(format!(
1949 "parts={} len={}\n",
1950 res.output.part_count(),
1951 res.output.len
1952 ))
1953 }
1954
1955 pub async fn clear_blob(
1956 datadriven: &MachineState,
1957 _args: DirectiveArgs<'_>,
1958 ) -> Result<String, anyhow::Error> {
1959 let mut to_delete = vec![];
1960 datadriven
1961 .client
1962 .blob
1963 .list_keys_and_metadata("", &mut |meta| {
1964 to_delete.push(meta.key.to_owned());
1965 })
1966 .await?;
1967 for blob in &to_delete {
1968 datadriven.client.blob.delete(blob).await?;
1969 }
1970 Ok(format!("deleted={}\n", to_delete.len()))
1971 }
1972
1973 pub async fn restore_blob(
1974 datadriven: &MachineState,
1975 _args: DirectiveArgs<'_>,
1976 ) -> Result<String, anyhow::Error> {
1977 let not_restored = crate::internal::restore::restore_blob(
1978 &datadriven.state_versions,
1979 datadriven.client.blob.as_ref(),
1980 &datadriven.client.cfg.build_version,
1981 datadriven.shard_id,
1982 &*datadriven.state_versions.metrics,
1983 )
1984 .await?;
1985 let mut out = String::new();
1986 for key in not_restored {
1987 writeln!(&mut out, "{key}");
1988 }
1989 Ok(out)
1990 }
1991
1992 #[allow(clippy::unused_async)]
1993 pub async fn rewrite_ts(
1994 datadriven: &mut MachineState,
1995 args: DirectiveArgs<'_>,
1996 ) -> Result<String, anyhow::Error> {
1997 let input = args.expect_str("input");
1998 let ts_rewrite = args.expect_antichain("frontier");
1999 let upper = args.expect_antichain("upper");
2000
2001 let batch = datadriven.batches.get_mut(input).expect("unknown batch");
2002 let mut hollow_batch = (*batch.batch).clone();
2003 let () = hollow_batch
2004 .rewrite_ts(&ts_rewrite, upper)
2005 .map_err(|err| anyhow!("invalid rewrite: {}", err))?;
2006 batch.batch = Arc::new(hollow_batch);
2007 Ok("ok\n".into())
2008 }
2009
2010 pub async fn gc(
2011 datadriven: &mut MachineState,
2012 args: DirectiveArgs<'_>,
2013 ) -> Result<String, anyhow::Error> {
2014 let new_seqno_since = args.expect("to_seqno");
2015
2016 let req = GcReq {
2017 shard_id: datadriven.shard_id,
2018 new_seqno_since,
2019 };
2020 let (maintenance, stats) =
2021 GarbageCollector::gc_and_truncate(&datadriven.machine, req).await;
2022 datadriven.routine.push(maintenance);
2023
2024 Ok(format!(
2025 "{} batch_parts={} rollups={} truncated={} state_rollups={}\n",
2026 datadriven.machine.seqno(),
2027 stats.batch_parts_deleted_from_blob,
2028 stats.rollups_deleted_from_blob,
2029 stats
2030 .truncated_consensus_to
2031 .iter()
2032 .map(|x| x.to_string())
2033 .collect::<Vec<_>>()
2034 .join(","),
2035 stats
2036 .rollups_removed_from_state
2037 .iter()
2038 .map(|x| x.to_string())
2039 .collect::<Vec<_>>()
2040 .join(","),
2041 ))
2042 }
2043
2044 pub async fn snapshot(
2045 datadriven: &MachineState,
2046 args: DirectiveArgs<'_>,
2047 ) -> Result<String, anyhow::Error> {
2048 let as_of = args.expect_antichain("as_of");
2049 let snapshot = datadriven
2050 .machine
2051 .unleased_snapshot(&as_of)
2052 .await
2053 .map_err(|err| anyhow!("{:?}", err))?;
2054
2055 let mut result = String::new();
2056
2057 for batch in snapshot {
2058 writeln!(
2059 result,
2060 "<batch {:?}-{:?}>",
2061 batch.desc.lower().elements(),
2062 batch.desc.upper().elements()
2063 );
2064 for (run, (_meta, parts)) in batch.runs().enumerate() {
2065 writeln!(result, "<run {run}>");
2066 let mut stream = pin!(
2067 futures::stream::iter(parts)
2068 .flat_map(|part| part.part_stream(
2069 datadriven.shard_id,
2070 &*datadriven.state_versions.blob,
2071 &*datadriven.state_versions.metrics
2072 ))
2073 .enumerate()
2074 );
2075
2076 while let Some((idx, part)) = stream.next().await {
2077 let part = &*part?;
2078 writeln!(result, "<part {idx}>");
2079
2080 let part = EncodedPart::fetch(
2081 &FetchConfig::from_persist_config(&datadriven.client.cfg),
2082 &datadriven.shard_id,
2083 datadriven.client.blob.as_ref(),
2084 datadriven.client.metrics.as_ref(),
2085 datadriven.machine.applier.shard_metrics.as_ref(),
2086 &datadriven.client.metrics.read.batch_fetcher,
2087 &batch.desc,
2088 part,
2089 )
2090 .await
2091 .expect("invalid batch part");
2092 let part = part
2093 .normalize(&datadriven.client.metrics.columnar)
2094 .into_part::<String, ()>(&*SCHEMAS.key, &*SCHEMAS.val);
2095
2096 let mut updates = Vec::new();
2097
2098 for ((k, _v), mut t, d) in part
2099 .decode_iter::<_, _, u64, i64>(&*SCHEMAS.key, &*SCHEMAS.val)
2100 .expect("valid schemas")
2101 {
2102 t.advance_by(as_of.borrow());
2103 updates.push((k, t, d));
2104 }
2105
2106 consolidate_updates(&mut updates);
2107
2108 for (k, t, d) in updates {
2109 writeln!(result, "{k} {t} {d}");
2110 }
2111 }
2112 }
2113 }
2114
2115 Ok(result)
2116 }
2117
2118 pub async fn register_listen(
2119 datadriven: &mut MachineState,
2120 args: DirectiveArgs<'_>,
2121 ) -> Result<String, anyhow::Error> {
2122 let output = args.expect_str("output");
2123 let as_of = args.expect_antichain("as_of");
2124 let read = datadriven
2125 .client
2126 .open_leased_reader::<String, (), u64, i64>(
2127 datadriven.shard_id,
2128 Arc::new(StringSchema),
2129 Arc::new(UnitSchema),
2130 Diagnostics::for_tests(),
2131 true,
2132 )
2133 .await
2134 .expect("invalid shard types");
2135 let listen = read
2136 .listen(as_of)
2137 .await
2138 .map_err(|err| anyhow!("{:?}", err))?;
2139 datadriven.listens.insert(output.to_owned(), listen);
2140 Ok("ok\n".into())
2141 }
2142
2143 pub async fn listen_through(
2144 datadriven: &mut MachineState,
2145 args: DirectiveArgs<'_>,
2146 ) -> Result<String, anyhow::Error> {
2147 let input = args.expect_str("input");
2148 let frontier = args.expect("frontier");
2151 let listen = datadriven.listens.get_mut(input).expect("unknown listener");
2152 let mut s = String::new();
2153 loop {
2154 for event in listen.fetch_next().await {
2155 match event {
2156 ListenEvent::Updates(x) => {
2157 for ((k, _v), t, d) in x.iter() {
2158 write!(s, "{} {} {}\n", k, t, d);
2159 }
2160 }
2161 ListenEvent::Progress(x) => {
2162 if !x.less_than(&frontier) {
2163 return Ok(s);
2164 }
2165 }
2166 }
2167 }
2168 }
2169 }
2170
2171 pub async fn register_critical_reader(
2172 datadriven: &mut MachineState,
2173 args: DirectiveArgs<'_>,
2174 ) -> Result<String, anyhow::Error> {
2175 let reader_id = args.expect("reader_id");
2176 let (state, maintenance) = datadriven
2177 .machine
2178 .register_critical_reader(&reader_id, Opaque::encode(&0u64), "tests")
2179 .await;
2180 datadriven.routine.push(maintenance);
2181 Ok(format!(
2182 "{} {:?}\n",
2183 datadriven.machine.seqno(),
2184 state.since.elements(),
2185 ))
2186 }
2187
2188 pub async fn register_leased_reader(
2189 datadriven: &mut MachineState,
2190 args: DirectiveArgs<'_>,
2191 ) -> Result<String, anyhow::Error> {
2192 let reader_id = args.expect("reader_id");
2193 let (reader_state, maintenance) = datadriven
2194 .machine
2195 .register_leased_reader(
2196 &reader_id,
2197 "tests",
2198 READER_LEASE_DURATION.get(&datadriven.client.cfg),
2199 false,
2200 )
2201 .await;
2202 datadriven.routine.push(maintenance);
2203 Ok(format!(
2204 "{} {:?}\n",
2205 datadriven.machine.seqno(),
2206 reader_state.since.elements(),
2207 ))
2208 }
2209
2210 pub async fn expire_critical_reader(
2211 datadriven: &mut MachineState,
2212 args: DirectiveArgs<'_>,
2213 ) -> Result<String, anyhow::Error> {
2214 let reader_id = args.expect("reader_id");
2215 let (_, maintenance) = datadriven.machine.expire_critical_reader(&reader_id).await;
2216 datadriven.routine.push(maintenance);
2217 Ok(format!("{} ok\n", datadriven.machine.seqno()))
2218 }
2219
2220 pub async fn expire_leased_reader(
2221 datadriven: &mut MachineState,
2222 args: DirectiveArgs<'_>,
2223 ) -> Result<String, anyhow::Error> {
2224 let reader_id = args.expect("reader_id");
2225 let (_, maintenance) = datadriven.machine.expire_leased_reader(&reader_id).await;
2226 datadriven.routine.push(maintenance);
2227 Ok(format!("{} ok\n", datadriven.machine.seqno()))
2228 }
2229
2230 pub async fn compare_and_append_batches(
2231 datadriven: &MachineState,
2232 args: DirectiveArgs<'_>,
2233 ) -> Result<String, anyhow::Error> {
2234 let expected_upper = args.expect_antichain("expected_upper");
2235 let new_upper = args.expect_antichain("new_upper");
2236
2237 let mut batches: Vec<Batch<String, (), u64, i64>> = args
2238 .args
2239 .get("batches")
2240 .expect("missing batches")
2241 .into_iter()
2242 .map(|batch| {
2243 let hollow = (*datadriven
2244 .batches
2245 .get(batch)
2246 .expect("unknown batch")
2247 .clone()
2248 .batch)
2249 .clone();
2250 datadriven.to_batch(hollow)
2251 })
2252 .collect();
2253
2254 let mut writer = datadriven
2255 .client
2256 .open_writer(
2257 datadriven.shard_id,
2258 Arc::new(StringSchema),
2259 Arc::new(UnitSchema),
2260 Diagnostics::for_tests(),
2261 )
2262 .await?;
2263
2264 let mut batch_refs: Vec<_> = batches.iter_mut().collect();
2265
2266 let () = writer
2267 .compare_and_append_batch(batch_refs.as_mut_slice(), expected_upper, new_upper, true)
2268 .await?
2269 .map_err(|err| anyhow!("upper mismatch: {:?}", err))?;
2270
2271 writer.expire().await;
2272
2273 Ok("ok\n".into())
2274 }
2275
2276 pub async fn expire_writer(
2277 datadriven: &mut MachineState,
2278 args: DirectiveArgs<'_>,
2279 ) -> Result<String, anyhow::Error> {
2280 let writer_id = args.expect("writer_id");
2281 let (_, maintenance) = datadriven.machine.expire_writer(&writer_id).await;
2282 datadriven.routine.push(maintenance);
2283 Ok(format!("{} ok\n", datadriven.machine.seqno()))
2284 }
2285
2286 pub(crate) async fn finalize(
2287 datadriven: &mut MachineState,
2288 _args: DirectiveArgs<'_>,
2289 ) -> anyhow::Result<String> {
2290 let maintenance = datadriven.machine.become_tombstone().await?;
2291 datadriven.routine.push(maintenance);
2292 Ok(format!("{} ok\n", datadriven.machine.seqno()))
2293 }
2294
2295 pub(crate) fn is_finalized(
2296 datadriven: &MachineState,
2297 _args: DirectiveArgs<'_>,
2298 ) -> anyhow::Result<String> {
2299 let seqno = datadriven.machine.seqno();
2300 let tombstone = datadriven.machine.is_finalized();
2301 Ok(format!("{seqno} {tombstone}\n"))
2302 }
2303
2304 pub async fn compare_and_append(
2305 datadriven: &mut MachineState,
2306 args: DirectiveArgs<'_>,
2307 ) -> Result<String, anyhow::Error> {
2308 let input = args.expect_str("input");
2309 let writer_id = args.expect("writer_id");
2310 let mut batch = datadriven
2311 .batches
2312 .get(input)
2313 .expect("unknown batch")
2314 .clone();
2315 let token = args.optional("token").unwrap_or_else(IdempotencyToken::new);
2316
2317 let (id, maintenance) = datadriven
2318 .machine
2319 .register_schema(&*SCHEMAS.key, &*SCHEMAS.val)
2320 .await;
2321 assert_eq!(id, SCHEMAS.id);
2322 datadriven.routine.push(maintenance);
2323 let maintenance = loop {
2324 let indeterminate = args
2325 .optional::<String>("prev_indeterminate")
2326 .map(|x| Indeterminate::new(anyhow::Error::msg(x)));
2327 let res = datadriven
2328 .machine
2329 .compare_and_append_idempotent(
2330 &batch.batch,
2331 &writer_id,
2332 &token,
2333 &HandleDebugState::default(),
2334 indeterminate,
2335 )
2336 .await;
2337 match res {
2338 CompareAndAppendRes::Success(_, x) => break x,
2339 CompareAndAppendRes::UpperMismatch(_seqno, upper) => {
2340 return Err(anyhow!("{:?}", Upper(upper)));
2341 }
2342 CompareAndAppendRes::InlineBackpressure => {
2343 let hollow_batch = (*batch.batch).clone();
2344 let mut b = datadriven.to_batch(hollow_batch);
2345 let cfg = BatchBuilderConfig::new(&datadriven.client.cfg, datadriven.shard_id);
2346 b.flush_to_blob(
2347 &cfg,
2348 &datadriven.client.metrics.user,
2349 &datadriven.client.isolated_runtime,
2350 &*SCHEMAS,
2351 )
2352 .await;
2353 batch.batch = Arc::new(b.into_hollow_batch());
2354 continue;
2355 }
2356 CompareAndAppendRes::InvalidUsage(_) => panic!("{:?}", res),
2357 };
2358 };
2359 datadriven.routine.push(maintenance.routine);
2362 Ok(format!(
2363 "{} {:?}\n",
2364 datadriven.machine.seqno(),
2365 datadriven.machine.applier.clone_upper().elements(),
2366 ))
2367 }
2368
2369 pub async fn apply_merge_res(
2370 datadriven: &mut MachineState,
2371 args: DirectiveArgs<'_>,
2372 ) -> Result<String, anyhow::Error> {
2373 let input = args.expect_str("input");
2374 let batch = datadriven
2375 .batches
2376 .get(input)
2377 .expect("unknown batch")
2378 .clone();
2379 let compact_req = datadriven
2380 .compactions
2381 .get(input)
2382 .expect("unknown compact req")
2383 .clone();
2384 let input_batches = compact_req
2385 .inputs
2386 .iter()
2387 .map(|x| x.id)
2388 .collect::<BTreeSet<_>>();
2389 let lower_spine_bound = input_batches
2390 .first()
2391 .map(|id| id.0)
2392 .expect("at least one batch must be present");
2393 let upper_spine_bound = input_batches
2394 .last()
2395 .map(|id| id.1)
2396 .expect("at least one batch must be present");
2397 let id = SpineId(lower_spine_bound, upper_spine_bound);
2398 let hollow_batch = (*batch.batch).clone();
2399
2400 let (merge_res, maintenance) = datadriven
2401 .machine
2402 .merge_res(&FueledMergeRes {
2403 output: hollow_batch,
2404 input: CompactionInput::IdRange(id),
2405 new_active_compaction: None,
2406 })
2407 .await;
2408 datadriven.routine.push(maintenance);
2409 Ok(format!(
2410 "{} {}\n",
2411 datadriven.machine.seqno(),
2412 merge_res.applied()
2413 ))
2414 }
2415
2416 pub async fn perform_maintenance(
2417 datadriven: &mut MachineState,
2418 _args: DirectiveArgs<'_>,
2419 ) -> Result<String, anyhow::Error> {
2420 let mut s = String::new();
2421 for maintenance in datadriven.routine.drain(..) {
2422 let () = maintenance
2423 .perform(&datadriven.machine, &datadriven.gc)
2424 .await;
2425 let () = datadriven
2426 .machine
2427 .applier
2428 .fetch_and_update_state(None)
2429 .await;
2430 write!(s, "{} ok\n", datadriven.machine.seqno());
2431 }
2432 Ok(s)
2433 }
2434}
2435
2436#[cfg(test)]
2437pub mod tests {
2438 use std::sync::Arc;
2439
2440 use mz_dyncfg::ConfigUpdates;
2441 use mz_ore::cast::CastFrom;
2442 use mz_ore::task::spawn;
2443 use mz_persist::intercept::{InterceptBlob, InterceptHandle};
2444 use mz_persist::location::SeqNo;
2445 use mz_persist_types::PersistLocation;
2446 use semver::Version;
2447 use timely::progress::Antichain;
2448
2449 use crate::batch::BatchBuilderConfig;
2450 use crate::cache::StateCache;
2451 use crate::internal::gc::{GarbageCollector, GcReq};
2452 use crate::internal::state::{HandleDebugState, ROLLUP_THRESHOLD};
2453 use crate::tests::{new_test_client, new_test_client_cache};
2454 use crate::{Diagnostics, PersistClient, ShardId};
2455
2456 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
2457 #[cfg_attr(miri, ignore)] async fn apply_unbatched_cmd_truncate(dyncfgs: ConfigUpdates) {
2459 mz_ore::test::init_logging();
2460
2461 let client = new_test_client(&dyncfgs).await;
2462 client.cfg.set_config(&ROLLUP_THRESHOLD, 5);
2464 let (mut write, read) = client
2465 .expect_open::<String, (), u64, i64>(ShardId::new())
2466 .await;
2467
2468 read.expire().await;
2470
2471 const NUM_BATCHES: u64 = 100;
2474 for idx in 0..NUM_BATCHES {
2475 let mut batch = write
2476 .expect_batch(&[((idx.to_string(), ()), idx, 1)], idx, idx + 1)
2477 .await;
2478 let cfg = BatchBuilderConfig::new(&client.cfg, write.shard_id());
2481 batch
2482 .flush_to_blob(
2483 &cfg,
2484 &client.metrics.user,
2485 &client.isolated_runtime,
2486 &write.write_schemas,
2487 )
2488 .await;
2489 let (_, writer_maintenance) = write
2490 .machine
2491 .compare_and_append(
2492 &batch.into_hollow_batch(),
2493 &write.writer_id,
2494 &HandleDebugState::default(),
2495 )
2496 .await
2497 .unwrap();
2498 writer_maintenance
2499 .perform(&write.machine, &write.gc, write.compact.as_ref())
2500 .await;
2501 }
2502 let live_diffs = write
2503 .machine
2504 .applier
2505 .state_versions
2506 .fetch_all_live_diffs(&write.machine.shard_id())
2507 .await;
2508 assert!(live_diffs.len() > 0);
2510 let max_live_diffs = 2 * usize::cast_from(NUM_BATCHES.next_power_of_two().trailing_zeros());
2514 assert!(
2515 live_diffs.len() <= max_live_diffs,
2516 "{} vs {}",
2517 live_diffs.len(),
2518 max_live_diffs
2519 );
2520 }
2521
2522 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
2526 #[cfg_attr(miri, ignore)] async fn regression_gc_skipped_req_and_interrupted(dyncfgs: ConfigUpdates) {
2528 let mut client = new_test_client(&dyncfgs).await;
2529 let intercept = InterceptHandle::default();
2530 client.blob = Arc::new(InterceptBlob::new(
2531 Arc::clone(&client.blob),
2532 intercept.clone(),
2533 ));
2534 let (_, mut read) = client
2535 .expect_open::<String, String, u64, i64>(ShardId::new())
2536 .await;
2537
2538 read.downgrade_since(&Antichain::from_elem(1)).await;
2540 let new_seqno_since = read.machine.applier.seqno_since();
2541
2542 assert!(new_seqno_since > SeqNo::minimum());
2547 intercept.set_post_delete(Some(Arc::new(|_, _| panic!("boom"))));
2548 let machine = read.machine.clone();
2549 let gc = spawn(|| "", async move {
2551 let req = GcReq {
2552 shard_id: machine.shard_id(),
2553 new_seqno_since,
2554 };
2555 GarbageCollector::gc_and_truncate(&machine, req).await
2556 });
2557 let _ = gc.await;
2560
2561 intercept.set_post_delete(None);
2564 let req = GcReq {
2565 shard_id: read.machine.shard_id(),
2566 new_seqno_since,
2567 };
2568 let _ = GarbageCollector::gc_and_truncate(&read.machine, req.clone()).await;
2569 }
2570
2571 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
2576 #[cfg_attr(miri, ignore)] async fn regression_update_state_after_upper_mismatch(dyncfgs: ConfigUpdates) {
2578 let client = new_test_client(&dyncfgs).await;
2579 let mut client2 = client.clone();
2580
2581 let new_state_cache = Arc::new(StateCache::new_no_metrics());
2584 client2.shared_states = new_state_cache;
2585
2586 let shard_id = ShardId::new();
2587 let (mut write1, _) = client.expect_open::<String, (), u64, i64>(shard_id).await;
2588 let (mut write2, _) = client2.expect_open::<String, (), u64, i64>(shard_id).await;
2589
2590 let data = [
2591 (("1".to_owned(), ()), 1, 1),
2592 (("2".to_owned(), ()), 2, 1),
2593 (("3".to_owned(), ()), 3, 1),
2594 (("4".to_owned(), ()), 4, 1),
2595 (("5".to_owned(), ()), 5, 1),
2596 ];
2597
2598 write1.expect_compare_and_append(&data[..1], 0, 2).await;
2599
2600 write2.expect_compare_and_append(&data[1..2], 2, 3).await;
2603 }
2604
2605 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
2606 #[cfg_attr(miri, ignore)]
2607 async fn version_upgrade(dyncfgs: ConfigUpdates) {
2608 let mut cache = new_test_client_cache(&dyncfgs);
2609 cache.cfg.build_version = Version::new(26, 1, 0);
2610 let shard_id = ShardId::new();
2611
2612 async fn fetch_catalog_upgrade_shard_version(
2613 persist_client: &PersistClient,
2614 upgrade_shard_id: ShardId,
2615 ) -> Option<semver::Version> {
2616 let shard_state = persist_client
2617 .inspect_shard::<u64>(&upgrade_shard_id)
2618 .await
2619 .ok()?;
2620 let json_state = serde_json::to_value(shard_state).expect("state serialization error");
2621 let upgrade_version = json_state
2622 .get("applier_version")
2623 .cloned()
2624 .expect("missing applier_version");
2625 let upgrade_version =
2626 serde_json::from_value(upgrade_version).expect("version deserialization error");
2627 Some(upgrade_version)
2628 }
2629
2630 cache.cfg.build_version = Version::new(26, 1, 0);
2631 let client = cache.open(PersistLocation::new_in_mem()).await.unwrap();
2632 let (write, mut reader) = client.expect_open::<String, (), u64, i64>(shard_id).await;
2633 reader.downgrade_since(&Antichain::from_elem(1)).await;
2634 assert_eq!(
2635 fetch_catalog_upgrade_shard_version(&client, shard_id).await,
2636 Some(Version::new(26, 1, 0)),
2637 );
2638
2639 write.expire().await;
2648 reader.expire().await;
2649
2650 cache.cfg.build_version = Version::new(27, 1, 0);
2652 let client = cache.open(PersistLocation::new_in_mem()).await.unwrap();
2653 let (write, mut reader) = client.expect_open::<String, (), u64, i64>(shard_id).await;
2654 reader.downgrade_since(&Antichain::from_elem(2)).await;
2655 assert_eq!(
2656 fetch_catalog_upgrade_shard_version(&client, shard_id).await,
2657 Some(Version::new(26, 1, 0)),
2658 );
2659
2660 client
2662 .upgrade_version::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
2663 .await
2664 .unwrap();
2665 assert_eq!(
2666 fetch_catalog_upgrade_shard_version(&client, shard_id).await,
2667 Some(Version::new(27, 1, 0)),
2668 );
2669
2670 write.expire().await;
2671 reader.expire().await;
2672 }
2673
2674 #[mz_persist_proc::test(tokio::test)]
2678 #[cfg_attr(miri, ignore)] async fn version_upgrade_tombstone(dyncfgs: ConfigUpdates) {
2680 async fn shard_version(
2681 persist_client: &PersistClient,
2682 shard_id: ShardId,
2683 ) -> Option<semver::Version> {
2684 let shard_state = persist_client.inspect_shard::<u64>(&shard_id).await.ok()?;
2685 let json_state = serde_json::to_value(shard_state).expect("state serialization error");
2686 let version = json_state
2687 .get("applier_version")
2688 .cloned()
2689 .expect("missing applier_version");
2690 Some(serde_json::from_value(version).expect("version deserialization error"))
2691 }
2692
2693 let mut cache = new_test_client_cache(&dyncfgs);
2694 cache.cfg.build_version = Version::new(26, 1, 0);
2695 let client = cache.open(PersistLocation::new_in_mem()).await.unwrap();
2696 let shard_id = ShardId::new();
2697
2698 let (mut write, mut read) = client.expect_open::<String, (), u64, i64>(shard_id).await;
2701 read.downgrade_since(&Antichain::new()).await;
2702 write.advance_upper(&Antichain::new()).await;
2703 write.expire().await;
2704 read.expire().await;
2705 client
2706 .finalize_shard::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
2707 .await
2708 .expect("finalization must succeed");
2709
2710 client
2713 .upgrade_version::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
2714 .await
2715 .expect("upgrade on tombstone must succeed");
2716
2717 cache.cfg.build_version = Version::new(27, 1, 0);
2719 let client = cache.open(PersistLocation::new_in_mem()).await.unwrap();
2720 client
2721 .upgrade_version::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
2722 .await
2723 .expect("upgrade on tombstone must succeed");
2724
2725 assert_eq!(
2726 shard_version(&client, shard_id).await,
2727 Some(Version::new(26, 1, 0)),
2728 );
2729 let is_finalized = client
2730 .is_finalized::<String, (), u64, i64>(shard_id, Diagnostics::for_tests())
2731 .await
2732 .expect("invalid persist usage");
2733 assert!(is_finalized, "shard must still be finalized");
2734 }
2735}