Skip to main content

risingwave_meta/barrier/
rpc.rs

1// Copyright 2024 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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                    // It may happen that the dns information of newly registered worker node
262                    // has not been propagated to the meta node and cause error. Wait for a while and retry
263                    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    /// Clear all nodes and response streams in the manager.
404    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                                                // This arm should be unreachable.
505                                                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    /// Extract information from the loaded runtime barrier worker snapshot info, and inject the initial barrier.
628    #[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                // Actors built during recovery is not treated as newly added actors.
671                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        // Batch-refresh jobs are rendered outside `jobs`, but their upstream tables
743        // must still start with log-store-enabled subscribers after recovery.
744        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        // Use a different `curr_epoch` for each recovery attempt.
803        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![], // no subscribers for backfilling jobs,
1071                )
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(), // no cdc backfill job for
1097                &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        // Recover batch refresh jobs (both idle and consuming snapshot).
1124        // Actors were already rendered by `render_runtime_info()`.
1125        for (job_id, render_result) in batch_refresh {
1126            background_jobs.remove(&job_id);
1127            debug!(%job_id, "recovered batch refresh job");
1128
1129            // Resolve committed epoch from state tables.
1130            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(), // batch refresh has no source splits
1169                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                // Record failure in event log.
1375                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    // Create the error from the single error.
1491    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    // Find the error with the highest score.
1502    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    // The errors do not have scores, so simply concatenate them.
1520    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}