risingwave_stream/executor/mview/
refresh_progress_table.rs1use std::collections::{HashMap, HashSet};
23use std::sync::Arc;
24
25use risingwave_common::bitmap::Bitmap;
26use risingwave_common::hash::VirtualNode;
27use risingwave_common::row::{OwnedRow, Row};
28use risingwave_common::types::{DataType, ScalarImpl, ScalarRefImpl};
29use risingwave_common::util::epoch::EpochPair;
30use risingwave_storage::StateStore;
31
32use crate::common::table::state_table::StateTablePostCommit;
33use crate::executor::StreamExecutorResult;
34use crate::executor::prelude::StateTable;
35
36pub struct RefreshProgressTable<S: StateStore> {
42 pub state_table: StateTable<S>,
44 cache: HashMap<VirtualNode, RefreshProgressEntry>,
46 pk_len: usize,
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct RefreshProgressEntry {
53 pub vnode: VirtualNode,
54 pub current_pos: Option<OwnedRow>,
55 pub is_completed: bool,
56 pub processed_rows: u64,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[repr(i32)]
63pub enum ProgressRefreshStage {
64 Normal = 0,
65 Refreshing = 1,
66 Merging = 2,
67 Cleanup = 3,
68}
69
70impl From<i32> for ProgressRefreshStage {
71 fn from(value: i32) -> Self {
72 match value {
73 0 => ProgressRefreshStage::Normal,
74 1 => ProgressRefreshStage::Refreshing,
75 2 => ProgressRefreshStage::Merging,
76 3 => ProgressRefreshStage::Cleanup,
77 _ => unreachable!(),
78 }
79 }
80}
81
82impl<S: StateStore> RefreshProgressTable<S> {
83 pub fn new(state_table: StateTable<S>, pk_len: usize) -> Self {
85 Self {
86 state_table,
87 cache: HashMap::new(),
88 pk_len,
89 }
90 }
91
92 pub async fn recover(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
94 self.state_table.init_epoch(epoch).await?;
95
96 let mut loaded_count = 0;
98
99 for vnode in self.state_table.vnodes().iter_ones() {
100 let row = self
101 .state_table
102 .get_row(OwnedRow::new(vec![
103 VirtualNode::from_index(vnode).to_datum(),
104 ]))
105 .await?;
106 if row.is_some()
107 && let Some(entry) = self.parse_row_to_entry(&row, self.pk_len)
108 {
109 self.cache.insert(entry.vnode, entry);
110 loaded_count += 1;
111 }
112 }
113
114 tracing::debug!(
115 loaded_count,
116 "Loading existing progress entries during initialization"
117 );
118
119 tracing::info!(
120 loaded_entries = self.cache.len(),
121 "Initialized refresh progress table"
122 );
123
124 Ok(())
125 }
126
127 pub fn set_progress(
129 &mut self,
130 vnode: VirtualNode,
131 current_pos: Option<OwnedRow>,
132 is_completed: bool,
133 processed_rows: u64,
134 ) -> StreamExecutorResult<()> {
135 let entry = RefreshProgressEntry {
136 vnode,
137 current_pos,
138 is_completed,
139 processed_rows,
140 };
141
142 self.cache.insert(vnode, entry.clone());
144
145 let row = self.entry_to_row(&entry, self.pk_len);
147 self.state_table.insert(&row);
148
149 Ok(())
150 }
151
152 pub fn get_progress(&self, vnode: VirtualNode) -> Option<&RefreshProgressEntry> {
154 self.cache.get(&vnode)
155 }
156
157 pub fn get_completed_vnodes(&self) -> HashSet<VirtualNode> {
159 self.cache
160 .iter()
161 .filter(|(_, entry)| entry.is_completed)
162 .map(|(&vnode, _)| vnode)
163 .collect()
164 }
165
166 pub fn get_vnodes_in_stage(&self, _stage: ProgressRefreshStage) -> Vec<VirtualNode> {
169 tracing::warn!(
170 "get_vnodes_in_stage called on simplified progress table - stage info no longer stored"
171 );
172 Vec::new()
173 }
174
175 pub fn clear_progress(&mut self, vnode: VirtualNode) -> StreamExecutorResult<()> {
177 if let Some(entry) = self.cache.remove(&vnode) {
178 let row = self.entry_to_row(&entry, self.pk_len);
179 self.state_table.delete(&row);
180 }
181
182 Ok(())
183 }
184
185 pub fn clear_all_progress(&mut self) -> StreamExecutorResult<()> {
187 for vnode in self.cache.keys().copied().collect::<Vec<_>>() {
188 self.clear_progress(vnode)?;
189 }
190 Ok(())
191 }
192
193 pub fn get_total_processed_rows(&self) -> u64 {
195 self.cache.values().map(|entry| entry.processed_rows).sum()
196 }
197
198 pub fn get_progress_stats(&self) -> RefreshProgressStats {
200 let total_vnodes = self.cache.len();
201 let completed_vnodes = self.get_completed_vnodes().len();
202 let total_processed_rows = self.get_total_processed_rows();
203
204 RefreshProgressStats {
205 total_vnodes,
206 completed_vnodes,
207 total_processed_rows,
208 }
209 }
210
211 pub async fn commit(
213 &mut self,
214 epoch: EpochPair,
215 ) -> StreamExecutorResult<StateTablePostCommit<'_, S>> {
216 self.state_table.commit(epoch).await
217 }
218
219 fn entry_to_row(&self, entry: &RefreshProgressEntry, pk_len: usize) -> OwnedRow {
222 let mut row_data = vec![entry.vnode.to_datum()];
223
224 if let Some(ref pos) = entry.current_pos {
226 row_data.extend(pos.iter().map(|d| d.map(|s| s.into_scalar_impl())));
227 } else {
228 for _ in 0..pk_len {
230 row_data.push(None);
231 }
232 }
233
234 row_data.push(Some(ScalarImpl::Bool(entry.is_completed)));
236 row_data.push(Some(ScalarImpl::Int64(entry.processed_rows as i64)));
237
238 OwnedRow::new(row_data)
239 }
240
241 fn parse_row_to_entry(&self, row: &impl Row, pk_len: usize) -> Option<RefreshProgressEntry> {
245 let datums = row.iter().collect::<Vec<_>>();
246 let expected_len = 1 + pk_len + 2; if datums.len() != expected_len {
249 tracing::warn!(
250 "Row length mismatch: got {}, expected {} (pk_len={}), row: {:?}",
251 datums.len(),
252 expected_len,
253 pk_len,
254 row,
255 );
256 return None;
257 }
258
259 let vnode = VirtualNode::from_datum_ref(datums[0]);
261
262 let current_pos = if pk_len > 0 {
264 let pos_datums: Vec<_> = datums[1..1 + pk_len]
265 .iter()
266 .map(|d| d.map(|s| s.into_scalar_impl()))
267 .collect();
268 if pos_datums.iter().all(|d| d.is_none()) {
270 None
271 } else {
272 Some(OwnedRow::new(pos_datums))
273 }
274 } else {
275 None
276 };
277
278 let is_completed = match datums[1 + pk_len]? {
280 ScalarRefImpl::Bool(val) => val,
281 _ => return None,
282 };
283
284 let processed_rows = match datums[1 + pk_len + 1]? {
286 ScalarRefImpl::Int64(val) => val as u64,
287 _ => return None,
288 };
289
290 Some(RefreshProgressEntry {
291 vnode,
292 current_pos,
293 is_completed,
294 processed_rows,
295 })
296 }
297
298 pub fn expected_schema(pk_data_types: &[DataType]) -> Vec<DataType> {
301 let mut schema = vec![VirtualNode::RW_TYPE]; schema.extend(pk_data_types.iter().cloned()); schema.push(DataType::Boolean); schema.push(DataType::Int64); schema
306 }
307
308 pub fn column_names(pk_column_names: &[&str]) -> Vec<String> {
311 let mut names = vec!["vnode".to_owned()];
312 for pk_name in pk_column_names {
313 names.push(format!("pos_{}", pk_name));
314 }
315 names.push("is_completed".to_owned());
316 names.push("processed_rows".to_owned());
317 names
318 }
319
320 pub fn vnodes(&self) -> &Arc<Bitmap> {
321 self.state_table.vnodes()
322 }
323}
324
325#[derive(Debug, Clone)]
327pub struct RefreshProgressStats {
328 pub total_vnodes: usize,
329 pub completed_vnodes: usize,
330 pub total_processed_rows: u64,
331}
332
333impl RefreshProgressStats {
334 pub fn is_complete(&self) -> bool {
336 self.total_vnodes > 0 && self.completed_vnodes == self.total_vnodes
337 }
338}