Skip to main content

risingwave_meta/hummock/
test_utils.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![cfg(any(test, feature = "test"))]
16
17use std::collections::{BTreeSet, HashMap};
18use std::sync::{Arc, LazyLock};
19use std::time::Duration;
20
21use bytes::Bytes;
22use itertools::Itertools;
23use risingwave_common::catalog::{TableId, TableOption};
24use risingwave_common::util::epoch::test_epoch;
25use risingwave_hummock_sdk::key::key_with_epoch;
26use risingwave_hummock_sdk::key_range::KeyRange;
27use risingwave_hummock_sdk::level::Levels;
28use risingwave_hummock_sdk::sstable_info::{SstableInfo, SstableInfoInner};
29use risingwave_hummock_sdk::table_watermark::TableWatermarks;
30use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionStateTableInfo};
31use risingwave_hummock_sdk::{
32    CompactionGroupId, HummockEpoch, HummockSstableObjectId, LocalSstableInfo, SyncResult,
33};
34use risingwave_meta_model::WorkerId;
35use risingwave_pb::common::worker_node::Property;
36use risingwave_pb::common::{HostAddress, WorkerType};
37use risingwave_pb::hummock::CompactionConfig;
38use risingwave_pb::hummock::compact_task::TaskStatus;
39use risingwave_rpc_client::HummockMetaClient;
40
41use crate::controller::catalog::CatalogController;
42use crate::controller::cluster::{ClusterController, ClusterControllerRef};
43use crate::hummock::compaction::compaction_config::CompactionConfigBuilder;
44use crate::hummock::compaction::in_progress_compaction::InProgressCompactionView;
45use crate::hummock::compaction::selector::{LocalSelectorStatistic, default_compaction_selector};
46use crate::hummock::compaction::{CompactionDeveloperConfig, CompactionSelectorContext};
47use crate::hummock::level_handler::LevelHandler;
48pub use crate::hummock::manager::CommitEpochInfo;
49use crate::hummock::model::CompactionGroup;
50use crate::hummock::{CompactorManager, HummockManager, HummockManagerRef};
51use crate::manager::MetaSrvEnv;
52use crate::rpc::metrics::MetaMetrics;
53
54static EMPTY_IN_PROGRESS_COMPACTION_VIEW: LazyLock<InProgressCompactionView> =
55    LazyLock::new(InProgressCompactionView::default);
56
57/// Advance virtual time without sleeping; Tokio tests must start with time paused.
58#[cfg(test)]
59pub(crate) async fn advance_time(duration: Duration) {
60    #[cfg(not(madsim))]
61    tokio::time::advance(duration).await;
62    #[cfg(madsim)]
63    tokio::time::advance(duration);
64}
65
66pub fn to_local_sstable_info(ssts: &[SstableInfo]) -> Vec<LocalSstableInfo> {
67    ssts.iter()
68        .map(|sst| LocalSstableInfo::for_test(sst.clone()))
69        .collect_vec()
70}
71
72// This function has 3 phases:
73// 1. add 3 ssts to
74// 2. trigger a compaction and replace the input from phase 1 with the 1 new sst
75// 3. add 1 new sst
76// Please make sure the function do what you want before using it.
77pub async fn add_test_tables(
78    hummock_manager: &HummockManager,
79    hummock_meta_client: Arc<dyn HummockMetaClient>,
80    compaction_group_id: CompactionGroupId,
81) -> Vec<Vec<SstableInfo>> {
82    // Increase version by 2.
83
84    use risingwave_common::util::epoch::EpochExt;
85
86    let mut epoch = test_epoch(1);
87    let sstable_ids = get_sst_ids(hummock_manager, 3).await;
88    let test_tables = generate_test_sstables_with_table_id(epoch, 1, sstable_ids);
89    register_sstable_infos_to_compaction_group(hummock_manager, &test_tables, compaction_group_id)
90        .await;
91    let test_local_tables = to_local_sstable_info(&test_tables);
92    hummock_meta_client
93        .commit_epoch(
94            epoch,
95            SyncResult {
96                uncommitted_ssts: test_local_tables,
97                ..Default::default()
98            },
99        )
100        .await
101        .unwrap();
102
103    // Simulate a compaction and increase version by 1.
104    let test_tables_2 = generate_test_tables(epoch, get_sst_ids(hummock_manager, 1).await);
105    register_sstable_infos_to_compaction_group(
106        hummock_manager,
107        &test_tables_2,
108        compaction_group_id,
109    )
110    .await;
111    let mut compact_task = hummock_manager
112        .get_compact_task(compaction_group_id, &mut *default_compaction_selector())
113        .await
114        .unwrap()
115        .unwrap();
116    assert_eq!(
117        compact_task
118            .input_ssts
119            .iter()
120            .map(|i| i.table_infos.len())
121            .sum::<usize>(),
122        3
123    );
124
125    compact_task.target_level = 6;
126    hummock_manager
127        .report_compact_task_for_test(
128            compact_task.task_id,
129            Some(compact_task),
130            TaskStatus::Success,
131            test_tables_2.clone(),
132            None,
133        )
134        .await
135        .unwrap();
136    // Increase version by 1.
137    epoch.inc_epoch();
138    let test_tables_3 = generate_test_tables(epoch, get_sst_ids(hummock_manager, 1).await);
139    register_sstable_infos_to_compaction_group(
140        hummock_manager,
141        &test_tables_3,
142        compaction_group_id,
143    )
144    .await;
145    let test_local_tables_3 = to_local_sstable_info(&test_tables_3);
146    hummock_meta_client
147        .commit_epoch(
148            epoch,
149            SyncResult {
150                uncommitted_ssts: test_local_tables_3,
151                ..Default::default()
152            },
153        )
154        .await
155        .unwrap();
156    vec![test_tables, test_tables_2, test_tables_3]
157}
158
159pub fn generate_test_sstables_with_table_id(
160    epoch: u64,
161    table_id: impl Into<TableId>,
162    sst_ids: Vec<u64>,
163) -> Vec<SstableInfo> {
164    let table_id = table_id.into();
165    let mut sst_info = vec![];
166    for (i, sst_id) in sst_ids.into_iter().enumerate() {
167        let object_size = 2;
168        sst_info.push(
169            SstableInfoInner {
170                object_id: sst_id.into(),
171                sst_id: sst_id.into(),
172                key_range: KeyRange {
173                    left: Bytes::from(key_with_epoch(
174                        format!("{:03}\0\0_key_test_{:05}", table_id, i + 1)
175                            .as_bytes()
176                            .to_vec(),
177                        epoch,
178                    )),
179                    right: Bytes::from(key_with_epoch(
180                        format!("{:03}\0\0_key_test_{:05}", table_id, (i + 1) * 10)
181                            .as_bytes()
182                            .to_vec(),
183                        epoch,
184                    )),
185                    right_exclusive: false,
186                },
187                file_size: object_size,
188                table_ids: vec![table_id],
189                uncompressed_file_size: object_size,
190                max_epoch: epoch,
191                sst_size: object_size,
192                ..Default::default()
193            }
194            .into(),
195        );
196    }
197    sst_info
198}
199
200pub fn generate_test_tables(epoch: u64, sst_ids: Vec<u64>) -> Vec<SstableInfo> {
201    let mut sst_info = vec![];
202    for (i, sst_id) in sst_ids.into_iter().enumerate() {
203        let object_size = 2;
204        sst_info.push(
205            SstableInfoInner {
206                object_id: sst_id.into(),
207                sst_id: sst_id.into(),
208                key_range: KeyRange {
209                    left: Bytes::from(iterator_test_key_of_epoch(sst_id, i + 1, epoch)),
210                    right: Bytes::from(iterator_test_key_of_epoch(sst_id, (i + 1) * 10, epoch)),
211                    right_exclusive: false,
212                },
213                file_size: object_size,
214                table_ids: vec![(sst_id as u32).into(), (sst_id as u32 * 10000).into()],
215                uncompressed_file_size: object_size,
216                max_epoch: epoch,
217                sst_size: object_size,
218                ..Default::default()
219            }
220            .into(),
221        );
222    }
223    sst_info
224}
225
226pub async fn register_sstable_infos_to_compaction_group(
227    compaction_group_manager_ref: &HummockManager,
228    sstable_infos: &[SstableInfo],
229    compaction_group_id: CompactionGroupId,
230) {
231    let table_ids = sstable_infos
232        .iter()
233        .flat_map(|sstable_info| &sstable_info.table_ids)
234        .sorted()
235        .dedup()
236        .copied()
237        .collect_vec();
238    register_table_ids_to_compaction_group(
239        compaction_group_manager_ref,
240        &table_ids,
241        compaction_group_id,
242    )
243    .await;
244}
245
246pub async fn register_table_ids_to_compaction_group(
247    hummock_manager_ref: &HummockManager,
248    table_ids: &[impl Into<TableId> + Copy],
249    compaction_group_id: CompactionGroupId,
250) {
251    hummock_manager_ref
252        .register_table_ids_for_test(
253            &table_ids
254                .iter()
255                .map(|table_id| (*table_id, compaction_group_id))
256                .collect_vec(),
257        )
258        .await
259        .unwrap();
260}
261
262pub async fn unregister_table_ids_from_compaction_group(
263    hummock_manager_ref: &HummockManager,
264    table_ids: &[impl Into<TableId> + Copy],
265) {
266    hummock_manager_ref
267        .unregister_table_ids(table_ids.iter().map(|table_id| (*table_id).into()))
268        .await
269        .unwrap();
270}
271
272/// Generate keys like `001_key_test_00002` with timestamp `epoch`.
273pub fn iterator_test_key_of_epoch(table: u64, idx: usize, ts: HummockEpoch) -> Vec<u8> {
274    // key format: {prefix_index}_version
275    key_with_epoch(
276        format!("{:03}\0\0_key_test_{:05}", table, idx)
277            .as_bytes()
278            .to_vec(),
279        ts,
280    )
281}
282
283pub fn get_sorted_object_ids(sstables: &[SstableInfo]) -> Vec<HummockSstableObjectId> {
284    sstables
285        .iter()
286        .map(|table| table.object_id)
287        .sorted()
288        .collect_vec()
289}
290
291pub fn get_sorted_committed_object_ids(
292    hummock_version: &HummockVersion,
293    compaction_group_id: CompactionGroupId,
294) -> Vec<HummockSstableObjectId> {
295    let levels = match hummock_version.levels.get(&compaction_group_id) {
296        Some(levels) => levels,
297        None => return vec![],
298    };
299    levels
300        .levels
301        .iter()
302        .chain(levels.l0.sub_levels.iter())
303        .flat_map(|levels| levels.table_infos.iter().map(|info| info.object_id))
304        .sorted()
305        .collect_vec()
306}
307
308pub async fn setup_compute_env_with_config(
309    port: i32,
310    config: CompactionConfig,
311) -> (
312    MetaSrvEnv,
313    HummockManagerRef,
314    ClusterControllerRef,
315    WorkerId,
316) {
317    setup_compute_env_with_metric(port, config, None).await
318}
319
320pub async fn setup_compute_env_with_metric(
321    port: i32,
322    config: CompactionConfig,
323    meta_metric: Option<MetaMetrics>,
324) -> (
325    MetaSrvEnv,
326    HummockManagerRef,
327    ClusterControllerRef,
328    WorkerId,
329) {
330    let env = MetaSrvEnv::for_test().await;
331    let cluster_ctl = Arc::new(
332        ClusterController::new(env.clone(), Duration::from_secs(1))
333            .await
334            .unwrap(),
335    );
336    let catalog_ctl = Arc::new(CatalogController::new(env.clone()).await.unwrap());
337
338    let compactor_manager = Arc::new(CompactorManager::for_test());
339
340    let (compactor_streams_change_tx, _compactor_streams_change_rx) =
341        tokio::sync::mpsc::unbounded_channel();
342
343    let hummock_manager = HummockManager::with_config(
344        env.clone(),
345        cluster_ctl.clone(),
346        catalog_ctl,
347        Arc::new(meta_metric.unwrap_or_default()),
348        compactor_manager,
349        config,
350        compactor_streams_change_tx,
351    )
352    .await;
353
354    let fake_host_address = HostAddress {
355        host: "127.0.0.1".to_owned(),
356        port,
357    };
358    let fake_parallelism = 4;
359    let worker_id = cluster_ctl
360        .add_worker(
361            WorkerType::ComputeNode,
362            fake_host_address,
363            Property {
364                is_streaming: true,
365                is_serving: true,
366                parallelism: fake_parallelism as _,
367                ..Default::default()
368            },
369            Default::default(),
370        )
371        .await
372        .unwrap();
373    (env, hummock_manager, cluster_ctl, worker_id)
374}
375
376pub async fn setup_compute_env(
377    port: i32,
378) -> (
379    MetaSrvEnv,
380    HummockManagerRef,
381    ClusterControllerRef,
382    WorkerId,
383) {
384    let config = CompactionConfigBuilder::new()
385        .level0_tier_compact_file_number(1)
386        .level0_max_compact_file_number(130)
387        .level0_sub_level_compact_level_count(1)
388        .level0_overlapping_sub_level_compact_level_count(1)
389        .build();
390    setup_compute_env_with_config(port, config).await
391}
392
393pub async fn get_sst_ids(hummock_manager: &HummockManager, number: u32) -> Vec<u64> {
394    let range = hummock_manager.get_new_object_ids(number).await.unwrap();
395    (range.start_id..range.end_id)
396        .map(|id| id.as_raw_id())
397        .collect_vec()
398}
399
400pub async fn add_ssts(
401    epoch: HummockEpoch,
402    hummock_manager: &HummockManager,
403    hummock_meta_client: Arc<dyn HummockMetaClient>,
404) -> Vec<SstableInfo> {
405    let table_ids = get_sst_ids(hummock_manager, 3).await;
406    let test_tables = generate_test_sstables_with_table_id(test_epoch(epoch), 1, table_ids);
407    let ssts = to_local_sstable_info(&test_tables);
408    hummock_meta_client
409        .commit_epoch(
410            epoch,
411            SyncResult {
412                uncommitted_ssts: ssts,
413                ..Default::default()
414            },
415        )
416        .await
417        .unwrap();
418    test_tables
419}
420
421pub fn compaction_selector_context<'a>(
422    group: &'a CompactionGroup,
423    levels: &'a Levels,
424    member_table_ids: &'a BTreeSet<TableId>,
425    level_handlers: &'a mut [LevelHandler],
426    selector_stats: &'a mut LocalSelectorStatistic,
427    table_id_to_options: &'a HashMap<TableId, TableOption>,
428    developer_config: Arc<CompactionDeveloperConfig>,
429    table_watermarks: &'a HashMap<TableId, Arc<TableWatermarks>>,
430    state_table_info: &'a HummockVersionStateTableInfo,
431) -> CompactionSelectorContext<'a> {
432    CompactionSelectorContext {
433        group,
434        levels,
435        member_table_ids,
436        level_handlers,
437        selector_stats,
438        table_id_to_options,
439        developer_config,
440        table_watermarks,
441        state_table_info,
442        in_progress_compactions: &EMPTY_IN_PROGRESS_COMPACTION_VIEW,
443    }
444}
445
446pub async fn get_compaction_group_id_by_table_id(
447    hummock_manager_ref: HummockManagerRef,
448    table_id: impl Into<TableId>,
449) -> CompactionGroupId {
450    let version = hummock_manager_ref.get_current_version().await;
451    let mapping = version.state_table_info.build_table_compaction_group_id();
452    *mapping.get(&table_id.into()).unwrap()
453}