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