1use std::collections::hash_map::Entry;
16use std::collections::{HashMap, HashSet};
17use std::error::Error;
18use std::fmt::{Debug, Formatter};
19use std::future::poll_fn;
20use std::sync::Arc;
21use std::task::{Context, Poll};
22use std::time::Duration;
23
24use anyhow::anyhow;
25use fail::fail_point;
26use futures::future::{BoxFuture, join_all};
27use futures::{FutureExt, StreamExt};
28use itertools::Itertools;
29use risingwave_common::bail;
30use risingwave_common::catalog::{DatabaseId, FragmentTypeFlag, TableId};
31use risingwave_common::id::JobId;
32use risingwave_common::util::epoch::Epoch;
33use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont;
34use risingwave_common::util::tracing::TracingContext;
35use risingwave_connector::source::SplitImpl;
36use risingwave_meta_model::WorkerId;
37use risingwave_pb::common::{HostAddress, WorkerNode};
38use risingwave_pb::hummock::HummockVersionStats;
39use risingwave_pb::id::PartialGraphId;
40use risingwave_pb::source::{PbCdcTableSnapshotSplits, PbCdcTableSnapshotSplitsWithGeneration};
41use risingwave_pb::stream_plan::barrier_mutation::Mutation;
42use risingwave_pb::stream_plan::stream_node::NodeBody;
43use risingwave_pb::stream_plan::{AddMutation, Barrier, BarrierMutation};
44use risingwave_pb::stream_service::inject_barrier_request::build_actor_info::UpstreamActors;
45use risingwave_pb::stream_service::inject_barrier_request::{
46 BuildActorInfo, FragmentBuildActorInfo,
47};
48use risingwave_pb::stream_service::streaming_control_stream_request::{
49 CreatePartialGraphRequest, PbCreatePartialGraphRequest, PbInitRequest,
50 RemovePartialGraphRequest, ResetPartialGraphsRequest,
51};
52use risingwave_pb::stream_service::{
53 InjectBarrierRequest, StreamingControlStreamRequest, streaming_control_stream_request,
54 streaming_control_stream_response,
55};
56use risingwave_rpc_client::StreamingControlHandle;
57use thiserror_ext::AsReport;
58use tokio::time::{Instant, sleep};
59use tokio_retry::strategy::ExponentialBackoff;
60use tracing::{debug, error, info, warn};
61use uuid::Uuid;
62
63use super::{BarrierKind, TracedEpoch};
64use crate::barrier::BackfillOrderState;
65use crate::barrier::backfill_order_control::get_nodes_with_backfill_dependencies;
66use crate::barrier::cdc_progress::CdcTableBackfillTracker;
67use crate::barrier::checkpoint::{
68 BarrierWorkerState, BatchRefreshJobCheckpointControl, BatchRefreshRenderResult,
69 CreatingStreamingJobControl, DatabaseCheckpointControl, DatabaseCheckpointControlMetrics,
70 IndependentCheckpointJobControl,
71};
72use crate::barrier::context::{GlobalBarrierWorkerContext, GlobalBarrierWorkerContextImpl};
73use crate::barrier::edge_builder::{EdgeBuilderFragmentInfo, FragmentEdgeBuilder};
74use crate::barrier::info::{
75 BarrierInfo, CreateStreamingJobStatus, InflightDatabaseInfo, InflightStreamingJobInfo,
76 SubscriberType,
77};
78use crate::barrier::partial_graph::PartialGraphRecoverer;
79use crate::barrier::progress::CreateMviewProgressTracker;
80use crate::barrier::utils::NodeToCollect;
81use crate::controller::fragment::InflightFragmentInfo;
82use crate::controller::utils::StreamingJobExtraInfo;
83use crate::manager::MetaSrvEnv;
84use crate::model::{
85 ActorId, FragmentDownstreamRelation, FragmentId, StreamActor, StreamJobActorsToCreate,
86 SubscriptionId,
87};
88use crate::stream::cdc::{
89 CdcTableSnapshotSplits, is_parallelized_backfill_enabled_cdc_scan_fragment,
90};
91use crate::stream::{
92 ExtendedFragmentBackfillOrder, StreamFragmentGraph, UserDefinedFragmentBackfillOrder,
93 build_actor_connector_splits,
94};
95use crate::{MetaError, MetaResult};
96
97pub(crate) fn to_partial_graph_id(
98 database_id: DatabaseId,
99 creating_job_id: Option<JobId>,
100) -> PartialGraphId {
101 let raw_job_id = creating_job_id
102 .map(|job_id| {
103 assert_ne!(job_id, u32::MAX);
104 job_id.as_raw_id()
105 })
106 .unwrap_or(u32::MAX);
107 (((database_id.as_raw_id() as u64) << 32) | (raw_job_id as u64)).into()
108}
109
110pub(super) fn from_partial_graph_id(
111 partial_graph_id: PartialGraphId,
112) -> (DatabaseId, Option<JobId>) {
113 let id = partial_graph_id.as_raw_id();
114 let database_id = (id >> 32) as u32;
115 let raw_creating_job_id = (id & ((1 << 32) - 1)) as u32;
116 let creating_job_id = if raw_creating_job_id == u32::MAX {
117 None
118 } else {
119 Some(JobId::new(raw_creating_job_id))
120 };
121 (database_id.into(), creating_job_id)
122}
123
124pub(super) fn build_locality_fragment_state_table_mapping(
125 fragment_infos: &HashMap<FragmentId, InflightFragmentInfo>,
126) -> HashMap<FragmentId, Vec<TableId>> {
127 let mut mapping = HashMap::new();
128
129 for (fragment_id, fragment_info) in fragment_infos {
130 let mut state_table_ids = Vec::new();
131 visit_stream_node_cont(&fragment_info.nodes, |stream_node| {
132 if let Some(NodeBody::LocalityProvider(locality_provider)) =
133 stream_node.node_body.as_ref()
134 {
135 let state_table_id = locality_provider
136 .state_table
137 .as_ref()
138 .expect("must have state table")
139 .id;
140 state_table_ids.push(state_table_id);
141 false
142 } else {
143 true
144 }
145 });
146 if !state_table_ids.is_empty() {
147 mapping.insert(*fragment_id, state_table_ids);
148 }
149 }
150
151 mapping
152}
153
154pub(super) fn database_partial_graphs<'a>(
155 database_id: DatabaseId,
156 creating_jobs: impl Iterator<Item = JobId> + Sized + 'a,
157) -> impl Iterator<Item = PartialGraphId> + 'a {
158 creating_jobs
159 .map(Some)
160 .chain([None])
161 .map(move |creating_job_id| to_partial_graph_id(database_id, creating_job_id))
162}
163
164struct ControlStreamNode {
165 worker_id: WorkerId,
166 host: HostAddress,
167 handle: StreamingControlHandle,
168}
169
170enum WorkerNodeState {
171 Connected {
172 control_stream: ControlStreamNode,
173 removed: bool,
174 },
175 Reconnecting(BoxFuture<'static, StreamingControlHandle>),
176}
177
178pub(super) struct ControlStreamManager {
179 workers: HashMap<WorkerId, (WorkerNode, WorkerNodeState)>,
180 pub env: MetaSrvEnv,
181}
182
183impl ControlStreamManager {
184 pub(super) fn new(env: MetaSrvEnv) -> Self {
185 Self {
186 workers: Default::default(),
187 env,
188 }
189 }
190
191 pub(super) fn host_addr(&self, worker_id: WorkerId) -> HostAddress {
192 self.workers[&worker_id].0.host.clone().unwrap()
193 }
194
195 pub(super) async fn add_worker(
196 &mut self,
197 node: WorkerNode,
198 partial_graphs: impl Iterator<Item = PartialGraphId>,
199 term_id: &String,
200 context: Arc<impl GlobalBarrierWorkerContext>,
201 ) {
202 let node_id = node.id;
203 if let Entry::Occupied(entry) = self.workers.entry(node_id) {
204 let (existing_node, worker_state) = entry.get();
205 assert_eq!(existing_node.host, node.host);
206 warn!(id = %node.id, host = ?node.host, "node already exists");
207 match worker_state {
208 WorkerNodeState::Connected { .. } => {
209 warn!(id = %node.id, host = ?node.host, "new node already connected");
210 return;
211 }
212 WorkerNodeState::Reconnecting(_) => {
213 warn!(id = %node.id, host = ?node.host, "remove previous pending worker connect request and reconnect");
214 entry.remove();
215 }
216 }
217 }
218 let node_host = node.host.clone().unwrap();
219 let mut backoff = ExponentialBackoff::from_millis(100)
220 .max_delay(Duration::from_secs(3))
221 .factor(5);
222 const MAX_RETRY: usize = 5;
223 for i in 1..=MAX_RETRY {
224 match context
225 .new_control_stream(
226 &node,
227 &PbInitRequest {
228 term_id: term_id.clone(),
229 },
230 )
231 .await
232 {
233 Ok(mut handle) => {
234 WorkerNodeConnected {
235 handle: &mut handle,
236 node: &node,
237 }
238 .initialize(partial_graphs);
239 info!(?node_host, "add control stream worker");
240 assert!(
241 self.workers
242 .insert(
243 node_id,
244 (
245 node,
246 WorkerNodeState::Connected {
247 control_stream: ControlStreamNode {
248 worker_id: node_id as _,
249 host: node_host,
250 handle,
251 },
252 removed: false
253 }
254 )
255 )
256 .is_none()
257 );
258 return;
259 }
260 Err(e) => {
261 let delay = backoff.next().unwrap();
264 error!(attempt = i, backoff_delay = ?delay, err = %e.as_report(), ?node_host, "fail to resolve worker node address");
265 sleep(delay).await;
266 }
267 }
268 }
269 error!(?node_host, "fail to create worker node after retry");
270 assert!(
271 self.workers
272 .insert(
273 node_id,
274 (
275 node.clone(),
276 WorkerNodeState::Reconnecting(ControlStreamManager::retry_connect(
277 node,
278 term_id.to_owned(),
279 context,
280 ))
281 )
282 )
283 .is_none()
284 );
285 }
286
287 pub(super) fn remove_worker(&mut self, node: WorkerNode) {
288 if let Entry::Occupied(mut entry) = self.workers.entry(node.id) {
289 let (_, worker_state) = entry.get_mut();
290 match worker_state {
291 WorkerNodeState::Connected { removed, .. } => {
292 info!(worker_id = %node.id, "mark connected worker as removed");
293 *removed = true;
294 }
295 WorkerNodeState::Reconnecting(_) => {
296 info!(worker_id = %node.id, "remove worker");
297 entry.remove();
298 }
299 }
300 }
301 }
302
303 fn retry_connect(
304 node: WorkerNode,
305 term_id: String,
306 context: Arc<impl GlobalBarrierWorkerContext>,
307 ) -> BoxFuture<'static, StreamingControlHandle> {
308 async move {
309 let mut attempt = 0;
310 let backoff = ExponentialBackoff::from_millis(100)
311 .max_delay(Duration::from_mins(1))
312 .factor(5);
313 let init_request = PbInitRequest { term_id };
314 for delay in backoff {
315 attempt += 1;
316 sleep(delay).await;
317 match context.new_control_stream(&node, &init_request).await {
318 Ok(handle) => {
319 return handle;
320 }
321 Err(e) => {
322 warn!(e = %e.as_report(), ?node, attempt, "fail to create control stream worker");
323 }
324 }
325 }
326 unreachable!("end of retry backoff")
327 }.boxed()
328 }
329
330 pub(super) async fn recover(
331 env: MetaSrvEnv,
332 nodes: &HashMap<WorkerId, WorkerNode>,
333 term_id: &str,
334 context: Arc<impl GlobalBarrierWorkerContext>,
335 ) -> Self {
336 let reset_start_time = Instant::now();
337 let init_request = PbInitRequest {
338 term_id: term_id.to_owned(),
339 };
340 let init_request = &init_request;
341 let nodes = join_all(nodes.iter().map(|(worker_id, node)| async {
342 let result = context.new_control_stream(node, init_request).await;
343 (*worker_id, node.clone(), result)
344 }))
345 .await;
346 let mut unconnected_workers = HashSet::new();
347 let mut workers = HashMap::new();
348 for (worker_id, node, result) in nodes {
349 match result {
350 Ok(handle) => {
351 let control_stream = ControlStreamNode {
352 worker_id: node.id,
353 host: node.host.clone().unwrap(),
354 handle,
355 };
356 assert!(
357 workers
358 .insert(
359 worker_id,
360 (
361 node,
362 WorkerNodeState::Connected {
363 control_stream,
364 removed: false
365 }
366 )
367 )
368 .is_none()
369 );
370 }
371 Err(e) => {
372 unconnected_workers.insert(worker_id);
373 warn!(
374 e = %e.as_report(),
375 %worker_id,
376 ?node,
377 "failed to connect to node"
378 );
379 assert!(
380 workers
381 .insert(
382 worker_id,
383 (
384 node.clone(),
385 WorkerNodeState::Reconnecting(Self::retry_connect(
386 node,
387 term_id.to_owned(),
388 context.clone()
389 ))
390 )
391 )
392 .is_none()
393 );
394 }
395 }
396 }
397
398 info!(elapsed=?reset_start_time.elapsed(), ?unconnected_workers, "control stream reset");
399
400 Self { workers, env }
401 }
402
403 pub(super) fn clear(&mut self) {
405 *self = Self::new(self.env.clone());
406 }
407}
408
409pub(super) struct WorkerNodeConnected<'a> {
410 node: &'a WorkerNode,
411 handle: &'a mut StreamingControlHandle,
412}
413
414impl<'a> WorkerNodeConnected<'a> {
415 pub(super) fn initialize(self, partial_graphs: impl Iterator<Item = PartialGraphId>) {
416 for partial_graph_id in partial_graphs {
417 if let Err(e) = self.handle.send_request(StreamingControlStreamRequest {
418 request: Some(
419 streaming_control_stream_request::Request::CreatePartialGraph(
420 PbCreatePartialGraphRequest { partial_graph_id },
421 ),
422 ),
423 }) {
424 warn!(e = %e.as_report(), node = ?self.node, "failed to send initial partial graph request");
425 }
426 }
427 }
428}
429
430pub(super) enum WorkerNodeEvent<'a> {
431 Response(MetaResult<streaming_control_stream_response::Response>),
432 Connected(WorkerNodeConnected<'a>),
433}
434
435impl ControlStreamManager {
436 fn poll_next_event<'a>(
437 this_opt: &mut Option<&'a mut Self>,
438 cx: &mut Context<'_>,
439 term_id: &str,
440 context: &Arc<impl GlobalBarrierWorkerContext>,
441 poll_reconnect: bool,
442 ) -> Poll<(WorkerId, WorkerNodeEvent<'a>)> {
443 let this = this_opt.as_mut().expect("Future polled after completion");
444 if this.workers.is_empty() {
445 return Poll::Pending;
446 }
447 {
448 for (&worker_id, (node, worker_state)) in &mut this.workers {
449 let control_stream = match worker_state {
450 WorkerNodeState::Connected { control_stream, .. } => control_stream,
451 WorkerNodeState::Reconnecting(_) if !poll_reconnect => {
452 continue;
453 }
454 WorkerNodeState::Reconnecting(join_handle) => {
455 match join_handle.poll_unpin(cx) {
456 Poll::Ready(handle) => {
457 info!(id=%node.id, host=?node.host, "reconnected to worker");
458 *worker_state = WorkerNodeState::Connected {
459 control_stream: ControlStreamNode {
460 worker_id: node.id,
461 host: node.host.clone().unwrap(),
462 handle,
463 },
464 removed: false,
465 };
466 let this = this_opt.take().expect("should exist");
467 let (node, worker_state) =
468 this.workers.get_mut(&worker_id).expect("should exist");
469 let WorkerNodeState::Connected { control_stream, .. } =
470 worker_state
471 else {
472 unreachable!()
473 };
474 return Poll::Ready((
475 worker_id,
476 WorkerNodeEvent::Connected(WorkerNodeConnected {
477 node,
478 handle: &mut control_stream.handle,
479 }),
480 ));
481 }
482 Poll::Pending => {
483 continue;
484 }
485 }
486 }
487 };
488 match control_stream.handle.response_stream.poll_next_unpin(cx) {
489 Poll::Ready(result) => {
490 {
491 let result = result
492 .ok_or_else(|| (false, anyhow!("end of stream").into()))
493 .and_then(|result| {
494 result.map_err(|err| -> (bool, MetaError) { (false, err.into()) }).and_then(|resp| {
495 match resp
496 .response
497 .ok_or_else(|| (false, anyhow!("empty response").into()))?
498 {
499 streaming_control_stream_response::Response::Shutdown(_) => Err((true, anyhow!(
500 "worker node {worker_id} is shutting down"
501 )
502 .into())),
503 streaming_control_stream_response::Response::Init(_) => {
504 Err((false, anyhow!("get unexpected init response").into()))
506 }
507 resp => {
508 if let streaming_control_stream_response::Response::CompleteBarrier(barrier_resp) = &resp {
509 assert_eq!(worker_id, barrier_resp.worker_id);
510 }
511 Ok(resp)
512 }
513 }
514 })
515 });
516 let result = match result {
517 Ok(resp) => Ok(resp),
518 Err((shutdown, err)) => {
519 warn!(worker_id = %node.id, host = ?node.host, err = %err.as_report(), "get error from response stream");
520 let WorkerNodeState::Connected { removed, .. } = worker_state
521 else {
522 unreachable!("checked connected")
523 };
524 if *removed || shutdown {
525 this.workers.remove(&worker_id);
526 } else {
527 *worker_state = WorkerNodeState::Reconnecting(
528 ControlStreamManager::retry_connect(
529 node.clone(),
530 term_id.to_owned(),
531 context.clone(),
532 ),
533 );
534 }
535 Err(err)
536 }
537 };
538 return Poll::Ready((worker_id, WorkerNodeEvent::Response(result)));
539 }
540 }
541 Poll::Pending => {
542 continue;
543 }
544 }
545 }
546 };
547
548 Poll::Pending
549 }
550
551 #[await_tree::instrument("control_stream_next_event")]
552 pub(super) async fn next_event<'a>(
553 &'a mut self,
554 term_id: &str,
555 context: &Arc<impl GlobalBarrierWorkerContext>,
556 ) -> (WorkerId, WorkerNodeEvent<'a>) {
557 let mut this = Some(self);
558 poll_fn(|cx| Self::poll_next_event(&mut this, cx, term_id, context, true)).await
559 }
560
561 #[await_tree::instrument("control_stream_next_response")]
562 pub(super) async fn next_response(
563 &mut self,
564 term_id: &str,
565 context: &Arc<impl GlobalBarrierWorkerContext>,
566 ) -> (
567 WorkerId,
568 MetaResult<streaming_control_stream_response::Response>,
569 ) {
570 let mut this = Some(self);
571 let (worker_id, event) =
572 poll_fn(|cx| Self::poll_next_event(&mut this, cx, term_id, context, false)).await;
573 match event {
574 WorkerNodeEvent::Response(result) => (worker_id, result),
575 WorkerNodeEvent::Connected(_) => {
576 unreachable!("set poll_reconnect=false")
577 }
578 }
579 }
580}
581
582pub(super) struct DatabaseInitialBarrierCollector {
583 pub(super) database_id: DatabaseId,
584 pub(super) initializing_partial_graphs: HashSet<PartialGraphId>,
585 pub(super) database: DatabaseCheckpointControl,
586}
587
588impl Debug for DatabaseInitialBarrierCollector {
589 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
590 f.debug_struct("DatabaseInitialBarrierCollector")
591 .field("database_id", &self.database_id)
592 .field("initializing_graphs", &self.initializing_partial_graphs)
593 .finish()
594 }
595}
596
597impl DatabaseInitialBarrierCollector {
598 pub(super) fn is_collected(&self) -> bool {
599 self.initializing_partial_graphs.is_empty()
600 }
601
602 pub(super) fn partial_graph_initialized(&mut self, partial_graph_id: PartialGraphId) {
603 assert!(self.initializing_partial_graphs.remove(&partial_graph_id));
604 }
605
606 pub(super) fn all_partial_graphs(&self) -> impl Iterator<Item = PartialGraphId> + '_ {
607 database_partial_graphs(
608 self.database_id,
609 self.database
610 .independent_checkpoint_job_controls
611 .keys()
612 .copied(),
613 )
614 }
615
616 pub(super) fn finish(self) -> DatabaseCheckpointControl {
617 assert!(self.is_collected());
618 self.database
619 }
620
621 pub(super) fn is_valid_after_worker_err(&self, worker_id: WorkerId) -> bool {
622 self.database.is_valid_after_worker_err(worker_id)
623 }
624}
625
626impl PartialGraphRecoverer<'_> {
627 #[expect(clippy::too_many_arguments)]
629 pub(super) fn inject_database_initial_barrier(
630 &mut self,
631 database_id: DatabaseId,
632 jobs: HashMap<JobId, HashMap<FragmentId, InflightFragmentInfo>>,
633 job_extra_info: &HashMap<JobId, StreamingJobExtraInfo>,
634 state_table_committed_epochs: &mut HashMap<TableId, u64>,
635 state_table_log_epochs: &mut HashMap<TableId, Vec<(Vec<u64>, u64)>>,
636 fragment_relations: &FragmentDownstreamRelation,
637 stream_actors: &HashMap<ActorId, StreamActor>,
638 source_splits: &mut HashMap<ActorId, Vec<SplitImpl>>,
639 background_jobs: &mut HashSet<JobId>,
640 mv_depended_subscriptions: &mut HashMap<TableId, HashMap<SubscriptionId, u64>>,
641 is_paused: bool,
642 hummock_version_stats: &HummockVersionStats,
643 cdc_table_snapshot_splits: &mut HashMap<JobId, CdcTableSnapshotSplits>,
644 batch_refresh: HashMap<JobId, BatchRefreshRenderResult>,
645 ) -> MetaResult<DatabaseCheckpointControl> {
646 fn collect_source_splits(
647 fragment_infos: impl Iterator<Item = &InflightFragmentInfo>,
648 source_splits: &mut HashMap<ActorId, Vec<SplitImpl>>,
649 ) -> HashMap<ActorId, Vec<SplitImpl>> {
650 fragment_infos
651 .flat_map(|info| info.actors.keys())
652 .filter_map(|actor_id| {
653 let actor_id = *actor_id as ActorId;
654 source_splits
655 .remove(&actor_id)
656 .map(|splits| (actor_id, splits))
657 })
658 .collect()
659 }
660 fn build_mutation(
661 splits: &HashMap<ActorId, Vec<SplitImpl>>,
662 cdc_table_snapshot_split_assignment: HashMap<ActorId, PbCdcTableSnapshotSplits>,
663 backfill_orders: &ExtendedFragmentBackfillOrder,
664 is_paused: bool,
665 ) -> Mutation {
666 let backfill_nodes_to_pause = get_nodes_with_backfill_dependencies(backfill_orders)
667 .into_iter()
668 .collect();
669 Mutation::Add(AddMutation {
670 actor_dispatchers: Default::default(),
672 added_actors: Default::default(),
673 actor_splits: build_actor_connector_splits(splits),
674 actor_cdc_table_snapshot_splits: Some(PbCdcTableSnapshotSplitsWithGeneration {
675 splits: cdc_table_snapshot_split_assignment,
676 }),
677 pause: is_paused,
678 subscriptions_to_add: Default::default(),
679 backfill_nodes_to_pause,
680 new_upstream_sinks: Default::default(),
681 dropped_actors: Default::default(),
682 sink_log_store_flush: Default::default(),
683 })
684 }
685
686 fn resolve_jobs_committed_epoch(
687 state_table_committed_epochs: &mut HashMap<TableId, u64>,
688 table_ids: impl Iterator<Item = TableId>,
689 ) -> u64 {
690 let mut epochs = table_ids.map(|table_id| {
691 (
692 table_id,
693 state_table_committed_epochs
694 .remove(&table_id)
695 .expect("should exist"),
696 )
697 });
698 let (first_table_id, prev_epoch) = epochs.next().expect("non-empty");
699 for (table_id, epoch) in epochs {
700 assert_eq!(
701 prev_epoch, epoch,
702 "{} has different committed epoch to {}",
703 first_table_id, table_id
704 );
705 }
706 prev_epoch
707 }
708 fn job_backfill_orders(
709 job_extra_info: &HashMap<JobId, StreamingJobExtraInfo>,
710 job_id: JobId,
711 ) -> UserDefinedFragmentBackfillOrder {
712 UserDefinedFragmentBackfillOrder::new(
713 job_extra_info
714 .get(&job_id)
715 .and_then(|info| info.backfill_orders.clone())
716 .map_or_else(HashMap::new, |orders| orders.0),
717 )
718 }
719
720 let mut subscribers: HashMap<_, HashMap<_, _>> = jobs
721 .keys()
722 .filter_map(|job_id| {
723 mv_depended_subscriptions
724 .remove(&job_id.as_mv_table_id())
725 .map(|subscriptions| {
726 (
727 job_id.as_mv_table_id(),
728 subscriptions
729 .into_iter()
730 .map(|(subscription_id, retention)| {
731 (
732 subscription_id.as_subscriber_id(),
733 SubscriberType::Subscription(retention),
734 )
735 })
736 .collect(),
737 )
738 })
739 })
740 .collect();
741
742 for (job_id, render_result) in &batch_refresh {
745 let snapshot_backfill_info = StreamFragmentGraph::collect_snapshot_backfill_info_impl(
746 render_result
747 .fragment_infos
748 .values()
749 .map(|fragment| (&fragment.nodes, fragment.fragment_type_mask)),
750 )?
751 .0
752 .ok_or_else(|| anyhow!("batch refresh job {} has no snapshot backfill info", job_id))?;
753
754 for upstream_table_id in snapshot_backfill_info
755 .upstream_mv_table_id_to_backfill_epoch
756 .keys()
757 {
758 subscribers
759 .entry(*upstream_table_id)
760 .or_default()
761 .try_insert(job_id.as_subscriber_id(), SubscriberType::SnapshotBackfill)
762 .expect("non-duplicate");
763 }
764 }
765
766 let mut database_jobs = HashMap::new();
767 let mut snapshot_backfill_jobs = HashMap::new();
768
769 for (job_id, job_fragments) in jobs {
770 if background_jobs.remove(&job_id) {
771 if job_fragments.values().any(|fragment| {
772 fragment
773 .fragment_type_mask
774 .contains(FragmentTypeFlag::SnapshotBackfillStreamScan)
775 }) {
776 debug!(%job_id, "recovered snapshot backfill job");
777 snapshot_backfill_jobs.insert(job_id, job_fragments);
778 } else {
779 database_jobs.insert(job_id, (job_fragments, true));
780 }
781 } else {
782 database_jobs.insert(job_id, (job_fragments, false));
783 }
784 }
785
786 let database_job_log_epochs: HashMap<_, _> = database_jobs
787 .keys()
788 .filter_map(|job_id| {
789 state_table_log_epochs
790 .remove(&job_id.as_mv_table_id())
791 .map(|epochs| (job_id.as_mv_table_id(), epochs))
792 })
793 .collect();
794
795 let prev_epoch = resolve_jobs_committed_epoch(
796 state_table_committed_epochs,
797 InflightFragmentInfo::existing_table_ids(
798 database_jobs.values().flat_map(|(job, _)| job.values()),
799 ),
800 );
801 let prev_epoch = TracedEpoch::new(Epoch(prev_epoch));
802 let curr_epoch = prev_epoch.next();
804 let barrier_info = BarrierInfo {
805 prev_epoch,
806 curr_epoch,
807 kind: BarrierKind::Initial,
808 };
809
810 let mut ongoing_snapshot_backfill_jobs: HashMap<JobId, _> = HashMap::new();
811 for (job_id, fragment_infos) in snapshot_backfill_jobs {
812 let committed_epoch = resolve_jobs_committed_epoch(
813 state_table_committed_epochs,
814 InflightFragmentInfo::existing_table_ids(fragment_infos.values()),
815 );
816 if committed_epoch == barrier_info.prev_epoch() {
817 info!(
818 "recovered creating snapshot backfill job {} catch up with upstream already",
819 job_id
820 );
821 database_jobs
822 .try_insert(job_id, (fragment_infos, true))
823 .expect("non-duplicate");
824 continue;
825 }
826 let snapshot_backfill_info = StreamFragmentGraph::collect_snapshot_backfill_info_impl(
827 fragment_infos
828 .values()
829 .map(|fragment| (&fragment.nodes, fragment.fragment_type_mask)),
830 )?
831 .0
832 .ok_or_else(|| {
833 anyhow!(
834 "recovered snapshot backfill job {} has no snapshot backfill info",
835 job_id
836 )
837 })?;
838 let mut snapshot_epoch = None;
839 let upstream_table_ids: HashSet<_> = snapshot_backfill_info
840 .upstream_mv_table_id_to_backfill_epoch
841 .keys()
842 .cloned()
843 .collect();
844 for (upstream_table_id, epoch) in
845 snapshot_backfill_info.upstream_mv_table_id_to_backfill_epoch
846 {
847 let epoch = epoch.ok_or_else(|| anyhow!("recovered snapshot backfill job {} to upstream {} has not set snapshot epoch", job_id, upstream_table_id))?;
848 let snapshot_epoch = snapshot_epoch.get_or_insert(epoch);
849 if *snapshot_epoch != epoch {
850 return Err(anyhow!("snapshot epoch {} to upstream {} different to snapshot epoch {} to previous upstream", epoch, upstream_table_id, snapshot_epoch).into());
851 }
852 }
853 let snapshot_epoch = snapshot_epoch.ok_or_else(|| {
854 anyhow!(
855 "snapshot backfill job {} has not set snapshot epoch",
856 job_id
857 )
858 })?;
859 for upstream_table_id in &upstream_table_ids {
860 subscribers
861 .entry(*upstream_table_id)
862 .or_default()
863 .try_insert(job_id.as_subscriber_id(), SubscriberType::SnapshotBackfill)
864 .expect("non-duplicate");
865 }
866 ongoing_snapshot_backfill_jobs
867 .try_insert(
868 job_id,
869 (
870 fragment_infos,
871 upstream_table_ids,
872 committed_epoch,
873 snapshot_epoch,
874 ),
875 )
876 .expect("non-duplicated");
877 }
878
879 let mut cdc_table_snapshot_split_assignment: HashMap<ActorId, PbCdcTableSnapshotSplits> =
880 HashMap::new();
881
882 let database_jobs: HashMap<JobId, InflightStreamingJobInfo> = {
883 database_jobs
884 .into_iter()
885 .map(|(job_id, (fragment_infos, is_background_creating))| {
886 let status = if is_background_creating {
887 let backfill_ordering = job_backfill_orders(job_extra_info, job_id);
888 let backfill_ordering = StreamFragmentGraph::extend_fragment_backfill_ordering_with_locality_backfill(
889 backfill_ordering,
890 fragment_relations,
891 || fragment_infos.iter().map(|(fragment_id, fragment)| {
892 (*fragment_id, fragment.fragment_type_mask, &fragment.nodes)
893 }));
894 let locality_fragment_state_table_mapping =
895 build_locality_fragment_state_table_mapping(&fragment_infos);
896 let backfill_order_state = BackfillOrderState::recover_from_fragment_infos(
897 &backfill_ordering,
898 &fragment_infos,
899 locality_fragment_state_table_mapping,
900 );
901 CreateStreamingJobStatus::Creating {
902 tracker: CreateMviewProgressTracker::recover(
903 job_id,
904 &fragment_infos,
905 backfill_order_state,
906 hummock_version_stats,
907 ),
908 }
909 } else {
910 CreateStreamingJobStatus::Created
911 };
912 let cdc_table_backfill_tracker =
913 if let Some(splits) = cdc_table_snapshot_splits.remove(&job_id) {
914 let cdc_fragment = fragment_infos
915 .values()
916 .find(|fragment| {
917 is_parallelized_backfill_enabled_cdc_scan_fragment(
918 fragment.fragment_type_mask,
919 &fragment.nodes,
920 )
921 .is_some()
922 })
923 .expect("should have parallel cdc fragment");
924 let cdc_actors = cdc_fragment.actors.keys().copied().collect();
925 let mut tracker =
926 CdcTableBackfillTracker::restore(cdc_fragment.fragment_id, splits);
927 cdc_table_snapshot_split_assignment
928 .extend(tracker.reassign_splits(cdc_actors)?);
929 Some(tracker)
930 } else {
931 None
932 };
933 Ok((
934 job_id,
935 InflightStreamingJobInfo {
936 job_id,
937 fragment_infos,
938 subscribers: subscribers
939 .remove(&job_id.as_mv_table_id())
940 .unwrap_or_default(),
941 status,
942 cdc_table_backfill_tracker,
943 },
944 ))
945 })
946 .try_collect::<_, _, MetaError>()
947 }?;
948
949 let control_stream_manager = self.control_stream_manager();
950 let mut builder = FragmentEdgeBuilder::new(
951 database_jobs
952 .values()
953 .flat_map(|job| {
954 let partial_graph_id = to_partial_graph_id(database_id, None);
955 job.fragment_infos().map(move |info| {
956 (
957 info.fragment_id,
958 EdgeBuilderFragmentInfo::from_inflight(
959 info,
960 partial_graph_id,
961 control_stream_manager,
962 ),
963 )
964 })
965 })
966 .chain(ongoing_snapshot_backfill_jobs.iter().flat_map(
967 |(job_id, (fragments, ..))| {
968 let partial_graph_id = to_partial_graph_id(database_id, Some(*job_id));
969 fragments.values().map(move |fragment| {
970 (
971 fragment.fragment_id,
972 EdgeBuilderFragmentInfo::from_inflight(
973 fragment,
974 partial_graph_id,
975 control_stream_manager,
976 ),
977 )
978 })
979 },
980 )),
981 );
982 builder.add_relations(fragment_relations);
983 let mut edges = builder.build();
984
985 {
986 let new_actors =
987 edges.collect_actors_to_create(database_jobs.values().flat_map(move |job| {
988 job.fragment_infos.values().map(move |fragment_infos| {
989 (
990 fragment_infos.fragment_id,
991 &fragment_infos.nodes,
992 fragment_infos.actors.iter().map(move |(actor_id, actor)| {
993 (
994 stream_actors.get(actor_id).expect("should exist"),
995 actor.worker_id,
996 )
997 }),
998 job.subscribers.keys().copied(),
999 )
1000 })
1001 }));
1002
1003 let nodes_actors =
1004 InflightFragmentInfo::actor_ids_to_collect(database_jobs.values().flatten());
1005 let database_job_source_splits =
1006 collect_source_splits(database_jobs.values().flatten(), source_splits);
1007 let database_backfill_orders =
1008 UserDefinedFragmentBackfillOrder::merge(database_jobs.values().map(|job| {
1009 if matches!(job.status, CreateStreamingJobStatus::Creating { .. }) {
1010 job_backfill_orders(job_extra_info, job.job_id)
1011 } else {
1012 UserDefinedFragmentBackfillOrder::default()
1013 }
1014 }));
1015 let database_backfill_orders =
1016 StreamFragmentGraph::extend_fragment_backfill_ordering_with_locality_backfill(
1017 database_backfill_orders,
1018 fragment_relations,
1019 || {
1020 database_jobs.values().flat_map(|job_fragments| {
1021 job_fragments
1022 .fragment_infos
1023 .iter()
1024 .map(|(fragment_id, fragment)| {
1025 (*fragment_id, fragment.fragment_type_mask, &fragment.nodes)
1026 })
1027 })
1028 },
1029 );
1030 let mutation = build_mutation(
1031 &database_job_source_splits,
1032 cdc_table_snapshot_split_assignment,
1033 &database_backfill_orders,
1034 is_paused,
1035 );
1036
1037 let partial_graph_id = to_partial_graph_id(database_id, None);
1038 self.recover_graph(
1039 partial_graph_id,
1040 mutation,
1041 &barrier_info,
1042 &nodes_actors,
1043 InflightFragmentInfo::existing_table_ids(database_jobs.values().flatten()),
1044 new_actors,
1045 DatabaseCheckpointControlMetrics::new(database_id),
1046 )?;
1047 debug!(
1048 %database_id,
1049 "inject initial barrier"
1050 );
1051 };
1052
1053 let mut independent_checkpoint_job_controls: HashMap<
1054 JobId,
1055 IndependentCheckpointJobControl,
1056 > = HashMap::new();
1057 for (job_id, (info, upstream_table_ids, committed_epoch, snapshot_epoch)) in
1058 ongoing_snapshot_backfill_jobs
1059 {
1060 let node_actors = edges.collect_actors_to_create(info.values().map(|fragment_infos| {
1061 (
1062 fragment_infos.fragment_id,
1063 &fragment_infos.nodes,
1064 fragment_infos.actors.iter().map(move |(actor_id, actor)| {
1065 (
1066 stream_actors.get(actor_id).expect("should exist"),
1067 actor.worker_id,
1068 )
1069 }),
1070 vec![], )
1072 }));
1073
1074 let database_job_source_splits =
1075 collect_source_splits(database_jobs.values().flatten(), source_splits);
1076 assert!(
1077 !cdc_table_snapshot_splits.contains_key(&job_id),
1078 "snapshot backfill job {job_id} should not have cdc backfill"
1079 );
1080 if is_paused {
1081 bail!("should not pause when having snapshot backfill job {job_id}");
1082 }
1083 let job_backfill_orders = job_backfill_orders(job_extra_info, job_id);
1084 let job_backfill_orders =
1085 StreamFragmentGraph::extend_fragment_backfill_ordering_with_locality_backfill(
1086 job_backfill_orders,
1087 fragment_relations,
1088 || {
1089 info.iter().map(|(fragment_id, fragment)| {
1090 (*fragment_id, fragment.fragment_type_mask, &fragment.nodes)
1091 })
1092 },
1093 );
1094 let mutation = build_mutation(
1095 &database_job_source_splits,
1096 Default::default(), &job_backfill_orders,
1098 false,
1099 );
1100
1101 let job = CreatingStreamingJobControl::recover(
1102 database_id,
1103 job_id,
1104 upstream_table_ids,
1105 &database_job_log_epochs,
1106 snapshot_epoch,
1107 committed_epoch,
1108 &barrier_info,
1109 info,
1110 job_backfill_orders,
1111 fragment_relations,
1112 hummock_version_stats,
1113 node_actors,
1114 mutation.clone(),
1115 self,
1116 )?;
1117 independent_checkpoint_job_controls.insert(
1118 job_id,
1119 IndependentCheckpointJobControl::CreatingStreamingJob(job),
1120 );
1121 }
1122
1123 for (job_id, render_result) in batch_refresh {
1126 background_jobs.remove(&job_id);
1127 debug!(%job_id, "recovered batch refresh job");
1128
1129 let committed_epoch = resolve_jobs_committed_epoch(
1131 state_table_committed_epochs,
1132 InflightFragmentInfo::existing_table_ids(render_result.fragment_infos.values()),
1133 );
1134
1135 let snapshot_backfill_info = StreamFragmentGraph::collect_snapshot_backfill_info_impl(
1136 render_result
1137 .fragment_infos
1138 .values()
1139 .map(|fragment| (&fragment.nodes, fragment.fragment_type_mask)),
1140 )?
1141 .0
1142 .ok_or_else(|| anyhow!("batch refresh job {} has no snapshot backfill info", job_id))?;
1143
1144 let upstream_table_ids: HashSet<TableId> = snapshot_backfill_info
1145 .upstream_mv_table_id_to_backfill_epoch
1146 .keys()
1147 .copied()
1148 .collect();
1149 let snapshot_epoch = snapshot_backfill_info
1150 .upstream_mv_table_id_to_backfill_epoch
1151 .values()
1152 .find_map(|e| *e)
1153 .unwrap_or(committed_epoch);
1154
1155 let job_backfill_orders = job_backfill_orders(job_extra_info, job_id);
1156 let job_backfill_orders =
1157 StreamFragmentGraph::extend_fragment_backfill_ordering_with_locality_backfill(
1158 job_backfill_orders,
1159 fragment_relations,
1160 || {
1161 render_result
1162 .fragment_infos
1163 .iter()
1164 .map(|(fid, f)| (*fid, f.fragment_type_mask, &f.nodes))
1165 },
1166 );
1167 let mutation = build_mutation(
1168 &Default::default(), Default::default(),
1170 &job_backfill_orders,
1171 false,
1172 );
1173
1174 let refresh_interval_sec = job_extra_info
1175 .get(&job_id)
1176 .and_then(|info| info.refresh_interval_sec)
1177 .expect("batch refresh job should have refresh_interval_sec in job extra info");
1178
1179 let job = BatchRefreshJobCheckpointControl::recover(
1180 database_id,
1181 job_id,
1182 upstream_table_ids,
1183 snapshot_epoch,
1184 committed_epoch,
1185 job_backfill_orders,
1186 hummock_version_stats,
1187 mutation,
1188 render_result,
1189 self,
1190 refresh_interval_sec,
1191 )?;
1192 independent_checkpoint_job_controls
1193 .insert(job_id, IndependentCheckpointJobControl::BatchRefresh(job));
1194 }
1195
1196 self.control_stream_manager()
1197 .env
1198 .shared_actor_infos()
1199 .recover_database(
1200 database_id,
1201 database_jobs
1202 .values()
1203 .flat_map(|info| {
1204 info.fragment_infos()
1205 .map(move |fragment| (fragment, info.job_id))
1206 })
1207 .chain(
1208 independent_checkpoint_job_controls
1209 .iter()
1210 .flat_map(|(job_id, job)| {
1211 let job_id = *job_id;
1212 job.fragment_infos()
1213 .into_iter()
1214 .flat_map(move |infos| infos.values().map(move |f| (f, job_id)))
1215 }),
1216 ),
1217 );
1218
1219 let committed_epoch = barrier_info.prev_epoch();
1220 let new_epoch = barrier_info.curr_epoch;
1221 let database_info = InflightDatabaseInfo::recover(
1222 database_id,
1223 database_jobs.into_values(),
1224 self.control_stream_manager()
1225 .env
1226 .shared_actor_infos()
1227 .clone(),
1228 );
1229 let database_state = BarrierWorkerState::recovery(new_epoch, is_paused);
1230 Ok(DatabaseCheckpointControl::recovery(
1231 database_id,
1232 database_state,
1233 committed_epoch,
1234 database_info,
1235 independent_checkpoint_job_controls,
1236 ))
1237 }
1238}
1239
1240impl ControlStreamManager {
1241 fn connected_workers(&self) -> impl Iterator<Item = (WorkerId, &ControlStreamNode)> + '_ {
1242 self.workers
1243 .iter()
1244 .filter_map(|(worker_id, (_, worker_state))| match worker_state {
1245 WorkerNodeState::Connected { control_stream, .. } => {
1246 Some((*worker_id, control_stream))
1247 }
1248 WorkerNodeState::Reconnecting(_) => None,
1249 })
1250 }
1251
1252 pub(super) fn inject_barrier(
1253 &mut self,
1254 partial_graph_id: PartialGraphId,
1255 mutation: Option<Mutation>,
1256 barrier_info: &BarrierInfo,
1257 node_actors: &HashMap<WorkerId, HashSet<ActorId>>,
1258 table_ids_to_sync: impl Iterator<Item = TableId>,
1259 nodes_to_sync_table: impl Iterator<Item = WorkerId>,
1260 mut new_actors: Option<StreamJobActorsToCreate>,
1261 ) -> MetaResult<NodeToCollect> {
1262 fail_point!("inject_barrier_err", |_| risingwave_common::bail!(
1263 "inject_barrier_err"
1264 ));
1265
1266 let nodes_to_sync_table: HashSet<_> = nodes_to_sync_table.collect();
1267
1268 nodes_to_sync_table.iter().for_each(|worker_id| {
1269 assert!(node_actors.contains_key(worker_id), "worker_id {worker_id} in nodes_to_sync_table {nodes_to_sync_table:?} but not in node_actors {node_actors:?}");
1270 });
1271
1272 let mut node_need_collect = NodeToCollect::new();
1273 let table_ids_to_sync = table_ids_to_sync.collect_vec();
1274
1275 node_actors.iter()
1276 .try_for_each(|(worker_id, actor_ids_to_collect)| {
1277 assert!(!actor_ids_to_collect.is_empty(), "empty actor_ids_to_collect on worker {worker_id} in node_actors {node_actors:?}");
1278 let table_ids_to_sync = if nodes_to_sync_table.contains(worker_id) {
1279 table_ids_to_sync.clone()
1280 } else {
1281 vec![]
1282 };
1283
1284 let node = if let Some((_, worker_state)) = self.workers.get(worker_id)
1285 &&
1286 let WorkerNodeState::Connected { control_stream, .. } = worker_state
1287 {
1288 control_stream
1289 } else {
1290 return Err(anyhow!("unconnected worker node {}", worker_id).into());
1291 };
1292
1293 {
1294 let mutation = mutation.clone();
1295 let barrier = Barrier {
1296 epoch: Some(risingwave_pb::data::Epoch {
1297 curr: barrier_info.curr_epoch(),
1298 prev: barrier_info.prev_epoch(),
1299 }),
1300 mutation: mutation.clone().map(|_| BarrierMutation { mutation }),
1301 tracing_context: TracingContext::from_span(barrier_info.curr_epoch.span())
1302 .to_protobuf(),
1303 kind: barrier_info.kind.to_protobuf() as i32,
1304 };
1305
1306 node.handle
1307 .request_sender
1308 .send(StreamingControlStreamRequest {
1309 request: Some(
1310 streaming_control_stream_request::Request::InjectBarrier(
1311 InjectBarrierRequest {
1312 request_id: Uuid::new_v4().to_string(),
1313 barrier: Some(barrier),
1314 actor_ids_to_collect: actor_ids_to_collect.iter().copied().collect(),
1315 table_ids_to_sync,
1316 partial_graph_id,
1317 actors_to_build: new_actors
1318 .as_mut()
1319 .map(|new_actors| new_actors.remove(worker_id))
1320 .into_iter()
1321 .flatten()
1322 .flatten()
1323 .map(|(fragment_id, (node, actors, initial_subscriber_ids))| {
1324 FragmentBuildActorInfo {
1325 fragment_id,
1326 node: Some(node),
1327 actors: actors
1328 .into_iter()
1329 .map(|(actor, upstreams, dispatchers)| {
1330 BuildActorInfo {
1331 actor_id: actor.actor_id,
1332 fragment_upstreams: upstreams
1333 .into_iter()
1334 .map(|(fragment_id, upstreams)| {
1335 (
1336 fragment_id,
1337 UpstreamActors {
1338 actors: upstreams
1339 .into_values()
1340 .collect(),
1341 },
1342 )
1343 })
1344 .collect(),
1345 dispatchers,
1346 vnode_bitmap: actor.vnode_bitmap.map(|bitmap| bitmap.to_protobuf()),
1347 mview_definition: actor.mview_definition,
1348 expr_context: actor.expr_context,
1349 config_override: actor.config_override.to_string(),
1350 initial_subscriber_ids: initial_subscriber_ids.iter().copied().collect(),
1351 }
1352 })
1353 .collect(),
1354 }
1355 })
1356 .collect(),
1357 },
1358 ),
1359 ),
1360 })
1361 .map_err(|_| {
1362 MetaError::from(anyhow!(
1363 "failed to send request to {} {:?}",
1364 node.worker_id,
1365 node.host
1366 ))
1367 })?;
1368
1369 node_need_collect.insert(*worker_id);
1370 Result::<_, MetaError>::Ok(())
1371 }
1372 })
1373 .inspect_err(|e| {
1374 use risingwave_pb::meta::event_log;
1376 let event = event_log::EventInjectBarrierFail {
1377 prev_epoch: barrier_info.prev_epoch(),
1378 cur_epoch: barrier_info.curr_epoch(),
1379 error: e.to_report_string(),
1380 };
1381 self.env
1382 .event_log_manager_ref()
1383 .add_event_logs(vec![event_log::Event::InjectBarrierFail(event)]);
1384 })?;
1385 Ok(node_need_collect)
1386 }
1387
1388 pub(super) fn add_partial_graph(&mut self, partial_graph_id: PartialGraphId) {
1389 self.connected_workers().for_each(|(_, node)| {
1390 if node
1391 .handle
1392 .request_sender
1393 .send(StreamingControlStreamRequest {
1394 request: Some(
1395 streaming_control_stream_request::Request::CreatePartialGraph(
1396 CreatePartialGraphRequest {
1397 partial_graph_id,
1398 },
1399 ),
1400 ),
1401 }).is_err() {
1402 let (database_id, creating_job_id) = from_partial_graph_id(partial_graph_id);
1403 warn!(%database_id, ?creating_job_id, worker_id = %node.worker_id, "fail to add partial graph to worker")
1404 }
1405 });
1406 }
1407
1408 pub(super) fn remove_partial_graphs(&mut self, partial_graph_ids: Vec<PartialGraphId>) {
1409 self.connected_workers().for_each(|(_, node)| {
1410 if node.handle
1411 .request_sender
1412 .send(StreamingControlStreamRequest {
1413 request: Some(
1414 streaming_control_stream_request::Request::RemovePartialGraph(
1415 RemovePartialGraphRequest {
1416 partial_graph_ids: partial_graph_ids.clone(),
1417 },
1418 ),
1419 ),
1420 })
1421 .is_err()
1422 {
1423 warn!(worker_id = %node.worker_id,node = ?node.host,"failed to send remove partial graph request");
1424 }
1425 })
1426 }
1427
1428 pub(super) fn reset_partial_graphs(
1429 &mut self,
1430 partial_graph_ids: Vec<PartialGraphId>,
1431 ) -> HashSet<WorkerId> {
1432 self.connected_workers()
1433 .filter_map(|(worker_id, node)| {
1434 if node
1435 .handle
1436 .request_sender
1437 .send(StreamingControlStreamRequest {
1438 request: Some(
1439 streaming_control_stream_request::Request::ResetPartialGraphs(
1440 ResetPartialGraphsRequest {
1441 partial_graph_ids: partial_graph_ids.clone(),
1442 },
1443 ),
1444 ),
1445 })
1446 .is_err()
1447 {
1448 warn!(%worker_id, node = ?node.host,"failed to send reset database request");
1449 None
1450 } else {
1451 Some(worker_id)
1452 }
1453 })
1454 .collect()
1455 }
1456}
1457
1458impl GlobalBarrierWorkerContextImpl {
1459 pub(super) async fn new_control_stream_impl(
1460 &self,
1461 node: &WorkerNode,
1462 init_request: &PbInitRequest,
1463 ) -> MetaResult<StreamingControlHandle> {
1464 let handle = self
1465 .env
1466 .stream_client_pool()
1467 .get(node)
1468 .await?
1469 .start_streaming_control(init_request.clone())
1470 .await?;
1471 Ok(handle)
1472 }
1473}
1474
1475pub(super) fn merge_node_rpc_errors<E: Error + Send + Sync + 'static>(
1476 message: &str,
1477 errors: impl IntoIterator<Item = (WorkerId, E)>,
1478) -> MetaError {
1479 use std::fmt::Write;
1480
1481 use risingwave_common::error::error_request_copy;
1482 use risingwave_common::error::tonic::extra::Score;
1483
1484 let errors = errors.into_iter().collect_vec();
1485
1486 if errors.is_empty() {
1487 return anyhow!(message.to_owned()).into();
1488 }
1489
1490 let single_error = |(worker_id, e)| {
1492 anyhow::Error::from(e)
1493 .context(format!("{message}, in worker node {worker_id}"))
1494 .into()
1495 };
1496
1497 if errors.len() == 1 {
1498 return single_error(errors.into_iter().next().unwrap());
1499 }
1500
1501 let max_score = errors
1503 .iter()
1504 .filter_map(|(_, e)| error_request_copy::<Score>(e))
1505 .max();
1506
1507 if let Some(max_score) = max_score {
1508 let mut errors = errors;
1509 let max_scored = errors
1510 .extract_if(.., |(_, e)| {
1511 error_request_copy::<Score>(e) == Some(max_score)
1512 })
1513 .next()
1514 .unwrap();
1515
1516 return single_error(max_scored);
1517 }
1518
1519 let concat: String = errors
1521 .into_iter()
1522 .fold(format!("{message}: "), |mut s, (w, e)| {
1523 write!(&mut s, " in worker node {}, {};", w, e.as_report()).unwrap();
1524 s
1525 });
1526 anyhow!(concat).into()
1527}
1528
1529#[cfg(test)]
1530mod test_partial_graph_id {
1531 use crate::barrier::rpc::{from_partial_graph_id, to_partial_graph_id};
1532
1533 #[test]
1534 fn test_partial_graph_id_conversion() {
1535 let database_id = 233.into();
1536 let job_id = 233.into();
1537 assert_eq!(
1538 (database_id, None),
1539 from_partial_graph_id(to_partial_graph_id(database_id, None))
1540 );
1541 assert_eq!(
1542 (database_id, Some(job_id)),
1543 from_partial_graph_id(to_partial_graph_id(database_id, Some(job_id)))
1544 );
1545 }
1546}