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