1use std::collections::{BTreeMap, BTreeSet};
36use std::future::Future;
37use std::ops::ControlFlow;
38use std::pin::Pin;
39use std::sync::{Arc, LazyLock};
40use std::time::{Duration, Instant};
41
42use derivative::Derivative;
43use futures::future::{BoxFuture, FutureExt};
44use mz_adapter_types::connection::ConnectionId;
45use mz_adapter_types::dyncfgs::GROUP_COMMIT_MAX_ATTEMPTS;
46use mz_catalog::builtin::{BuiltinTable, MZ_SESSIONS};
47use mz_dyncfg::{ConfigSet, ConfigValHandle};
48use mz_expr::CollectionPlan;
49use mz_ore::assert_none;
50use mz_ore::halt;
51use mz_ore::instrument;
52use mz_ore::now::NowFn;
53use mz_ore::task;
54use mz_repr::{CatalogItemId, GlobalId, Timestamp};
55use mz_sql::names::ResolvedIds;
56use mz_sql::plan::{ExplainPlanPlan, ExplainTimestampPlan, Explainee, ExplaineeStatement, Plan};
57use mz_sql::session::metadata::SessionMetadata;
58use mz_storage_client::client::TableData;
59use mz_storage_client::controller::{TableRegistration, TableWriteHandle};
60use mz_storage_types::controller::StorageError;
61use mz_timestamp_oracle::{TimestampOracle, WriteTimestamp};
62use smallvec::SmallVec;
63use tokio::sync::{Notify, OwnedMutexGuard, OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
64use tracing::{Instrument, Span, info, warn};
65
66use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogUpperHandle};
67use crate::coord::{Coordinator, Message, PendingTxn, PlanValidity};
68use crate::metrics::Metrics;
69use crate::session::{GroupCommitWriteLocks, Session, WriteLocks};
70use crate::statement_logging::StatementLoggingId;
71use crate::util::{CompletedClientTransmitter, ResultExt};
72use crate::{AdapterError, ExecuteContext};
73
74pub(crate) static REQUIRED_BUILTIN_TABLES: &[&LazyLock<BuiltinTable>] = &[&MZ_SESSIONS];
76
77#[derive(Debug)]
81pub enum DeferredOp {
82 Plan(DeferredPlan),
84 Write(DeferredWrite),
86}
87
88impl DeferredOp {
89 pub(crate) fn can_be_optimistically_retried(&self) -> bool {
101 match self {
102 DeferredOp::Plan(_) => false,
103 DeferredOp::Write(_) => true,
104 }
105 }
106
107 pub fn required_locks(&self) -> impl Iterator<Item = CatalogItemId> + '_ {
109 match self {
110 DeferredOp::Plan(plan) => {
111 let iter = plan.requires_locks.iter().copied();
112 itertools::Either::Left(iter)
113 }
114 DeferredOp::Write(write) => {
115 let iter = write.writes.keys().copied();
116 itertools::Either::Right(iter)
117 }
118 }
119 }
120
121 pub fn conn_id(&self) -> &ConnectionId {
123 match self {
124 DeferredOp::Plan(plan) => plan.ctx.session().conn_id(),
125 DeferredOp::Write(write) => write.pending_txn.ctx.session().conn_id(),
126 }
127 }
128
129 pub fn into_ctx(self) -> ExecuteContext {
131 match self {
132 DeferredOp::Plan(plan) => plan.ctx,
133 DeferredOp::Write(write) => write.pending_txn.ctx,
134 }
135 }
136}
137
138#[derive(Derivative)]
140#[derivative(Debug)]
141pub struct DeferredPlan {
142 #[derivative(Debug = "ignore")]
143 pub ctx: ExecuteContext,
144 pub plan: Plan,
145 pub validity: PlanValidity,
146 pub requires_locks: BTreeSet<CatalogItemId>,
147 pub resolved_ids: ResolvedIds,
148 pub sql_impl_resolved_ids: ResolvedIds,
149}
150
151#[derive(Debug)]
152pub struct DeferredWrite {
153 pub span: Span,
154 pub writes: BTreeMap<CatalogItemId, SmallVec<[TableData; 1]>>,
155 pub pending_txn: PendingTxn,
156}
157
158#[derive(Debug)]
160pub(crate) enum BuiltinTableUpdateSource {
161 Internal(oneshot::Sender<()>),
163 Background(oneshot::Sender<()>),
165}
166
167#[derive(Debug)]
169pub(crate) enum UserWriteResponder {
170 Session(PendingTxn),
173}
174
175#[derive(Debug)]
177pub(crate) enum PendingWriteTxn {
178 User {
183 span: Span,
184 writes: BTreeMap<CatalogItemId, SmallVec<[TableData; 1]>>,
186 write_locks: Option<WriteLocks>,
188 responder: UserWriteResponder,
190 },
191 System {
193 updates: Vec<BuiltinTableUpdate>,
194 source: BuiltinTableUpdateSource,
195 },
196}
197
198impl PendingWriteTxn {
199 fn is_internal_system(&self) -> bool {
200 match self {
201 PendingWriteTxn::System {
202 source: BuiltinTableUpdateSource::Internal(_),
203 ..
204 } => true,
205 _ => false,
206 }
207 }
208}
209
210pub(crate) enum TableWriteCmd {
211 GroupCommit(GroupCommitRequest),
212 Register {
213 tables: Vec<TableRegistration>,
214 result: oneshot::Sender<Timestamp>,
215 },
216 Forget {
217 ids: Vec<GlobalId>,
218 result: oneshot::Sender<Timestamp>,
219 },
220}
221
222pub(crate) struct GroupCommitRequest {
224 appends: Vec<(GlobalId, Vec<TableData>)>,
226 responses: Vec<CompletedClientTransmitter>,
227 statement_logging_ids: Vec<StatementLoggingId>,
228 notifies: Vec<oneshot::Sender<()>>,
229 write_locks: GroupCommitWriteLocks,
230 permits: Vec<GroupCommitPermit>,
232 contains_internal_system_write: bool,
233 span: Span,
234}
235
236impl GroupCommitRequest {
237 fn merge(&mut self, other: GroupCommitRequest) {
238 let GroupCommitRequest {
239 appends,
240 responses,
241 statement_logging_ids,
242 notifies,
243 write_locks,
244 permits,
245 contains_internal_system_write,
246 span: _,
247 } = other;
248 self.appends.extend(appends);
249 self.responses.extend(responses);
250 self.statement_logging_ids.extend(statement_logging_ids);
251 self.notifies.extend(notifies);
252 self.write_locks.extend(write_locks);
253 self.permits.extend(permits);
254 self.contains_internal_system_write |= contains_internal_system_write;
255 }
256}
257
258pub(crate) struct GroupCommitter {
263 rx: mpsc::UnboundedReceiver<TableWriteCmd>,
264 oracle: Arc<dyn TimestampOracle<Timestamp> + Send + Sync>,
265 table_write_handle: Arc<dyn TableWriteHandle>,
266 catalog_upper: CatalogUpperHandle,
267 internal_cmd_tx: mpsc::UnboundedSender<Message>,
268 now: NowFn,
269 metrics: Metrics,
270 max_attempts: ConfigValHandle<usize>,
271}
272
273impl GroupCommitter {
274 async fn run(mut self) {
275 while let Some(cmd) = self.rx.recv().await {
276 let mut next = Some(cmd);
279 while let Some(cmd) = next.take() {
280 match cmd {
281 TableWriteCmd::GroupCommit(request) => {
282 let span = request.span.clone();
283 match self.commit(request).instrument(span).await {
284 ControlFlow::Continue(deferred) => next = deferred,
285 ControlFlow::Break(()) => return,
286 }
287 }
288 TableWriteCmd::Register { tables, result } => {
289 let Some(write_ts) = self
290 .write_to_txns(None, |ts, _advance_to| {
291 self.table_write_handle.register(ts, tables.clone())
292 })
293 .await
294 else {
295 return;
296 };
297 let _ = result.send(write_ts.timestamp);
298 }
299 TableWriteCmd::Forget { ids, result } => {
300 let Some(write_ts) = self
301 .write_to_txns(None, |ts, _advance_to| {
302 self.table_write_handle.forget(ts, ids.clone())
303 })
304 .await
305 else {
306 return;
307 };
308 let _ = result.send(write_ts.timestamp);
309 }
310 }
311 }
312 }
313 }
314
315 async fn write_to_txns(
319 &self,
320 op_duration_metric: Option<&prometheus::Histogram>,
321 mut op: impl FnMut(Timestamp, Timestamp) -> oneshot::Receiver<Result<(), StorageError>>,
322 ) -> Option<WriteTimestamp> {
323 let mut attempt = 0;
325 let write_ts = loop {
326 let max_attempts = self.max_attempts.get().max(1);
327 if attempt >= max_attempts {
328 halt!(
329 "txns-shard write reached attempt limit {max_attempts} after {attempt} conflicts, rebuilding"
330 );
331 }
332 attempt += 1;
333 let write_ts = self.oracle.write_ts().await;
334
335 let catalog_upper_start = Instant::now();
338 self.catalog_upper
339 .advance_upper(write_ts.advance_to)
340 .await
341 .unwrap_or_terminate("unable to advance catalog upper");
342 self.metrics
343 .group_commit_catalog_upper_seconds
344 .observe(catalog_upper_start.elapsed().as_secs_f64());
345
346 let op_start = Instant::now();
347 let op_res = op(write_ts.timestamp, write_ts.advance_to).await;
348 if let Some(metric) = op_duration_metric {
349 metric.observe(op_start.elapsed().as_secs_f64());
350 }
351
352 match op_res {
353 Ok(Ok(())) => break write_ts,
354 Ok(Err(StorageError::InvalidUppers(_))) => {
355 warn!(
356 write_ts = %write_ts.timestamp,
357 attempt,
358 "txns-shard write conflicted with another writer, retrying at a fresh timestamp"
359 );
360 continue;
361 }
362 Ok(Err(other)) => {
363 Err::<(), _>(other).unwrap_or_terminate("cannot fail to write to txns shard");
364 unreachable!("unwrap_or_terminate does not return on Err");
365 }
366 Err(_recv) => {
367 warn!("table write worker gone (process shutting down), winding down");
369 return None;
370 }
371 }
372 };
373
374 let now: Timestamp = (self.now)().into();
375 crate::coord::timeline::check_runaway_write_ts(&now, write_ts.timestamp);
376
377 self.oracle.apply_write(write_ts.timestamp).await;
378
379 Some(write_ts)
380 }
381
382 async fn commit(
387 &mut self,
388 mut request: GroupCommitRequest,
389 ) -> ControlFlow<(), Option<TableWriteCmd>> {
390 let mut deferred_cmd = None;
391 let mut rx_closed = false;
394
395 loop {
399 while deferred_cmd.is_none() {
400 match self.rx.try_recv() {
401 Ok(TableWriteCmd::GroupCommit(other)) => request.merge(other),
402 Ok(other) => deferred_cmd = Some(other),
403 Err(_) => break,
404 }
405 }
406
407 if request.contains_internal_system_write || deferred_cmd.is_some() {
410 break;
411 }
412 let ts = self.oracle.peek_write_ts().await;
413 let now: Timestamp = (self.now)().into();
414 if ts <= now {
415 break;
416 }
417 let remaining_ms = std::cmp::min(ts.saturating_sub(now), Timestamp::from(1_000u64));
420 let sleep = tokio::time::sleep(Duration::from_millis(remaining_ms.into()));
421 tokio::pin!(sleep);
422 loop {
423 tokio::select! {
424 _ = &mut sleep => break,
425 cmd = self.rx.recv(), if deferred_cmd.is_none() && !rx_closed => match cmd {
426 Some(TableWriteCmd::GroupCommit(other)) => request.merge(other),
427 Some(other) => deferred_cmd = Some(other),
428 None => rx_closed = true,
429 },
430 }
431 if request.contains_internal_system_write || deferred_cmd.is_some() {
432 break;
433 }
434 }
435 }
436
437 let GroupCommitRequest {
438 appends,
439 responses,
440 statement_logging_ids,
441 notifies,
442 write_locks,
443 permits,
444 contains_internal_system_write: _,
445 span: _,
446 } = request;
447
448 let append_metric = self.metrics.append_table_duration_seconds.clone();
449 let Some(write_ts) = self
450 .write_to_txns(Some(&append_metric), |ts, advance_to| {
451 self.table_write_handle
452 .append(ts, advance_to, appends.clone())
453 })
454 .await
455 else {
456 return ControlFlow::Break(());
458 };
459 let timestamp = write_ts.timestamp;
460
461 let modified_tables: Vec<_> = appends
462 .iter()
463 .filter_map(|(id, updates)| {
464 (id.is_user() && !updates.iter().all(|u| u.is_empty())).then_some(id)
465 })
466 .collect();
467 if !modified_tables.is_empty() {
468 info!(
469 "Appending to tables, {modified_tables:?}, at {timestamp}, advancing to {}",
470 write_ts.advance_to
471 );
472 }
473
474 drop(permits);
477 drop(write_locks);
478
479 for notify in notifies {
480 let _ = notify.send(());
481 }
482
483 if self
486 .internal_cmd_tx
487 .send(Message::GroupCommitApplied {
488 responses,
489 statement_logging_ids,
490 write_ts: timestamp,
491 })
492 .is_err()
493 {
494 warn!("coordinator shut down before a group commit could be finalized");
495 }
496
497 ControlFlow::Continue(deferred_cmd)
498 }
499}
500
501pub(crate) fn spawn_group_committer(
502 rx: mpsc::UnboundedReceiver<TableWriteCmd>,
503 oracle: Arc<dyn TimestampOracle<Timestamp> + Send + Sync>,
504 table_write_handle: Arc<dyn TableWriteHandle>,
505 catalog_upper: CatalogUpperHandle,
506 internal_cmd_tx: mpsc::UnboundedSender<Message>,
507 now: NowFn,
508 metrics: Metrics,
509 dyncfgs: &ConfigSet,
510) {
511 let committer = GroupCommitter {
512 rx,
513 oracle,
514 table_write_handle,
515 catalog_upper,
516 internal_cmd_tx,
517 now,
518 metrics,
519 max_attempts: GROUP_COMMIT_MAX_ATTEMPTS.handle(dyncfgs),
520 };
521 task::spawn(|| "group_committer", committer.run());
522}
523
524impl Coordinator {
525 pub(crate) fn trigger_group_commit(&mut self) {
527 self.group_commit_tx.notify();
528 self.advance_timelines_interval.reset();
532 }
533
534 pub(crate) async fn try_deferred(
539 &mut self,
540 conn_id: ConnectionId,
541 acquired_lock: Option<(CatalogItemId, tokio::sync::OwnedMutexGuard<()>)>,
542 ) {
543 let Some(op) = self.deferred_write_ops.remove(&conn_id) else {
545 tracing::warn!(%conn_id, "no deferred op found, it must have been canceled?");
546 return;
547 };
548 tracing::info!(%conn_id, "trying deferred plan");
549
550 let write_locks = match acquired_lock {
552 Some((acquired_gid, acquired_lock)) => {
553 let mut write_locks = WriteLocks::builder(op.required_locks());
554
555 write_locks.insert_lock(acquired_gid, acquired_lock);
557
558 for gid in op.required_locks().filter(|gid| *gid != acquired_gid) {
560 if let Some(lock) = self.try_grant_object_write_lock(gid) {
561 write_locks.insert_lock(gid, lock);
562 }
563 }
564
565 let locks = match write_locks.all_or_nothing(op.conn_id()) {
567 Ok(locks) => locks,
568 Err(failed_to_acquire) => {
569 let acquire_future = self
570 .grant_object_write_lock(failed_to_acquire)
571 .map(Option::Some);
572 self.defer_op(acquire_future, op);
573 return;
574 }
575 };
576
577 Some(locks)
578 }
579 None => None,
580 };
581
582 match op {
583 DeferredOp::Plan(mut deferred) => {
584 if let Err(e) = deferred.validity.check(self.catalog()) {
585 deferred.ctx.retire(Err(e))
586 } else {
587 if let Some(locks) = write_locks {
589 let conn_id = deferred.ctx.session().conn_id().clone();
590 if let Err(existing) =
591 deferred.ctx.session_mut().try_grant_write_locks(locks)
592 {
593 tracing::error!(
594 %conn_id,
595 ?existing,
596 "session already write locks granted?",
597 );
598 return deferred.ctx.retire(Err(AdapterError::WrongSetOfLocks));
599 }
600 };
601
602 self.sequence_plan(
604 deferred.ctx,
605 deferred.plan,
606 deferred.resolved_ids,
607 deferred.sql_impl_resolved_ids,
608 )
609 .await;
610 }
611 }
612 DeferredOp::Write(DeferredWrite {
613 span,
614 writes,
615 pending_txn,
616 }) => {
617 self.submit_write(PendingWriteTxn::User {
618 span,
619 writes,
620 write_locks,
621 responder: UserWriteResponder::Session(pending_txn),
622 });
623 }
624 }
625 }
626
627 #[instrument(name = "coord::stage_group_commit")]
631 pub(crate) fn stage_group_commit(&mut self, permit: Option<GroupCommitPermit>) {
632 let mut validated_writes = Vec::new();
633 let mut deferred_writes = Vec::new();
634 let mut group_write_locks = GroupCommitWriteLocks::default();
635
636 let pending_writes: Vec<_> = self.pending_writes.drain(..).collect();
639
640 for pending_write in pending_writes {
642 match pending_write {
643 PendingWriteTxn::System { .. } => validated_writes.push(pending_write),
645 PendingWriteTxn::User {
647 span,
648 write_locks: Some(write_locks),
649 writes,
650 responder: UserWriteResponder::Session(pending_txn),
651 } => match write_locks.validate(writes.keys().copied()) {
652 Ok(validated_locks) => {
653 group_write_locks.merge(validated_locks);
656
657 let validated_write = PendingWriteTxn::User {
658 span,
659 writes,
660 write_locks: None,
661 responder: UserWriteResponder::Session(pending_txn),
662 };
663 validated_writes.push(validated_write);
664 }
665 Err(missing) => {
670 let writes: Vec<_> = writes.keys().collect();
671 panic!(
672 "got to group commit with partial set of locks!\nmissing: {:?}, writes: {:?}, txn: {:?}",
673 missing, writes, pending_txn,
674 );
675 }
676 },
677 PendingWriteTxn::User {
679 span,
680 writes,
681 write_locks: None,
682 responder: UserWriteResponder::Session(pending_txn),
683 } => {
684 let missing = group_write_locks.missing_locks(writes.keys().copied());
685
686 if missing.is_empty() {
687 let validated_write = PendingWriteTxn::User {
689 span,
690 writes,
691 write_locks: None,
692 responder: UserWriteResponder::Session(pending_txn),
693 };
694 validated_writes.push(validated_write);
695 } else {
696 let mut just_in_time_locks = WriteLocks::builder(missing.clone());
698 for collection in missing {
699 if let Some(lock) = self.try_grant_object_write_lock(collection) {
700 just_in_time_locks.insert_lock(collection, lock);
701 }
702 }
703
704 match just_in_time_locks.all_or_nothing(pending_txn.ctx.session().conn_id())
705 {
706 Ok(locks) => {
708 group_write_locks.merge(locks);
709 let validated_write = PendingWriteTxn::User {
710 span,
711 writes,
712 write_locks: None,
713 responder: UserWriteResponder::Session(pending_txn),
714 };
715 validated_writes.push(validated_write);
716 }
717 Err(missing) => {
719 let acquire_future =
720 self.grant_object_write_lock(missing).map(Option::Some);
721 let write = DeferredWrite {
722 span,
723 writes,
724 pending_txn,
725 };
726 deferred_writes.push((acquire_future, write));
727 }
728 }
729 }
730 }
731 }
732 }
733
734 for (acquire_future, write) in deferred_writes {
736 self.defer_op(acquire_future, DeferredOp::Write(write));
737 }
738
739 let contains_internal_system_write = validated_writes
740 .iter()
741 .any(|write| write.is_internal_system());
742
743 let mut appends: BTreeMap<CatalogItemId, SmallVec<[TableData; 1]>> = BTreeMap::new();
744 let mut responses = Vec::with_capacity(validated_writes.len());
745 let mut statement_logging_ids = Vec::new();
746 let mut notifies = Vec::new();
747
748 for validated_write_txn in validated_writes {
749 match validated_write_txn {
750 PendingWriteTxn::User {
751 span: _,
752 writes,
753 write_locks,
754 responder:
755 UserWriteResponder::Session(PendingTxn {
756 ctx,
757 response,
758 action,
759 }),
760 } => {
761 assert_none!(write_locks, "should have merged together all locks above");
762 for (id, table_data) in writes {
763 if self.catalog().try_get_entry(&id).is_some() {
769 appends.entry(id).or_default().extend(table_data);
770 }
771 }
772 if let Some(id) = ctx.extra().contents() {
773 statement_logging_ids.push(id);
774 }
775
776 responses.push(CompletedClientTransmitter::new(ctx, response, action));
777 }
778 PendingWriteTxn::System { updates, source } => {
779 for update in updates {
780 appends.entry(update.id).or_default().push(update.data);
781 }
782 match source {
784 BuiltinTableUpdateSource::Internal(tx)
785 | BuiltinTableUpdateSource::Background(tx) => notifies.push(tx),
786 }
787 }
788 }
789 }
790
791 let mut all_appends = Vec::with_capacity(appends.len());
794 for (item_id, table_data) in appends.into_iter() {
795 let mut all_rows = Vec::new();
796 let mut all_data = Vec::new();
797 for data in table_data {
798 match data {
799 TableData::Rows(rows) => all_rows.extend(rows),
800 TableData::Batches(_) => all_data.push(data),
801 }
802 }
803 differential_dataflow::consolidation::consolidate(&mut all_rows);
804 all_data.push(TableData::Rows(all_rows));
805
806 all_appends.push((item_id, all_data));
808 }
809
810 let appends: Vec<_> = all_appends
811 .into_iter()
812 .map(|(id, updates)| {
813 let gid = self.catalog().get_entry(&id).latest_global_id();
814 (gid, updates)
815 })
816 .collect();
817
818 let request = GroupCommitRequest {
820 appends,
821 responses,
822 statement_logging_ids,
823 notifies,
824 write_locks: group_write_locks,
825 permits: permit.into_iter().collect(),
826 contains_internal_system_write,
827 span: Span::current(),
828 };
829 if self
830 .group_committer_tx
831 .send(TableWriteCmd::GroupCommit(request))
832 .is_err()
833 {
834 warn!("group committer task gone, dropping staged group commit");
836 }
837 }
838
839 pub(crate) async fn register_tables_via_committer(
841 &self,
842 tables: Vec<TableRegistration>,
843 ) -> Timestamp {
844 let (tx, rx) = oneshot::channel();
845 if self
846 .group_committer_tx
847 .send(TableWriteCmd::Register { tables, result: tx })
848 .is_err()
849 {
850 halt!("group committer terminated before a table registration could be submitted");
851 }
852 match rx.await {
853 Ok(ts) => ts,
854 Err(_) => halt!("group committer terminated with a table registration outstanding"),
855 }
856 }
857
858 pub(crate) async fn forget_tables_via_committer(&self, ids: Vec<GlobalId>) -> Timestamp {
860 let (tx, rx) = oneshot::channel();
861 if self
862 .group_committer_tx
863 .send(TableWriteCmd::Forget { ids, result: tx })
864 .is_err()
865 {
866 halt!("group committer terminated before a table forget could be submitted");
867 }
868 match rx.await {
869 Ok(ts) => ts,
870 Err(_) => halt!("group committer terminated with a table forget outstanding"),
871 }
872 }
873
874 pub(crate) fn submit_write(&mut self, pending_write_txn: PendingWriteTxn) {
876 if self.controller.read_only() {
877 panic!(
878 "attempting table write in read-only mode: {:?}",
879 pending_write_txn
880 );
881 }
882 self.pending_writes.push(pending_write_txn);
883 self.trigger_group_commit();
884 }
885
886 pub(crate) fn builtin_table_update<'a>(&'a mut self) -> BuiltinTableAppend<'a> {
888 BuiltinTableAppend { coord: self }
889 }
890
891 pub(crate) fn defer_op<F>(&mut self, acquire_future: F, op: DeferredOp)
892 where
893 F: Future<Output = Option<(CatalogItemId, tokio::sync::OwnedMutexGuard<()>)>>
894 + Send
895 + 'static,
896 {
897 let conn_id = op.conn_id().clone();
898
899 let is_optimistic = op.can_be_optimistically_retried();
901 self.deferred_write_ops.insert(conn_id.clone(), op);
902
903 let internal_cmd_tx = self.internal_cmd_tx.clone();
904 let conn_id_ = conn_id.clone();
905 mz_ore::task::spawn(|| format!("defer op {conn_id_}"), async move {
906 tracing::info!(%conn_id, "deferring plan");
907 let acquired_lock = acquire_future.await;
912
913 let acquired_lock = match (acquired_lock, is_optimistic) {
917 (Some(_lock), true) => None,
918 (Some(lock), false) => Some(lock),
919 (None, _) => None,
920 };
921
922 let _ = internal_cmd_tx.send(Message::TryDeferred {
924 conn_id,
925 acquired_lock,
926 });
927 });
928 }
929
930 pub(crate) fn grant_object_write_lock(
932 &mut self,
933 object_id: CatalogItemId,
934 ) -> impl Future<Output = (CatalogItemId, OwnedMutexGuard<()>)> + 'static {
935 let write_lock_handle = self
936 .write_locks
937 .entry(object_id)
938 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())));
939 let write_lock_handle = Arc::clone(write_lock_handle);
940
941 write_lock_handle
942 .lock_owned()
943 .map(move |guard| (object_id, guard))
944 }
945
946 pub(crate) fn try_grant_object_write_lock(
949 &mut self,
950 object_id: CatalogItemId,
951 ) -> Option<OwnedMutexGuard<()>> {
952 let write_lock_handle = self
953 .write_locks
954 .entry(object_id)
955 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())));
956 let write_lock_handle = Arc::clone(write_lock_handle);
957
958 write_lock_handle.try_lock_owned().ok()
959 }
960}
961
962pub struct BuiltinTableAppend<'a> {
964 coord: &'a mut Coordinator,
965}
966
967pub type BuiltinTableAppendNotify = Pin<Box<dyn Future<Output = ()> + Send + Sync + 'static>>;
978
979pub struct BuiltinTableAppendCompletion {
981 notify: BuiltinTableAppendNotify,
982}
983
984impl BuiltinTableAppendCompletion {
985 pub fn new(notify: BuiltinTableAppendNotify) -> Self {
986 Self { notify }
987 }
988
989 pub fn into_notify(self) -> BuiltinTableAppendNotify {
990 self.notify
991 }
992}
993
994impl<'a> BuiltinTableAppend<'a> {
995 pub fn background(self, mut updates: Vec<BuiltinTableUpdate>) -> BuiltinTableAppendNotify {
1006 if self.coord.controller.read_only() {
1007 self.coord
1008 .buffered_builtin_table_updates
1009 .as_mut()
1010 .expect("in read-only mode")
1011 .append(&mut updates);
1012
1013 return Box::pin(futures::future::ready(()));
1014 }
1015
1016 let (tx, rx) = oneshot::channel();
1017 self.coord.pending_writes.push(PendingWriteTxn::System {
1018 updates,
1019 source: BuiltinTableUpdateSource::Background(tx),
1020 });
1021
1022 Box::pin(rx.map(|_| ()))
1023 }
1024
1025 pub fn defer(self, mut updates: Vec<BuiltinTableUpdate>) -> BuiltinTableAppendNotify {
1034 if self.coord.controller.read_only() {
1035 self.coord
1036 .buffered_builtin_table_updates
1037 .as_mut()
1038 .expect("in read-only mode")
1039 .append(&mut updates);
1040
1041 return Box::pin(futures::future::ready(()));
1042 }
1043
1044 let (tx, rx) = oneshot::channel();
1045 self.coord.pending_writes.push(PendingWriteTxn::System {
1046 updates,
1047 source: BuiltinTableUpdateSource::Internal(tx),
1048 });
1049 self.coord.trigger_group_commit();
1050
1051 Box::pin(rx.map(|_| ()))
1052 }
1053
1054 pub fn execute(self, mut updates: Vec<BuiltinTableUpdate>) -> BuiltinTableAppendNotify {
1058 if self.coord.controller.read_only() {
1059 self.coord
1060 .buffered_builtin_table_updates
1061 .as_mut()
1062 .expect("in read-only mode")
1063 .append(&mut updates);
1064
1065 return Box::pin(futures::future::ready(()));
1066 }
1067
1068 let (tx, rx) = oneshot::channel();
1069
1070 self.coord.pending_writes.push(PendingWriteTxn::System {
1073 updates,
1074 source: BuiltinTableUpdateSource::Internal(tx),
1075 });
1076 self.coord.stage_group_commit(None);
1077
1078 self.coord.advance_timelines_interval.reset();
1080
1081 Box::pin(rx.map(|_| ()))
1082 }
1083}
1084
1085pub fn notifier() -> (GroupCommitNotifier, GroupCommitWaiter) {
1088 let notify = Arc::new(Notify::new());
1089 let in_progress = Arc::new(Semaphore::new(1));
1090
1091 let notifier = GroupCommitNotifier {
1092 notify: Arc::clone(¬ify),
1093 };
1094 let waiter = GroupCommitWaiter {
1095 notify,
1096 in_progress,
1097 };
1098
1099 (notifier, waiter)
1100}
1101
1102#[derive(Debug, Clone)]
1105pub struct GroupCommitNotifier {
1106 notify: Arc<Notify>,
1108}
1109
1110impl GroupCommitNotifier {
1111 pub fn notify(&self) {
1113 self.notify.notify_one()
1114 }
1115}
1116
1117#[derive(Debug)]
1120pub struct GroupCommitWaiter {
1121 notify: Arc<Notify>,
1123 in_progress: Arc<Semaphore>,
1125}
1126static_assertions::assert_not_impl_all!(GroupCommitWaiter: Clone);
1127
1128impl GroupCommitWaiter {
1129 pub async fn ready(&self) -> GroupCommitPermit {
1140 let permit = Semaphore::acquire_owned(Arc::clone(&self.in_progress))
1141 .await
1142 .expect("semaphore should not close");
1143
1144 self.notify.notified().await;
1147
1148 GroupCommitPermit {
1149 _permit: Some(permit),
1150 }
1151 }
1152}
1153
1154#[derive(Debug)]
1159pub struct GroupCommitPermit {
1160 _permit: Option<OwnedSemaphorePermit>,
1164}
1165
1166pub(crate) fn waiting_on_startup_appends(
1182 catalog: &Catalog,
1183 session: &mut Session,
1184 plan: &Plan,
1185) -> Option<(BTreeSet<CatalogItemId>, BoxFuture<'static, ()>)> {
1186 let depends_on = match plan {
1190 Plan::Select(plan) => plan.source.depends_on(),
1191 Plan::ReadThenWrite(plan) => plan.selection.depends_on(),
1192 Plan::ShowColumns(plan) => plan.select_plan.source.depends_on(),
1193 Plan::Subscribe(plan) => plan.from.depends_on(),
1194 Plan::ExplainPlan(ExplainPlanPlan {
1195 explainee: Explainee::Statement(ExplaineeStatement::Select { plan, .. }),
1196 ..
1197 }) => plan.source.depends_on(),
1198 Plan::ExplainTimestamp(ExplainTimestampPlan { raw_plan, .. }) => raw_plan.depends_on(),
1199 Plan::CreateConnection(_)
1200 | Plan::CreateDatabase(_)
1201 | Plan::CreateSchema(_)
1202 | Plan::CreateRole(_)
1203 | Plan::CreateNetworkPolicy(_)
1204 | Plan::CreateCluster(_)
1205 | Plan::CreateClusterReplica(_)
1206 | Plan::CreateSource(_)
1207 | Plan::CreateSources(_)
1208 | Plan::CreateSecret(_)
1209 | Plan::CreateSink(_)
1210 | Plan::CreateTable(_)
1211 | Plan::CreateView(_)
1212 | Plan::CreateMaterializedView(_)
1213 | Plan::CreateIndex(_)
1214 | Plan::CreateType(_)
1215 | Plan::Comment(_)
1216 | Plan::DiscardTemp
1217 | Plan::DiscardAll
1218 | Plan::DropObjects(_)
1219 | Plan::DropOwned(_)
1220 | Plan::EmptyQuery
1221 | Plan::ShowAllVariables
1222 | Plan::ShowCreate(_)
1223 | Plan::ShowVariable(_)
1224 | Plan::InspectShard(_)
1225 | Plan::SetVariable(_)
1226 | Plan::ResetVariable(_)
1227 | Plan::SetTransaction(_)
1228 | Plan::StartTransaction(_)
1229 | Plan::CommitTransaction(_)
1230 | Plan::AbortTransaction(_)
1231 | Plan::CopyFrom(_)
1232 | Plan::CopyTo(_)
1233 | Plan::ExplainPlan(_)
1234 | Plan::ExplainPushdown(_)
1235 | Plan::ExplainSinkSchema(_)
1236 | Plan::Insert(_)
1237 | Plan::AlterNetworkPolicy(_)
1238 | Plan::AlterNoop(_)
1239 | Plan::AlterClusterRename(_)
1240 | Plan::AlterClusterSwap(_)
1241 | Plan::AlterClusterReplicaRename(_)
1242 | Plan::AlterCluster(_)
1243 | Plan::AlterConnection(_)
1244 | Plan::AlterSource(_)
1245 | Plan::AlterSetCluster(_)
1246 | Plan::AlterItemRename(_)
1247 | Plan::AlterRetainHistory(_)
1248 | Plan::AlterSourceTimestampInterval(_)
1249 | Plan::AlterSchemaRename(_)
1250 | Plan::AlterSchemaSwap(_)
1251 | Plan::AlterSecret(_)
1252 | Plan::AlterSink(_)
1253 | Plan::AlterSystemSet(_)
1254 | Plan::AlterSystemReset(_)
1255 | Plan::AlterSystemResetAll(_)
1256 | Plan::AlterRole(_)
1257 | Plan::AlterOwner(_)
1258 | Plan::AlterTableAddColumn(_)
1259 | Plan::AlterMaterializedViewApplyReplacement(_)
1260 | Plan::Declare(_)
1261 | Plan::Fetch(_)
1262 | Plan::Close(_)
1263 | Plan::Prepare(_)
1264 | Plan::Execute(_)
1265 | Plan::Deallocate(_)
1266 | Plan::Raise(_)
1267 | Plan::GrantRole(_)
1268 | Plan::RevokeRole(_)
1269 | Plan::GrantPrivileges(_)
1270 | Plan::RevokePrivileges(_)
1271 | Plan::AlterDefaultPrivileges(_)
1272 | Plan::ReassignOwned(_)
1273 | Plan::ValidateConnection(_)
1274 | Plan::SideEffectingFunc(_) => BTreeSet::default(),
1275 };
1276 let depends_on_required_id = REQUIRED_BUILTIN_TABLES
1277 .iter()
1278 .map(|table| catalog.resolve_builtin_table(&**table))
1279 .any(|id| {
1280 catalog
1281 .get_global_ids(&id)
1282 .any(|gid| depends_on.contains(&gid))
1283 });
1284
1285 if !depends_on_required_id {
1288 return None;
1289 }
1290
1291 match session.clear_builtin_table_updates() {
1298 Some(wait_future) => {
1299 let depends_on = depends_on
1300 .into_iter()
1301 .map(|gid| catalog.get_entry_by_global_id(&gid).id())
1302 .collect();
1303 Some((depends_on, wait_future.boxed()))
1304 }
1305 None => None,
1306 }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use std::sync::Mutex;
1312 use std::sync::atomic::{AtomicUsize, Ordering};
1313
1314 use async_trait::async_trait;
1315 use mz_ore::metrics::MetricsRegistry;
1316 use mz_ore::now::SYSTEM_TIME;
1317 use timely::progress::Antichain;
1318
1319 use super::*;
1320 use crate::catalog::Catalog;
1321
1322 #[derive(Debug, Default)]
1323 struct MemTimestampOracle {
1324 read_write_ts: Mutex<(Timestamp, Timestamp)>,
1325 apply_writes: AtomicUsize,
1326 }
1327
1328 impl MemTimestampOracle {
1329 fn starting_at(ts: Timestamp) -> Self {
1330 Self {
1331 read_write_ts: Mutex::new((ts, ts)),
1332 apply_writes: AtomicUsize::new(0),
1333 }
1334 }
1335 }
1336
1337 #[async_trait]
1338 impl TimestampOracle<Timestamp> for MemTimestampOracle {
1339 async fn write_ts(&self) -> WriteTimestamp {
1340 let (read_ts, write_ts) = &mut *self.read_write_ts.lock().expect("lock poisoned");
1341 let new_write_ts = std::cmp::max(*read_ts, *write_ts).step_forward();
1342 *write_ts = new_write_ts;
1343 WriteTimestamp {
1344 timestamp: new_write_ts,
1345 advance_to: new_write_ts.step_forward(),
1346 }
1347 }
1348
1349 async fn peek_write_ts(&self) -> Timestamp {
1350 let (_, write_ts) = &*self.read_write_ts.lock().expect("lock poisoned");
1351 *write_ts
1352 }
1353
1354 async fn read_ts(&self) -> Timestamp {
1355 let (read_ts, _) = &*self.read_write_ts.lock().expect("lock poisoned");
1356 *read_ts
1357 }
1358
1359 async fn apply_write(&self, lower_bound: Timestamp) {
1360 self.apply_writes.fetch_add(1, Ordering::SeqCst);
1361 let (read_ts, write_ts) = &mut *self.read_write_ts.lock().expect("lock poisoned");
1362 *read_ts = std::cmp::max(*read_ts, lower_bound);
1363 *write_ts = std::cmp::max(*read_ts, *write_ts);
1364 }
1365 }
1366
1367 #[derive(Debug)]
1368 struct ConflictingTableWriteHandle {
1369 conflicts: usize,
1370 calls: AtomicUsize,
1371 write_timestamps: Mutex<Vec<Timestamp>>,
1372 }
1373
1374 impl ConflictingTableWriteHandle {
1375 fn new(conflicts: usize) -> Self {
1376 Self {
1377 conflicts,
1378 calls: AtomicUsize::new(0),
1379 write_timestamps: Mutex::new(Vec::new()),
1380 }
1381 }
1382
1383 fn respond(&self, write_ts: Timestamp) -> oneshot::Receiver<Result<(), StorageError>> {
1384 let call = self.calls.fetch_add(1, Ordering::SeqCst);
1385 self.write_timestamps
1386 .lock()
1387 .expect("lock poisoned")
1388 .push(write_ts);
1389 let (tx, rx) = oneshot::channel();
1390 let result = if call < self.conflicts {
1391 Err(StorageError::InvalidUppers(vec![
1392 mz_storage_types::controller::InvalidUpper {
1393 id: GlobalId::User(1),
1394 current_upper: Antichain::from_elem(write_ts.step_forward()),
1395 },
1396 ]))
1397 } else {
1398 Ok(())
1399 };
1400 tx.send(result).expect("receiver still in scope");
1401 rx
1402 }
1403 }
1404
1405 impl TableWriteHandle for ConflictingTableWriteHandle {
1406 fn append(
1407 &self,
1408 write_ts: Timestamp,
1409 _advance_to: Timestamp,
1410 _commands: Vec<(GlobalId, Vec<TableData>)>,
1411 ) -> oneshot::Receiver<Result<(), StorageError>> {
1412 self.respond(write_ts)
1413 }
1414
1415 fn register(
1416 &self,
1417 register_ts: Timestamp,
1418 _tables: Vec<TableRegistration>,
1419 ) -> oneshot::Receiver<Result<(), StorageError>> {
1420 self.respond(register_ts)
1421 }
1422
1423 fn forget(
1424 &self,
1425 forget_ts: Timestamp,
1426 _ids: Vec<GlobalId>,
1427 ) -> oneshot::Receiver<Result<(), StorageError>> {
1428 self.respond(forget_ts)
1429 }
1430 }
1431
1432 #[mz_ore::test(tokio::test)]
1433 #[cfg_attr(miri, ignore)] async fn test_write_to_txns_conflict_retry() {
1435 Catalog::with_debug(|catalog| async move {
1436 let initial_upper = catalog.current_upper().await;
1438 let oracle = Arc::new(MemTimestampOracle::starting_at(initial_upper));
1439 let handle = Arc::new(ConflictingTableWriteHandle::new(1));
1440 let oracle_dyn: Arc<dyn TimestampOracle<Timestamp> + Send + Sync> =
1441 Arc::<MemTimestampOracle>::clone(&oracle);
1442 let handle_dyn: Arc<dyn TableWriteHandle> =
1443 Arc::<ConflictingTableWriteHandle>::clone(&handle);
1444 let (_tx, rx) = mpsc::unbounded_channel();
1445 let (internal_cmd_tx, _internal_cmd_rx) = mpsc::unbounded_channel();
1446 let committer = GroupCommitter {
1447 rx,
1448 oracle: oracle_dyn,
1449 table_write_handle: handle_dyn,
1450 catalog_upper: catalog.upper_handle(),
1451 internal_cmd_tx,
1452 now: SYSTEM_TIME.clone(),
1453 metrics: Metrics::register_into(&MetricsRegistry::new()),
1454 max_attempts: ConfigValHandle::disconnected(2),
1455 };
1456
1457 let write_ts = committer
1458 .write_to_txns(None, |ts, advance_to| {
1459 assert_eq!(advance_to, ts.step_forward());
1460 handle.append(ts, advance_to, Vec::new())
1461 })
1462 .await
1463 .expect("worker stays alive in this test");
1464
1465 let attempts = handle
1466 .write_timestamps
1467 .lock()
1468 .expect("lock poisoned")
1469 .clone();
1470 assert_eq!(attempts.len(), 2, "one conflict, one success");
1471 assert!(
1472 attempts[0] > initial_upper,
1473 "attempts start past the initial catalog upper: {attempts:?} vs {initial_upper}"
1474 );
1475 assert!(
1476 attempts[0] < attempts[1],
1477 "retry must use a fresh, larger timestamp: {attempts:?}"
1478 );
1479 assert_eq!(write_ts.timestamp, attempts[1]);
1480
1481 assert_eq!(oracle.apply_writes.load(Ordering::SeqCst), 1);
1482 assert_eq!(oracle.read_ts().await, write_ts.timestamp);
1483
1484 let catalog_upper = catalog.current_upper().await;
1485 assert!(
1486 catalog_upper >= write_ts.advance_to,
1487 "catalog upper {catalog_upper} must cover the write's advance_to {}",
1488 write_ts.advance_to
1489 );
1490
1491 catalog.expire().await;
1492 })
1493 .await;
1494 }
1495
1496 #[mz_ore::test(tokio::test)]
1497 #[cfg_attr(miri, ignore)] async fn test_write_to_txns_zero_limit_allows_one_attempt() {
1499 Catalog::with_debug(|catalog| async move {
1500 let initial_upper = catalog.current_upper().await;
1501 let oracle = Arc::new(MemTimestampOracle::starting_at(initial_upper));
1502 let handle = Arc::new(ConflictingTableWriteHandle::new(0));
1503 let oracle_dyn: Arc<dyn TimestampOracle<Timestamp> + Send + Sync> =
1504 Arc::<MemTimestampOracle>::clone(&oracle);
1505 let handle_dyn: Arc<dyn TableWriteHandle> =
1506 Arc::<ConflictingTableWriteHandle>::clone(&handle);
1507 let (_tx, rx) = mpsc::unbounded_channel();
1508 let (internal_cmd_tx, _internal_cmd_rx) = mpsc::unbounded_channel();
1509 let committer = GroupCommitter {
1510 rx,
1511 oracle: oracle_dyn,
1512 table_write_handle: handle_dyn,
1513 catalog_upper: catalog.upper_handle(),
1514 internal_cmd_tx,
1515 now: SYSTEM_TIME.clone(),
1516 metrics: Metrics::register_into(&MetricsRegistry::new()),
1517 max_attempts: ConfigValHandle::disconnected(0),
1518 };
1519
1520 let write_ts = committer
1521 .write_to_txns(None, |ts, advance_to| {
1522 handle.append(ts, advance_to, Vec::new())
1523 })
1524 .await
1525 .expect("zero limit permits one successful attempt");
1526
1527 let attempts = handle
1528 .write_timestamps
1529 .lock()
1530 .expect("lock poisoned")
1531 .clone();
1532 assert_eq!(attempts, vec![write_ts.timestamp]);
1533 assert_eq!(oracle.apply_writes.load(Ordering::SeqCst), 1);
1534
1535 catalog.expire().await;
1536 })
1537 .await;
1538 }
1539}