1use async_stream::stream;
13use std::collections::BTreeMap;
14use std::fmt::Debug;
15use std::future::Future;
16use std::sync::Arc;
17use std::time::{Duration, Instant};
18
19use differential_dataflow::Hashable;
20use differential_dataflow::consolidation::consolidate_updates;
21use differential_dataflow::difference::Monoid;
22use differential_dataflow::lattice::Lattice;
23use futures::Stream;
24use futures_util::{StreamExt, stream};
25use mz_dyncfg::{Config, ParameterScope};
26use mz_ore::cast::CastLossy;
27use mz_ore::halt;
28use mz_ore::instrument;
29use mz_ore::task::JoinHandle;
30use mz_persist::location::{Blob, SeqNo};
31use mz_persist_types::columnar::{ColumnDecoder, Schema};
32use mz_persist_types::{Codec, Codec64};
33use proptest_derive::Arbitrary;
34use serde::{Deserialize, Serialize};
35use timely::PartialOrder;
36use timely::order::TotalOrder;
37use timely::progress::{Antichain, Timestamp};
38use tracing::warn;
39use uuid::Uuid;
40
41use crate::batch::BLOB_TARGET_SIZE;
42use crate::cfg::{COMPACTION_MEMORY_BOUND_BYTES, RetryParameters};
43use crate::fetch::FetchConfig;
44use crate::fetch::{FetchBatchFilter, FetchedPart, Lease, LeasedBatchPart, fetch_leased_part};
45use crate::internal::encoding::Schemas;
46use crate::internal::machine::{Machine, next_listen_batch_retry_params};
47use crate::internal::metrics::{Metrics, ReadMetrics, ShardMetrics};
48use crate::internal::state::{HollowBatch, LeasedReaderState, SnapshotErr};
49use crate::internal::watch::{AwaitableState, StateWatch};
50use crate::iter::{Consolidator, StructuredSort};
51use crate::schema::SchemaCache;
52use crate::stats::{SnapshotPartStats, SnapshotPartsStats, SnapshotStats};
53use crate::{GarbageCollector, PersistConfig, ShardId, parse_id};
54
55pub use crate::internal::encoding::LazyPartStats;
56pub use crate::internal::state::Since;
57
58#[derive(
60 Arbitrary,
61 Clone,
62 PartialEq,
63 Eq,
64 PartialOrd,
65 Ord,
66 Hash,
67 Serialize,
68 Deserialize
69)]
70#[serde(try_from = "String", into = "String")]
71pub struct LeasedReaderId(pub(crate) [u8; 16]);
72
73impl std::fmt::Display for LeasedReaderId {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "r{}", Uuid::from_bytes(self.0))
76 }
77}
78
79impl std::fmt::Debug for LeasedReaderId {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "LeasedReaderId({})", Uuid::from_bytes(self.0))
82 }
83}
84
85impl std::str::FromStr for LeasedReaderId {
86 type Err = String;
87
88 fn from_str(s: &str) -> Result<Self, Self::Err> {
89 parse_id("r", "LeasedReaderId", s).map(LeasedReaderId)
90 }
91}
92
93impl From<LeasedReaderId> for String {
94 fn from(reader_id: LeasedReaderId) -> Self {
95 reader_id.to_string()
96 }
97}
98
99impl TryFrom<String> for LeasedReaderId {
100 type Error = String;
101
102 fn try_from(s: String) -> Result<Self, Self::Error> {
103 s.parse()
104 }
105}
106
107impl LeasedReaderId {
108 pub(crate) fn new() -> Self {
109 LeasedReaderId(*Uuid::new_v4().as_bytes())
110 }
111}
112
113#[derive(Debug)]
118pub struct Subscribe<K: Codec, V: Codec, T, D> {
119 snapshot: Option<Vec<LeasedBatchPart<T>>>,
120 listen: Listen<K, V, T, D>,
121}
122
123impl<K, V, T, D> Subscribe<K, V, T, D>
124where
125 K: Debug + Codec,
126 V: Debug + Codec,
127 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
128 D: Monoid + Codec64 + Send + Sync,
129{
130 fn new(snapshot_parts: Vec<LeasedBatchPart<T>>, listen: Listen<K, V, T, D>) -> Self {
131 Subscribe {
132 snapshot: Some(snapshot_parts),
133 listen,
134 }
135 }
136
137 #[instrument(level = "debug", fields(shard = %self.listen.handle.machine.shard_id()))]
145 pub async fn next(
146 &mut self,
147 listen_retry: Option<RetryParameters>,
149 ) -> Vec<ListenEvent<T, LeasedBatchPart<T>>> {
150 match self.snapshot.take() {
151 Some(parts) => vec![ListenEvent::Updates(parts)],
152 None => {
153 let (parts, upper) = self.listen.next(listen_retry).await;
154 vec![ListenEvent::Updates(parts), ListenEvent::Progress(upper)]
155 }
156 }
157 }
158}
159
160impl<K, V, T, D> Subscribe<K, V, T, D>
161where
162 K: Debug + Codec,
163 V: Debug + Codec,
164 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
165 D: Monoid + Codec64 + Send + Sync,
166{
167 #[instrument(level = "debug", fields(shard = %self.listen.handle.machine.shard_id()))]
170 pub async fn fetch_next(&mut self) -> Vec<ListenEvent<T, ((K, V), T, D)>> {
171 let events = self.next(None).await;
172 let new_len = events
173 .iter()
174 .map(|event| match event {
175 ListenEvent::Updates(parts) => parts.len(),
176 ListenEvent::Progress(_) => 1,
177 })
178 .sum();
179 let mut ret = Vec::with_capacity(new_len);
180 for event in events {
181 match event {
182 ListenEvent::Updates(parts) => {
183 for part in parts {
184 let fetched_part = self.listen.fetch_batch_part(part).await;
185 let updates = fetched_part.collect::<Vec<_>>();
186 if !updates.is_empty() {
187 ret.push(ListenEvent::Updates(updates));
188 }
189 }
190 }
191 ListenEvent::Progress(progress) => ret.push(ListenEvent::Progress(progress)),
192 }
193 }
194 ret
195 }
196
197 pub async fn fetch_batch_part(&mut self, part: LeasedBatchPart<T>) -> FetchedPart<K, V, T, D> {
199 self.listen.fetch_batch_part(part).await
200 }
201}
202
203impl<K, V, T, D> Subscribe<K, V, T, D>
204where
205 K: Debug + Codec,
206 V: Debug + Codec,
207 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
208 D: Monoid + Codec64 + Send + Sync,
209{
210 pub async fn expire(mut self) {
217 let _ = self.snapshot.take(); self.listen.expire().await;
219 }
220}
221
222#[derive(Debug, PartialEq)]
226pub enum ListenEvent<T, D> {
227 Progress(Antichain<T>),
229 Updates(Vec<D>),
231}
232
233#[derive(Debug)]
235pub struct Listen<K: Codec, V: Codec, T, D> {
236 handle: ReadHandle<K, V, T, D>,
237 as_of: Antichain<T>,
238 since: Antichain<T>,
239 frontier: Antichain<T>,
240}
241
242impl<K, V, T, D> Listen<K, V, T, D>
243where
244 K: Debug + Codec,
245 V: Debug + Codec,
246 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
247 D: Monoid + Codec64 + Send + Sync,
248{
249 async fn new(
250 mut handle: ReadHandle<K, V, T, D>,
251 as_of: Antichain<T>,
252 ) -> Result<Self, Since<T>> {
253 let () = handle.machine.verify_listen(&as_of)?;
254
255 let since = as_of.clone();
256 if !PartialOrder::less_equal(handle.since(), &since) {
257 return Err(Since(handle.since().clone()));
260 }
261 handle.downgrade_since(&since).await;
265 Ok(Listen {
266 handle,
267 since,
268 frontier: as_of.clone(),
269 as_of,
270 })
271 }
272
273 pub fn frontier(&self) -> &Antichain<T> {
275 &self.frontier
276 }
277
278 pub async fn next(
286 &mut self,
287 retry: Option<RetryParameters>,
289 ) -> (Vec<LeasedBatchPart<T>>, Antichain<T>) {
290 let retry = retry
292 .unwrap_or_else(|| next_listen_batch_retry_params(&self.handle.machine.applier.cfg));
293 self.handle
294 .machine
295 .wait_for_upper_past(
296 &self.frontier,
297 &mut self.handle.watch,
298 Some(&self.handle.reader_id),
299 &self.handle.metrics.retries.next_listen_batch,
300 retry,
301 )
302 .await;
303
304 let lease = self.handle.lease_seqno().await;
306 let batch = match self
307 .handle
308 .machine
309 .applier
310 .next_listen_batch(&self.frontier)
311 {
312 Ok(batch) => batch,
313 Err(seqno) => {
314 panic!(
315 "waited for upper past {frontier:?}, but no listen batch was available at {seqno:?}!",
316 frontier = self.frontier.elements()
317 );
318 }
319 };
320
321 let acceptable_desc = PartialOrder::less_than(batch.desc.since(), &self.frontier)
331 || (self.frontier == self.as_of
336 && PartialOrder::less_equal(batch.desc.since(), &self.frontier));
337 if !acceptable_desc {
338 let lease_state = self
339 .handle
340 .machine
341 .applier
342 .reader_lease(self.handle.reader_id.clone());
343 if let Some(lease) = lease_state {
344 panic!(
345 "Listen on {} received a batch {:?} advanced past the listen frontier {:?}, but the lease has not expired: {:?}",
346 self.handle.machine.shard_id(),
347 batch.desc,
348 self.frontier,
349 lease
350 )
351 } else {
352 halt!(
355 "Listen on {} received a batch {:?} advanced past the listen frontier {:?} after the reader has expired. \
356 This can happen in exceptional cases: a machine goes to sleep or is running out of memory or CPU, for example.",
357 self.handle.machine.shard_id(),
358 batch.desc,
359 self.frontier
360 )
361 }
362 }
363
364 let new_frontier = batch.desc.upper().clone();
365
366 for x in self.frontier.elements().iter() {
389 let less_than_upper = batch.desc.upper().elements().iter().any(|u| x.less_than(u));
390 if less_than_upper {
391 self.since.join_assign(&Antichain::from_elem(x.clone()));
392 }
393 }
394
395 let filter = FetchBatchFilter::Listen {
400 as_of: self.as_of.clone(),
401 lower: self.frontier.clone(),
402 };
403 let parts = self
404 .handle
405 .lease_batch_parts(lease, batch, filter)
406 .collect()
407 .await;
408
409 self.handle.maybe_downgrade_since(&self.since).await;
410
411 self.frontier = new_frontier;
414
415 (parts, self.frontier.clone())
416 }
417}
418
419impl<K, V, T, D> Listen<K, V, T, D>
420where
421 K: Debug + Codec,
422 V: Debug + Codec,
423 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
424 D: Monoid + Codec64 + Send + Sync,
425{
426 #[instrument(level = "debug", name = "listen::next", fields(shard = %self.handle.machine.shard_id()))]
437 pub async fn fetch_next(&mut self) -> Vec<ListenEvent<T, ((K, V), T, D)>> {
438 let (parts, progress) = self.next(None).await;
439 let mut ret = Vec::with_capacity(parts.len() + 1);
440 for part in parts {
441 let fetched_part = self.fetch_batch_part(part).await;
442 let updates = fetched_part.collect::<Vec<_>>();
443 if !updates.is_empty() {
444 ret.push(ListenEvent::Updates(updates));
445 }
446 }
447 ret.push(ListenEvent::Progress(progress));
448 ret
449 }
450
451 pub fn into_stream(mut self) -> impl Stream<Item = ListenEvent<T, ((K, V), T, D)>> {
453 async_stream::stream!({
454 loop {
455 for msg in self.fetch_next().await {
456 yield msg;
457 }
458 }
459 })
460 }
461
462 #[cfg(test)]
466 #[track_caller]
467 pub async fn read_until(&mut self, ts: &T) -> (Vec<((K, V), T, D)>, Antichain<T>) {
468 let mut updates = Vec::new();
469 let mut frontier = Antichain::from_elem(T::minimum());
470 while self.frontier.less_than(ts) {
471 for event in self.fetch_next().await {
472 match event {
473 ListenEvent::Updates(mut x) => updates.append(&mut x),
474 ListenEvent::Progress(x) => frontier = x,
475 }
476 }
477 }
478 (updates, frontier)
481 }
482}
483
484impl<K, V, T, D> Listen<K, V, T, D>
485where
486 K: Debug + Codec,
487 V: Debug + Codec,
488 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
489 D: Monoid + Codec64 + Send + Sync,
490{
491 async fn fetch_batch_part(&mut self, part: LeasedBatchPart<T>) -> FetchedPart<K, V, T, D> {
496 let fetched_part = fetch_leased_part(
497 &self.handle.cfg,
498 &part,
499 self.handle.blob.as_ref(),
500 Arc::clone(&self.handle.metrics),
501 &self.handle.metrics.read.listen,
502 &self.handle.machine.applier.shard_metrics,
503 &self.handle.reader_id,
504 self.handle.read_schemas.clone(),
505 &mut self.handle.schema_cache,
506 )
507 .await;
508 fetched_part
509 }
510
511 pub async fn expire(self) {
518 self.handle.expire().await
519 }
520}
521
522#[derive(Debug)]
525pub(crate) struct ReadHolds<T> {
526 held_since: Antichain<T>,
528 applied_since: Antichain<T>,
531 recent_seqno: SeqNo,
533 leases: BTreeMap<SeqNo, Lease>,
536 expired: bool,
538 request_sync: bool,
541}
542
543impl<T> ReadHolds<T>
544where
545 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
546{
547 pub fn downgrade_since(&mut self, since: &Antichain<T>) {
548 self.held_since.join_assign(since);
549 }
550
551 pub fn observe_seqno(&mut self, seqno: SeqNo) {
552 self.recent_seqno = seqno.max(self.recent_seqno);
553 }
554
555 pub fn lease_seqno(&mut self) -> Lease {
556 let seqno = self.recent_seqno;
557 let lease = self
558 .leases
559 .entry(seqno)
560 .or_insert_with(|| Lease::new(seqno));
561 lease.clone()
562 }
563
564 pub fn outstanding_seqno(&mut self) -> SeqNo {
565 while let Some(first) = self.leases.first_entry() {
566 if first.get().count() <= 1 {
567 first.remove();
568 } else {
569 return *first.key();
570 }
571 }
572 self.recent_seqno
573 }
574}
575
576#[derive(Debug)]
597pub struct ReadHandle<K: Codec, V: Codec, T, D> {
598 pub(crate) cfg: PersistConfig,
599 pub(crate) metrics: Arc<Metrics>,
600 pub(crate) machine: Machine<K, V, T, D>,
601 pub(crate) gc: GarbageCollector<K, V, T, D>,
602 pub(crate) blob: Arc<dyn Blob>,
603 watch: StateWatch<K, V, T, D>,
604
605 pub(crate) reader_id: LeasedReaderId,
606 pub(crate) read_schemas: Schemas<K, V>,
607 pub(crate) schema_cache: SchemaCache<K, V, T, D>,
608
609 since: Antichain<T>,
610 pub(crate) hold_state: AwaitableState<ReadHolds<T>>,
611 pub(crate) unexpired_state: Option<UnexpiredReadHandleState>,
612}
613
614pub(crate) const READER_LEASE_DURATION: Config<Duration> = Config::new(
617 "persist_reader_lease_duration",
618 Duration::from_secs(60 * 15),
619 "The time after which we'll clean up stale read leases",
620 ParameterScope::Environment,
621);
622
623impl<K, V, T, D> ReadHandle<K, V, T, D>
624where
625 K: Debug + Codec,
626 V: Debug + Codec,
627 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
628 D: Monoid + Codec64 + Send + Sync,
629{
630 #[allow(clippy::unused_async)]
631 pub(crate) async fn new(
632 cfg: PersistConfig,
633 metrics: Arc<Metrics>,
634 machine: Machine<K, V, T, D>,
635 gc: GarbageCollector<K, V, T, D>,
636 blob: Arc<dyn Blob>,
637 reader_id: LeasedReaderId,
638 read_schemas: Schemas<K, V>,
639 state: LeasedReaderState<T>,
640 ) -> Self {
641 let schema_cache = machine.applier.schema_cache();
642 let hold_state = AwaitableState::new(ReadHolds {
643 held_since: state.since.clone(),
644 applied_since: state.since.clone(),
645 recent_seqno: state.seqno,
646 leases: Default::default(),
647 expired: false,
648 request_sync: false,
649 });
650 ReadHandle {
651 cfg,
652 metrics: Arc::clone(&metrics),
653 machine: machine.clone(),
654 gc: gc.clone(),
655 blob,
656 watch: machine.applier.watch(),
657 reader_id: reader_id.clone(),
658 read_schemas,
659 schema_cache,
660 since: state.since,
661 hold_state: hold_state.clone(),
662 unexpired_state: Some(UnexpiredReadHandleState {
663 heartbeat_task: Self::start_reader_heartbeat_task(
664 machine, reader_id, gc, hold_state,
665 ),
666 }),
667 }
668 }
669
670 fn start_reader_heartbeat_task(
671 machine: Machine<K, V, T, D>,
672 reader_id: LeasedReaderId,
673 gc: GarbageCollector<K, V, T, D>,
674 leased_seqnos: AwaitableState<ReadHolds<T>>,
675 ) -> JoinHandle<()> {
676 let metrics = Arc::clone(&machine.applier.metrics);
677 let name = format!(
678 "persist::heartbeat_read({},{})",
679 machine.shard_id(),
680 reader_id
681 );
682 mz_ore::task::spawn(|| name, {
683 metrics.tasks.heartbeat_read.instrument_task(async move {
684 Self::reader_heartbeat_task(machine, reader_id, gc, leased_seqnos).await
685 })
686 })
687 }
688
689 async fn reader_heartbeat_task(
690 machine: Machine<K, V, T, D>,
691 reader_id: LeasedReaderId,
692 gc: GarbageCollector<K, V, T, D>,
693 leased_seqnos: AwaitableState<ReadHolds<T>>,
694 ) {
695 let sleep_duration = READER_LEASE_DURATION.get(&machine.applier.cfg) / 4;
696 let jitter: f64 = f64::cast_lossy(reader_id.hashed()) / f64::cast_lossy(u64::MAX);
699 let mut interval = tokio::time::interval_at(
700 tokio::time::Instant::now() + sleep_duration.mul_f64(jitter),
701 sleep_duration,
702 );
703 let mut held_since = leased_seqnos.read(|s| s.held_since.clone());
704 loop {
705 let before_sleep = Instant::now();
706 let _woke_by_tick = tokio::select! {
707 _tick = interval.tick() => {
708 true
709 }
710 _whatever = leased_seqnos.wait_while(|s| !s.request_sync) => {
711 false
712 }
713 };
714
715 let elapsed_since_before_sleeping = before_sleep.elapsed();
716 if elapsed_since_before_sleeping > sleep_duration + Duration::from_secs(60) {
717 warn!(
718 "reader ({}) of shard ({}) went {}s between heartbeats",
719 reader_id,
720 machine.shard_id(),
721 elapsed_since_before_sleeping.as_secs_f64()
722 );
723 }
724
725 let before_heartbeat = Instant::now();
726 let current_seqno = machine.seqno();
727 let result = leased_seqnos.modify(|s| {
728 if s.expired {
729 Err(())
730 } else {
731 s.observe_seqno(current_seqno);
732 s.request_sync = false;
733 held_since.join_assign(&s.held_since);
734 Ok(s.outstanding_seqno())
735 }
736 });
737 let actual_since = match result {
738 Ok(held_seqno) => {
739 let (seqno, actual_since, maintenance) = machine
740 .downgrade_since(&reader_id, held_seqno, &held_since)
741 .await;
742 leased_seqnos.modify(|s| {
743 s.applied_since.clone_from(&actual_since.0);
744 s.observe_seqno(seqno)
745 });
746 maintenance.start_performing(&machine, &gc);
747 actual_since
748 }
749 Err(()) => {
750 let (seqno, maintenance) = machine.expire_leased_reader(&reader_id).await;
751 leased_seqnos.modify(|s| s.observe_seqno(seqno));
752 maintenance.start_performing(&machine, &gc);
753 break;
754 }
755 };
756
757 let elapsed_since_heartbeat = before_heartbeat.elapsed();
758 if elapsed_since_heartbeat > Duration::from_secs(60) {
759 warn!(
760 "reader ({}) of shard ({}) heartbeat call took {}s",
761 reader_id,
762 machine.shard_id(),
763 elapsed_since_heartbeat.as_secs_f64(),
764 );
765 }
766
767 if PartialOrder::less_than(&held_since, &actual_since.0) {
768 warn!(
776 "heartbeat task for reader ({}) of shard ({}) exiting due to expired lease \
777 while read handle is live",
778 reader_id,
779 machine.shard_id(),
780 );
781 return;
782 }
783 }
784 }
785
786 pub fn shard_id(&self) -> ShardId {
788 self.machine.shard_id()
789 }
790
791 pub fn since(&self) -> &Antichain<T> {
795 &self.since
796 }
797
798 pub fn shared_upper(&self) -> Antichain<T> {
804 self.machine.applier.clone_upper()
805 }
806
807 #[cfg(test)]
808 fn outstanding_seqno(&self) -> SeqNo {
809 let current_seqno = self.machine.seqno();
810 self.hold_state.modify(|s| {
811 s.observe_seqno(current_seqno);
812 s.outstanding_seqno()
813 })
814 }
815
816 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
823 pub async fn downgrade_since(&mut self, new_since: &Antichain<T>) {
824 self.since = new_since.clone();
825 self.hold_state.modify(|s| {
826 s.downgrade_since(new_since);
827 s.request_sync = true;
828 });
829 self.hold_state
830 .wait_while(|s| PartialOrder::less_than(&s.applied_since, new_since))
831 .await;
832 }
833
834 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
851 pub async fn listen(self, as_of: Antichain<T>) -> Result<Listen<K, V, T, D>, Since<T>> {
852 Listen::new(self, as_of).await
853 }
854
855 async fn snapshot_batches(
856 &mut self,
857 as_of: Antichain<T>,
858 ) -> Result<(Lease, Vec<HollowBatch<T>>), Since<T>> {
859 self.machine
860 .wait_for_upper_past(
861 &as_of,
862 &mut self.watch,
863 Some(&self.reader_id),
864 &self.metrics.retries.snapshot,
865 RetryParameters::persist_defaults(),
866 )
867 .await;
868 let lease = self.lease_seqno().await;
869 let batches = match self.machine.applier.snapshot(&as_of) {
870 Ok(data) => data,
871 Err(SnapshotErr::AsOfHistoricalDistinctionsLost(since)) => return Err(since),
872 Err(SnapshotErr::AsOfNotYetAvailable(seqno, upper)) => {
873 panic!(
874 "waited for upper past {as_of:?}, but at latest seqno {seqno:?} the frontier was only {upper:?}",
875 as_of = as_of.elements(),
876 upper = upper.0.elements(),
877 )
878 }
879 };
880 Ok((lease, batches))
881 }
882
883 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
897 pub async fn snapshot(
898 &mut self,
899 as_of: Antichain<T>,
900 ) -> Result<Vec<LeasedBatchPart<T>>, Since<T>> {
901 let (lease, batches) = self.snapshot_batches(as_of.clone()).await?;
902
903 if !PartialOrder::less_equal(self.since(), &as_of) {
904 return Err(Since(self.since().clone()));
905 }
906
907 let filter = FetchBatchFilter::Snapshot { as_of };
908 let mut leased_parts = Vec::new();
909 for batch in batches {
910 leased_parts.extend(
915 self.lease_batch_parts(lease.clone(), batch, filter.clone())
916 .collect::<Vec<_>>()
917 .await,
918 );
919 }
920 Ok(leased_parts)
921 }
922
923 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
929 pub async fn subscribe(
930 mut self,
931 as_of: Antichain<T>,
932 ) -> Result<Subscribe<K, V, T, D>, Since<T>> {
933 let snapshot_parts = self.snapshot(as_of.clone()).await?;
934 let listen = self.listen(as_of.clone()).await?;
935 Ok(Subscribe::new(snapshot_parts, listen))
936 }
937
938 fn lease_batch_parts(
939 &mut self,
940 lease: Lease,
941 batch: HollowBatch<T>,
942 filter: FetchBatchFilter<T>,
943 ) -> impl Stream<Item = LeasedBatchPart<T>> + '_ {
944 stream! {
945 let blob = Arc::clone(&self.blob);
946 let metrics = Arc::clone(&self.metrics);
947 let desc = batch.desc.clone();
948 let bounds_truncated = batch.runs().any(|(meta, _)| meta.bounds_truncated());
949 for await part in batch.part_stream(self.shard_id(), &*blob, &*metrics) {
950 yield LeasedBatchPart {
951 metrics: Arc::clone(&self.metrics),
952 shard_id: self.machine.shard_id(),
953 filter: filter.clone(),
954 desc: desc.clone(),
955 part: part.expect("leased part").into_owned(),
956 lease: lease.clone(),
957 reader_id: self.reader_id.clone(),
958 filter_pushdown_audit: false,
959 bounds_truncated,
960 }
961 }
962 }
963 }
964
965 async fn lease_seqno(&mut self) -> Lease {
975 let current_seqno = self.machine.seqno();
976 let lease = self.hold_state.modify(|s| {
977 s.observe_seqno(current_seqno);
978 s.lease_seqno()
979 });
980 self.watch.wait_for_seqno_ge(lease.seqno()).await;
985 lease
986 }
987
988 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
991 pub async fn clone(&self, purpose: &str) -> Self {
992 let new_reader_id = LeasedReaderId::new();
993 let machine = self.machine.clone();
994 let gc = self.gc.clone();
995 let (reader_state, maintenance) = machine
996 .register_leased_reader(
997 &new_reader_id,
998 purpose,
999 READER_LEASE_DURATION.get(&self.cfg),
1000 false,
1001 )
1002 .await;
1003 maintenance.start_performing(&machine, &gc);
1004 assert!(PartialOrder::less_equal(&reader_state.since, &self.since));
1008 let new_reader = ReadHandle::new(
1009 self.cfg.clone(),
1010 Arc::clone(&self.metrics),
1011 machine,
1012 gc,
1013 Arc::clone(&self.blob),
1014 new_reader_id,
1015 self.read_schemas.clone(),
1016 reader_state,
1017 )
1018 .await;
1019 new_reader
1020 }
1021
1022 #[allow(clippy::unused_async)]
1027 pub async fn maybe_downgrade_since(&mut self, new_since: &Antichain<T>) {
1028 self.since = new_since.clone();
1029 self.hold_state.modify(|s| {
1030 s.downgrade_since(new_since);
1031 });
1032 }
1033
1034 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
1040 pub async fn expire(mut self) {
1041 self.hold_state.modify(|s| {
1042 s.expired = true;
1043 s.request_sync = true;
1044 });
1045 let Some(unexpired_state) = self.unexpired_state.take() else {
1046 return;
1047 };
1048 unexpired_state.heartbeat_task.await;
1049 }
1050
1051 #[cfg(test)]
1053 #[track_caller]
1054 pub async fn expect_listen(self, as_of: T) -> Listen<K, V, T, D> {
1055 self.listen(Antichain::from_elem(as_of))
1056 .await
1057 .expect("cannot serve requested as_of")
1058 }
1059}
1060
1061#[derive(Debug)]
1063pub(crate) struct UnexpiredReadHandleState {
1064 pub(crate) heartbeat_task: JoinHandle<()>,
1065}
1066
1067#[derive(Debug)]
1073pub struct Cursor<K: Codec, V: Codec, T: Timestamp + Codec64, D: Codec64, L = Lease> {
1074 consolidator: Consolidator<T, D, StructuredSort<K, V, T, D>>,
1075 max_len: usize,
1076 max_bytes: usize,
1077 _lease: L,
1078 read_schemas: Schemas<K, V>,
1079}
1080
1081impl<K: Codec, V: Codec, T: Timestamp + Codec64, D: Codec64, L> Cursor<K, V, T, D, L> {
1082 pub fn into_lease(self: Self) -> L {
1085 self._lease
1086 }
1087}
1088
1089impl<K, V, T, D, L> Cursor<K, V, T, D, L>
1090where
1091 K: Debug + Codec + Ord,
1092 V: Debug + Codec + Ord,
1093 T: Timestamp + Lattice + Codec64 + Sync,
1094 D: Monoid + Ord + Codec64 + Send + Sync,
1095{
1096 pub async fn next(&mut self) -> Option<impl Iterator<Item = ((K, V), T, D)> + '_> {
1098 let Self {
1099 consolidator,
1100 max_len,
1101 max_bytes,
1102 _lease,
1103 read_schemas: _,
1104 } = self;
1105
1106 let part = consolidator
1107 .next_chunk(*max_len, *max_bytes)
1108 .await
1109 .expect("fetching a leased part")?;
1110 let key_decoder = self
1111 .read_schemas
1112 .key
1113 .decoder_any(part.key.as_ref())
1114 .expect("ok");
1115 let val_decoder = self
1116 .read_schemas
1117 .val
1118 .decoder_any(part.val.as_ref())
1119 .expect("ok");
1120 let iter = (0..part.len()).map(move |i| {
1121 let mut k = K::default();
1122 let mut v = V::default();
1123 key_decoder.decode(i, &mut k);
1124 val_decoder.decode(i, &mut v);
1125 let t = T::decode(part.time.value(i).to_le_bytes());
1126 let d = D::decode(part.diff.value(i).to_le_bytes());
1127 ((k, v), t, d)
1128 });
1129
1130 Some(iter)
1131 }
1132}
1133
1134impl<K, V, T, D> ReadHandle<K, V, T, D>
1135where
1136 K: Debug + Codec + Ord,
1137 V: Debug + Codec + Ord,
1138 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1139 D: Monoid + Ord + Codec64 + Send + Sync,
1140{
1141 pub async fn snapshot_and_fetch(
1155 &mut self,
1156 as_of: Antichain<T>,
1157 ) -> Result<Vec<((K, V), T, D)>, Since<T>> {
1158 let mut cursor = self.snapshot_cursor(as_of, |_| true).await?;
1159 let mut contents = Vec::new();
1160 while let Some(iter) = cursor.next().await {
1161 contents.extend(iter);
1162 }
1163
1164 let old_len = contents.len();
1167 consolidate_updates(&mut contents);
1168 if old_len != contents.len() {
1169 self.machine
1171 .applier
1172 .shard_metrics
1173 .unconsolidated_snapshot
1174 .inc();
1175 }
1176
1177 Ok(contents)
1178 }
1179
1180 pub async fn snapshot_cursor(
1187 &mut self,
1188 as_of: Antichain<T>,
1189 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
1190 ) -> Result<Cursor<K, V, T, D>, Since<T>> {
1191 let (lease, batches) = self.snapshot_batches(as_of.clone()).await?;
1192
1193 Self::read_batches_consolidated(
1194 &self.cfg,
1195 Arc::clone(&self.metrics),
1196 Arc::clone(&self.machine.applier.shard_metrics),
1197 self.metrics.read.snapshot.clone(),
1198 Arc::clone(&self.blob),
1199 self.shard_id(),
1200 as_of,
1201 self.read_schemas.clone(),
1202 &batches,
1203 lease,
1204 should_fetch_part,
1205 COMPACTION_MEMORY_BOUND_BYTES.get(&self.cfg),
1206 )
1207 }
1208
1209 pub(crate) fn read_batches_consolidated<L>(
1210 persist_cfg: &PersistConfig,
1211 metrics: Arc<Metrics>,
1212 shard_metrics: Arc<ShardMetrics>,
1213 read_metrics: ReadMetrics,
1214 blob: Arc<dyn Blob>,
1215 shard_id: ShardId,
1216 as_of: Antichain<T>,
1217 schemas: Schemas<K, V>,
1218 batches: &[HollowBatch<T>],
1219 lease: L,
1220 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
1221 memory_budget_bytes: usize,
1222 ) -> Result<Cursor<K, V, T, D, L>, Since<T>> {
1223 let context = format!("{}[as_of={:?}]", shard_id, as_of.elements());
1224 let filter = FetchBatchFilter::Snapshot {
1225 as_of: as_of.clone(),
1226 };
1227
1228 let mut consolidator = Consolidator::new(
1229 context,
1230 FetchConfig::from_persist_config(persist_cfg),
1231 shard_id,
1232 StructuredSort::new(schemas.clone()),
1233 blob,
1234 metrics,
1235 shard_metrics,
1236 read_metrics,
1237 filter,
1238 None,
1239 memory_budget_bytes,
1240 );
1241 for batch in batches {
1242 for (meta, run) in batch.runs() {
1243 consolidator.enqueue_run(
1244 &batch.desc,
1245 meta,
1246 run.into_iter()
1247 .filter(|p| should_fetch_part(p.stats()))
1248 .cloned(),
1249 );
1250 }
1251 }
1252 let max_len = persist_cfg.compaction_yield_after_n_updates;
1256 let max_bytes = BLOB_TARGET_SIZE.get(persist_cfg).max(1);
1257
1258 Ok(Cursor {
1259 consolidator,
1260 max_len,
1261 max_bytes,
1262 _lease: lease,
1263 read_schemas: schemas,
1264 })
1265 }
1266
1267 pub fn snapshot_stats(
1279 &self,
1280 as_of: Option<Antichain<T>>,
1281 ) -> impl Future<Output = Result<SnapshotStats, Since<T>>> + Send + 'static {
1282 let machine = self.machine.clone();
1283 async move {
1284 let batches = match as_of {
1285 Some(as_of) => machine.unleased_snapshot(&as_of).await?,
1286 None => machine.applier.all_batches(),
1287 };
1288 let num_updates = batches.iter().map(|b| b.len).sum();
1289 Ok(SnapshotStats {
1290 shard_id: machine.shard_id(),
1291 num_updates,
1292 })
1293 }
1294 }
1295
1296 pub async fn snapshot_parts_stats(
1307 &self,
1308 as_of: Antichain<T>,
1309 ) -> Result<SnapshotPartsStats, Since<T>> {
1310 let batches = self.machine.unleased_snapshot(&as_of).await?;
1311 let parts = stream::iter(&batches)
1312 .flat_map(|b| b.part_stream(self.shard_id(), &*self.blob, &*self.metrics))
1313 .map(|p| {
1314 let p = p.expect("live batch");
1315 SnapshotPartStats {
1316 encoded_size_bytes: p.encoded_size_bytes(),
1317 stats: p.stats().cloned(),
1318 }
1319 })
1320 .collect()
1321 .await;
1322 Ok(SnapshotPartsStats {
1323 metrics: Arc::clone(&self.machine.applier.metrics),
1324 shard_id: self.machine.shard_id(),
1325 parts,
1326 })
1327 }
1328}
1329
1330impl<K, V, T, D> ReadHandle<K, V, T, D>
1331where
1332 K: Debug + Codec + Ord,
1333 V: Debug + Codec + Ord,
1334 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1335 D: Monoid + Codec64 + Send + Sync,
1336{
1337 pub async fn snapshot_and_stream(
1342 &mut self,
1343 as_of: Antichain<T>,
1344 ) -> Result<impl Stream<Item = ((K, V), T, D)> + use<K, V, T, D>, Since<T>> {
1345 let snap = self.snapshot(as_of).await?;
1346
1347 let blob = Arc::clone(&self.blob);
1348 let metrics = Arc::clone(&self.metrics);
1349 let snapshot_metrics = self.metrics.read.snapshot.clone();
1350 let shard_metrics = Arc::clone(&self.machine.applier.shard_metrics);
1351 let reader_id = self.reader_id.clone();
1352 let schemas = self.read_schemas.clone();
1353 let mut schema_cache = self.schema_cache.clone();
1354 let persist_cfg = self.cfg.clone();
1355 let stream = async_stream::stream! {
1356 for part in snap {
1357 let mut fetched_part = fetch_leased_part(
1358 &persist_cfg,
1359 &part,
1360 blob.as_ref(),
1361 Arc::clone(&metrics),
1362 &snapshot_metrics,
1363 &shard_metrics,
1364 &reader_id,
1365 schemas.clone(),
1366 &mut schema_cache,
1367 )
1368 .await;
1369
1370 while let Some(next) = fetched_part.next() {
1371 yield next;
1372 }
1373 }
1374 };
1375
1376 Ok(stream)
1377 }
1378}
1379
1380impl<K, V, T, D> ReadHandle<K, V, T, D>
1381where
1382 K: Debug + Codec + Ord,
1383 V: Debug + Codec + Ord,
1384 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1385 D: Monoid + Ord + Codec64 + Send + Sync,
1386{
1387 #[cfg(test)]
1390 #[track_caller]
1391 pub async fn expect_snapshot_and_fetch(&mut self, as_of: T) -> Vec<((K, V), T, D)> {
1392 let mut ret = self
1393 .snapshot_and_fetch(Antichain::from_elem(as_of))
1394 .await
1395 .expect("cannot serve requested as_of");
1396
1397 ret.sort();
1398 ret
1399 }
1400}
1401
1402impl<K: Codec, V: Codec, T, D> Drop for ReadHandle<K, V, T, D> {
1403 fn drop(&mut self) {
1404 self.hold_state.modify(|s| {
1405 s.expired = true;
1406 s.request_sync = true;
1407 });
1408 }
1409}
1410
1411#[cfg(test)]
1412mod tests {
1413 use std::pin;
1414 use std::str::FromStr;
1415
1416 use mz_dyncfg::ConfigUpdates;
1417 use mz_ore::cast::CastFrom;
1418 use mz_ore::metrics::MetricsRegistry;
1419 use mz_persist::mem::{MemBlob, MemBlobConfig, MemConsensus};
1420 use mz_persist::unreliable::{UnreliableConsensus, UnreliableHandle};
1421 use serde::{Deserialize, Serialize};
1422 use serde_json::json;
1423 use tokio_stream::StreamExt;
1424
1425 use crate::async_runtime::IsolatedRuntime;
1426 use crate::batch::BLOB_TARGET_SIZE;
1427 use crate::cache::StateCache;
1428 use crate::internal::metrics::Metrics;
1429 use crate::rpc::NoopPubSubSender;
1430 use crate::tests::{all_ok, new_test_client};
1431 use crate::{Diagnostics, PersistClient, PersistConfig, ShardId};
1432
1433 use super::*;
1434
1435 #[mz_persist_proc::test(tokio::test)]
1437 #[cfg_attr(miri, ignore)] async fn drop_unused_subscribe(dyncfgs: ConfigUpdates) {
1439 let data = [
1440 (("0".to_owned(), "zero".to_owned()), 0, 1),
1441 (("1".to_owned(), "one".to_owned()), 1, 1),
1442 (("2".to_owned(), "two".to_owned()), 2, 1),
1443 ];
1444
1445 let (mut write, read) = new_test_client(&dyncfgs)
1446 .await
1447 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1448 .await;
1449
1450 write.expect_compare_and_append(&data[0..1], 0, 1).await;
1451 write.expect_compare_and_append(&data[1..2], 1, 2).await;
1452 write.expect_compare_and_append(&data[2..3], 2, 3).await;
1453
1454 let subscribe = read
1455 .subscribe(timely::progress::Antichain::from_elem(2))
1456 .await
1457 .unwrap();
1458 assert!(
1459 !subscribe.snapshot.as_ref().unwrap().is_empty(),
1460 "snapshot must have batches for test to be meaningful"
1461 );
1462 drop(subscribe);
1463 }
1464
1465 #[mz_ore::test(tokio::test)]
1470 #[cfg_attr(miri, ignore)] async fn maybe_optimize_ignores_truncated_batches() {
1472 use arrow::array::{ArrayRef, StringArray};
1473
1474 use crate::batch::{INLINE_WRITES_SINGLE_MAX_BYTES, INLINE_WRITES_TOTAL_MAX_BYTES};
1475 use crate::cache::PersistClientCache;
1476
1477 let mut cache = PersistClientCache::new_no_metrics();
1478 cache.cfg.compaction_enabled = false;
1481 cache.cfg.set_config(&INLINE_WRITES_SINGLE_MAX_BYTES, 0);
1483 cache.cfg.set_config(&INLINE_WRITES_TOTAL_MAX_BYTES, 0);
1484 let client = cache
1485 .open(crate::PersistLocation::new_in_mem())
1486 .await
1487 .expect("client construction failed");
1488 let (mut write, mut read) = client
1489 .expect_open::<String, String, u64, i64>(ShardId::new())
1490 .await;
1491
1492 write
1493 .expect_compare_and_append(&[(("0".to_owned(), "zero".to_owned()), 0, 1)], 0, 1)
1494 .await;
1495
1496 let mut batch = write
1499 .expect_batch(
1500 &[
1501 (("stale".to_owned(), "stale".to_owned()), 0, 1),
1502 (("1".to_owned(), "one".to_owned()), 1, 1),
1503 (("2".to_owned(), "two".to_owned()), 2, 1),
1504 ],
1505 0,
1506 3,
1507 )
1508 .await;
1509 write
1510 .compare_and_append_batch(
1511 &mut [&mut batch],
1512 Antichain::from_elem(1),
1513 Antichain::from_elem(3),
1514 true,
1515 )
1516 .await
1517 .expect("invalid usage")
1518 .expect("unexpected upper");
1519
1520 write
1522 .expect_compare_and_append(&[(("3".to_owned(), "three".to_owned()), 3, 1)], 3, 4)
1523 .await;
1524
1525 let parts = read
1526 .snapshot(Antichain::from_elem(3))
1527 .await
1528 .expect("as_of unavailable");
1529
1530 let mut faked = 0;
1531 let mut kept_truncated = 0;
1532 for mut part in parts {
1533 let key: ArrayRef = Arc::new(StringArray::from(vec!["k"]));
1534 let val: ArrayRef = Arc::new(StringArray::from(vec!["v"]));
1535 part.maybe_optimize(&cache.cfg, key, val);
1536 match part.desc.lower().elements() {
1537 [0] => {
1538 assert!(!part.bounds_truncated);
1541 assert!(part.part.is_inline(), "expected a faked part");
1542 faked += 1;
1543 }
1544 [1] => {
1545 assert!(part.bounds_truncated);
1548 assert!(
1549 !part.part.is_inline(),
1550 "must not substitute statistics for a truncated part"
1551 );
1552 kept_truncated += 1;
1553 }
1554 [3] => (),
1555 x => panic!("unexpected part lower {:?}", x),
1556 }
1557 }
1558 assert_eq!(faked, 1);
1559 assert_eq!(kept_truncated, 1);
1560 }
1561
1562 #[mz_persist_proc::test(tokio::test)]
1564 #[cfg_attr(miri, ignore)] async fn streaming_consolidate(dyncfgs: ConfigUpdates) {
1566 let data = &[
1567 (("k".to_owned(), "v".to_owned()), 0, 1),
1569 (("k".to_owned(), "v".to_owned()), 1, 1),
1570 (("k".to_owned(), "v".to_owned()), 2, 1),
1571 (("k2".to_owned(), "v".to_owned()), 0, 1),
1573 (("k2".to_owned(), "v".to_owned()), 1, -1),
1574 ];
1575
1576 let (mut write, read) = {
1577 let client = new_test_client(&dyncfgs).await;
1578 client.cfg.set_config(&BLOB_TARGET_SIZE, 1000); client
1580 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1581 .await
1582 };
1583
1584 write.expect_compare_and_append(data, 0, 5).await;
1585
1586 let mut snapshot = read
1587 .subscribe(timely::progress::Antichain::from_elem(4))
1588 .await
1589 .unwrap();
1590
1591 let mut updates = vec![];
1592 'outer: loop {
1593 for event in snapshot.fetch_next().await {
1594 match event {
1595 ListenEvent::Progress(t) => {
1596 if !t.less_than(&4) {
1597 break 'outer;
1598 }
1599 }
1600 ListenEvent::Updates(data) => {
1601 updates.extend(data);
1602 }
1603 }
1604 }
1605 }
1606 assert_eq!(updates, &[(("k".to_owned(), "v".to_owned()), 4u64, 3i64)],)
1607 }
1608
1609 #[mz_persist_proc::test(tokio::test)]
1610 #[cfg_attr(miri, ignore)] async fn snapshot_and_stream(dyncfgs: ConfigUpdates) {
1612 let data = &mut [
1613 (("k1".to_owned(), "v1".to_owned()), 0, 1),
1614 (("k2".to_owned(), "v2".to_owned()), 1, 1),
1615 (("k3".to_owned(), "v3".to_owned()), 2, 1),
1616 (("k4".to_owned(), "v4".to_owned()), 2, 1),
1617 (("k5".to_owned(), "v5".to_owned()), 3, 1),
1618 ];
1619
1620 let (mut write, mut read) = {
1621 let client = new_test_client(&dyncfgs).await;
1622 client.cfg.set_config(&BLOB_TARGET_SIZE, 0); client
1624 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1625 .await
1626 };
1627
1628 write.expect_compare_and_append(&data[0..2], 0, 2).await;
1629 write.expect_compare_and_append(&data[2..4], 2, 3).await;
1630 write.expect_compare_and_append(&data[4..], 3, 4).await;
1631
1632 let as_of = Antichain::from_elem(3);
1633 let mut snapshot = pin::pin!(read.snapshot_and_stream(as_of.clone()).await.unwrap());
1634
1635 let mut snapshot_rows = vec![];
1636 while let Some(((k, v), t, d)) = snapshot.next().await {
1637 snapshot_rows.push(((k, v), t, d));
1638 }
1639
1640 for ((_k, _v), t, _d) in data.as_mut_slice() {
1641 t.advance_by(as_of.borrow());
1642 }
1643
1644 assert_eq!(data.as_slice(), snapshot_rows.as_slice());
1645 }
1646
1647 #[mz_persist_proc::test(tokio::test)]
1649 #[cfg_attr(miri, ignore)] async fn seqno_leases(dyncfgs: ConfigUpdates) {
1651 let mut data = vec![];
1652 for i in 0..20 {
1653 data.push(((i.to_string(), i.to_string()), i, 1))
1654 }
1655
1656 let shard_id = ShardId::new();
1657
1658 let client = new_test_client(&dyncfgs).await;
1659 let (mut write, read) = client
1660 .expect_open::<String, String, u64, i64>(shard_id)
1661 .await;
1662
1663 let mut offset = 0;
1665 let mut width = 2;
1666
1667 for i in offset..offset + width {
1668 write
1669 .expect_compare_and_append(
1670 &data[i..i + 1],
1671 u64::cast_from(i),
1672 u64::cast_from(i) + 1,
1673 )
1674 .await;
1675 }
1676 offset += width;
1677
1678 let mut fetcher = client
1680 .create_batch_fetcher::<String, String, u64, i64>(
1681 shard_id,
1682 Default::default(),
1683 Default::default(),
1684 false,
1685 Diagnostics::for_tests(),
1686 )
1687 .await
1688 .unwrap();
1689
1690 let mut subscribe = read
1691 .subscribe(timely::progress::Antichain::from_elem(1))
1692 .await
1693 .expect("cannot serve requested as_of");
1694
1695 let original_seqno_since = subscribe.listen.handle.outstanding_seqno();
1697 if let Some(snapshot) = &subscribe.snapshot {
1698 for part in snapshot {
1699 assert!(
1700 part.lease.seqno() >= original_seqno_since,
1701 "our seqno hold must cover all parts"
1702 );
1703 }
1704 }
1705
1706 let mut parts = vec![];
1707
1708 width = 4;
1709 for i in offset..offset + width {
1711 for event in subscribe.next(None).await {
1712 if let ListenEvent::Updates(mut new_parts) = event {
1713 parts.append(&mut new_parts);
1714 subscribe
1717 .listen
1718 .handle
1719 .downgrade_since(&subscribe.listen.since)
1720 .await;
1721 }
1722 }
1723
1724 write
1725 .expect_compare_and_append(
1726 &data[i..i + 1],
1727 u64::cast_from(i),
1728 u64::cast_from(i) + 1,
1729 )
1730 .await;
1731
1732 assert_eq!(
1734 subscribe.listen.handle.machine.applier.seqno_since(),
1735 original_seqno_since
1736 );
1737 }
1738
1739 offset += width;
1740
1741 let mut seqno_since = subscribe.listen.handle.machine.applier.seqno_since();
1742
1743 assert_eq!(seqno_since, original_seqno_since);
1745
1746 let mut subsequent_parts = vec![];
1749
1750 let mut this_seqno = SeqNo::minimum();
1754
1755 for (mut i, part) in parts.into_iter().enumerate() {
1757 let part_seqno = part.lease.seqno();
1758 let last_seqno = this_seqno;
1759 this_seqno = part_seqno;
1760 assert!(this_seqno >= last_seqno);
1761
1762 let (part, lease) = part.into_exchangeable_part();
1763 let _ = fetcher.fetch_leased_part(part).await;
1764 drop(lease);
1765
1766 for event in subscribe.next(None).await {
1768 if let ListenEvent::Updates(parts) = event {
1769 for part in parts {
1770 let (_, lease) = part.into_exchangeable_part();
1771 subsequent_parts.push(lease);
1772 }
1773 }
1774 }
1775
1776 subscribe
1777 .listen
1778 .handle
1779 .downgrade_since(&subscribe.listen.since)
1780 .await;
1781
1782 i += offset;
1784 write
1785 .expect_compare_and_append(
1786 &data[i..i + 1],
1787 u64::cast_from(i),
1788 u64::cast_from(i) + 1,
1789 )
1790 .await;
1791
1792 let expect_downgrade = subscribe.listen.handle.outstanding_seqno() > part_seqno;
1795
1796 let new_seqno_since = subscribe.listen.handle.machine.applier.seqno_since();
1797 if expect_downgrade {
1798 assert!(new_seqno_since > seqno_since);
1799 } else {
1800 assert_eq!(new_seqno_since, seqno_since);
1801 }
1802 seqno_since = new_seqno_since;
1803 }
1804
1805 assert!(seqno_since > original_seqno_since);
1807
1808 drop(subsequent_parts);
1810 drop(subscribe);
1811 }
1812
1813 #[mz_ore::test]
1814 fn reader_id_human_readable_serde() {
1815 #[derive(Debug, Serialize, Deserialize)]
1816 struct Container {
1817 reader_id: LeasedReaderId,
1818 }
1819
1820 let id =
1822 LeasedReaderId::from_str("r00000000-1234-5678-0000-000000000000").expect("valid id");
1823 assert_eq!(
1824 id,
1825 serde_json::from_value(serde_json::to_value(id.clone()).expect("serializable"))
1826 .expect("deserializable")
1827 );
1828
1829 assert_eq!(
1831 id,
1832 serde_json::from_str("\"r00000000-1234-5678-0000-000000000000\"")
1833 .expect("deserializable")
1834 );
1835
1836 let json = json!({ "reader_id": id });
1838 assert_eq!(
1839 "{\"reader_id\":\"r00000000-1234-5678-0000-000000000000\"}",
1840 &json.to_string()
1841 );
1842 let container: Container = serde_json::from_value(json).expect("deserializable");
1843 assert_eq!(container.reader_id, id);
1844 }
1845
1846 #[mz_ore::test(tokio::test)]
1850 #[cfg_attr(miri, ignore)] async fn skip_consensus_fetch_optimization() {
1852 let data = vec![
1853 (("0".to_owned(), "zero".to_owned()), 0, 1),
1854 (("1".to_owned(), "one".to_owned()), 1, 1),
1855 (("2".to_owned(), "two".to_owned()), 2, 1),
1856 ];
1857
1858 let cfg = PersistConfig::new_for_tests();
1859 let blob = Arc::new(MemBlob::open(MemBlobConfig::default()));
1860 let consensus = Arc::new(MemConsensus::default());
1861 let unreliable = UnreliableHandle::default();
1862 unreliable.totally_available();
1863 let consensus = Arc::new(UnreliableConsensus::new(consensus, unreliable.clone()));
1864 let metrics = Arc::new(Metrics::new(&cfg, &MetricsRegistry::new()));
1865 let pubsub_sender = Arc::new(NoopPubSubSender);
1866 let (mut write, mut read) = PersistClient::new(
1867 cfg,
1868 blob,
1869 consensus,
1870 metrics,
1871 Arc::new(IsolatedRuntime::new_for_tests()),
1872 Arc::new(StateCache::new_no_metrics()),
1873 pubsub_sender,
1874 )
1875 .expect("client construction failed")
1876 .expect_open::<String, String, u64, i64>(ShardId::new())
1877 .await;
1878
1879 write.expect_compare_and_append(&data[0..1], 0, 1).await;
1880 write.expect_compare_and_append(&data[1..2], 1, 2).await;
1881 write.expect_compare_and_append(&data[2..3], 2, 3).await;
1882
1883 let snapshot = read.expect_snapshot_and_fetch(2).await;
1884 let mut listen = read.expect_listen(0).await;
1885
1886 let listen_actual = listen.fetch_next().await;
1891 let expected_events = vec![ListenEvent::Progress(Antichain::from_elem(1))];
1892 assert_eq!(listen_actual, expected_events);
1893
1894 unreliable.totally_unavailable();
1897 assert_eq!(snapshot, all_ok(&data, 2));
1898 assert_eq!(
1899 listen.read_until(&3).await,
1900 (all_ok(&data[1..], 1), Antichain::from_elem(3))
1901 );
1902 }
1903}