1use std::collections::{HashMap, VecDeque};
16use std::ops::RangeBounds;
17
18use risingwave_common::catalog::TableId;
19use risingwave_pb::hummock::{PbEpochNewChangeLog, PbSstableInfo, PbTableChangeLog};
20use tracing::warn;
21
22use crate::HummockObjectId;
23use crate::sstable_info::SstableInfo;
24use crate::version::ObjectIdReader;
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct TableChangeLogCommon<T>(
28 VecDeque<EpochNewChangeLogCommon<T>>,
30);
31
32impl<T> TableChangeLogCommon<T> {
33 pub fn new(logs: impl IntoIterator<Item = EpochNewChangeLogCommon<T>>) -> Self {
34 let logs = logs.into_iter().collect::<VecDeque<_>>();
35 debug_assert!(logs.iter().flat_map(|log| log.epochs()).is_sorted());
36 Self(logs)
37 }
38
39 pub fn iter(&self) -> impl Iterator<Item = &EpochNewChangeLogCommon<T>> {
40 self.0.iter()
41 }
42
43 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut EpochNewChangeLogCommon<T>> {
44 self.0.iter_mut()
45 }
46
47 pub fn first(&self) -> Option<&EpochNewChangeLogCommon<T>> {
48 self.0.front()
49 }
50
51 pub fn last(&self) -> Option<&EpochNewChangeLogCommon<T>> {
52 self.0.back()
53 }
54
55 pub fn get(&self, index: usize) -> Option<&EpochNewChangeLogCommon<T>> {
56 self.0.get(index)
57 }
58
59 pub fn range(
60 &self,
61 range: impl RangeBounds<usize>,
62 ) -> impl Iterator<Item = &EpochNewChangeLogCommon<T>> + '_ {
63 self.0.range(range)
64 }
65
66 pub fn add_change_log(&mut self, new_change_log: EpochNewChangeLogCommon<T>) {
67 if let Some(prev_log) = self.0.back() {
68 assert!(prev_log.checkpoint_epoch < new_change_log.first_epoch());
69 }
70 self.0.push_back(new_change_log);
71 }
72
73 pub fn epochs(&self) -> impl Iterator<Item = u64> + '_ {
74 self.0
75 .iter()
76 .flat_map(|epoch_change_log| epoch_change_log.epochs())
77 }
78
79 pub fn is_empty(&self) -> bool {
80 self.0.is_empty()
81 }
82
83 pub fn binary_search_by_checkpoint_epoch(&self, epoch: u64) -> Result<usize, usize> {
84 self.0
85 .binary_search_by_key(&epoch, |log| log.checkpoint_epoch)
86 }
87}
88
89impl<T> IntoIterator for TableChangeLogCommon<T> {
90 type Item = EpochNewChangeLogCommon<T>;
91
92 type IntoIter = impl Iterator<Item = EpochNewChangeLogCommon<T>>;
93
94 fn into_iter(self) -> Self::IntoIter {
95 self.0.into_iter()
96 }
97}
98
99pub type TableChangeLog = TableChangeLogCommon<SstableInfo>;
100pub type TableChangeLogs = HashMap<TableId, TableChangeLog>;
101
102impl TableChangeLog {
103 pub fn get_object_ids(&self) -> impl Iterator<Item = HummockObjectId> + '_ {
104 self.0.iter().flat_map(|c| {
105 c.old_value
106 .iter()
107 .chain(c.new_value.iter())
108 .map(|t| HummockObjectId::Sstable(t.object_id()))
109 })
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
114pub struct EpochNewChangeLogCommon<T> {
115 pub new_value: Vec<T>,
116 pub old_value: Vec<T>,
117 pub non_checkpoint_epochs: Vec<u64>,
119 pub checkpoint_epoch: u64,
120}
121
122impl EpochNewChangeLog {
123 pub fn change_log_ssts(&self) -> impl Iterator<Item = &SstableInfo> + '_ {
124 self.new_value.iter().chain(self.old_value.iter())
125 }
126}
127
128pub(crate) fn resolve_pb_log_epochs(epochs: &Vec<u64>) -> (Vec<u64>, u64) {
129 (
130 Vec::from(&epochs[0..(epochs.len() - 1)]),
131 *epochs.last().expect("non-empty"),
132 )
133}
134
135impl<T> EpochNewChangeLogCommon<T> {
136 pub fn epochs(&self) -> impl Iterator<Item = u64> + '_ {
137 self.non_checkpoint_epochs
138 .iter()
139 .cloned()
140 .chain([self.checkpoint_epoch])
141 }
142
143 pub fn first_epoch(&self) -> u64 {
144 self.non_checkpoint_epochs
145 .first()
146 .cloned()
147 .unwrap_or(self.checkpoint_epoch)
148 }
149}
150
151pub type EpochNewChangeLog = EpochNewChangeLogCommon<SstableInfo>;
152
153impl<T> From<&EpochNewChangeLogCommon<T>> for PbEpochNewChangeLog
154where
155 PbSstableInfo: for<'a> From<&'a T>,
156{
157 fn from(val: &EpochNewChangeLogCommon<T>) -> Self {
158 Self {
159 new_value: val.new_value.iter().map(|a| a.into()).collect(),
160 old_value: val.old_value.iter().map(|a| a.into()).collect(),
161 epochs: val.epochs().collect(),
162 }
163 }
164}
165
166impl<T> From<&PbEpochNewChangeLog> for EpochNewChangeLogCommon<T>
167where
168 T: for<'a> From<&'a PbSstableInfo>,
169{
170 fn from(value: &PbEpochNewChangeLog) -> Self {
171 let (non_checkpoint_epochs, checkpoint_epoch) = resolve_pb_log_epochs(&value.epochs);
172 Self {
173 new_value: value.new_value.iter().map(|a| a.into()).collect(),
174 old_value: value.old_value.iter().map(|a| a.into()).collect(),
175 non_checkpoint_epochs,
176 checkpoint_epoch,
177 }
178 }
179}
180
181impl<T> From<EpochNewChangeLogCommon<T>> for PbEpochNewChangeLog
182where
183 PbSstableInfo: From<T>,
184{
185 fn from(val: EpochNewChangeLogCommon<T>) -> Self {
186 Self {
187 epochs: val.epochs().collect(),
188 new_value: val.new_value.into_iter().map(|a| a.into()).collect(),
189 old_value: val.old_value.into_iter().map(|a| a.into()).collect(),
190 }
191 }
192}
193
194impl<T> From<PbEpochNewChangeLog> for EpochNewChangeLogCommon<T>
195where
196 T: From<PbSstableInfo>,
197{
198 fn from(value: PbEpochNewChangeLog) -> Self {
199 let (non_checkpoint_epochs, checkpoint_epoch) = resolve_pb_log_epochs(&value.epochs);
200 Self {
201 new_value: value.new_value.into_iter().map(|a| a.into()).collect(),
202 old_value: value.old_value.into_iter().map(|a| a.into()).collect(),
203 non_checkpoint_epochs,
204 checkpoint_epoch,
205 }
206 }
207}
208
209impl<T> TableChangeLogCommon<T> {
210 pub fn filter_epoch(
211 &self,
212 (min_epoch, max_epoch): (u64, u64),
213 ) -> impl Iterator<Item = &EpochNewChangeLogCommon<T>> + '_ {
214 assert!(
215 min_epoch <= max_epoch,
216 "invalid epoch range: {min_epoch}..={max_epoch}"
217 );
218 let start = self
219 .0
220 .partition_point(|epoch_change_log| epoch_change_log.checkpoint_epoch < min_epoch);
221 let end = self
222 .0
223 .partition_point(|epoch_change_log| epoch_change_log.first_epoch() <= max_epoch);
224 self.0.range(start..end)
225 }
226
227 #[expect(clippy::result_unit_err)]
233 pub fn next_epoch(&self, epoch: u64) -> Result<Option<u64>, ()> {
234 let start = self
235 .0
236 .partition_point(|epoch_change_log| epoch_change_log.checkpoint_epoch < epoch);
237 debug_assert!(
238 self.0
239 .range(start..)
240 .flat_map(|epoch_change_log| epoch_change_log.epochs())
241 .is_sorted()
242 );
243 let mut later_epochs = self
244 .0
245 .range(start..)
246 .flat_map(|epoch_change_log| epoch_change_log.epochs())
247 .skip_while(|log_epoch| *log_epoch < epoch);
248 if let Some(first_epoch) = later_epochs.next() {
249 assert!(
250 first_epoch >= epoch,
251 "first_epoch {} < epoch {}",
252 first_epoch,
253 epoch
254 );
255 if first_epoch != epoch {
256 return Err(());
257 }
258 if let Some(next_epoch) = later_epochs.next() {
259 assert!(
260 next_epoch > epoch,
261 "next_epoch {} not exceed epoch {}",
262 next_epoch,
263 epoch
264 );
265 Ok(Some(next_epoch))
266 } else {
267 Ok(None)
269 }
270 } else {
271 Ok(None)
273 }
274 }
275
276 pub fn truncate(&mut self, truncate_epoch: u64) {
277 while let Some(change_log) = self.0.front()
278 && change_log.checkpoint_epoch < truncate_epoch
279 {
280 let _change_log = self.0.pop_front().expect("non-empty");
281 }
282 if let Some(first_log) = self.0.front_mut() {
283 first_log
284 .non_checkpoint_epochs
285 .retain(|epoch| *epoch >= truncate_epoch);
286 }
287 }
288}
289
290impl<T> TableChangeLogCommon<T>
291where
292 PbSstableInfo: for<'a> From<&'a T>,
293{
294 pub fn to_protobuf(&self) -> PbTableChangeLog {
295 PbTableChangeLog {
296 change_logs: self.0.iter().map(|a| a.into()).collect(),
297 }
298 }
299}
300
301impl<T> TableChangeLogCommon<T>
302where
303 T: for<'a> From<&'a PbSstableInfo>,
304{
305 pub fn from_protobuf(val: &PbTableChangeLog) -> Self {
306 Self(val.change_logs.iter().map(|a| a.into()).collect())
307 }
308}
309
310impl<T> TableChangeLogCommon<T>
311where
312 T: From<PbSstableInfo>,
313{
314 pub fn from_protobuf_owned(val: PbTableChangeLog) -> Self {
315 Self(val.change_logs.into_iter().map(|a| a.into()).collect())
316 }
317}
318
319pub fn build_table_change_log_delta<'a>(
320 old_value_ssts: impl Iterator<Item = SstableInfo>,
321 new_value_ssts: impl Iterator<Item = &'a SstableInfo>,
322 epochs: &Vec<u64>,
323 log_store_table_ids: impl Iterator<Item = (TableId, u64)>,
324) -> HashMap<TableId, ChangeLogDelta> {
325 let mut table_change_log: HashMap<_, _> = log_store_table_ids
326 .map(|(table_id, truncate_epoch)| {
327 let (non_checkpoint_epochs, checkpoint_epoch) = resolve_pb_log_epochs(epochs);
328 (
329 table_id,
330 ChangeLogDelta {
331 truncate_epoch,
332 new_log: EpochNewChangeLog {
333 new_value: vec![],
334 old_value: vec![],
335 non_checkpoint_epochs,
336 checkpoint_epoch,
337 },
338 },
339 )
340 })
341 .collect();
342 for sst in old_value_ssts {
343 for table_id in &sst.table_ids {
344 match table_change_log.get_mut(table_id) {
345 Some(log) => {
346 log.new_log.old_value.push(sst.clone());
347 }
348 None => {
349 warn!(%table_id, ?sst, "old value sst contains non-log-store table");
350 }
351 }
352 }
353 }
354 for sst in new_value_ssts {
355 for table_id in &sst.table_ids {
356 if let Some(log) = table_change_log.get_mut(table_id) {
357 log.new_log.new_value.push(sst.clone());
358 }
359 }
360 }
361 table_change_log
362}
363
364#[derive(Debug, PartialEq, Clone)]
365pub struct ChangeLogDeltaCommon<T> {
366 pub truncate_epoch: u64,
367 pub new_log: EpochNewChangeLogCommon<T>,
368}
369
370pub type ChangeLogDelta = ChangeLogDeltaCommon<SstableInfo>;
371
372#[cfg(test)]
373mod tests {
374 use itertools::Itertools;
375
376 use crate::change_log::{EpochNewChangeLog, TableChangeLogCommon};
377 use crate::sstable_info::SstableInfo;
378
379 #[test]
380 fn test_filter_epoch() {
381 let table_change_log = TableChangeLogCommon::<SstableInfo>::new([
382 EpochNewChangeLog {
383 new_value: vec![],
384 old_value: vec![],
385 non_checkpoint_epochs: vec![],
386 checkpoint_epoch: 2,
387 },
388 EpochNewChangeLog {
389 new_value: vec![],
390 old_value: vec![],
391 non_checkpoint_epochs: vec![3],
392 checkpoint_epoch: 4,
393 },
394 EpochNewChangeLog {
395 new_value: vec![],
396 old_value: vec![],
397 non_checkpoint_epochs: vec![],
398 checkpoint_epoch: 6,
399 },
400 EpochNewChangeLog {
401 new_value: vec![],
402 old_value: vec![],
403 non_checkpoint_epochs: vec![8],
404 checkpoint_epoch: 10,
405 },
406 ]);
407
408 let epochs = (1..=11).collect_vec();
409 for i in 0..epochs.len() {
410 for j in i..epochs.len() {
411 let min_epoch = epochs[i];
412 let max_epoch = epochs[j];
413 let expected = table_change_log
414 .0
415 .iter()
416 .filter(|log| {
417 min_epoch <= log.checkpoint_epoch && log.first_epoch() <= max_epoch
418 })
419 .cloned()
420 .collect_vec();
421 let actual = table_change_log
422 .filter_epoch((min_epoch, max_epoch))
423 .cloned()
424 .collect_vec();
425 assert_eq!(expected, actual, "{:?}", (min_epoch, max_epoch));
426 }
427 }
428
429 let existing_epochs = table_change_log.epochs().collect_vec();
430 assert!(existing_epochs.is_sorted());
431 for &epoch in &epochs {
432 let expected = match existing_epochs
433 .iter()
434 .position(|existing_epoch| *existing_epoch >= epoch)
435 {
436 None => {
437 Ok(None)
439 }
440 Some(i) => {
441 let this_epoch = existing_epochs[i];
442 assert!(this_epoch >= epoch);
443 if this_epoch == epoch {
444 if i + 1 == existing_epochs.len() {
445 Ok(None)
447 } else {
448 Ok(Some(existing_epochs[i + 1]))
449 }
450 } else {
451 Err(())
453 }
454 }
455 };
456 assert_eq!(expected, table_change_log.next_epoch(epoch));
457 }
458 }
459
460 #[test]
461 fn test_truncate() {
462 let mut table_change_log = TableChangeLogCommon::<SstableInfo>::new([
463 EpochNewChangeLog {
464 new_value: vec![],
465 old_value: vec![],
466 non_checkpoint_epochs: vec![],
467 checkpoint_epoch: 1,
468 },
469 EpochNewChangeLog {
470 new_value: vec![],
471 old_value: vec![],
472 non_checkpoint_epochs: vec![],
473 checkpoint_epoch: 2,
474 },
475 EpochNewChangeLog {
476 new_value: vec![],
477 old_value: vec![],
478 non_checkpoint_epochs: vec![3],
479 checkpoint_epoch: 4,
480 },
481 EpochNewChangeLog {
482 new_value: vec![],
483 old_value: vec![],
484 non_checkpoint_epochs: vec![],
485 checkpoint_epoch: 5,
486 },
487 ]);
488 let origin_table_change_log = table_change_log.clone();
489 for truncate_epoch in 0..6 {
490 table_change_log.truncate(truncate_epoch);
491 let expected_table_change_log = TableChangeLogCommon(
492 origin_table_change_log
493 .0
494 .iter()
495 .filter_map(|epoch_change_log| {
496 let mut epoch_change_log = epoch_change_log.clone();
497 epoch_change_log
498 .non_checkpoint_epochs
499 .retain(|epoch| *epoch >= truncate_epoch);
500 if epoch_change_log.non_checkpoint_epochs.is_empty()
501 && epoch_change_log.checkpoint_epoch < truncate_epoch
502 {
503 None
504 } else {
505 Some(epoch_change_log)
506 }
507 })
508 .collect(),
509 );
510 assert_eq!(expected_table_change_log, table_change_log);
511 }
512 }
513}