Skip to main content

risingwave_hummock_test/
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
15use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18use bytes::Bytes;
19use itertools::Itertools;
20use risingwave_common::catalog::TableId;
21use risingwave_common::config::Role;
22use risingwave_common::hash::VirtualNode;
23use risingwave_common::id::WorkerId;
24use risingwave_common_service::ObserverManager;
25use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId;
26use risingwave_hummock_sdk::key::TableKey;
27pub use risingwave_hummock_sdk::key::{gen_key_from_bytes, gen_key_from_str};
28use risingwave_hummock_sdk::vector_index::VectorIndexDelta;
29use risingwave_meta::controller::cluster::ClusterControllerRef;
30use risingwave_meta::hummock::test_utils::{
31    register_table_ids_to_compaction_group, setup_compute_env,
32};
33use risingwave_meta::hummock::{
34    CommitEpochInfo, HummockManagerRef, MockHummockMetaClient, NewTableFragmentInfo,
35};
36use risingwave_meta::manager::MetaSrvEnv;
37use risingwave_pb::catalog::{PbTable, Table};
38use risingwave_pb::hummock::vector_index_delta::PbVectorIndexInit;
39use risingwave_rpc_client::HummockMetaClient;
40use risingwave_storage::compaction_catalog_manager::{
41    CompactionCatalogManager, CompactionCatalogManagerRef,
42};
43use risingwave_storage::error::StorageResult;
44use risingwave_storage::hummock::HummockStorage;
45use risingwave_storage::hummock::backup_reader::BackupReader;
46use risingwave_storage::hummock::event_handler::{HummockObserverEvent, HummockVersionUpdate};
47use risingwave_storage::hummock::iterator::test_utils::mock_sstable_store;
48use risingwave_storage::hummock::local_version::pinned_version::PinnedVersion;
49use risingwave_storage::hummock::observer_manager::HummockObserverNode;
50use risingwave_storage::hummock::test_utils::*;
51use risingwave_storage::hummock::write_limiter::WriteLimiter;
52use risingwave_storage::storage_value::StorageValue;
53use risingwave_storage::store::*;
54use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
55
56use crate::mock_notification_client::get_notification_client_for_test;
57
58pub async fn prepare_first_valid_version(
59    env: MetaSrvEnv,
60    hummock_manager_ref: HummockManagerRef,
61    cluster_controller_ref: ClusterControllerRef,
62    worker_id: WorkerId,
63) -> (
64    PinnedVersion,
65    UnboundedSender<HummockObserverEvent>,
66    UnboundedReceiver<HummockObserverEvent>,
67) {
68    let (observer_event_tx, mut observer_event_rx) = unbounded_channel();
69
70    let notification_client = get_notification_client_for_test(
71        env,
72        hummock_manager_ref.clone(),
73        cluster_controller_ref,
74        worker_id,
75    )
76    .await;
77    let backup_manager = BackupReader::unused().await;
78    let write_limiter = WriteLimiter::unused();
79    let observer_manager = ObserverManager::new(
80        notification_client,
81        HummockObserverNode::new(
82            Role::None,
83            Arc::new(CompactionCatalogManager::default()),
84            backup_manager,
85            observer_event_tx.clone(),
86            write_limiter,
87        ),
88    )
89    .await;
90    observer_manager.start().await;
91    let hummock_version = match observer_event_rx.recv().await {
92        Some(HummockObserverEvent::VersionUpdate(HummockVersionUpdate::PinnedVersion(version))) => {
93            version
94        }
95        _ => unreachable!("should be full version"),
96    };
97
98    (
99        PinnedVersion::new(*hummock_version, unbounded_channel().0),
100        observer_event_tx,
101        observer_event_rx,
102    )
103}
104
105#[async_trait::async_trait]
106pub trait TestIngestBatch: LocalStateStore {
107    async fn ingest_batch(
108        &mut self,
109        kv_pairs: Vec<(TableKey<Bytes>, StorageValue)>,
110    ) -> StorageResult<usize>;
111}
112
113#[async_trait::async_trait]
114impl<S: LocalStateStore> TestIngestBatch for S {
115    async fn ingest_batch(
116        &mut self,
117        kv_pairs: Vec<(TableKey<Bytes>, StorageValue)>,
118    ) -> StorageResult<usize> {
119        for (key, value) in kv_pairs {
120            match value.user_value {
121                None => self.delete(key, Bytes::new())?,
122                Some(value) => self.insert(key, value, None)?,
123            }
124        }
125        self.flush().await
126    }
127}
128
129pub async fn with_hummock_storage(
130    table_id: TableId,
131) -> (HummockStorage, Arc<MockHummockMetaClient>) {
132    let sstable_store = mock_sstable_store().await;
133    let hummock_options = Arc::new(default_opts_for_test());
134    let (env, hummock_manager_ref, cluster_ctl_ref, worker_id) = setup_compute_env(8080).await;
135    let meta_client = Arc::new(MockHummockMetaClient::new(
136        hummock_manager_ref.clone(),
137        worker_id as _,
138    ));
139
140    let hummock_storage = HummockStorage::for_test(
141        hummock_options,
142        sstable_store,
143        meta_client.clone(),
144        get_notification_client_for_test(
145            env,
146            hummock_manager_ref.clone(),
147            cluster_ctl_ref,
148            worker_id,
149        )
150        .await,
151    )
152    .await
153    .unwrap();
154
155    register_tables_with_id_for_test(
156        hummock_storage.compaction_catalog_manager_ref(),
157        &hummock_manager_ref,
158        &[table_id],
159    )
160    .await;
161
162    (hummock_storage, meta_client)
163}
164
165pub fn update_filter_key_extractor_for_table_ids(
166    compaction_catalog_manager_ref: CompactionCatalogManagerRef,
167    table_ids: &[TableId],
168) {
169    for table_id in table_ids {
170        let mock_table = PbTable {
171            id: *table_id,
172            // The low-level hummock tests register table IDs without a real table catalog, but
173            // some of them still pass full-key prefix hints explicitly. Use an invalid prefix
174            // length to keep the legacy full-key filter extractor in these tests.
175            read_prefix_len_hint: 1,
176            maybe_vnode_count: Some(VirtualNode::COUNT_FOR_TEST as u32),
177            ..Default::default()
178        };
179        compaction_catalog_manager_ref.update(*table_id, mock_table);
180    }
181}
182
183pub async fn register_tables_with_id_for_test(
184    compaction_catalog_manager_ref: CompactionCatalogManagerRef,
185    hummock_manager_ref: &HummockManagerRef,
186    table_ids: &[TableId],
187) {
188    update_filter_key_extractor_for_table_ids(compaction_catalog_manager_ref, table_ids);
189    register_table_ids_to_compaction_group(
190        hummock_manager_ref,
191        table_ids,
192        StaticCompactionGroupId::StateDefault,
193    )
194    .await;
195}
196
197pub fn update_filter_key_extractor_for_tables(
198    compaction_catalog_manager_ref: CompactionCatalogManagerRef,
199    tables: &[PbTable],
200) {
201    for table in tables {
202        compaction_catalog_manager_ref.update(table.id, table.clone())
203    }
204}
205pub async fn register_tables_with_catalog_for_test(
206    compaction_catalog_manager_ref: CompactionCatalogManagerRef,
207    hummock_manager_ref: &HummockManagerRef,
208    tables: &[Table],
209) {
210    update_filter_key_extractor_for_tables(compaction_catalog_manager_ref, tables);
211    let table_ids = tables.iter().map(|t| t.id).collect_vec();
212    register_table_ids_to_compaction_group(
213        hummock_manager_ref,
214        &table_ids,
215        StaticCompactionGroupId::StateDefault,
216    )
217    .await;
218}
219
220pub struct HummockTestEnv {
221    pub storage: HummockStorage,
222    pub manager: HummockManagerRef,
223    pub meta_client: Arc<MockHummockMetaClient>,
224}
225
226impl HummockTestEnv {
227    async fn wait_version_sync(&self) {
228        self.storage
229            .wait_version(self.manager.get_current_version().await)
230            .await
231    }
232
233    pub async fn register_table_id(&self, table_id: TableId) {
234        register_tables_with_id_for_test(
235            self.storage.compaction_catalog_manager_ref(),
236            &self.manager,
237            &[table_id],
238        )
239        .await;
240        self.wait_version_sync().await;
241    }
242
243    pub async fn register_vector_index(
244        &self,
245        table_id: TableId,
246        init_epoch: u64,
247        init_config: PbVectorIndexInit,
248    ) {
249        self.manager
250            .commit_epoch(CommitEpochInfo {
251                sstables: vec![],
252                new_table_watermarks: Default::default(),
253                sst_to_context: Default::default(),
254                new_table_fragment_infos: vec![NewTableFragmentInfo {
255                    table_ids: HashSet::from_iter([table_id]),
256                }],
257                change_log_delta: Default::default(),
258                vector_index_delta: HashMap::from_iter([(
259                    table_id,
260                    VectorIndexDelta::Init(init_config),
261                )]),
262                tables_to_commit: HashMap::from_iter([(table_id, init_epoch)]),
263                truncate_tables: HashSet::new(),
264            })
265            .await
266            .unwrap();
267    }
268
269    pub async fn register_table(&self, table: PbTable) {
270        register_tables_with_catalog_for_test(
271            self.storage.compaction_catalog_manager_ref(),
272            &self.manager,
273            &[table],
274        )
275        .await;
276        self.wait_version_sync().await;
277    }
278
279    // Seal, sync and commit a epoch.
280    // On completion of this function call, the provided epoch should be committed and visible.
281    pub async fn commit_epoch(&self, epoch: u64) {
282        let table_ids = self
283            .manager
284            .get_current_version()
285            .await
286            .state_table_info
287            .info()
288            .keys()
289            .cloned()
290            .collect();
291        let res = self
292            .storage
293            .seal_and_sync_epoch(epoch, table_ids)
294            .await
295            .unwrap();
296        self.meta_client.commit_epoch(epoch, res).await.unwrap();
297
298        self.wait_sync_committed_version().await;
299    }
300
301    pub async fn wait_sync_committed_version(&self) {
302        let version = self.manager.get_current_version().await;
303        self.storage.wait_version(version).await;
304    }
305}
306
307pub async fn prepare_hummock_test_env() -> HummockTestEnv {
308    let sstable_store = mock_sstable_store().await;
309    let hummock_options = Arc::new(default_opts_for_test());
310    let (env, hummock_manager_ref, cluster_ctl_ref, worker_id) = setup_compute_env(8080).await;
311
312    let hummock_meta_client = Arc::new(MockHummockMetaClient::new(
313        hummock_manager_ref.clone(),
314        worker_id as _,
315    ));
316
317    let notification_client = get_notification_client_for_test(
318        env,
319        hummock_manager_ref.clone(),
320        cluster_ctl_ref,
321        worker_id,
322    )
323    .await;
324
325    let storage = HummockStorage::for_test(
326        hummock_options,
327        sstable_store,
328        hummock_meta_client.clone(),
329        notification_client,
330    )
331    .await
332    .unwrap();
333
334    HummockTestEnv {
335        storage,
336        manager: hummock_manager_ref,
337        meta_client: hummock_meta_client,
338    }
339}