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 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::manager::iceberg_pk_index_sink::IcebergPkIndexPreCommitMetadata;
42use crate::rpc::metrics::GLOBAL_META_METRICS;
43use crate::{MetaError, MetaResult};
44
45pub(super) enum CompletingTask {
46 None,
47 Completing {
48 #[expect(clippy::type_complexity)]
49 epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)>,
51
52 join_handle: JoinHandle<MetaResult<HummockVersionStats>>,
56 },
57 #[expect(dead_code)]
58 Err(MetaError),
59}
60
61#[derive(Default)]
63pub(super) struct CompleteBarrierTask {
64 pub(super) commit_info: CommitEpochInfo,
65 pub(super) finished_jobs: Vec<TrackingJob>,
66 pub(super) finished_cdc_table_backfill: Vec<JobId>,
67 pub(super) epoch_infos: HashMap<PartialGraphId, PartialGraphBarrierInfo>,
69 pub(super) list_finished_source_ids: Vec<PbListFinishedSource>,
71 pub(super) load_finished_source_ids: Vec<PbLoadFinishedSource>,
73 pub(super) refresh_finished_table_job_ids: Vec<JobId>,
75 pub(super) iceberg_pk_index_pre_commit_metadata: Vec<IcebergPkIndexPreCommitMetadata>,
78}
79
80impl CompleteBarrierTask {
81 #[expect(clippy::type_complexity)]
82 pub(super) fn epochs_to_ack(&self) -> HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)> {
83 let mut epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)> =
84 HashMap::new();
85 for (partial_graph_id, info) in &self.epoch_infos {
86 let (database_id, creating_job_id) = from_partial_graph_id(*partial_graph_id);
87 let epoch = info.barrier_info.prev_epoch();
88 let (database, jobs) = epochs_to_ack.entry(database_id).or_default();
89 if let Some(job_id) = creating_job_id {
90 jobs.push((job_id, epoch));
91 } else {
92 *database = Some(epoch);
93 }
94 }
95 epochs_to_ack
96 }
97}
98
99impl CompleteBarrierTask {
100 pub(super) async fn complete_barrier(
101 self,
102 context: &impl GlobalBarrierWorkerContext,
103 env: MetaSrvEnv,
104 ) -> MetaResult<HummockVersionStats> {
105 let mut notifiers = Vec::new();
106 let result: MetaResult<HummockVersionStats> = try {
107 let wait_commit_timer = GLOBAL_META_METRICS
108 .barrier_wait_commit_latency
109 .start_timer();
110
111 let mut iceberg_pk_index_commit_sink_ids = Vec::new();
118 if !self.iceberg_pk_index_pre_commit_metadata.is_empty() {
119 let res = context
120 .pre_commit_iceberg_pk_index_sink_metadata(
121 self.iceberg_pk_index_pre_commit_metadata,
122 )
123 .await?;
124 iceberg_pk_index_commit_sink_ids = res;
125 }
126
127 let version_stats = context.commit_epoch(self.commit_info).await?;
128
129 if !iceberg_pk_index_commit_sink_ids.is_empty() {
130 context
131 .commit_iceberg_pk_index_sink_metadata(iceberg_pk_index_commit_sink_ids)
132 .await?;
133 }
134 let epochs = self
135 .epoch_infos
136 .iter()
137 .map(|(id, info)| (*id, info.barrier_info.prev_epoch()));
138 context.advance_iceberg_pk_index_sink_committed_epochs(epochs);
139
140 if !self.list_finished_source_ids.is_empty() {
147 context
148 .handle_list_finished_source_ids(self.list_finished_source_ids.clone())
149 .await?;
150 }
151
152 if !self.load_finished_source_ids.is_empty() {
155 context
156 .handle_load_finished_source_ids(self.load_finished_source_ids.clone())
157 .await?;
158 }
159
160 if !self.refresh_finished_table_job_ids.is_empty() {
162 context
163 .handle_refresh_finished_table_ids(self.refresh_finished_table_job_ids.clone())
164 .await?;
165 }
166
167 for (partial_graph_id, info) in self.epoch_infos {
168 let (database_id, job_id) = from_partial_graph_id(partial_graph_id);
169 let command_name = info.post_collect_command.command_name().to_owned();
170 let elapsed_secs = info.elapsed_secs();
171 notifiers.extend(info.notifier);
172 context
173 .post_collect_command(info.post_collect_command)
174 .await?;
175 if job_id.is_none() {
176 Self::report_complete_event(
177 &env,
178 database_id,
179 elapsed_secs,
180 &info.barrier_info,
181 command_name,
182 );
183 }
184 }
185
186 wait_commit_timer.observe_duration();
187 version_stats
188 };
189
190 let version_stats = {
191 let version_stats = match result {
192 Ok(version_stats) => version_stats,
193 Err(e) => {
194 for notifier in notifiers {
195 notifier.notify_collection_failed(e.clone());
196 }
197 return Err(e);
198 }
199 };
200 notifiers.into_iter().for_each(|notifier| {
201 notifier.notify_collected();
202 });
203 try_join_all(
204 self.finished_jobs
205 .into_iter()
206 .map(|finished_job| context.finish_creating_job(finished_job)),
207 )
208 .await?;
209 try_join_all(
210 self.finished_cdc_table_backfill
211 .into_iter()
212 .map(|job_id| context.finish_cdc_table_backfill(job_id)),
213 )
214 .await?;
215 version_stats
216 };
217
218 Ok(version_stats)
219 }
220}
221
222impl CompleteBarrierTask {
223 fn report_complete_event(
224 env: &MetaSrvEnv,
225 database_id: DatabaseId,
226 duration_sec: f64,
227 barrier_info: &BarrierInfo,
228 command: String,
229 ) {
230 use risingwave_pb::meta::event_log;
232 let event = event_log::EventBarrierComplete {
233 prev_epoch: barrier_info.prev_epoch(),
234 cur_epoch: barrier_info.curr_epoch(),
235 duration_sec,
236 command,
237 barrier_kind: barrier_info.kind.as_str_name().to_owned(),
238 database_id: database_id.as_raw_id(),
239 };
240 if cfg!(debug_assertions) || Deployment::current().is_ci() {
241 if duration_sec > 5.0 {
243 tracing::warn!(event = ?event,"high barrier latency observed!")
244 }
245 }
246 env.event_log_manager_ref()
247 .add_event_logs(vec![event_log::Event::BarrierComplete(event)]);
248 }
249}
250
251pub(super) struct BarrierCompleteOutput {
252 #[expect(clippy::type_complexity)]
253 pub epochs_to_ack: HashMap<DatabaseId, (Option<u64>, Vec<(JobId, u64)>)>,
255 pub hummock_version_stats: HummockVersionStats,
256}
257
258impl CompletingTask {
259 pub(super) fn next_completed_barrier<'a>(
260 &'a mut self,
261 periodic_barriers: &mut PeriodicBarriers,
262 checkpoint_control: &mut CheckpointControl,
263 partial_graph_manager: &mut PartialGraphManager,
264 context: &Arc<impl GlobalBarrierWorkerContext>,
265 env: &MetaSrvEnv,
266 ) -> impl Future<Output = MetaResult<BarrierCompleteOutput>> + 'a {
267 if let CompletingTask::None = self
270 && let Some(task) = checkpoint_control
271 .next_complete_barrier_task(periodic_barriers, partial_graph_manager)
272 {
273 {
274 let epochs_to_ack = task.epochs_to_ack();
275 let context = context.clone();
276 let await_tree_reg = env.await_tree_reg().clone();
277 let env = env.clone();
278
279 let fut = async move { task.complete_barrier(&*context, env).await };
280 let fut = await_tree_reg
281 .register_derived_root("Barrier Completion Task")
282 .instrument(fut);
283 let join_handle = tokio::spawn(fut);
284
285 *self = CompletingTask::Completing {
286 epochs_to_ack,
287 join_handle,
288 };
289 }
290 }
291
292 async move {
293 if !matches!(self, CompletingTask::Completing { .. }) {
294 return pending().await;
295 };
296 self.next_completed_barrier_inner().await
297 }
298 }
299
300 #[await_tree::instrument]
301 pub(super) async fn wait_completing_task(
302 &mut self,
303 ) -> MetaResult<Option<BarrierCompleteOutput>> {
304 match self {
305 CompletingTask::None => Ok(None),
306 CompletingTask::Completing { .. } => {
307 self.next_completed_barrier_inner().await.map(Some)
308 }
309 CompletingTask::Err(_) => {
310 unreachable!("should not be called on previous err")
311 }
312 }
313 }
314
315 async fn next_completed_barrier_inner(&mut self) -> MetaResult<BarrierCompleteOutput> {
316 let CompletingTask::Completing { join_handle, .. } = self else {
317 unreachable!()
318 };
319
320 {
321 {
322 let join_result: MetaResult<_> = try {
323 join_handle
324 .await
325 .context("failed to join completing command")
326 .map_err(MetaError::from)??
327 };
328 let next_completing_command_status = if let Err(e) = &join_result {
331 CompletingTask::Err(e.clone())
332 } else {
333 CompletingTask::None
334 };
335 let completed_command = replace(self, next_completing_command_status);
336 let hummock_version_stats = join_result?;
337
338 must_match!(completed_command, CompletingTask::Completing {
339 epochs_to_ack,
340 ..
341 } => {
342 Ok(BarrierCompleteOutput {
343 epochs_to_ack,
344 hummock_version_stats,
345 })
346 })
347 }
348 }
349 }
350}