1use std::cmp;
16use std::collections::Bound::{Excluded, Included};
17use std::collections::{BTreeMap, HashMap, HashSet};
18use std::sync::Arc;
19
20use itertools::Itertools;
21use risingwave_hummock_sdk::change_log::{EpochNewChangeLog, TableChangeLog, TableChangeLogs};
22use risingwave_hummock_sdk::compaction_group::StateTableId;
23use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{
24 BranchedSstInfo, get_compaction_group_ids, get_table_compaction_group_id_mapping,
25};
26use risingwave_hummock_sdk::sstable_info::SstableInfo;
27use risingwave_hummock_sdk::table_stats::{PbTableStatsMap, add_prost_table_stats_map};
28use risingwave_hummock_sdk::version::{
29 HummockVersion, HummockVersionDelta, MAX_HUMMOCK_VERSION_ID,
30};
31use risingwave_hummock_sdk::{
32 CompactionGroupId, HummockContextId, HummockObjectId, HummockSstableId, HummockSstableObjectId,
33 HummockVersionId, get_stale_object_ids,
34};
35use risingwave_pb::common::WorkerNode;
36use risingwave_pb::hummock::write_limits::WriteLimit;
37use risingwave_pb::hummock::{HummockPinnedVersion, HummockVersionStats};
38use risingwave_pb::id::TableId;
39use risingwave_pb::meta::subscribe_response::{Info, Operation};
40
41use super::GroupStateValidator;
42use crate::MetaResult;
43use crate::hummock::HummockManager;
44use crate::hummock::error::{Error, Result};
45use crate::hummock::manager::checkpoint::HummockVersionCheckpoint;
46use crate::hummock::manager::commit_multi_var;
47use crate::hummock::manager::context::ContextInfo;
48use crate::hummock::manager::transaction::HummockVersionTransaction;
49use crate::hummock::metrics_utils::{LocalTableMetrics, trigger_write_stop_stats};
50use crate::hummock::model::CompactionGroup;
51use crate::model::VarTransaction;
52
53#[derive(Default)]
54pub struct Versioning {
55 pub disable_commit_epochs: bool,
59 pub current_version: Arc<HummockVersion>,
61 pub local_metrics: HashMap<TableId, LocalTableMetrics>,
62 pub time_travel_snapshot_interval_counter: u64,
63 pub last_time_travel_snapshot_sst_ids: HashSet<HummockSstableId>,
65
66 pub hummock_version_deltas: BTreeMap<HummockVersionId, HummockVersionDelta>,
68 pub version_stats: HummockVersionStats,
70 pub checkpoint: HummockVersionCheckpoint,
71 pub table_change_log: HashMap<TableId, TableChangeLog>,
72}
73
74impl ContextInfo {
75 pub fn min_pinned_version_id(&self) -> HummockVersionId {
76 let mut min_pinned_version_id = MAX_HUMMOCK_VERSION_ID;
77 for id in self
78 .pinned_versions
79 .values()
80 .map(|v| v.min_pinned_id)
81 .chain(self.version_safe_points.iter().cloned())
82 {
83 min_pinned_version_id = cmp::min(id, min_pinned_version_id);
84 }
85 min_pinned_version_id
86 }
87}
88
89impl Versioning {
90 pub(super) fn mark_next_time_travel_version_snapshot(&mut self) {
91 self.time_travel_snapshot_interval_counter = u64::MAX;
92 }
93
94 pub fn get_tracked_object_ids(
95 &self,
96 min_pinned_version_id: HummockVersionId,
97 ) -> HashSet<HummockObjectId> {
98 let mut tracked_object_ids = self
100 .checkpoint
101 .version
102 .get_object_ids()
103 .chain(
104 self.table_change_log
105 .values()
106 .flat_map(|c| c.get_object_ids()),
107 )
108 .collect::<HashSet<_>>();
109 for (_, delta) in self.hummock_version_deltas.range((
111 Excluded(self.checkpoint.version.id),
112 Included(self.current_version.id),
113 )) {
114 tracked_object_ids.extend(delta.newly_added_object_ids());
115 }
116 tracked_object_ids.extend(
118 self.checkpoint
119 .stale_objects
120 .iter()
121 .filter(|(version_id, _)| **version_id >= min_pinned_version_id)
122 .flat_map(|(_, objects)| get_stale_object_ids(objects)),
123 );
124 tracked_object_ids
125 }
126}
127
128impl HummockManager {
129 pub async fn list_pinned_version(&self) -> Vec<HummockPinnedVersion> {
130 self.context_info
131 .read_with_process_name("list_pinned_version")
132 .await
133 .pinned_versions
134 .values()
135 .cloned()
136 .collect_vec()
137 }
138
139 pub async fn list_workers(
140 &self,
141 context_ids: &[HummockContextId],
142 ) -> MetaResult<HashMap<HummockContextId, WorkerNode>> {
143 let mut workers = HashMap::new();
144 for context_id in context_ids {
145 if let Some(worker_node) = self
146 .metadata_manager()
147 .get_worker_by_id(*context_id as _)
148 .await?
149 {
150 workers.insert(*context_id, worker_node);
151 }
152 }
153 Ok(workers)
154 }
155
156 #[cfg(any(test, feature = "test"))]
161 pub async fn get_current_version(&self) -> HummockVersion {
162 self.on_current_version(|version| version.clone()).await
163 }
164
165 pub async fn on_current_version<T>(&self, mut f: impl FnMut(&HummockVersion) -> T) -> T {
166 f(self
167 .versioning
168 .read_with_process_name("on_current_version")
169 .await
170 .current_version
171 .as_ref())
172 }
173
174 pub async fn on_current_version_and_table_change_log<T>(
175 &self,
176 mut f: impl FnMut(&HummockVersion, &TableChangeLogs) -> T,
177 ) -> T {
178 let guard = self
179 .versioning
180 .read_with_process_name("on_current_version_and_table_change_log")
181 .await;
182 f(&guard.current_version, &guard.table_change_log)
183 }
184
185 pub async fn get_version_id(&self) -> HummockVersionId {
186 self.on_current_version(|version| version.id).await
187 }
188
189 pub async fn get_table_compaction_group_id_mapping(
191 &self,
192 ) -> HashMap<StateTableId, CompactionGroupId> {
193 get_table_compaction_group_id_mapping(
194 &self
195 .versioning
196 .read_with_process_name("get_table_compaction_group_id_mapping")
197 .await
198 .current_version,
199 )
200 }
201
202 pub async fn list_version_deltas(
204 &self,
205 start_id: HummockVersionId,
206 num_limit: u32,
207 ) -> Result<Vec<HummockVersionDelta>> {
208 let versioning = self
209 .versioning
210 .read_with_process_name("list_version_deltas")
211 .await;
212 let version_deltas = versioning
213 .hummock_version_deltas
214 .range(start_id..)
215 .map(|(_id, delta)| delta)
216 .take(num_limit as _)
217 .cloned()
218 .collect();
219 Ok(version_deltas)
220 }
221
222 pub async fn get_version_stats(&self) -> HummockVersionStats {
223 self.versioning
224 .read_with_process_name("get_version_stats")
225 .await
226 .version_stats
227 .clone()
228 }
229
230 pub(super) async fn try_update_write_limits(
234 &self,
235 target_group_ids: &[CompactionGroupId],
236 ) -> bool {
237 let versioning = self
238 .versioning
239 .read_with_process_name("try_update_write_limits")
240 .await;
241 let mut cg_manager = self
242 .compaction_group_manager
243 .write_with_process_name("try_update_write_limits")
244 .await;
245 let target_group_configs = target_group_ids
246 .iter()
247 .filter_map(|id| {
248 cg_manager
249 .try_get_compaction_group_config(*id)
250 .map(|config| (*id, config))
251 })
252 .collect();
253 let mut new_write_limits = calc_new_write_limits(
254 target_group_configs,
255 cg_manager.write_limit.clone(),
256 &versioning.current_version,
257 );
258 let all_group_ids: HashSet<_> =
259 HashSet::from_iter(get_compaction_group_ids(&versioning.current_version));
260 new_write_limits.retain(|group_id, _| all_group_ids.contains(group_id));
261 if new_write_limits == cg_manager.write_limit {
262 return false;
263 }
264 tracing::debug!("Hummock stopped write is updated: {:#?}", new_write_limits);
265 trigger_write_stop_stats(&self.metrics, &new_write_limits);
266 cg_manager.write_limit = new_write_limits;
267 self.env
268 .notification_manager()
269 .notify_hummock_without_version(
270 Operation::Add,
271 Info::HummockWriteLimits(risingwave_pb::hummock::WriteLimits {
272 write_limits: cg_manager.write_limit.clone(),
273 }),
274 );
275 true
276 }
277
278 pub async fn write_limits(&self) -> HashMap<CompactionGroupId, WriteLimit> {
281 let guard = self
282 .compaction_group_manager
283 .read_with_process_name("write_limits")
284 .await;
285 guard.write_limit.clone()
286 }
287
288 pub async fn list_branched_objects(&self) -> BTreeMap<HummockSstableObjectId, BranchedSstInfo> {
289 let guard = self
290 .versioning
291 .read_with_process_name("list_branched_objects")
292 .await;
293 guard.current_version.build_branched_sst_info()
294 }
295
296 pub async fn rebuild_table_stats(&self) -> Result<()> {
297 let mut versioning = self
298 .versioning
299 .write_with_process_name("rebuild_table_stats")
300 .await;
301 let new_stats = rebuild_table_stats(&versioning.current_version);
302 let mut version_stats = VarTransaction::new(&mut versioning.version_stats);
303 version_stats.table_stats = new_stats.table_stats;
305 commit_multi_var!(self.meta_store_ref(), version_stats)?;
306 Ok(())
307 }
308
309 pub async fn may_fill_backward_state_table_info(&self) -> Result<()> {
310 let mut versioning = self
311 .versioning
312 .write_with_process_name("may_fill_backward_state_table_info")
313 .await;
314 if versioning
315 .current_version
316 .need_fill_backward_compatible_state_table_info_delta()
317 {
318 let versioning: &mut Versioning = &mut versioning;
319 let mut version = HummockVersionTransaction::new(
320 &mut versioning.current_version,
321 &mut versioning.hummock_version_deltas,
322 &mut versioning.table_change_log,
323 self.env.notification_manager(),
324 None,
325 &self.metrics,
326 &self.env.opts,
327 &self.version_stat_tx,
328 );
329 let mut new_version_delta = version.new_delta();
330 new_version_delta.with_latest_version(|version, delta| {
331 version.may_fill_backward_compatible_state_table_info_delta(delta)
332 });
333 new_version_delta.pre_apply();
334 commit_multi_var!(self.meta_store_ref(), version)?;
335 }
336 Ok(())
337 }
338
339 pub async fn get_table_change_logs(
340 &self,
341 epoch_only: bool,
342 start_epoch_inclusive: Option<u64>,
343 end_epoch_inclusive: Option<u64>,
344 table_ids: Option<HashSet<TableId>>,
345 exclude_empty: bool,
346 limit: Option<u32>,
347 ) -> Result<TableChangeLogs> {
348 let _timer = self.metrics.table_change_log_get_latency.start_timer();
349 let start_epoch = start_epoch_inclusive.unwrap_or(0);
350 let end_epoch = end_epoch_inclusive.unwrap_or(u64::MAX);
351 if start_epoch > end_epoch {
352 return Err(Error::InvalidEpochRange {
353 start_epoch,
354 end_epoch,
355 });
356 }
357 let table_change_logs = self
358 .on_current_version_and_table_change_log(|_, table_change_logs| {
359 table_change_logs
360 .iter()
361 .filter_map(|(id, change_log)| {
362 if let Some(table_filter) = &table_ids
363 && !table_filter.contains(id)
364 {
365 return None;
366 }
367 let filtered_change_logs = change_log
368 .filter_epoch((start_epoch, end_epoch))
369 .filter(|change_log| {
370 if exclude_empty
371 && change_log.new_value.is_empty()
372 && change_log.old_value.is_empty()
373 {
374 return false;
375 }
376 true
377 })
378 .take(limit.map(|l| l as usize).unwrap_or(usize::MAX))
379 .map(|change_log| {
380 if epoch_only {
381 EpochNewChangeLog {
382 new_value: vec![],
383 old_value: vec![],
384 non_checkpoint_epochs: change_log
385 .non_checkpoint_epochs
386 .clone(),
387 checkpoint_epoch: change_log.checkpoint_epoch,
388 }
389 } else {
390 change_log.clone()
391 }
392 });
393 Some((id.to_owned(), TableChangeLog::new(filtered_change_logs)))
394 })
395 .collect()
396 })
397 .await;
398 Ok(table_change_logs)
399 }
400}
401
402pub(super) fn calc_new_write_limits(
405 target_groups: HashMap<CompactionGroupId, CompactionGroup>,
406 origin_snapshot: HashMap<CompactionGroupId, WriteLimit>,
407 version: &HummockVersion,
408) -> HashMap<CompactionGroupId, WriteLimit> {
409 let mut new_write_limits = origin_snapshot;
410 for (id, config) in &target_groups {
411 let levels = match version.levels.get(id) {
412 None => {
413 new_write_limits.remove(id);
414 continue;
415 }
416 Some(levels) => levels,
417 };
418
419 let group_state = GroupStateValidator::check_single_group_write_stop(
420 levels,
421 config.compaction_config.as_ref(),
422 );
423
424 if group_state.is_write_stop() {
425 new_write_limits.insert(
426 *id,
427 WriteLimit {
428 table_ids: version
429 .state_table_info
430 .compaction_group_member_table_ids(*id)
431 .iter()
432 .copied()
433 .collect(),
434 reason: group_state.reason().unwrap().to_owned(),
435 },
436 );
437 continue;
438 }
439 new_write_limits.remove(id);
441 }
442 new_write_limits
443}
444
445fn rebuild_table_stats(version: &HummockVersion) -> HummockVersionStats {
448 let mut stats = HummockVersionStats {
449 hummock_version_id: version.id,
450 table_stats: Default::default(),
451 };
452 for level in version.get_combined_levels() {
453 for sst in &level.table_infos {
454 let changes = estimate_table_stats(sst);
455 add_prost_table_stats_map(&mut stats.table_stats, &changes);
456 }
457 }
458 stats
459}
460
461fn estimate_table_stats(sst: &SstableInfo) -> PbTableStatsMap {
466 let mut changes: PbTableStatsMap = HashMap::default();
467 let weighted_value =
468 |value: i64| -> i64 { (value as f64 / sst.table_ids.len() as f64).ceil() as i64 };
469 let key_range = &sst.key_range;
470 let estimated_key_size: u64 = (key_range.left.len() + key_range.right.len()) as u64 / 2;
471 let mut estimated_total_key_size = estimated_key_size * sst.total_key_count;
472 if estimated_total_key_size > sst.uncompressed_file_size {
473 estimated_total_key_size = sst.uncompressed_file_size / 2;
474 tracing::warn!(
475 %sst.sst_id,
476 "Calculated estimated_total_key_size {} > uncompressed_file_size {}. Use uncompressed_file_size/2 as estimated_total_key_size instead.",
477 estimated_total_key_size,
478 sst.uncompressed_file_size
479 );
480 }
481 let estimated_total_value_size = sst.uncompressed_file_size - estimated_total_key_size;
482 for table_id in &sst.table_ids {
483 let e = changes.entry(*table_id).or_default();
484 e.total_key_count += weighted_value(sst.total_key_count as i64);
485 e.total_key_size += weighted_value(estimated_total_key_size as i64);
486 e.total_value_size += weighted_value(estimated_total_value_size as i64);
487 }
488 changes
489}
490
491#[cfg(test)]
492mod tests {
493 use std::collections::HashMap;
494 use std::sync::Arc;
495
496 use itertools::Itertools;
497 use risingwave_hummock_sdk::key_range::KeyRange;
498 use risingwave_hummock_sdk::level::{Level, Levels};
499 use risingwave_hummock_sdk::sstable_info::SstableInfoInner;
500 use risingwave_hummock_sdk::version::{HummockVersion, MAX_HUMMOCK_VERSION_ID};
501 use risingwave_hummock_sdk::{CompactionGroupId, HummockVersionId};
502 use risingwave_pb::hummock::write_limits::WriteLimit;
503 use risingwave_pb::hummock::{HummockPinnedVersion, HummockVersionStats};
504
505 use crate::hummock::compaction::compaction_config::CompactionConfigBuilder;
506 use crate::hummock::manager::context::ContextInfo;
507 use crate::hummock::manager::versioning::{
508 calc_new_write_limits, estimate_table_stats, rebuild_table_stats,
509 };
510 use crate::hummock::model::CompactionGroup;
511
512 #[test]
513 fn test_min_pinned_version_id() {
514 let mut context_info = ContextInfo::default();
515 assert_eq!(context_info.min_pinned_version_id(), MAX_HUMMOCK_VERSION_ID);
516 context_info.pinned_versions.insert(
517 1.into(),
518 HummockPinnedVersion {
519 context_id: 1.into(),
520 min_pinned_id: 10.into(),
521 },
522 );
523 assert_eq!(context_info.min_pinned_version_id(), 10);
524 context_info
525 .version_safe_points
526 .push(HummockVersionId::new(5));
527 assert_eq!(context_info.min_pinned_version_id(), 5);
528 context_info.version_safe_points.clear();
529 assert_eq!(context_info.min_pinned_version_id(), 10);
530 context_info.pinned_versions.clear();
531 assert_eq!(context_info.min_pinned_version_id(), MAX_HUMMOCK_VERSION_ID);
532 }
533
534 #[test]
535 fn test_calc_new_write_limits() {
536 let add_level_to_l0 = |levels: &mut Levels| {
537 levels.l0.sub_levels.push(Level::default());
538 };
539 let set_sub_level_number_threshold_for_group_1 =
540 |target_groups: &mut HashMap<CompactionGroupId, CompactionGroup>,
541 sub_level_number_threshold: u64| {
542 target_groups.insert(
543 1.into(),
544 CompactionGroup {
545 group_id: 1.into(),
546 compaction_config: Arc::new(
547 CompactionConfigBuilder::new()
548 .level0_stop_write_threshold_sub_level_number(
549 sub_level_number_threshold,
550 )
551 .build(),
552 ),
553 },
554 );
555 };
556
557 let set_level_0_max_sst_count_threshold_for_group_1 =
558 |target_groups: &mut HashMap<CompactionGroupId, CompactionGroup>,
559 max_sst_count_threshold: u32| {
560 target_groups.insert(
561 1.into(),
562 CompactionGroup {
563 group_id: 1.into(),
564 compaction_config: Arc::new(
565 CompactionConfigBuilder::new()
566 .level0_stop_write_threshold_max_sst_count(Some(
567 max_sst_count_threshold,
568 ))
569 .build(),
570 ),
571 },
572 );
573 };
574
575 let set_level_0_max_size_threshold_for_group_1 =
576 |target_groups: &mut HashMap<CompactionGroupId, CompactionGroup>,
577 max_size_threshold: u64| {
578 target_groups.insert(
579 1.into(),
580 CompactionGroup {
581 group_id: 1.into(),
582 compaction_config: Arc::new(
583 CompactionConfigBuilder::new()
584 .level0_stop_write_threshold_max_size(Some(max_size_threshold))
585 .build(),
586 ),
587 },
588 );
589 };
590
591 let mut target_groups: HashMap<CompactionGroupId, CompactionGroup> = Default::default();
592 set_sub_level_number_threshold_for_group_1(&mut target_groups, 10);
593 let origin_snapshot: HashMap<CompactionGroupId, WriteLimit> = [(
594 2.into(),
595 WriteLimit {
596 table_ids: [1, 2, 3].into_iter().map_into().collect(),
597 reason: "for test".to_owned(),
598 },
599 )]
600 .into_iter()
601 .collect();
602 let mut version: HummockVersion = Default::default();
603 for group_id in 1..=3 {
604 version.levels.insert(group_id.into(), Levels::default());
605 }
606 let new_write_limits =
607 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
608 assert_eq!(
609 new_write_limits, origin_snapshot,
610 "write limit should not be triggered for group 1"
611 );
612 assert_eq!(new_write_limits.len(), 1);
613 for _ in 1..=10 {
614 add_level_to_l0(version.levels.get_mut(&1).unwrap());
615 let new_write_limits =
616 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
617 assert_eq!(
618 new_write_limits, origin_snapshot,
619 "write limit should not be triggered for group 1"
620 );
621 }
622 add_level_to_l0(version.levels.get_mut(&1).unwrap());
623 let new_write_limits =
624 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
625 assert_ne!(
626 new_write_limits, origin_snapshot,
627 "write limit should be triggered for group 1"
628 );
629 assert_eq!(
630 new_write_limits.get(&1).as_ref().unwrap().reason,
631 "WriteStop(l0_level_count: 11, threshold: 10) too many L0 sub levels"
632 );
633 assert_eq!(new_write_limits.len(), 2);
634
635 set_sub_level_number_threshold_for_group_1(&mut target_groups, 100);
636 let new_write_limits =
637 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
638 assert_eq!(
639 new_write_limits, origin_snapshot,
640 "write limit should not be triggered for group 1"
641 );
642
643 set_sub_level_number_threshold_for_group_1(&mut target_groups, 5);
644 let new_write_limits =
645 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
646 assert_ne!(
647 new_write_limits, origin_snapshot,
648 "write limit should be triggered for group 1"
649 );
650 assert_eq!(
651 new_write_limits.get(&1).as_ref().unwrap().reason,
652 "WriteStop(l0_level_count: 11, threshold: 5) too many L0 sub levels"
653 );
654
655 set_sub_level_number_threshold_for_group_1(&mut target_groups, 100);
656 let last_level = version
657 .levels
658 .get_mut(&1)
659 .unwrap()
660 .l0
661 .sub_levels
662 .last_mut()
663 .unwrap();
664 last_level.table_infos.extend(vec![
665 SstableInfoInner {
666 key_range: KeyRange::default(),
667 table_ids: vec![1.into(), 2.into(), 3.into()],
668 total_key_count: 100,
669 sst_size: 100,
670 uncompressed_file_size: 100,
671 ..Default::default()
672 }
673 .into(),
674 SstableInfoInner {
675 key_range: KeyRange::default(),
676 table_ids: vec![1.into(), 2.into(), 3.into()],
677 total_key_count: 100,
678 sst_size: 100,
679 uncompressed_file_size: 100,
680 ..Default::default()
681 }
682 .into(),
683 ]);
684 version.levels.get_mut(&1).unwrap().l0.total_file_size += 200;
685 let new_write_limits =
686 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
687 assert_eq!(
688 new_write_limits, origin_snapshot,
689 "write limit should not be triggered for group 1"
690 );
691
692 set_level_0_max_size_threshold_for_group_1(&mut target_groups, 10);
693 let new_write_limits =
694 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
695 assert_ne!(
696 new_write_limits, origin_snapshot,
697 "write limit should be triggered for group 1"
698 );
699 assert_eq!(
700 new_write_limits.get(&1).as_ref().unwrap().reason,
701 "WriteStop(l0_size: 200, threshold: 10) too large L0 size"
702 );
703
704 set_level_0_max_size_threshold_for_group_1(&mut target_groups, 10000);
705 let new_write_limits =
706 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
707 assert_eq!(
708 new_write_limits, origin_snapshot,
709 "write limit should not be triggered for group 1"
710 );
711
712 set_level_0_max_sst_count_threshold_for_group_1(&mut target_groups, 1);
713 let new_write_limits =
714 calc_new_write_limits(target_groups.clone(), origin_snapshot.clone(), &version);
715 assert_ne!(
716 new_write_limits, origin_snapshot,
717 "write limit should be triggered for group 1"
718 );
719 assert_eq!(
720 new_write_limits.get(&1).as_ref().unwrap().reason,
721 "WriteStop(l0_sst_count: 2, threshold: 1) too many L0 sst files"
722 );
723
724 set_level_0_max_sst_count_threshold_for_group_1(&mut target_groups, 100);
725 let new_write_limits =
726 calc_new_write_limits(target_groups, origin_snapshot.clone(), &version);
727
728 assert_eq!(
729 new_write_limits, origin_snapshot,
730 "write limit should not be triggered for group 1"
731 );
732 }
733
734 #[test]
735 fn test_estimate_table_stats() {
736 let sst = SstableInfoInner {
737 key_range: KeyRange {
738 left: vec![1; 10].into(),
739 right: vec![1; 20].into(),
740 ..Default::default()
741 },
742 table_ids: vec![1.into(), 2.into(), 3.into()],
743 total_key_count: 6000,
744 uncompressed_file_size: 6_000_000,
745 ..Default::default()
746 }
747 .into();
748 let changes = estimate_table_stats(&sst);
749 assert_eq!(changes.len(), 3);
750 for stats in changes.values() {
751 assert_eq!(stats.total_key_count, 6000 / 3);
752 assert_eq!(stats.total_key_size, (10 + 20) / 2 * 6000 / 3);
753 assert_eq!(
754 stats.total_value_size,
755 (6_000_000 - (10 + 20) / 2 * 6000) / 3
756 );
757 }
758
759 let mut version = HummockVersion::default();
760 version.id = HummockVersionId::new(123);
761
762 for cg in 1..3 {
763 version.levels.insert(
764 cg.into(),
765 Levels {
766 levels: vec![Level {
767 table_infos: vec![sst.clone()],
768 ..Default::default()
769 }],
770 ..Default::default()
771 },
772 );
773 }
774 let HummockVersionStats {
775 hummock_version_id,
776 table_stats,
777 } = rebuild_table_stats(&version);
778 assert_eq!(hummock_version_id, version.id);
779 assert_eq!(table_stats.len(), 3);
780 for (tid, stats) in table_stats {
781 assert_eq!(
782 stats.total_key_count,
783 changes.get(&tid).unwrap().total_key_count * 2
784 );
785 assert_eq!(
786 stats.total_key_size,
787 changes.get(&tid).unwrap().total_key_size * 2
788 );
789 assert_eq!(
790 stats.total_value_size,
791 changes.get(&tid).unwrap().total_value_size * 2
792 );
793 }
794 }
795
796 #[test]
797 fn test_estimate_table_stats_large_key_range() {
798 let sst = SstableInfoInner {
799 key_range: KeyRange {
800 left: vec![1; 1000].into(),
801 right: vec![1; 2000].into(),
802 ..Default::default()
803 },
804 table_ids: vec![1.into(), 2.into(), 3.into()],
805 total_key_count: 6000,
806 uncompressed_file_size: 60_000,
807 ..Default::default()
808 }
809 .into();
810 let changes = estimate_table_stats(&sst);
811 assert_eq!(changes.len(), 3);
812 for t in &sst.table_ids {
813 let stats = changes.get(t).unwrap();
814 assert_eq!(stats.total_key_count, 6000 / 3);
815 assert_eq!(stats.total_key_size, 60_000 / 2 / 3);
816 assert_eq!(stats.total_value_size, (60_000 - 60_000 / 2) / 3);
817 }
818 }
819}