1use std::collections::{BTreeMap, BTreeSet};
14use std::fmt::Debug;
15use std::fmt::Write;
16use std::sync::Arc;
17
18use futures::StreamExt;
19use futures::stream::FuturesUnordered;
20use itertools::Itertools;
21use mz_ore::tracing::OpenTelemetryContext;
22use mz_persist_client::write::WriteHandle;
23use mz_persist_client::{Diagnostics, PersistClient, ShardId};
24use mz_persist_types::codec_impls::UnitSchema;
25use mz_repr::{GlobalId, Timestamp};
26use mz_storage_client::client::{TableData, Update};
27use mz_storage_client::controller::TableRegistration;
28use mz_storage_types::StorageDiff;
29use mz_storage_types::controller::{InvalidUpper, TxnsCodecRow};
30use mz_storage_types::sources::SourceData;
31use mz_txn_wal::txns::{Tidy, TxnsHandle};
32use timely::progress::Antichain;
33use tokio::sync::mpsc::UnboundedSender;
34use tokio::sync::oneshot;
35use tracing::{Instrument, Span, debug, info_span};
36
37use crate::StorageError;
38
39mod read_only_table_worker;
40
41#[derive(Debug, Clone)]
42pub struct PersistTableWriteWorker {
43 inner: Arc<PersistTableWriteWorkerInner>,
44}
45
46#[derive(Debug)]
48enum PersistTableWriteCmd {
49 Register(
50 Timestamp,
51 Vec<TableRegistration>,
52 tokio::sync::oneshot::Sender<Result<(), StorageError>>,
53 ),
54 DropHandles {
55 forget_ts: Timestamp,
56 ids: Vec<GlobalId>,
58 tx: oneshot::Sender<Result<(), StorageError>>,
60 },
61 Append {
62 write_ts: Timestamp,
63 advance_to: Timestamp,
64 updates: Vec<(GlobalId, Vec<TableData>)>,
65 tx: tokio::sync::oneshot::Sender<Result<(), StorageError>>,
66 },
67 Shutdown,
68}
69
70impl PersistTableWriteCmd {
71 fn name(&self) -> &'static str {
72 match self {
73 PersistTableWriteCmd::Register(_, _, _) => "PersistTableWriteCmd::Register",
74 PersistTableWriteCmd::DropHandles { .. } => "PersistTableWriteCmd::DropHandle",
75 PersistTableWriteCmd::Append { .. } => "PersistTableWriteCmd::Append",
76 PersistTableWriteCmd::Shutdown => "PersistTableWriteCmd::Shutdown",
77 }
78 }
79}
80
81async fn open_table_write_handles(
85 persist_client: &PersistClient,
86 tables: Vec<TableRegistration>,
87) -> Vec<(
88 GlobalId,
89 WriteHandle<SourceData, (), Timestamp, StorageDiff>,
90)> {
91 futures::stream::iter(tables)
92 .map(|table| async move {
93 let mut write = persist_client
94 .open_writer(
95 table.data_shard,
96 Arc::new(table.relation_desc),
97 Arc::new(UnitSchema),
98 Diagnostics {
99 shard_name: table.id.to_string(),
100 handle_purpose: format!("table write worker data for {}", table.id),
101 },
102 )
103 .await
104 .expect("invalid persist usage");
105 write.fetch_recent_upper().await;
108 (table.id, write)
109 })
110 .buffer_unordered(50)
111 .collect()
112 .await
113}
114
115async fn append_work(
116 write_handles: &mut BTreeMap<GlobalId, WriteHandle<SourceData, (), Timestamp, StorageDiff>>,
117 mut commands: BTreeMap<
118 GlobalId,
119 (
120 tracing::Span,
121 Vec<Update>,
122 Antichain<Timestamp>,
123 Antichain<Timestamp>,
124 ),
125 >,
126) -> Result<(), Vec<(GlobalId, Antichain<Timestamp>)>> {
127 let futs = FuturesUnordered::new();
128
129 for (id, write) in write_handles.iter_mut() {
137 if let Some((span, updates, expected_upper, new_upper)) = commands.remove(id) {
138 let updates = updates.into_iter().map(|u| {
139 (
140 (SourceData(Ok(u.row)), ()),
141 u.timestamp,
142 u.diff.into_inner(),
143 )
144 });
145
146 futs.push(async move {
147 write
148 .compare_and_append(updates.clone(), expected_upper.clone(), new_upper.clone())
149 .instrument(span.clone())
150 .await
151 .expect("cannot append updates")
152 .or_else(|upper_mismatch| Err((*id, upper_mismatch.current)))?;
153
154 Ok::<_, (GlobalId, Antichain<Timestamp>)>((*id, new_upper))
155 })
156 }
157 }
158
159 let (_new_uppers, failed_appends): (Vec<_>, Vec<_>) = futs
161 .collect::<Vec<_>>()
162 .await
163 .into_iter()
164 .partition_result();
165
166 if failed_appends.is_empty() {
167 Ok(())
168 } else {
169 Err(failed_appends)
170 }
171}
172
173impl PersistTableWriteWorker {
174 pub(crate) fn new_read_only_mode(
183 txns_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff>,
184 persist_client: PersistClient,
185 ) -> Self {
186 let (tx, rx) =
187 tokio::sync::mpsc::unbounded_channel::<(tracing::Span, PersistTableWriteCmd)>();
188 mz_ore::task::spawn(
189 || "PersistTableWriteWorker",
190 read_only_table_worker::read_only_mode_table_worker(rx, txns_handle, persist_client),
191 );
192 Self {
193 inner: Arc::new(PersistTableWriteWorkerInner::new(tx)),
194 }
195 }
196
197 pub(crate) fn new_txns(
198 txns: TxnsHandle<SourceData, (), Timestamp, StorageDiff, TxnsCodecRow>,
199 persist_client: PersistClient,
200 ) -> Self {
201 let (tx, rx) =
202 tokio::sync::mpsc::unbounded_channel::<(tracing::Span, PersistTableWriteCmd)>();
203 mz_ore::task::spawn(|| "PersistTableWriteWorker", async move {
204 let mut worker = TxnsTableWorker {
205 txns,
206 persist_client,
207 write_handles: BTreeMap::new(),
208 tidy: Tidy::default(),
209 };
210 worker.run(rx).await
211 });
212 Self {
213 inner: Arc::new(PersistTableWriteWorkerInner::new(tx)),
214 }
215 }
216
217 pub(crate) fn register(
218 &self,
219 register_ts: Timestamp,
220 tables: Vec<TableRegistration>,
221 ) -> tokio::sync::oneshot::Receiver<Result<(), StorageError>> {
222 let span = info_span!("PersistTableWriteCmd::Register");
224 let (tx, rx) = tokio::sync::oneshot::channel();
225 let cmd = PersistTableWriteCmd::Register(register_ts, tables, tx);
226 self.inner.send_with_span(span, cmd);
227 rx
228 }
229
230 pub(crate) fn append(
231 &self,
232 write_ts: Timestamp,
233 advance_to: Timestamp,
234 updates: Vec<(GlobalId, Vec<TableData>)>,
235 ) -> tokio::sync::oneshot::Receiver<Result<(), StorageError>> {
236 let (tx, rx) = tokio::sync::oneshot::channel();
237 self.send(PersistTableWriteCmd::Append {
242 write_ts,
243 advance_to,
244 updates,
245 tx,
246 });
247 rx
248 }
249
250 fn send(&self, cmd: PersistTableWriteCmd) {
251 self.inner.send(cmd);
252 }
253}
254
255#[derive(Debug, Clone)]
258pub(crate) struct TableWriteWorkerHandle(pub(crate) PersistTableWriteWorker);
259
260impl mz_storage_client::controller::TableWriteHandle for TableWriteWorkerHandle {
261 fn append(
262 &self,
263 write_ts: Timestamp,
264 advance_to: Timestamp,
265 commands: Vec<(GlobalId, Vec<TableData>)>,
266 ) -> oneshot::Receiver<Result<(), StorageError>> {
267 self.0.append(write_ts, advance_to, commands)
268 }
269
270 fn register(
271 &self,
272 register_ts: Timestamp,
273 tables: Vec<TableRegistration>,
274 ) -> oneshot::Receiver<Result<(), StorageError>> {
275 self.0.register(register_ts, tables)
276 }
277
278 fn forget(
279 &self,
280 forget_ts: Timestamp,
281 ids: Vec<GlobalId>,
282 ) -> oneshot::Receiver<Result<(), StorageError>> {
283 let (tx, rx) = oneshot::channel();
284 self.0
285 .send(PersistTableWriteCmd::DropHandles { forget_ts, ids, tx });
286 rx
287 }
288}
289
290struct TxnsTableWorker {
291 txns: TxnsHandle<SourceData, (), Timestamp, StorageDiff, TxnsCodecRow>,
292 persist_client: PersistClient,
293 write_handles: BTreeMap<GlobalId, ShardId>,
294 tidy: Tidy,
295}
296
297impl TxnsTableWorker {
298 async fn run(
299 &mut self,
300 mut rx: tokio::sync::mpsc::UnboundedReceiver<(Span, PersistTableWriteCmd)>,
301 ) {
302 while let Some((span, command)) = rx.recv().await {
303 match command {
304 PersistTableWriteCmd::Register(register_ts, tables, tx) => {
305 let res = self.register(register_ts, tables).instrument(span).await;
306 let _ = tx.send(res);
308 }
309 PersistTableWriteCmd::DropHandles { forget_ts, ids, tx } => {
310 let res = self.drop_handles(ids, forget_ts).instrument(span).await;
311 let _ = tx.send(res);
313 }
314 PersistTableWriteCmd::Append {
315 write_ts,
316 advance_to,
317 updates,
318 tx,
319 } => {
320 self.append(write_ts, advance_to, updates, tx)
321 .instrument(span)
322 .await
323 }
324 PersistTableWriteCmd::Shutdown => {
325 tracing::info!("PersistTableWriteWorker shutting down via command");
326 return;
327 }
328 }
329 }
330
331 tracing::info!("PersistTableWriteWorker shutting down via input exhaustion");
332 }
333
334 async fn register(
335 &mut self,
336 register_ts: Timestamp,
337 tables: Vec<TableRegistration>,
338 ) -> Result<(), StorageError> {
339 let mut ids_handles = open_table_write_handles(&self.persist_client, tables).await;
340 ids_handles.sort_unstable_by_key(|(gid, _handle)| *gid);
344
345 for (id, write_handle) in ids_handles.iter() {
346 debug!(
347 "tables register {} {:.9}",
348 id,
349 write_handle.shard_id().to_string()
350 );
351 let previous = self.write_handles.insert(*id, write_handle.shard_id());
352 if previous.is_some() {
353 panic!("already registered a WriteHandle for collection {:?}", id);
354 }
355 }
356
357 let new_ids = ids_handles.iter().map(|(id, _)| *id).collect_vec();
359 let handles = ids_handles.into_iter().map(|(_, handle)| handle);
360 let res = self.txns.register(register_ts, handles).await;
361 match res {
362 Ok(tidy) => {
363 self.tidy.merge(tidy);
364 Ok(())
365 }
366 Err(current) => {
367 debug!(
370 "register at {:?} conflicted with txns upper {:?}, rolling back {:?}",
371 register_ts, current, new_ids
372 );
373 for id in &new_ids {
374 self.write_handles.remove(id);
375 }
376 Err(StorageError::InvalidUppers(
377 new_ids
378 .into_iter()
379 .map(|id| InvalidUpper {
380 id,
381 current_upper: Antichain::from_elem(current),
382 })
383 .collect(),
384 ))
385 }
386 }
387 }
388
389 async fn drop_handles(
390 &mut self,
391 ids: Vec<GlobalId>,
392 forget_ts: Timestamp,
393 ) -> Result<(), StorageError> {
394 tracing::info!(?ids, "drop tables");
395 let removed = ids
396 .iter()
397 .filter_map(|id| self.write_handles.remove(id).map(|shard| (*id, shard)))
402 .collect::<Vec<_>>();
403 let data_ids = removed
404 .iter()
405 .map(|(_, shard)| *shard)
406 .collect::<BTreeSet<_>>();
407 if !data_ids.is_empty() {
408 match self.txns.forget(forget_ts, data_ids.clone()).await {
409 Ok(tidy) => {
410 self.tidy.merge(tidy);
411 Ok(())
412 }
413 Err(current) => {
414 debug!(
416 "forget at {:?} conflicted with txns upper {:?}, restoring {:?}",
417 forget_ts, current, ids
418 );
419 for (id, shard) in removed {
420 self.write_handles.insert(id, shard);
421 }
422 Err(StorageError::InvalidUppers(
423 ids.into_iter()
424 .map(|id| InvalidUpper {
425 id,
426 current_upper: Antichain::from_elem(current),
427 })
428 .collect(),
429 ))
430 }
431 }
432 } else {
433 Ok(())
434 }
435 }
436
437 async fn append(
438 &mut self,
439 write_ts: Timestamp,
440 advance_to: Timestamp,
441 updates: Vec<(GlobalId, Vec<TableData>)>,
442 tx: tokio::sync::oneshot::Sender<Result<(), StorageError>>,
443 ) {
444 debug!(
445 "tables append timestamp={:?} advance_to={:?} len={} ids={:?}{}",
446 write_ts,
447 advance_to,
448 updates.iter().flat_map(|(_, x)| x).count(),
449 updates
450 .iter()
451 .map(|(x, _)| x.to_string())
452 .collect::<BTreeSet<_>>(),
453 updates.iter().filter(|(_, v)| !v.is_empty()).fold(
454 String::new(),
455 |mut output, (k, v)| {
456 let _ = write!(output, "\n {}: {:?}", k, v.first());
457 output
458 }
459 )
460 );
461 assert_eq!(
467 advance_to,
468 mz_persist_types::StepForward::step_forward(&write_ts)
469 );
470
471 let mut txn = self.txns.begin();
472 for (id, updates) in updates {
473 let Some(data_id) = self.write_handles.get(&id) else {
474 assert!(
478 updates.iter().all(|u| u.is_empty()),
479 "{}: {:?}",
480 id,
481 updates
482 );
483 continue;
484 };
485 for update in updates {
486 match update {
487 TableData::Rows(updates) => {
488 for (row, diff) in updates {
489 let () = txn
490 .write(data_id, SourceData(Ok(row)), (), diff.into_inner())
491 .await;
492 }
493 }
494 TableData::Batches(batches) => {
495 for batch in batches {
496 let () = txn.write_batch(data_id, batch);
497 }
498 }
499 }
500 }
501 }
502 txn.tidy(std::mem::take(&mut self.tidy));
504 let txn_res = txn.commit_at(&mut self.txns, write_ts).await;
505 let response = match txn_res {
506 Ok(apply) => {
507 debug!("applying {:?}", apply);
510 let tidy = apply.apply(&mut self.txns).await;
511 self.tidy.merge(tidy);
512
513 let () = self.txns.compact_to(write_ts).await;
517
518 Ok(())
519 }
520 Err(current) => {
521 self.tidy.merge(txn.take_tidy());
522 debug!(
523 "unable to commit txn at {:?} current={:?}",
524 write_ts, current
525 );
526 Err(StorageError::InvalidUppers(
527 self.write_handles
528 .keys()
529 .copied()
530 .map(|id| InvalidUpper {
531 id,
532 current_upper: Antichain::from_elem(current),
533 })
534 .collect(),
535 ))
536 }
537 };
538 let _ = tx.send(response);
540 }
541}
542
543#[derive(Debug)]
552struct PersistTableWriteWorkerInner {
553 tx: UnboundedSender<(tracing::Span, PersistTableWriteCmd)>,
555}
556
557impl Drop for PersistTableWriteWorkerInner {
558 fn drop(&mut self) {
559 self.send(PersistTableWriteCmd::Shutdown);
560 }
562}
563
564impl PersistTableWriteWorkerInner {
565 fn new(tx: UnboundedSender<(tracing::Span, PersistTableWriteCmd)>) -> Self {
566 PersistTableWriteWorkerInner { tx }
567 }
568
569 fn send(&self, cmd: PersistTableWriteCmd) {
570 let span =
571 info_span!(parent: None, "PersistTableWriteWorkerInner::send", otel.name = cmd.name());
572 OpenTelemetryContext::obtain().attach_as_parent_to(&span);
573 self.send_with_span(span, cmd)
574 }
575
576 fn send_with_span(&self, span: Span, cmd: PersistTableWriteCmd) {
577 match self.tx.send((span, cmd)) {
578 Ok(()) => (), Err(e) => {
580 tracing::trace!("could not forward command: {:?}", e);
581 }
582 }
583 }
584}