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