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