risingwave_meta/barrier/checkpoint/independent_job/
mod.rs1use std::collections::{HashMap, HashSet};
16use std::mem::take;
17
18use risingwave_common::catalog::TableId;
19use risingwave_common::id::JobId;
20use risingwave_common::util::epoch::Epoch;
21use risingwave_pb::id::{FragmentId, PartialGraphId};
22use risingwave_pb::stream_plan::PbSubscriptionUpstreamInfo;
23use risingwave_pb::stream_plan::barrier::PbBarrierKind;
24
25pub(crate) mod batch_refresh_job;
26pub(crate) mod creating_job;
27
28pub(crate) use batch_refresh_job::{
29 BatchRefreshJobCheckpointControl, BatchRefreshJobTriggerContext, BatchRefreshLogicalFragments,
30 BatchRefreshRenderResult,
31};
32pub(crate) use creating_job::CreatingStreamingJobControl;
33
34use crate::barrier::info::BarrierInfo;
35use crate::barrier::notifier::{CollectionNotifier, NotifierStarter};
36use crate::barrier::partial_graph::{CollectedBarrier, PartialGraphManager};
37use crate::barrier::{BackfillProgress, BarrierKind, FragmentBackfillProgress, TracedEpoch};
38use crate::controller::fragment::InflightFragmentInfo;
39
40fn new_fake_barrier(
44 prev_epoch_fake_physical_time: &mut u64,
45 pending_non_checkpoint_barriers: &mut Vec<u64>,
46 kind: PbBarrierKind,
47) -> BarrierInfo {
48 let prev_epoch = TracedEpoch::new(Epoch::from_physical_time(*prev_epoch_fake_physical_time));
49 *prev_epoch_fake_physical_time += 1;
50 let curr_epoch = TracedEpoch::new(Epoch::from_physical_time(*prev_epoch_fake_physical_time));
51 let kind = match kind {
52 PbBarrierKind::Unspecified => unreachable!(),
53 PbBarrierKind::Initial => {
54 assert!(pending_non_checkpoint_barriers.is_empty());
55 BarrierKind::Initial
56 }
57 PbBarrierKind::Barrier => {
58 pending_non_checkpoint_barriers.push(prev_epoch.value().0);
59 BarrierKind::Barrier
60 }
61 PbBarrierKind::Checkpoint => {
62 pending_non_checkpoint_barriers.push(prev_epoch.value().0);
63 BarrierKind::Checkpoint(take(pending_non_checkpoint_barriers))
64 }
65 };
66 BarrierInfo {
67 prev_epoch,
68 curr_epoch,
69 kind,
70 }
71}
72
73pub(crate) enum IndependentCheckpointJob {
78 CreatingStreamingJob(CreatingStreamingJobControl),
79 BatchRefresh(BatchRefreshJobCheckpointControl),
80}
81
82#[derive(Debug, PartialEq, Eq)]
83pub(crate) enum IndependentCheckpointJobStatus {
84 Initial { snapshot_epoch: u64 },
87 Ready,
89}
90
91pub(crate) enum IndependentCheckpointJobControl {
93 Running {
94 status: IndependentCheckpointJobStatus,
95 job_id: JobId,
96 partial_graph_id: PartialGraphId,
97 job: IndependentCheckpointJob,
98 },
99 Resetting {
100 pinned_upstream_tables: HashSet<TableId>,
103 subscriptions_to_drop: Vec<PbSubscriptionUpstreamInfo>,
104 notifiers: Vec<CollectionNotifier>,
105 },
106}
107
108impl IndependentCheckpointJob {
109 fn can_drop_independently(&self) -> bool {
110 match self {
111 Self::CreatingStreamingJob(j) => j.can_drop_independently(),
112 Self::BatchRefresh(_) => true,
113 }
114 }
115
116 fn pinned_upstream_tables(&self) -> &HashSet<TableId> {
117 match self {
118 Self::CreatingStreamingJob(j) => j.pinned_upstream_tables(),
119 Self::BatchRefresh(j) => j.pinned_upstream_tables(),
120 }
121 }
122}
123
124impl IndependentCheckpointJobStatus {
125 fn on_upstream_database_ack_completed(&mut self, committed_epoch: u64) {
126 if let Self::Initial { snapshot_epoch } = self
127 && committed_epoch >= *snapshot_epoch
128 {
129 *self = Self::Ready;
130 }
131 }
132}
133
134impl IndependentCheckpointJobControl {
135 pub(crate) fn creating_streaming_job(
136 job_id: JobId,
137 partial_graph_id: PartialGraphId,
138 status: IndependentCheckpointJobStatus,
139 job: CreatingStreamingJobControl,
140 ) -> Self {
141 Self::Running {
142 status,
143 job_id,
144 partial_graph_id,
145 job: IndependentCheckpointJob::CreatingStreamingJob(job),
146 }
147 }
148
149 pub(crate) fn batch_refresh(
150 job_id: JobId,
151 partial_graph_id: PartialGraphId,
152 status: IndependentCheckpointJobStatus,
153 job: BatchRefreshJobCheckpointControl,
154 ) -> Self {
155 Self::Running {
156 status,
157 job_id,
158 partial_graph_id,
159 job: IndependentCheckpointJob::BatchRefresh(job),
160 }
161 }
162
163 pub(crate) fn running(&self) -> Option<&IndependentCheckpointJob> {
164 match self {
165 Self::Running { job, .. } => Some(job),
166 Self::Resetting { .. } => None,
167 }
168 }
169
170 pub(crate) fn running_mut(&mut self) -> Option<&mut IndependentCheckpointJob> {
171 match self {
172 Self::Running { job, .. } => Some(job),
173 Self::Resetting { .. } => None,
174 }
175 }
176
177 pub(crate) fn ready_mut(&mut self) -> Option<&mut IndependentCheckpointJob> {
178 match self {
179 Self::Running {
180 status: IndependentCheckpointJobStatus::Ready,
181 job,
182 ..
183 } => Some(job),
184 Self::Running {
185 status: IndependentCheckpointJobStatus::Initial { .. },
186 ..
187 }
188 | Self::Resetting { .. } => None,
189 }
190 }
191
192 pub(crate) fn on_upstream_database_ack_completed(&mut self, committed_epoch: u64) {
193 if let Self::Running { status, .. } = self {
194 status.on_upstream_database_ack_completed(committed_epoch);
195 }
196 }
197
198 pub(crate) fn gen_backfill_progress(&self) -> Option<BackfillProgress> {
199 match self.running()? {
200 IndependentCheckpointJob::CreatingStreamingJob(j) => Some(j.gen_backfill_progress()),
201 IndependentCheckpointJob::BatchRefresh(j) => j.gen_backfill_progress(),
202 }
203 }
204
205 pub(crate) fn collect(&mut self, collected_barrier: CollectedBarrier<'_>) -> bool {
207 let job = self
208 .running_mut()
209 .expect("barriers should only be collected from a running partial graph");
210 match job {
211 IndependentCheckpointJob::CreatingStreamingJob(j) => j.collect(collected_barrier),
212 IndependentCheckpointJob::BatchRefresh(j) => j.collect(collected_barrier),
213 }
214 }
215
216 pub(crate) fn gen_fragment_backfill_progress(&self) -> Vec<FragmentBackfillProgress> {
217 match self.running() {
218 Some(IndependentCheckpointJob::CreatingStreamingJob(j)) => {
219 j.gen_fragment_backfill_progress()
220 }
221 Some(IndependentCheckpointJob::BatchRefresh(j)) => j.gen_fragment_backfill_progress(),
222 None => vec![],
223 }
224 }
225
226 pub(crate) fn pinned_upstream_tables(&self) -> &HashSet<TableId> {
227 match self {
228 Self::Running { job, .. } => job.pinned_upstream_tables(),
229 Self::Resetting {
230 pinned_upstream_tables,
231 ..
232 } => pinned_upstream_tables,
233 }
234 }
235
236 pub(crate) fn fragment_infos(&self) -> Option<&HashMap<FragmentId, InflightFragmentInfo>> {
237 match self.running()? {
238 IndependentCheckpointJob::CreatingStreamingJob(j) => j.fragment_infos(),
239 IndependentCheckpointJob::BatchRefresh(j) => j.fragment_infos(),
240 }
241 }
242
243 pub(crate) fn ack_completed(
244 &mut self,
245 partial_graph_manager: &mut PartialGraphManager,
246 epoch: u64,
247 ) {
248 match self {
249 Self::Running {
250 status: IndependentCheckpointJobStatus::Ready,
251 job: IndependentCheckpointJob::CreatingStreamingJob(j),
252 ..
253 } => j.ack_completed(partial_graph_manager, epoch),
254 Self::Running {
255 status: IndependentCheckpointJobStatus::Ready,
256 job: IndependentCheckpointJob::BatchRefresh(j),
257 ..
258 } => j.ack_completed(partial_graph_manager, epoch),
259 Self::Running {
260 status: IndependentCheckpointJobStatus::Initial { .. },
261 ..
262 } => {
263 panic!("an initial job should transition to ready before completing a barrier")
264 }
265 Self::Resetting { .. } => {
266 }
269 }
270 }
271
272 pub(crate) fn on_partial_graph_reset(self) -> Vec<PbSubscriptionUpstreamInfo> {
273 match self {
274 Self::Resetting {
275 subscriptions_to_drop,
276 notifiers,
277 ..
278 } => {
279 for notifier in notifiers {
280 notifier.notify_collected();
281 }
282 subscriptions_to_drop
283 }
284 Self::Running { .. } => {
285 panic!("should be resetting when receiving reset partial graph resp")
286 }
287 }
288 }
289
290 pub(crate) fn drop(
291 &mut self,
292 notifier: Option<&mut NotifierStarter>,
293 partial_graph_manager: &mut PartialGraphManager,
294 ) -> bool {
295 match self {
296 Self::Resetting { notifiers, .. } => {
297 notifiers.extend(notifier.map(NotifierStarter::add_notify));
298 true
299 }
300 Self::Running { job, .. } if !job.can_drop_independently() => false,
301 Self::Running {
302 job_id,
303 partial_graph_id,
304 job,
305 ..
306 } => {
307 let subscriptions_to_drop = job
308 .pinned_upstream_tables()
309 .iter()
310 .map(|upstream_mv_table_id| PbSubscriptionUpstreamInfo {
311 subscriber_id: job_id.as_subscriber_id(),
312 upstream_mv_table_id: *upstream_mv_table_id,
313 })
314 .collect();
315 let pinned_upstream_tables = job.pinned_upstream_tables().clone();
317 partial_graph_manager.reset_partial_graphs([*partial_graph_id]);
318 *self = Self::Resetting {
319 pinned_upstream_tables,
320 subscriptions_to_drop,
321 notifiers: notifier
322 .map(NotifierStarter::add_notify)
323 .into_iter()
324 .collect(),
325 };
326 true
327 }
328 }
329 }
330
331 pub(crate) fn reset(self) -> bool {
336 match self {
337 Self::Running { .. } => false,
340 Self::Resetting { notifiers, .. } => {
341 for notifier in notifiers {
342 notifier.notify_collected();
343 }
344 true
345 }
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn test_resetting_has_shared_inactive_behavior_and_keeps_pin() {
356 let pinned_upstream_tables = HashSet::from([TableId::new(7)]);
357 let job = IndependentCheckpointJobControl::Resetting {
358 pinned_upstream_tables: pinned_upstream_tables.clone(),
359 subscriptions_to_drop: vec![],
360 notifiers: vec![],
361 };
362
363 assert!(job.running().is_none());
364 assert!(job.gen_backfill_progress().is_none());
365 assert!(job.gen_fragment_backfill_progress().is_empty());
366 assert_eq!(job.pinned_upstream_tables(), &pinned_upstream_tables);
367 assert!(job.fragment_infos().is_none());
368 assert!(job.reset());
369 }
370
371 #[test]
372 fn test_reset_completion_returns_subscriptions_to_drop() {
373 let subscriptions_to_drop = vec![PbSubscriptionUpstreamInfo {
374 subscriber_id: JobId::new(8).as_subscriber_id(),
375 upstream_mv_table_id: TableId::new(7),
376 }];
377 let job = IndependentCheckpointJobControl::Resetting {
378 pinned_upstream_tables: HashSet::from([TableId::new(7)]),
379 subscriptions_to_drop: subscriptions_to_drop.clone(),
380 notifiers: vec![],
381 };
382
383 assert_eq!(job.on_partial_graph_reset(), subscriptions_to_drop);
384 }
385
386 #[test]
387 fn test_initial_status_transitions_on_upstream_database_ack() {
388 let mut status = IndependentCheckpointJobStatus::Initial { snapshot_epoch: 10 };
389
390 status.on_upstream_database_ack_completed(9);
391 assert_eq!(
392 status,
393 IndependentCheckpointJobStatus::Initial { snapshot_epoch: 10 }
394 );
395 status.on_upstream_database_ack_completed(10);
396 assert_eq!(status, IndependentCheckpointJobStatus::Ready);
397 status.on_upstream_database_ack_completed(11);
398 assert_eq!(status, IndependentCheckpointJobStatus::Ready);
399 }
400}