mz_storage/source/reclock/
compat.rs1use std::cell::RefCell;
14use std::rc::Rc;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17
18use anyhow::Context;
19use differential_dataflow::lattice::Lattice;
20use fail::fail_point;
21use futures::StreamExt;
22use futures::stream::LocalBoxStream;
23use mz_ore::soft_panic_or_log;
24use mz_persist_client::Diagnostics;
25use mz_persist_client::cache::PersistClientCache;
26use mz_persist_client::error::UpperMismatch;
27use mz_persist_client::read::ListenEvent;
28use mz_persist_client::write::WriteHandle;
29use mz_persist_types::Codec64;
30use mz_persist_types::codec_impls::UnitSchema;
31use mz_repr::{Diff, GlobalId, RelationDesc};
32use mz_storage_client::util::remap_handle::{RemapHandle, RemapHandleReader};
33use mz_storage_types::StorageDiff;
34use mz_storage_types::controller::CollectionMetadata;
35use mz_storage_types::sources::{SourceData, SourceTimestamp};
36use timely::order::{PartialOrder, TotalOrder};
37use timely::progress::Timestamp;
38use timely::progress::frontier::Antichain;
39use tokio::sync::watch;
40
41pub struct PersistHandle<FromTime: SourceTimestamp, IntoTime: Timestamp + Lattice + Codec64> {
43 events:
44 LocalBoxStream<'static, ListenEvent<IntoTime, ((SourceData, ()), IntoTime, StorageDiff)>>,
45 write_handle: WriteHandle<SourceData, (), IntoTime, StorageDiff>,
46 read_only_rx: watch::Receiver<bool>,
48 pending_batch: Vec<(FromTime, IntoTime, Diff)>,
49 shared_write_frontier: Rc<RefCell<Antichain<IntoTime>>>,
51}
52
53impl<FromTime: Timestamp, IntoTime: Timestamp + Sync> PersistHandle<FromTime, IntoTime>
54where
55 FromTime: SourceTimestamp,
56 IntoTime: Timestamp + TotalOrder + Lattice + Codec64,
57{
58 pub async fn new(
59 persist_clients: Arc<PersistClientCache>,
60 read_only_rx: watch::Receiver<bool>,
61 remap_metadata: CollectionMetadata,
62 as_of: Antichain<IntoTime>,
63 shared_write_frontier: Rc<RefCell<Antichain<IntoTime>>>,
64 id: GlobalId,
66 operator: &str,
67 worker_id: usize,
68 worker_count: usize,
69 remap_relation_desc: RelationDesc,
76 remap_collection_id: GlobalId,
77 ) -> anyhow::Result<Self> {
78 let persist_client = persist_clients
79 .open(remap_metadata.persist_location.clone())
80 .await
81 .context("error creating persist client")?;
82
83 let (mut write_handle, mut read_handle) = persist_client
84 .open(
85 remap_metadata.data_shard,
86 Arc::new(remap_relation_desc),
87 Arc::new(UnitSchema),
88 Diagnostics {
89 shard_name: remap_collection_id.to_string(),
90 handle_purpose: format!("reclock for {}", id),
91 },
92 false,
93 )
94 .await
95 .expect("invalid usage");
96
97 let upper = write_handle.fetch_recent_upper().await;
98 let since = read_handle.since();
104
105 fail_point!("invalid_remap_as_of");
108
109 if since.is_empty() {
110 tracing::info!(
120 source_id = %id,
121 %worker_id,
122 "since of remap shard is the empty antichain, suspending...");
123
124 tokio::time::sleep(Duration::from_secs(5 * 60 * 60)).await;
127
128 soft_panic_or_log!(
131 "since of remap shard is the empty antichain, source_id = {id}, worker_id = {worker_id}"
132 );
133 }
134
135 if !PartialOrder::less_equal(since, &as_of) {
136 anyhow::bail!(
137 "invalid as_of: as_of({as_of:?}) < since({since:?}), \
138 source {id}, \
139 remap_shard: {:?}",
140 remap_metadata.data_shard
141 );
142 }
143
144 assert!(
145 as_of.elements() == [IntoTime::minimum()] || PartialOrder::less_than(&as_of, upper),
146 "invalid as_of: upper({upper:?}) <= as_of({as_of:?})",
147 );
148
149 tracing::info!(
150 ?since,
151 ?as_of,
152 ?upper,
153 "{operator}({id}) {worker_id}/{worker_count} initializing PersistHandle"
154 );
155
156 let operator = operator.to_string();
158
159 use futures::stream;
160 let events = stream::once(async move {
161 tracing::info!(
165 ?as_of,
166 "{operator}({id}) {worker_id}/{worker_count} reading remap snapshot"
167 );
168 let snapshot_start = Instant::now();
169 let updates = read_handle
170 .snapshot_and_fetch(as_of.clone())
171 .await
172 .expect("since <= as_of asserted");
173 tracing::info!(
174 ?as_of,
175 update_count = updates.len(),
176 elapsed = ?snapshot_start.elapsed(),
177 "{operator}({id}) {worker_id}/{worker_count} read remap snapshot"
178 );
179 let snapshot = stream::once(std::future::ready(ListenEvent::Updates(updates)));
180
181 let listener = read_handle
182 .listen(as_of.clone())
183 .await
184 .expect("since <= as_of asserted");
185
186 let listen_stream = stream::unfold(listener, |mut listener| async move {
187 let events = stream::iter(listener.fetch_next().await);
188 Some((events, listener))
189 })
190 .flatten();
191
192 snapshot.chain(listen_stream)
193 })
194 .flatten()
195 .boxed_local();
196
197 Ok(Self {
198 events,
199 write_handle,
200 read_only_rx,
201 pending_batch: vec![],
202 shared_write_frontier,
203 })
204 }
205}
206
207#[async_trait::async_trait(?Send)]
208impl<FromTime, IntoTime> RemapHandleReader for PersistHandle<FromTime, IntoTime>
209where
210 FromTime: SourceTimestamp,
211 IntoTime: Timestamp + Lattice + Codec64,
212{
213 type FromTime = FromTime;
214 type IntoTime = IntoTime;
215
216 async fn next(
217 &mut self,
218 ) -> Option<(
219 Vec<(Self::FromTime, Self::IntoTime, Diff)>,
220 Antichain<Self::IntoTime>,
221 )> {
222 while let Some(event) = self.events.next().await {
223 match event {
224 ListenEvent::Progress(new_upper) => {
225 let batch = self
227 .pending_batch
228 .extract_if(.., |(_, ts, _)| !new_upper.less_equal(ts))
229 .collect();
230 return Some((batch, new_upper));
231 }
232 ListenEvent::Updates(msgs) => {
233 for ((update, _), into_ts, diff) in msgs {
234 let from_ts = FromTime::decode_row(&update.0.expect("invalid row"));
235 self.pending_batch.push((from_ts, into_ts, diff.into()));
236 }
237 }
238 }
239 }
240 None
241 }
242}
243
244#[async_trait::async_trait(?Send)]
245impl<FromTime, IntoTime> RemapHandle for PersistHandle<FromTime, IntoTime>
246where
247 FromTime: SourceTimestamp,
248 IntoTime: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
249{
250 async fn compare_and_append(
251 &mut self,
252 updates: Vec<(Self::FromTime, Self::IntoTime, Diff)>,
253 upper: Antichain<Self::IntoTime>,
254 new_upper: Antichain<Self::IntoTime>,
255 ) -> Result<(), UpperMismatch<Self::IntoTime>> {
256 if *self.read_only_rx.borrow() {
257 loop {
263 tracing::trace!(
264 ?upper,
265 ?new_upper,
266 persist_upper = ?self.write_handle.upper(),
267 "persist remap handle is in read-only mode, waiting until we come out of it or the shard upper advances");
268
269 let _ =
274 tokio::time::timeout(Duration::from_secs(1), self.read_only_rx.changed()).await;
275
276 if !*self.read_only_rx.borrow() {
277 tracing::trace!(
278 ?upper,
279 ?new_upper,
280 persist_upper = ?self.write_handle.upper(),
281 "persist remap handle has come out of read-only mode"
282 );
283
284 break;
286 }
287
288 let current_upper = self.write_handle.fetch_recent_upper().await;
289
290 if PartialOrder::less_than(&upper, current_upper) {
291 tracing::trace!(
292 ?upper,
293 ?new_upper,
294 persist_upper = ?current_upper,
295 "someone else advanced the upper, aborting write"
296 );
297
298 return Err(UpperMismatch {
299 current: current_upper.clone(),
300 expected: upper,
301 });
302 }
303 }
304 }
305
306 let row_updates = updates.into_iter().map(|(from_ts, into_ts, diff)| {
307 (
308 (SourceData(Ok(from_ts.encode_row())), ()),
309 into_ts,
310 diff.into_inner(),
311 )
312 });
313
314 match self
315 .write_handle
316 .compare_and_append(row_updates, upper, new_upper.clone())
317 .await
318 {
319 Ok(result) => {
320 *self.shared_write_frontier.borrow_mut() = new_upper;
321 return result;
322 }
323 Err(invalid_use) => panic!("compare_and_append failed: {invalid_use}"),
324 }
325 }
326
327 fn upper(&self) -> &Antichain<Self::IntoTime> {
328 self.write_handle.upper()
329 }
330}