risingwave_meta/barrier/
complete_task.rs1use std::collections::HashMap;
16use std::future::{Future, pending};
17use std::mem::replace;
18use std::sync::Arc;
19
20use anyhow::Context;
21use futures::future::try_join_all;
22use risingwave_common::id::JobId;
23use risingwave_common::must_match;
24use risingwave_common::util::deployment::Deployment;
25use risingwave_pb::hummock::HummockVersionStats;
26use risingwave_pb::id::{DatabaseId, PartialGraphId};
27use risingwave_pb::stream_service::barrier_complete_response::{
28 PbIcebergPkIndexSinkMetadata, PbListFinishedSource, PbLoadFinishedSource,
29};
30use tokio::task::JoinHandle;
31
32use crate::barrier::checkpoint::CheckpointControl;
33use crate::barrier::context::GlobalBarrierWorkerContext;
34use crate::barrier::info::BarrierInfo;
35use crate::barrier::partial_graph::{PartialGraphBarrierInfo, PartialGraphManager};
36use crate::barrier::progress::TrackingJob;
37use crate::barrier::rpc::from_partial_graph_id;
38use crate::barrier::schedule::PeriodicBarriers;
39use crate::hummock::CommitEpochInfo;
40use crate::manager::MetaSrvEnv;
41use crate::rpc::metrics::GLOBAL_META_METRICS;
42use crate::{MetaError, MetaResult};
43
44pub(super) enum CompletingTask {
45 None,
46 Completing {
47 #[expect(clippy::type_complexity)]
48 epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)>,
50
51 join_handle: JoinHandle<MetaResult<HummockVersionStats>>,
55 },
56 #[expect(dead_code)]
57 Err(MetaError),
58}
59
60#[derive(Default)]
62pub(super) struct CompleteBarrierTask {
63 pub(super) commit_info: CommitEpochInfo,
64 pub(super) finished_jobs: Vec<TrackingJob>,
65 pub(super) finished_cdc_table_backfill: Vec<JobId>,
66 pub(super) epoch_infos: HashMap<PartialGraphId, PartialGraphBarrierInfo>,
68 pub(super) list_finished_source_ids: Vec<PbListFinishedSource>,
70 pub(super) load_finished_source_ids: Vec<PbLoadFinishedSource>,
72 pub(super) refresh_finished_table_job_ids: Vec<JobId>,
74 pub(super) iceberg_pk_index_sink_metadata: Vec<PbIcebergPkIndexSinkMetadata>,
76}
77
78impl CompleteBarrierTask {
79 #[expect(clippy::type_complexity)]
80 pub(super) fn epochs_to_ack(&self) -> HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)> {
81 let mut epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)> =
82 HashMap::new();
83 for (partial_graph_id, info) in &self.epoch_infos {
84 let (database_id, creating_job_id) = from_partial_graph_id(*partial_graph_id);
85 let epoch = info.barrier_info.prev_epoch();
86 let (database, jobs) = epochs_to_ack.entry(database_id).or_default();
87 if let Some(job_id) = creating_job_id {
88 jobs.push((job_id, epoch));
89 } else {
90 *database = Some(epoch);
91 }
92 }
93 epochs_to_ack
94 }
95}
96
97impl CompleteBarrierTask {
98 pub(super) async fn complete_barrier(
99 self,
100 context: &impl GlobalBarrierWorkerContext,
101 env: MetaSrvEnv,
102 ) -> MetaResult<HummockVersionStats> {
103 let mut notifiers = Vec::new();
104 let result: MetaResult<HummockVersionStats> = try {
105 let wait_commit_timer = GLOBAL_META_METRICS
106 .barrier_wait_commit_latency
107 .start_timer();
108
109 let mut iceberg_pk_index_commit_sink_ids = Vec::new();
114 if !self.iceberg_pk_index_sink_metadata.is_empty() {
115 let res = context
116 .pre_commit_iceberg_pk_index_sink_metadata(self.iceberg_pk_index_sink_metadata)
117 .await?;
118 iceberg_pk_index_commit_sink_ids = res;
119 }
120
121 let version_stats = context.commit_epoch(self.commit_info).await?;
122
123 if !iceberg_pk_index_commit_sink_ids.is_empty() {
124 context
125 .commit_iceberg_pk_index_sink_metadata(iceberg_pk_index_commit_sink_ids)
126 .await?;
127 }
128
129 if !self.list_finished_source_ids.is_empty() {
136 context
137 .handle_list_finished_source_ids(self.list_finished_source_ids.clone())
138 .await?;
139 }
140
141 if !self.load_finished_source_ids.is_empty() {
144 context
145 .handle_load_finished_source_ids(self.load_finished_source_ids.clone())
146 .await?;
147 }
148
149 if !self.refresh_finished_table_job_ids.is_empty() {
151 context
152 .handle_refresh_finished_table_ids(self.refresh_finished_table_job_ids.clone())
153 .await?;
154 }
155
156 for (partial_graph_id, info) in self.epoch_infos {
157 let (database_id, job_id) = from_partial_graph_id(partial_graph_id);
158 let command_name = info.post_collect_command.command_name().to_owned();
159 let elapsed_secs = info.elapsed_secs();
160 notifiers.extend(info.notifiers);
161 context
162 .post_collect_command(info.post_collect_command)
163 .await?;
164 if job_id.is_none() {
165 Self::report_complete_event(
166 &env,
167 database_id,
168 elapsed_secs,
169 &info.barrier_info,
170 command_name,
171 );
172 }
173 }
174
175 wait_commit_timer.observe_duration();
176 version_stats
177 };
178
179 let version_stats = {
180 let version_stats = match result {
181 Ok(version_stats) => version_stats,
182 Err(e) => {
183 for notifier in notifiers {
184 notifier.notify_collection_failed(e.clone());
185 }
186 return Err(e);
187 }
188 };
189 notifiers.into_iter().for_each(|notifier| {
190 notifier.notify_collected();
191 });
192 try_join_all(
193 self.finished_jobs
194 .into_iter()
195 .map(|finished_job| context.finish_creating_job(finished_job)),
196 )
197 .await?;
198 try_join_all(
199 self.finished_cdc_table_backfill
200 .into_iter()
201 .map(|job_id| context.finish_cdc_table_backfill(job_id)),
202 )
203 .await?;
204 version_stats
205 };
206
207 Ok(version_stats)
208 }
209}
210
211impl CompleteBarrierTask {
212 fn report_complete_event(
213 env: &MetaSrvEnv,
214 database_id: DatabaseId,
215 duration_sec: f64,
216 barrier_info: &BarrierInfo,
217 command: String,
218 ) {
219 use risingwave_pb::meta::event_log;
221 let event = event_log::EventBarrierComplete {
222 prev_epoch: barrier_info.prev_epoch(),
223 cur_epoch: barrier_info.curr_epoch(),
224 duration_sec,
225 command,
226 barrier_kind: barrier_info.kind.as_str_name().to_owned(),
227 database_id: database_id.as_raw_id(),
228 };
229 if cfg!(debug_assertions) || Deployment::current().is_ci() {
230 if duration_sec > 5.0 {
232 tracing::warn!(event = ?event,"high barrier latency observed!")
233 }
234 }
235 env.event_log_manager_ref()
236 .add_event_logs(vec![event_log::Event::BarrierComplete(event)]);
237 }
238}
239
240pub(super) struct BarrierCompleteOutput {
241 #[expect(clippy::type_complexity)]
242 pub epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)>,
244 pub hummock_version_stats: HummockVersionStats,
245}
246
247impl CompletingTask {
248 pub(super) fn next_completed_barrier<'a>(
249 &'a mut self,
250 periodic_barriers: &mut PeriodicBarriers,
251 checkpoint_control: &mut CheckpointControl,
252 partial_graph_manager: &mut PartialGraphManager,
253 context: &Arc<impl GlobalBarrierWorkerContext>,
254 env: &MetaSrvEnv,
255 ) -> impl Future<Output = MetaResult<BarrierCompleteOutput>> + 'a {
256 if let CompletingTask::None = self
259 && let Some(task) = checkpoint_control
260 .next_complete_barrier_task(periodic_barriers, partial_graph_manager)
261 {
262 {
263 let epochs_to_ack = task.epochs_to_ack();
264 let context = context.clone();
265 let await_tree_reg = env.await_tree_reg().clone();
266 let env = env.clone();
267
268 let fut = async move { task.complete_barrier(&*context, env).await };
269 let fut = await_tree_reg
270 .register_derived_root("Barrier Completion Task")
271 .instrument(fut);
272 let join_handle = tokio::spawn(fut);
273
274 *self = CompletingTask::Completing {
275 epochs_to_ack,
276 join_handle,
277 };
278 }
279 }
280
281 async move {
282 if !matches!(self, CompletingTask::Completing { .. }) {
283 return pending().await;
284 };
285 self.next_completed_barrier_inner().await
286 }
287 }
288
289 #[await_tree::instrument]
290 pub(super) async fn wait_completing_task(
291 &mut self,
292 ) -> MetaResult<Option<BarrierCompleteOutput>> {
293 match self {
294 CompletingTask::None => Ok(None),
295 CompletingTask::Completing { .. } => {
296 self.next_completed_barrier_inner().await.map(Some)
297 }
298 CompletingTask::Err(_) => {
299 unreachable!("should not be called on previous err")
300 }
301 }
302 }
303
304 async fn next_completed_barrier_inner(&mut self) -> MetaResult<BarrierCompleteOutput> {
305 let CompletingTask::Completing { join_handle, .. } = self else {
306 unreachable!()
307 };
308
309 {
310 {
311 let join_result: MetaResult<_> = try {
312 join_handle
313 .await
314 .context("failed to join completing command")
315 .map_err(MetaError::from)??
316 };
317 let next_completing_command_status = if let Err(e) = &join_result {
320 CompletingTask::Err(e.clone())
321 } else {
322 CompletingTask::None
323 };
324 let completed_command = replace(self, next_completing_command_status);
325 let hummock_version_stats = join_result?;
326
327 must_match!(completed_command, CompletingTask::Completing {
328 epochs_to_ack,
329 ..
330 } => {
331 Ok(BarrierCompleteOutput {
332 epochs_to_ack,
333 hummock_version_stats,
334 })
335 })
336 }
337 }
338 }
339}