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::process::halt_with_code;
30use mz_ore::task::JoinHandle;
31use mz_persist::location::{Blob, SeqNo};
32use mz_persist_types::columnar::{ColumnDecoder, Schema};
33use mz_persist_types::{Codec, Codec64};
34use proptest_derive::Arbitrary;
35use serde::{Deserialize, Serialize};
36use timely::PartialOrder;
37use timely::order::TotalOrder;
38use timely::progress::{Antichain, Timestamp};
39use tracing::warn;
40use uuid::Uuid;
41
42use crate::batch::BLOB_TARGET_SIZE;
43use crate::cfg::{COMPACTION_MEMORY_BOUND_BYTES, RetryParameters};
44use crate::fetch::FetchConfig;
45use crate::fetch::{FetchBatchFilter, FetchedPart, Lease, LeasedBatchPart, fetch_leased_part};
46use crate::internal::encoding::Schemas;
47use crate::internal::machine::{Machine, next_listen_batch_retry_params};
48use crate::internal::metrics::{Metrics, ReadMetrics, ShardMetrics};
49use crate::internal::state::{HollowBatch, LeasedReaderState, SnapshotErr};
50use crate::internal::watch::{AwaitableState, StateWatch};
51use crate::iter::{Consolidator, StructuredSort};
52use crate::schema::SchemaCache;
53use crate::stats::{SnapshotPartStats, SnapshotPartsStats, SnapshotStats};
54use crate::{GarbageCollector, PersistConfig, ShardId, parse_id};
55
56pub use crate::internal::encoding::LazyPartStats;
57pub use crate::internal::state::Since;
58
59#[derive(
61 Arbitrary,
62 Clone,
63 PartialEq,
64 Eq,
65 PartialOrd,
66 Ord,
67 Hash,
68 Serialize,
69 Deserialize
70)]
71#[serde(try_from = "String", into = "String")]
72pub struct LeasedReaderId(pub(crate) [u8; 16]);
73
74impl std::fmt::Display for LeasedReaderId {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(f, "r{}", Uuid::from_bytes(self.0))
77 }
78}
79
80impl std::fmt::Debug for LeasedReaderId {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(f, "LeasedReaderId({})", Uuid::from_bytes(self.0))
83 }
84}
85
86impl std::str::FromStr for LeasedReaderId {
87 type Err = String;
88
89 fn from_str(s: &str) -> Result<Self, Self::Err> {
90 parse_id("r", "LeasedReaderId", s).map(LeasedReaderId)
91 }
92}
93
94impl From<LeasedReaderId> for String {
95 fn from(reader_id: LeasedReaderId) -> Self {
96 reader_id.to_string()
97 }
98}
99
100impl TryFrom<String> for LeasedReaderId {
101 type Error = String;
102
103 fn try_from(s: String) -> Result<Self, Self::Error> {
104 s.parse()
105 }
106}
107
108impl LeasedReaderId {
109 pub(crate) fn new() -> Self {
110 LeasedReaderId(*Uuid::new_v4().as_bytes())
111 }
112}
113
114#[derive(Debug)]
119pub struct Subscribe<K: Codec, V: Codec, T, D> {
120 snapshot: Option<Vec<LeasedBatchPart<T>>>,
121 listen: Listen<K, V, T, D>,
122}
123
124impl<K, V, T, D> Subscribe<K, V, T, D>
125where
126 K: Debug + Codec,
127 V: Debug + Codec,
128 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
129 D: Monoid + Codec64 + Send + Sync,
130{
131 fn new(snapshot_parts: Vec<LeasedBatchPart<T>>, listen: Listen<K, V, T, D>) -> Self {
132 Subscribe {
133 snapshot: Some(snapshot_parts),
134 listen,
135 }
136 }
137
138 #[instrument(level = "debug", fields(shard = %self.listen.handle.machine.shard_id()))]
146 pub async fn next(
147 &mut self,
148 listen_retry: Option<RetryParameters>,
150 ) -> Vec<ListenEvent<T, LeasedBatchPart<T>>> {
151 match self.snapshot.take() {
152 Some(parts) => vec![ListenEvent::Updates(parts)],
153 None => {
154 let (parts, upper) = self.listen.next(listen_retry).await;
155 vec![ListenEvent::Updates(parts), ListenEvent::Progress(upper)]
156 }
157 }
158 }
159}
160
161impl<K, V, T, D> Subscribe<K, V, T, D>
162where
163 K: Debug + Codec,
164 V: Debug + Codec,
165 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
166 D: Monoid + Codec64 + Send + Sync,
167{
168 #[instrument(level = "debug", fields(shard = %self.listen.handle.machine.shard_id()))]
171 pub async fn fetch_next(&mut self) -> Vec<ListenEvent<T, ((K, V), T, D)>> {
172 let events = self.next(None).await;
173 let new_len = events
174 .iter()
175 .map(|event| match event {
176 ListenEvent::Updates(parts) => parts.len(),
177 ListenEvent::Progress(_) => 1,
178 })
179 .sum();
180 let mut ret = Vec::with_capacity(new_len);
181 for event in events {
182 match event {
183 ListenEvent::Updates(parts) => {
184 for part in parts {
185 let fetched_part = self.listen.fetch_batch_part(part).await;
186 let updates = fetched_part.collect::<Vec<_>>();
187 if !updates.is_empty() {
188 ret.push(ListenEvent::Updates(updates));
189 }
190 }
191 }
192 ListenEvent::Progress(progress) => ret.push(ListenEvent::Progress(progress)),
193 }
194 }
195 ret
196 }
197
198 pub async fn fetch_batch_part(&mut self, part: LeasedBatchPart<T>) -> FetchedPart<K, V, T, D> {
200 self.listen.fetch_batch_part(part).await
201 }
202}
203
204impl<K, V, T, D> Subscribe<K, V, T, D>
205where
206 K: Debug + Codec,
207 V: Debug + Codec,
208 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
209 D: Monoid + Codec64 + Send + Sync,
210{
211 pub async fn expire(mut self) {
218 let _ = self.snapshot.take(); self.listen.expire().await;
220 }
221}
222
223#[derive(Debug, PartialEq)]
227pub enum ListenEvent<T, D> {
228 Progress(Antichain<T>),
230 Updates(Vec<D>),
232}
233
234#[derive(Debug)]
236pub struct Listen<K: Codec, V: Codec, T, D> {
237 handle: ReadHandle<K, V, T, D>,
238 as_of: Antichain<T>,
239 since: Antichain<T>,
240 frontier: Antichain<T>,
241}
242
243impl<K, V, T, D> Listen<K, V, T, D>
244where
245 K: Debug + Codec,
246 V: Debug + Codec,
247 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
248 D: Monoid + Codec64 + Send + Sync,
249{
250 async fn new(
251 mut handle: ReadHandle<K, V, T, D>,
252 as_of: Antichain<T>,
253 ) -> Result<Self, Since<T>> {
254 let () = handle.machine.verify_listen(&as_of)?;
255
256 let since = as_of.clone();
257 if !PartialOrder::less_equal(handle.since(), &since) {
258 return Err(Since(handle.since().clone()));
261 }
262 handle.downgrade_since(&since).await;
266 Ok(Listen {
267 handle,
268 since,
269 frontier: as_of.clone(),
270 as_of,
271 })
272 }
273
274 pub fn frontier(&self) -> &Antichain<T> {
276 &self.frontier
277 }
278
279 pub async fn next(
287 &mut self,
288 retry: Option<RetryParameters>,
290 ) -> (Vec<LeasedBatchPart<T>>, Antichain<T>) {
291 let retry = retry
293 .unwrap_or_else(|| next_listen_batch_retry_params(&self.handle.machine.applier.cfg));
294 self.handle
295 .machine
296 .wait_for_upper_past(
297 &self.frontier,
298 &mut self.handle.watch,
299 Some(&self.handle.reader_id),
300 &self.handle.metrics.retries.next_listen_batch,
301 retry,
302 )
303 .await;
304
305 let lease = self.handle.lease_seqno().await;
307 let batch = match self
308 .handle
309 .machine
310 .applier
311 .next_listen_batch(&self.frontier)
312 {
313 Ok(batch) => batch,
314 Err(seqno) => {
315 panic!(
316 "waited for upper past {frontier:?}, but no listen batch was available at {seqno:?}!",
317 frontier = self.frontier.elements()
318 );
319 }
320 };
321
322 let acceptable_desc = PartialOrder::less_than(batch.desc.since(), &self.frontier)
332 || (self.frontier == self.as_of
337 && PartialOrder::less_equal(batch.desc.since(), &self.frontier));
338 if !acceptable_desc {
339 let lease_state = self
340 .handle
341 .machine
342 .applier
343 .reader_lease(self.handle.reader_id.clone());
344 if let Some(lease) = lease_state {
345 panic!(
346 "Listen on {} received a batch {:?} advanced past the listen frontier {:?}, but the lease has not expired: {:?}",
347 self.handle.machine.shard_id(),
348 batch.desc,
349 self.frontier,
350 lease
351 )
352 } else if self.handle.machine.applier.is_tombstone() {
353 halt!(
355 "Listen on {} received a batch {:?} advanced past the listen frontier {:?} after the shard was tombstoned",
356 self.handle.machine.shard_id(),
357 batch.desc,
358 self.frontier
359 )
360 } else {
361 halt_with_code(
364 READER_LEASE_EXPIRED_EXIT_CODE,
365 format_args!(
366 "Listen on {} received a batch {:?} advanced past the listen frontier {:?} after the reader has expired. \
367 This can happen in exceptional cases: a machine goes to sleep or is running out of memory or CPU, for example.",
368 self.handle.machine.shard_id(),
369 batch.desc,
370 self.frontier
371 ),
372 )
373 }
374 }
375
376 let new_frontier = batch.desc.upper().clone();
377
378 for x in self.frontier.elements().iter() {
401 let less_than_upper = batch.desc.upper().elements().iter().any(|u| x.less_than(u));
402 if less_than_upper {
403 self.since.join_assign(&Antichain::from_elem(x.clone()));
404 }
405 }
406
407 let filter = FetchBatchFilter::Listen {
412 as_of: self.as_of.clone(),
413 lower: self.frontier.clone(),
414 };
415 let parts = self
416 .handle
417 .lease_batch_parts(lease, batch, filter)
418 .collect()
419 .await;
420
421 self.handle.maybe_downgrade_since(&self.since).await;
422
423 self.frontier = new_frontier;
426
427 (parts, self.frontier.clone())
428 }
429}
430
431impl<K, V, T, D> Listen<K, V, T, D>
432where
433 K: Debug + Codec,
434 V: Debug + Codec,
435 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
436 D: Monoid + Codec64 + Send + Sync,
437{
438 #[instrument(level = "debug", name = "listen::next", fields(shard = %self.handle.machine.shard_id()))]
449 pub async fn fetch_next(&mut self) -> Vec<ListenEvent<T, ((K, V), T, D)>> {
450 let (parts, progress) = self.next(None).await;
451 let mut ret = Vec::with_capacity(parts.len() + 1);
452 for part in parts {
453 let fetched_part = self.fetch_batch_part(part).await;
454 let updates = fetched_part.collect::<Vec<_>>();
455 if !updates.is_empty() {
456 ret.push(ListenEvent::Updates(updates));
457 }
458 }
459 ret.push(ListenEvent::Progress(progress));
460 ret
461 }
462
463 pub fn into_stream(mut self) -> impl Stream<Item = ListenEvent<T, ((K, V), T, D)>> {
465 async_stream::stream!({
466 loop {
467 for msg in self.fetch_next().await {
468 yield msg;
469 }
470 }
471 })
472 }
473
474 #[cfg(test)]
478 #[track_caller]
479 pub async fn read_until(&mut self, ts: &T) -> (Vec<((K, V), T, D)>, Antichain<T>) {
480 let mut updates = Vec::new();
481 let mut frontier = Antichain::from_elem(T::minimum());
482 while self.frontier.less_than(ts) {
483 for event in self.fetch_next().await {
484 match event {
485 ListenEvent::Updates(mut x) => updates.append(&mut x),
486 ListenEvent::Progress(x) => frontier = x,
487 }
488 }
489 }
490 (updates, frontier)
493 }
494}
495
496impl<K, V, T, D> Listen<K, V, T, D>
497where
498 K: Debug + Codec,
499 V: Debug + Codec,
500 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
501 D: Monoid + Codec64 + Send + Sync,
502{
503 async fn fetch_batch_part(&mut self, part: LeasedBatchPart<T>) -> FetchedPart<K, V, T, D> {
508 let fetched_part = fetch_leased_part(
509 &self.handle.cfg,
510 &part,
511 self.handle.blob.as_ref(),
512 Arc::clone(&self.handle.metrics),
513 &self.handle.metrics.read.listen,
514 &self.handle.machine.applier.shard_metrics,
515 &self.handle.reader_id,
516 self.handle.read_schemas.clone(),
517 &mut self.handle.schema_cache,
518 )
519 .await;
520 fetched_part
521 }
522
523 pub async fn expire(self) {
530 self.handle.expire().await
531 }
532}
533
534#[derive(Debug)]
537pub(crate) struct ReadHolds<T> {
538 held_since: Antichain<T>,
540 applied_since: Antichain<T>,
543 recent_seqno: SeqNo,
545 leases: BTreeMap<SeqNo, Lease>,
548 expired: bool,
550 request_sync: bool,
553}
554
555impl<T> ReadHolds<T>
556where
557 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
558{
559 pub fn downgrade_since(&mut self, since: &Antichain<T>) {
560 self.held_since.join_assign(since);
561 }
562
563 pub fn observe_seqno(&mut self, seqno: SeqNo) {
564 self.recent_seqno = seqno.max(self.recent_seqno);
565 }
566
567 pub fn lease_seqno(&mut self) -> Lease {
568 let seqno = self.recent_seqno;
569 let lease = self
570 .leases
571 .entry(seqno)
572 .or_insert_with(|| Lease::new(seqno));
573 lease.clone()
574 }
575
576 pub fn outstanding_seqno(&mut self) -> SeqNo {
577 while let Some(first) = self.leases.first_entry() {
578 if first.get().count() <= 1 {
579 first.remove();
580 } else {
581 return *first.key();
582 }
583 }
584 self.recent_seqno
585 }
586}
587
588#[derive(Debug)]
609pub struct ReadHandle<K: Codec, V: Codec, T, D> {
610 pub(crate) cfg: PersistConfig,
611 pub(crate) metrics: Arc<Metrics>,
612 pub(crate) machine: Machine<K, V, T, D>,
613 pub(crate) gc: GarbageCollector<K, V, T, D>,
614 pub(crate) blob: Arc<dyn Blob>,
615 watch: StateWatch<K, V, T, D>,
616
617 pub(crate) reader_id: LeasedReaderId,
618 pub(crate) read_schemas: Schemas<K, V>,
619 pub(crate) schema_cache: SchemaCache<K, V, T, D>,
620
621 since: Antichain<T>,
622 pub(crate) hold_state: AwaitableState<ReadHolds<T>>,
623 pub(crate) unexpired_state: Option<UnexpiredReadHandleState>,
624}
625
626pub const READER_LEASE_EXPIRED_EXIT_CODE: i32 = 168;
632
633pub(crate) const READER_LEASE_DURATION: Config<Duration> = Config::new(
636 "persist_reader_lease_duration",
637 Duration::from_secs(60 * 15),
638 "The time after which we'll clean up stale read leases",
639 ParameterScope::Environment,
640);
641
642impl<K, V, T, D> ReadHandle<K, V, T, D>
643where
644 K: Debug + Codec,
645 V: Debug + Codec,
646 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
647 D: Monoid + Codec64 + Send + Sync,
648{
649 #[allow(clippy::unused_async)]
650 pub(crate) async fn new(
651 cfg: PersistConfig,
652 metrics: Arc<Metrics>,
653 machine: Machine<K, V, T, D>,
654 gc: GarbageCollector<K, V, T, D>,
655 blob: Arc<dyn Blob>,
656 reader_id: LeasedReaderId,
657 read_schemas: Schemas<K, V>,
658 state: LeasedReaderState<T>,
659 ) -> Self {
660 let schema_cache = machine.applier.schema_cache();
661 let hold_state = AwaitableState::new(ReadHolds {
662 held_since: state.since.clone(),
663 applied_since: state.since.clone(),
664 recent_seqno: state.seqno,
665 leases: Default::default(),
666 expired: false,
667 request_sync: false,
668 });
669 ReadHandle {
670 cfg,
671 metrics: Arc::clone(&metrics),
672 machine: machine.clone(),
673 gc: gc.clone(),
674 blob,
675 watch: machine.applier.watch(),
676 reader_id: reader_id.clone(),
677 read_schemas,
678 schema_cache,
679 since: state.since,
680 hold_state: hold_state.clone(),
681 unexpired_state: Some(UnexpiredReadHandleState {
682 heartbeat_task: Self::start_reader_heartbeat_task(
683 machine, reader_id, gc, hold_state,
684 ),
685 }),
686 }
687 }
688
689 fn start_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 ) -> JoinHandle<()> {
695 let metrics = Arc::clone(&machine.applier.metrics);
696 let name = format!(
697 "persist::heartbeat_read({},{})",
698 machine.shard_id(),
699 reader_id
700 );
701 mz_ore::task::spawn(|| name, {
702 metrics.tasks.heartbeat_read.instrument_task(async move {
703 Self::reader_heartbeat_task(machine, reader_id, gc, leased_seqnos).await
704 })
705 })
706 }
707
708 async fn reader_heartbeat_task(
709 machine: Machine<K, V, T, D>,
710 reader_id: LeasedReaderId,
711 gc: GarbageCollector<K, V, T, D>,
712 leased_seqnos: AwaitableState<ReadHolds<T>>,
713 ) {
714 let sleep_duration = READER_LEASE_DURATION.get(&machine.applier.cfg) / 4;
715 let jitter: f64 = f64::cast_lossy(reader_id.hashed()) / f64::cast_lossy(u64::MAX);
718 let mut interval = tokio::time::interval_at(
719 tokio::time::Instant::now() + sleep_duration.mul_f64(jitter),
720 sleep_duration,
721 );
722 let mut held_since = leased_seqnos.read(|s| s.held_since.clone());
723 loop {
724 let before_sleep = Instant::now();
725 let _woke_by_tick = tokio::select! {
726 _tick = interval.tick() => {
727 true
728 }
729 _whatever = leased_seqnos.wait_while(|s| !s.request_sync) => {
730 false
731 }
732 };
733
734 let elapsed_since_before_sleeping = before_sleep.elapsed();
735 if elapsed_since_before_sleeping > sleep_duration + Duration::from_secs(60) {
736 warn!(
737 "reader ({}) of shard ({}) went {}s between heartbeats",
738 reader_id,
739 machine.shard_id(),
740 elapsed_since_before_sleeping.as_secs_f64()
741 );
742 }
743
744 let before_heartbeat = Instant::now();
745 let current_seqno = machine.seqno();
746 let result = leased_seqnos.modify(|s| {
747 if s.expired {
748 Err(())
749 } else {
750 s.observe_seqno(current_seqno);
751 s.request_sync = false;
752 held_since.join_assign(&s.held_since);
753 Ok(s.outstanding_seqno())
754 }
755 });
756 let actual_since = match result {
757 Ok(held_seqno) => {
758 let (seqno, actual_since, maintenance) = machine
759 .downgrade_since(&reader_id, held_seqno, &held_since)
760 .await;
761 leased_seqnos.modify(|s| {
762 s.applied_since.clone_from(&actual_since.0);
763 s.observe_seqno(seqno)
764 });
765 maintenance.start_performing(&machine, &gc);
766 actual_since
767 }
768 Err(()) => {
769 let (seqno, maintenance) = machine.expire_leased_reader(&reader_id).await;
770 leased_seqnos.modify(|s| s.observe_seqno(seqno));
771 maintenance.start_performing(&machine, &gc);
772 break;
773 }
774 };
775
776 let elapsed_since_heartbeat = before_heartbeat.elapsed();
777 if elapsed_since_heartbeat > Duration::from_secs(60) {
778 warn!(
779 "reader ({}) of shard ({}) heartbeat call took {}s",
780 reader_id,
781 machine.shard_id(),
782 elapsed_since_heartbeat.as_secs_f64(),
783 );
784 }
785
786 if PartialOrder::less_than(&held_since, &actual_since.0) {
787 warn!(
795 "heartbeat task for reader ({}) of shard ({}) exiting due to expired lease \
796 while read handle is live",
797 reader_id,
798 machine.shard_id(),
799 );
800 return;
801 }
802 }
803 }
804
805 pub fn shard_id(&self) -> ShardId {
807 self.machine.shard_id()
808 }
809
810 pub fn since(&self) -> &Antichain<T> {
814 &self.since
815 }
816
817 pub fn shared_upper(&self) -> Antichain<T> {
823 self.machine.applier.clone_upper()
824 }
825
826 #[cfg(test)]
827 fn outstanding_seqno(&self) -> SeqNo {
828 let current_seqno = self.machine.seqno();
829 self.hold_state.modify(|s| {
830 s.observe_seqno(current_seqno);
831 s.outstanding_seqno()
832 })
833 }
834
835 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
842 pub async fn downgrade_since(&mut self, new_since: &Antichain<T>) {
843 self.since = new_since.clone();
844 self.hold_state.modify(|s| {
845 s.downgrade_since(new_since);
846 s.request_sync = true;
847 });
848 self.hold_state
849 .wait_while(|s| PartialOrder::less_than(&s.applied_since, new_since))
850 .await;
851 }
852
853 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
870 pub async fn listen(self, as_of: Antichain<T>) -> Result<Listen<K, V, T, D>, Since<T>> {
871 Listen::new(self, as_of).await
872 }
873
874 async fn snapshot_batches(
875 &mut self,
876 as_of: Antichain<T>,
877 ) -> Result<(Lease, Vec<HollowBatch<T>>), Since<T>> {
878 self.machine
879 .wait_for_upper_past(
880 &as_of,
881 &mut self.watch,
882 Some(&self.reader_id),
883 &self.metrics.retries.snapshot,
884 RetryParameters::persist_defaults(),
885 )
886 .await;
887 let lease = self.lease_seqno().await;
888 let batches = match self.machine.applier.snapshot(&as_of) {
889 Ok(data) => data,
890 Err(SnapshotErr::AsOfHistoricalDistinctionsLost(since)) => return Err(since),
891 Err(SnapshotErr::AsOfNotYetAvailable(seqno, upper)) => {
892 panic!(
893 "waited for upper past {as_of:?}, but at latest seqno {seqno:?} the frontier was only {upper:?}",
894 as_of = as_of.elements(),
895 upper = upper.0.elements(),
896 )
897 }
898 };
899 Ok((lease, batches))
900 }
901
902 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
916 pub async fn snapshot(
917 &mut self,
918 as_of: Antichain<T>,
919 ) -> Result<Vec<LeasedBatchPart<T>>, Since<T>> {
920 let (lease, batches) = self.snapshot_batches(as_of.clone()).await?;
921
922 if !PartialOrder::less_equal(self.since(), &as_of) {
923 return Err(Since(self.since().clone()));
924 }
925
926 let filter = FetchBatchFilter::Snapshot { as_of };
927 let mut leased_parts = Vec::new();
928 for batch in batches {
929 leased_parts.extend(
934 self.lease_batch_parts(lease.clone(), batch, filter.clone())
935 .collect::<Vec<_>>()
936 .await,
937 );
938 }
939 Ok(leased_parts)
940 }
941
942 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
948 pub async fn subscribe(
949 mut self,
950 as_of: Antichain<T>,
951 ) -> Result<Subscribe<K, V, T, D>, Since<T>> {
952 let snapshot_parts = self.snapshot(as_of.clone()).await?;
953 let listen = self.listen(as_of.clone()).await?;
954 Ok(Subscribe::new(snapshot_parts, listen))
955 }
956
957 fn lease_batch_parts(
958 &mut self,
959 lease: Lease,
960 batch: HollowBatch<T>,
961 filter: FetchBatchFilter<T>,
962 ) -> impl Stream<Item = LeasedBatchPart<T>> + '_ {
963 stream! {
964 let blob = Arc::clone(&self.blob);
965 let metrics = Arc::clone(&self.metrics);
966 let desc = batch.desc.clone();
967 let bounds_truncated = batch.runs().any(|(meta, _)| meta.bounds_truncated());
968 for await part in batch.part_stream(self.shard_id(), &*blob, &*metrics) {
969 yield LeasedBatchPart {
970 metrics: Arc::clone(&self.metrics),
971 shard_id: self.machine.shard_id(),
972 filter: filter.clone(),
973 desc: desc.clone(),
974 part: part.expect("leased part").into_owned(),
975 lease: lease.clone(),
976 reader_id: self.reader_id.clone(),
977 filter_pushdown_audit: false,
978 bounds_truncated,
979 }
980 }
981 }
982 }
983
984 async fn lease_seqno(&mut self) -> Lease {
994 let current_seqno = self.machine.seqno();
995 let lease = self.hold_state.modify(|s| {
996 s.observe_seqno(current_seqno);
997 s.lease_seqno()
998 });
999 self.watch.wait_for_seqno_ge(lease.seqno()).await;
1004 lease
1005 }
1006
1007 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
1010 pub async fn clone(&self, purpose: &str) -> Self {
1011 let new_reader_id = LeasedReaderId::new();
1012 let machine = self.machine.clone();
1013 let gc = self.gc.clone();
1014 let (reader_state, maintenance) = machine
1015 .register_leased_reader(
1016 &new_reader_id,
1017 purpose,
1018 READER_LEASE_DURATION.get(&self.cfg),
1019 false,
1020 )
1021 .await;
1022 maintenance.start_performing(&machine, &gc);
1023 assert!(PartialOrder::less_equal(&reader_state.since, &self.since));
1027 let new_reader = ReadHandle::new(
1028 self.cfg.clone(),
1029 Arc::clone(&self.metrics),
1030 machine,
1031 gc,
1032 Arc::clone(&self.blob),
1033 new_reader_id,
1034 self.read_schemas.clone(),
1035 reader_state,
1036 )
1037 .await;
1038 new_reader
1039 }
1040
1041 #[allow(clippy::unused_async)]
1046 pub async fn maybe_downgrade_since(&mut self, new_since: &Antichain<T>) {
1047 self.since = new_since.clone();
1048 self.hold_state.modify(|s| {
1049 s.downgrade_since(new_since);
1050 });
1051 }
1052
1053 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
1059 pub async fn expire(mut self) {
1060 self.hold_state.modify(|s| {
1061 s.expired = true;
1062 s.request_sync = true;
1063 });
1064 let Some(unexpired_state) = self.unexpired_state.take() else {
1065 return;
1066 };
1067 unexpired_state.heartbeat_task.await;
1068 }
1069
1070 #[cfg(test)]
1072 #[track_caller]
1073 pub async fn expect_listen(self, as_of: T) -> Listen<K, V, T, D> {
1074 self.listen(Antichain::from_elem(as_of))
1075 .await
1076 .expect("cannot serve requested as_of")
1077 }
1078}
1079
1080#[derive(Debug)]
1082pub(crate) struct UnexpiredReadHandleState {
1083 pub(crate) heartbeat_task: JoinHandle<()>,
1084}
1085
1086#[derive(Debug)]
1092pub struct Cursor<K: Codec, V: Codec, T: Timestamp + Codec64, D: Codec64, L = Lease> {
1093 consolidator: Consolidator<T, D, StructuredSort<K, V, T, D>>,
1094 max_len: usize,
1095 max_bytes: usize,
1096 _lease: L,
1097 read_schemas: Schemas<K, V>,
1098}
1099
1100impl<K: Codec, V: Codec, T: Timestamp + Codec64, D: Codec64, L> Cursor<K, V, T, D, L> {
1101 pub fn into_lease(self: Self) -> L {
1104 self._lease
1105 }
1106}
1107
1108impl<K, V, T, D, L> Cursor<K, V, T, D, L>
1109where
1110 K: Debug + Codec + Ord,
1111 V: Debug + Codec + Ord,
1112 T: Timestamp + Lattice + Codec64 + Sync,
1113 D: Monoid + Ord + Codec64 + Send + Sync,
1114{
1115 pub async fn next(&mut self) -> Option<impl Iterator<Item = ((K, V), T, D)> + '_> {
1117 let Self {
1118 consolidator,
1119 max_len,
1120 max_bytes,
1121 _lease,
1122 read_schemas: _,
1123 } = self;
1124
1125 let part = consolidator
1126 .next_chunk(*max_len, *max_bytes)
1127 .await
1128 .expect("fetching a leased part")?;
1129 let key_decoder = self
1130 .read_schemas
1131 .key
1132 .decoder_any(part.key.as_ref())
1133 .expect("ok");
1134 let val_decoder = self
1135 .read_schemas
1136 .val
1137 .decoder_any(part.val.as_ref())
1138 .expect("ok");
1139 let iter = (0..part.len()).map(move |i| {
1140 let mut k = K::default();
1141 let mut v = V::default();
1142 key_decoder.decode(i, &mut k);
1143 val_decoder.decode(i, &mut v);
1144 let t = T::decode(part.time.value(i).to_le_bytes());
1145 let d = D::decode(part.diff.value(i).to_le_bytes());
1146 ((k, v), t, d)
1147 });
1148
1149 Some(iter)
1150 }
1151}
1152
1153impl<K, V, T, D> ReadHandle<K, V, T, D>
1154where
1155 K: Debug + Codec + Ord,
1156 V: Debug + Codec + Ord,
1157 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1158 D: Monoid + Ord + Codec64 + Send + Sync,
1159{
1160 pub async fn snapshot_and_fetch(
1174 &mut self,
1175 as_of: Antichain<T>,
1176 ) -> Result<Vec<((K, V), T, D)>, Since<T>> {
1177 let mut cursor = self.snapshot_cursor(as_of, |_| true).await?;
1178 let mut contents = Vec::new();
1179 while let Some(iter) = cursor.next().await {
1180 contents.extend(iter);
1181 }
1182
1183 let old_len = contents.len();
1186 consolidate_updates(&mut contents);
1187 if old_len != contents.len() {
1188 self.machine
1190 .applier
1191 .shard_metrics
1192 .unconsolidated_snapshot
1193 .inc();
1194 }
1195
1196 Ok(contents)
1197 }
1198
1199 pub async fn snapshot_cursor(
1206 &mut self,
1207 as_of: Antichain<T>,
1208 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
1209 ) -> Result<Cursor<K, V, T, D>, Since<T>> {
1210 let (lease, batches) = self.snapshot_batches(as_of.clone()).await?;
1211
1212 Self::read_batches_consolidated(
1213 &self.cfg,
1214 Arc::clone(&self.metrics),
1215 Arc::clone(&self.machine.applier.shard_metrics),
1216 self.metrics.read.snapshot.clone(),
1217 Arc::clone(&self.blob),
1218 self.shard_id(),
1219 as_of,
1220 self.read_schemas.clone(),
1221 &batches,
1222 lease,
1223 should_fetch_part,
1224 COMPACTION_MEMORY_BOUND_BYTES.get(&self.cfg),
1225 )
1226 }
1227
1228 pub(crate) fn read_batches_consolidated<L>(
1229 persist_cfg: &PersistConfig,
1230 metrics: Arc<Metrics>,
1231 shard_metrics: Arc<ShardMetrics>,
1232 read_metrics: ReadMetrics,
1233 blob: Arc<dyn Blob>,
1234 shard_id: ShardId,
1235 as_of: Antichain<T>,
1236 schemas: Schemas<K, V>,
1237 batches: &[HollowBatch<T>],
1238 lease: L,
1239 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
1240 memory_budget_bytes: usize,
1241 ) -> Result<Cursor<K, V, T, D, L>, Since<T>> {
1242 let context = format!("{}[as_of={:?}]", shard_id, as_of.elements());
1243 let filter = FetchBatchFilter::Snapshot {
1244 as_of: as_of.clone(),
1245 };
1246
1247 let mut consolidator = Consolidator::new(
1248 context,
1249 FetchConfig::from_persist_config(persist_cfg),
1250 shard_id,
1251 StructuredSort::new(schemas.clone()),
1252 blob,
1253 metrics,
1254 shard_metrics,
1255 read_metrics,
1256 filter,
1257 None,
1258 memory_budget_bytes,
1259 );
1260 for batch in batches {
1261 for (meta, run) in batch.runs() {
1262 consolidator.enqueue_run(
1263 &batch.desc,
1264 meta,
1265 run.into_iter()
1266 .filter(|p| should_fetch_part(p.stats()))
1267 .cloned(),
1268 );
1269 }
1270 }
1271 let max_len = persist_cfg.compaction_yield_after_n_updates;
1275 let max_bytes = BLOB_TARGET_SIZE.get(persist_cfg).max(1);
1276
1277 Ok(Cursor {
1278 consolidator,
1279 max_len,
1280 max_bytes,
1281 _lease: lease,
1282 read_schemas: schemas,
1283 })
1284 }
1285
1286 pub fn snapshot_stats(
1298 &self,
1299 as_of: Option<Antichain<T>>,
1300 ) -> impl Future<Output = Result<SnapshotStats, Since<T>>> + Send + 'static {
1301 let machine = self.machine.clone();
1302 async move {
1303 let batches = match as_of {
1304 Some(as_of) => machine.unleased_snapshot(&as_of).await?,
1305 None => machine.applier.all_batches(),
1306 };
1307 let num_updates = batches.iter().map(|b| b.len).sum();
1308 Ok(SnapshotStats {
1309 shard_id: machine.shard_id(),
1310 num_updates,
1311 })
1312 }
1313 }
1314
1315 pub async fn snapshot_parts_stats(
1326 &self,
1327 as_of: Antichain<T>,
1328 ) -> Result<SnapshotPartsStats, Since<T>> {
1329 let batches = self.machine.unleased_snapshot(&as_of).await?;
1330 let parts = stream::iter(&batches)
1331 .flat_map(|b| b.part_stream(self.shard_id(), &*self.blob, &*self.metrics))
1332 .map(|p| {
1333 let p = p.expect("live batch");
1334 SnapshotPartStats {
1335 encoded_size_bytes: p.encoded_size_bytes(),
1336 stats: p.stats().cloned(),
1337 }
1338 })
1339 .collect()
1340 .await;
1341 Ok(SnapshotPartsStats {
1342 metrics: Arc::clone(&self.machine.applier.metrics),
1343 shard_id: self.machine.shard_id(),
1344 parts,
1345 })
1346 }
1347}
1348
1349impl<K, V, T, D> ReadHandle<K, V, T, D>
1350where
1351 K: Debug + Codec + Ord,
1352 V: Debug + Codec + Ord,
1353 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1354 D: Monoid + Codec64 + Send + Sync,
1355{
1356 pub async fn snapshot_and_stream(
1361 &mut self,
1362 as_of: Antichain<T>,
1363 ) -> Result<impl Stream<Item = ((K, V), T, D)> + use<K, V, T, D>, Since<T>> {
1364 let snap = self.snapshot(as_of).await?;
1365
1366 let blob = Arc::clone(&self.blob);
1367 let metrics = Arc::clone(&self.metrics);
1368 let snapshot_metrics = self.metrics.read.snapshot.clone();
1369 let shard_metrics = Arc::clone(&self.machine.applier.shard_metrics);
1370 let reader_id = self.reader_id.clone();
1371 let schemas = self.read_schemas.clone();
1372 let mut schema_cache = self.schema_cache.clone();
1373 let persist_cfg = self.cfg.clone();
1374 let stream = async_stream::stream! {
1375 for part in snap {
1376 let mut fetched_part = fetch_leased_part(
1377 &persist_cfg,
1378 &part,
1379 blob.as_ref(),
1380 Arc::clone(&metrics),
1381 &snapshot_metrics,
1382 &shard_metrics,
1383 &reader_id,
1384 schemas.clone(),
1385 &mut schema_cache,
1386 )
1387 .await;
1388
1389 while let Some(next) = fetched_part.next() {
1390 yield next;
1391 }
1392 }
1393 };
1394
1395 Ok(stream)
1396 }
1397}
1398
1399impl<K, V, T, D> ReadHandle<K, V, T, D>
1400where
1401 K: Debug + Codec + Ord,
1402 V: Debug + Codec + Ord,
1403 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
1404 D: Monoid + Ord + Codec64 + Send + Sync,
1405{
1406 #[cfg(test)]
1409 #[track_caller]
1410 pub async fn expect_snapshot_and_fetch(&mut self, as_of: T) -> Vec<((K, V), T, D)> {
1411 let mut ret = self
1412 .snapshot_and_fetch(Antichain::from_elem(as_of))
1413 .await
1414 .expect("cannot serve requested as_of");
1415
1416 ret.sort();
1417 ret
1418 }
1419}
1420
1421impl<K: Codec, V: Codec, T, D> Drop for ReadHandle<K, V, T, D> {
1422 fn drop(&mut self) {
1423 self.hold_state.modify(|s| {
1424 s.expired = true;
1425 s.request_sync = true;
1426 });
1427 }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432 use std::pin;
1433 use std::str::FromStr;
1434
1435 use mz_dyncfg::ConfigUpdates;
1436 use mz_ore::cast::CastFrom;
1437 use mz_ore::metrics::MetricsRegistry;
1438 use mz_persist::mem::{MemBlob, MemBlobConfig, MemConsensus};
1439 use mz_persist::unreliable::{UnreliableConsensus, UnreliableHandle};
1440 use serde::{Deserialize, Serialize};
1441 use serde_json::json;
1442 use tokio_stream::StreamExt;
1443
1444 use crate::async_runtime::IsolatedRuntime;
1445 use crate::batch::BLOB_TARGET_SIZE;
1446 use crate::cache::StateCache;
1447 use crate::internal::metrics::Metrics;
1448 use crate::rpc::NoopPubSubSender;
1449 use crate::tests::{all_ok, new_test_client};
1450 use crate::{Diagnostics, PersistClient, PersistConfig, ShardId};
1451
1452 use super::*;
1453
1454 #[mz_persist_proc::test(tokio::test)]
1456 #[cfg_attr(miri, ignore)] async fn drop_unused_subscribe(dyncfgs: ConfigUpdates) {
1458 let data = [
1459 (("0".to_owned(), "zero".to_owned()), 0, 1),
1460 (("1".to_owned(), "one".to_owned()), 1, 1),
1461 (("2".to_owned(), "two".to_owned()), 2, 1),
1462 ];
1463
1464 let (mut write, read) = new_test_client(&dyncfgs)
1465 .await
1466 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1467 .await;
1468
1469 write.expect_compare_and_append(&data[0..1], 0, 1).await;
1470 write.expect_compare_and_append(&data[1..2], 1, 2).await;
1471 write.expect_compare_and_append(&data[2..3], 2, 3).await;
1472
1473 let subscribe = read
1474 .subscribe(timely::progress::Antichain::from_elem(2))
1475 .await
1476 .unwrap();
1477 assert!(
1478 !subscribe.snapshot.as_ref().unwrap().is_empty(),
1479 "snapshot must have batches for test to be meaningful"
1480 );
1481 drop(subscribe);
1482 }
1483
1484 #[mz_ore::test(tokio::test)]
1489 #[cfg_attr(miri, ignore)] async fn maybe_optimize_ignores_truncated_batches() {
1491 use arrow::array::{ArrayRef, StringArray};
1492
1493 use crate::batch::{INLINE_WRITES_SINGLE_MAX_BYTES, INLINE_WRITES_TOTAL_MAX_BYTES};
1494 use crate::cache::PersistClientCache;
1495
1496 let mut cache = PersistClientCache::new_no_metrics();
1497 cache.cfg.compaction_enabled = false;
1500 cache.cfg.set_config(&INLINE_WRITES_SINGLE_MAX_BYTES, 0);
1502 cache.cfg.set_config(&INLINE_WRITES_TOTAL_MAX_BYTES, 0);
1503 let client = cache
1504 .open(crate::PersistLocation::new_in_mem())
1505 .await
1506 .expect("client construction failed");
1507 let (mut write, mut read) = client
1508 .expect_open::<String, String, u64, i64>(ShardId::new())
1509 .await;
1510
1511 write
1512 .expect_compare_and_append(&[(("0".to_owned(), "zero".to_owned()), 0, 1)], 0, 1)
1513 .await;
1514
1515 let mut batch = write
1518 .expect_batch(
1519 &[
1520 (("stale".to_owned(), "stale".to_owned()), 0, 1),
1521 (("1".to_owned(), "one".to_owned()), 1, 1),
1522 (("2".to_owned(), "two".to_owned()), 2, 1),
1523 ],
1524 0,
1525 3,
1526 )
1527 .await;
1528 write
1529 .compare_and_append_batch(
1530 &mut [&mut batch],
1531 Antichain::from_elem(1),
1532 Antichain::from_elem(3),
1533 true,
1534 )
1535 .await
1536 .expect("invalid usage")
1537 .expect("unexpected upper");
1538
1539 write
1541 .expect_compare_and_append(&[(("3".to_owned(), "three".to_owned()), 3, 1)], 3, 4)
1542 .await;
1543
1544 let parts = read
1545 .snapshot(Antichain::from_elem(3))
1546 .await
1547 .expect("as_of unavailable");
1548
1549 let mut faked = 0;
1550 let mut kept_truncated = 0;
1551 for mut part in parts {
1552 let key: ArrayRef = Arc::new(StringArray::from(vec!["k"]));
1553 let val: ArrayRef = Arc::new(StringArray::from(vec!["v"]));
1554 part.maybe_optimize(&cache.cfg, key, val);
1555 match part.desc.lower().elements() {
1556 [0] => {
1557 assert!(!part.bounds_truncated);
1560 assert!(part.part.is_inline(), "expected a faked part");
1561 faked += 1;
1562 }
1563 [1] => {
1564 assert!(part.bounds_truncated);
1567 assert!(
1568 !part.part.is_inline(),
1569 "must not substitute statistics for a truncated part"
1570 );
1571 kept_truncated += 1;
1572 }
1573 [3] => (),
1574 x => panic!("unexpected part lower {:?}", x),
1575 }
1576 }
1577 assert_eq!(faked, 1);
1578 assert_eq!(kept_truncated, 1);
1579 }
1580
1581 #[mz_persist_proc::test(tokio::test)]
1583 #[cfg_attr(miri, ignore)] async fn streaming_consolidate(dyncfgs: ConfigUpdates) {
1585 let data = &[
1586 (("k".to_owned(), "v".to_owned()), 0, 1),
1588 (("k".to_owned(), "v".to_owned()), 1, 1),
1589 (("k".to_owned(), "v".to_owned()), 2, 1),
1590 (("k2".to_owned(), "v".to_owned()), 0, 1),
1592 (("k2".to_owned(), "v".to_owned()), 1, -1),
1593 ];
1594
1595 let (mut write, read) = {
1596 let client = new_test_client(&dyncfgs).await;
1597 client.cfg.set_config(&BLOB_TARGET_SIZE, 1000); client
1599 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1600 .await
1601 };
1602
1603 write.expect_compare_and_append(data, 0, 5).await;
1604
1605 let mut snapshot = read
1606 .subscribe(timely::progress::Antichain::from_elem(4))
1607 .await
1608 .unwrap();
1609
1610 let mut updates = vec![];
1611 'outer: loop {
1612 for event in snapshot.fetch_next().await {
1613 match event {
1614 ListenEvent::Progress(t) => {
1615 if !t.less_than(&4) {
1616 break 'outer;
1617 }
1618 }
1619 ListenEvent::Updates(data) => {
1620 updates.extend(data);
1621 }
1622 }
1623 }
1624 }
1625 assert_eq!(updates, &[(("k".to_owned(), "v".to_owned()), 4u64, 3i64)],)
1626 }
1627
1628 #[mz_persist_proc::test(tokio::test)]
1629 #[cfg_attr(miri, ignore)] async fn snapshot_and_stream(dyncfgs: ConfigUpdates) {
1631 let data = &mut [
1632 (("k1".to_owned(), "v1".to_owned()), 0, 1),
1633 (("k2".to_owned(), "v2".to_owned()), 1, 1),
1634 (("k3".to_owned(), "v3".to_owned()), 2, 1),
1635 (("k4".to_owned(), "v4".to_owned()), 2, 1),
1636 (("k5".to_owned(), "v5".to_owned()), 3, 1),
1637 ];
1638
1639 let (mut write, mut read) = {
1640 let client = new_test_client(&dyncfgs).await;
1641 client.cfg.set_config(&BLOB_TARGET_SIZE, 0); client
1643 .expect_open::<String, String, u64, i64>(crate::ShardId::new())
1644 .await
1645 };
1646
1647 write.expect_compare_and_append(&data[0..2], 0, 2).await;
1648 write.expect_compare_and_append(&data[2..4], 2, 3).await;
1649 write.expect_compare_and_append(&data[4..], 3, 4).await;
1650
1651 let as_of = Antichain::from_elem(3);
1652 let mut snapshot = pin::pin!(read.snapshot_and_stream(as_of.clone()).await.unwrap());
1653
1654 let mut snapshot_rows = vec![];
1655 while let Some(((k, v), t, d)) = snapshot.next().await {
1656 snapshot_rows.push(((k, v), t, d));
1657 }
1658
1659 for ((_k, _v), t, _d) in data.as_mut_slice() {
1660 t.advance_by(as_of.borrow());
1661 }
1662
1663 assert_eq!(data.as_slice(), snapshot_rows.as_slice());
1664 }
1665
1666 #[mz_persist_proc::test(tokio::test)]
1668 #[cfg_attr(miri, ignore)] async fn seqno_leases(dyncfgs: ConfigUpdates) {
1670 let mut data = vec![];
1671 for i in 0..20 {
1672 data.push(((i.to_string(), i.to_string()), i, 1))
1673 }
1674
1675 let shard_id = ShardId::new();
1676
1677 let client = new_test_client(&dyncfgs).await;
1678 let (mut write, read) = client
1679 .expect_open::<String, String, u64, i64>(shard_id)
1680 .await;
1681
1682 let mut offset = 0;
1684 let mut width = 2;
1685
1686 for i in offset..offset + width {
1687 write
1688 .expect_compare_and_append(
1689 &data[i..i + 1],
1690 u64::cast_from(i),
1691 u64::cast_from(i) + 1,
1692 )
1693 .await;
1694 }
1695 offset += width;
1696
1697 let mut fetcher = client
1699 .create_batch_fetcher::<String, String, u64, i64>(
1700 shard_id,
1701 Default::default(),
1702 Default::default(),
1703 false,
1704 Diagnostics::for_tests(),
1705 )
1706 .await
1707 .unwrap();
1708
1709 let mut subscribe = read
1710 .subscribe(timely::progress::Antichain::from_elem(1))
1711 .await
1712 .expect("cannot serve requested as_of");
1713
1714 let original_seqno_since = subscribe.listen.handle.outstanding_seqno();
1716 if let Some(snapshot) = &subscribe.snapshot {
1717 for part in snapshot {
1718 assert!(
1719 part.lease.seqno() >= original_seqno_since,
1720 "our seqno hold must cover all parts"
1721 );
1722 }
1723 }
1724
1725 let mut parts = vec![];
1726
1727 width = 4;
1728 for i in offset..offset + width {
1730 for event in subscribe.next(None).await {
1731 if let ListenEvent::Updates(mut new_parts) = event {
1732 parts.append(&mut new_parts);
1733 subscribe
1736 .listen
1737 .handle
1738 .downgrade_since(&subscribe.listen.since)
1739 .await;
1740 }
1741 }
1742
1743 write
1744 .expect_compare_and_append(
1745 &data[i..i + 1],
1746 u64::cast_from(i),
1747 u64::cast_from(i) + 1,
1748 )
1749 .await;
1750
1751 assert_eq!(
1753 subscribe.listen.handle.machine.applier.seqno_since(),
1754 original_seqno_since
1755 );
1756 }
1757
1758 offset += width;
1759
1760 let mut seqno_since = subscribe.listen.handle.machine.applier.seqno_since();
1761
1762 assert_eq!(seqno_since, original_seqno_since);
1764
1765 let mut subsequent_parts = vec![];
1768
1769 let mut this_seqno = SeqNo::minimum();
1773
1774 for (mut i, part) in parts.into_iter().enumerate() {
1776 let part_seqno = part.lease.seqno();
1777 let last_seqno = this_seqno;
1778 this_seqno = part_seqno;
1779 assert!(this_seqno >= last_seqno);
1780
1781 let (part, lease) = part.into_exchangeable_part();
1782 let _ = fetcher.fetch_leased_part(part).await;
1783 drop(lease);
1784
1785 for event in subscribe.next(None).await {
1787 if let ListenEvent::Updates(parts) = event {
1788 for part in parts {
1789 let (_, lease) = part.into_exchangeable_part();
1790 subsequent_parts.push(lease);
1791 }
1792 }
1793 }
1794
1795 subscribe
1796 .listen
1797 .handle
1798 .downgrade_since(&subscribe.listen.since)
1799 .await;
1800
1801 i += offset;
1803 write
1804 .expect_compare_and_append(
1805 &data[i..i + 1],
1806 u64::cast_from(i),
1807 u64::cast_from(i) + 1,
1808 )
1809 .await;
1810
1811 let expect_downgrade = subscribe.listen.handle.outstanding_seqno() > part_seqno;
1814
1815 let new_seqno_since = subscribe.listen.handle.machine.applier.seqno_since();
1816 if expect_downgrade {
1817 assert!(new_seqno_since > seqno_since);
1818 } else {
1819 assert_eq!(new_seqno_since, seqno_since);
1820 }
1821 seqno_since = new_seqno_since;
1822 }
1823
1824 assert!(seqno_since > original_seqno_since);
1826
1827 drop(subsequent_parts);
1829 drop(subscribe);
1830 }
1831
1832 #[mz_ore::test]
1833 fn reader_id_human_readable_serde() {
1834 #[derive(Debug, Serialize, Deserialize)]
1835 struct Container {
1836 reader_id: LeasedReaderId,
1837 }
1838
1839 let id =
1841 LeasedReaderId::from_str("r00000000-1234-5678-0000-000000000000").expect("valid id");
1842 assert_eq!(
1843 id,
1844 serde_json::from_value(serde_json::to_value(id.clone()).expect("serializable"))
1845 .expect("deserializable")
1846 );
1847
1848 assert_eq!(
1850 id,
1851 serde_json::from_str("\"r00000000-1234-5678-0000-000000000000\"")
1852 .expect("deserializable")
1853 );
1854
1855 let json = json!({ "reader_id": id });
1857 assert_eq!(
1858 "{\"reader_id\":\"r00000000-1234-5678-0000-000000000000\"}",
1859 &json.to_string()
1860 );
1861 let container: Container = serde_json::from_value(json).expect("deserializable");
1862 assert_eq!(container.reader_id, id);
1863 }
1864
1865 #[mz_ore::test(tokio::test)]
1869 #[cfg_attr(miri, ignore)] async fn skip_consensus_fetch_optimization() {
1871 let data = vec![
1872 (("0".to_owned(), "zero".to_owned()), 0, 1),
1873 (("1".to_owned(), "one".to_owned()), 1, 1),
1874 (("2".to_owned(), "two".to_owned()), 2, 1),
1875 ];
1876
1877 let cfg = PersistConfig::new_for_tests();
1878 let blob = Arc::new(MemBlob::open(MemBlobConfig::default()));
1879 let consensus = Arc::new(MemConsensus::default());
1880 let unreliable = UnreliableHandle::default();
1881 unreliable.totally_available();
1882 let consensus = Arc::new(UnreliableConsensus::new(consensus, unreliable.clone()));
1883 let metrics = Arc::new(Metrics::new(&cfg, &MetricsRegistry::new()));
1884 let pubsub_sender = Arc::new(NoopPubSubSender);
1885 let (mut write, mut read) = PersistClient::new(
1886 cfg,
1887 blob,
1888 consensus,
1889 metrics,
1890 Arc::new(IsolatedRuntime::new_for_tests()),
1891 Arc::new(StateCache::new_no_metrics()),
1892 pubsub_sender,
1893 )
1894 .expect("client construction failed")
1895 .expect_open::<String, String, u64, i64>(ShardId::new())
1896 .await;
1897
1898 write.expect_compare_and_append(&data[0..1], 0, 1).await;
1899 write.expect_compare_and_append(&data[1..2], 1, 2).await;
1900 write.expect_compare_and_append(&data[2..3], 2, 3).await;
1901
1902 let snapshot = read.expect_snapshot_and_fetch(2).await;
1903 let mut listen = read.expect_listen(0).await;
1904
1905 let listen_actual = listen.fetch_next().await;
1910 let expected_events = vec![ListenEvent::Progress(Antichain::from_elem(1))];
1911 assert_eq!(listen_actual, expected_events);
1912
1913 unreliable.totally_unavailable();
1916 assert_eq!(snapshot, all_ok(&data, 2));
1917 assert_eq!(
1918 listen.read_until(&3).await,
1919 (all_ok(&data[1..], 1), Antichain::from_elem(3))
1920 );
1921 }
1922}