Skip to main content

risingwave_meta/hummock/
mock_hummock_meta_client.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::{BTreeSet, HashSet};
16use std::sync::Arc;
17use std::sync::atomic::{AtomicU32, Ordering};
18use std::time::SystemTime;
19
20use anyhow::anyhow;
21use async_trait::async_trait;
22use fail::fail_point;
23use futures::stream::BoxStream;
24use futures::{Stream, StreamExt};
25use itertools::Itertools;
26use risingwave_hummock_sdk::change_log::{TableChangeLogs, build_table_change_log_delta};
27use risingwave_hummock_sdk::compact_task::CompactTask;
28use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId;
29use risingwave_hummock_sdk::sstable_info::SstableInfo;
30use risingwave_hummock_sdk::vector_index::VectorIndexDelta;
31use risingwave_hummock_sdk::version::HummockVersion;
32use risingwave_hummock_sdk::{
33    CompactionGroupId, HummockContextId, HummockEpoch, HummockVersionId, LocalSstableInfo,
34    ObjectIdRange, SyncResult,
35};
36use risingwave_pb::common::{HostAddress, WorkerType};
37use risingwave_pb::hummock::compact_task::{TaskStatus, TaskType};
38use risingwave_pb::hummock::subscribe_compaction_event_request::{Event, ReportTask};
39use risingwave_pb::hummock::subscribe_compaction_event_response::Event as ResponseEvent;
40use risingwave_pb::hummock::{
41    PbHummockVersion, SubscribeCompactionEventRequest, SubscribeCompactionEventResponse,
42    compact_task,
43};
44use risingwave_pb::iceberg_compaction::SubscribeIcebergCompactionEventRequest;
45use risingwave_pb::id::{HummockSstableId, JobId, TableId};
46use risingwave_rpc_client::error::{Result, RpcError};
47use risingwave_rpc_client::{
48    CompactionEventItem, HummockMetaClient, HummockMetaClientChangeLogInfo,
49    IcebergCompactionEventItem,
50};
51use thiserror_ext::AsReport;
52use tokio::sync::mpsc::{UnboundedSender, unbounded_channel};
53use tokio::task::JoinHandle;
54use tokio_stream::wrappers::UnboundedReceiverStream;
55
56use crate::hummock::compaction::selector::{
57    CompactionSelector, SpaceReclaimCompactionSelector, default_compaction_selector,
58};
59use crate::hummock::error::Error;
60use crate::hummock::{CommitEpochInfo, HummockManager, NewTableFragmentInfo};
61
62pub struct MockHummockMetaClient {
63    hummock_manager: Arc<HummockManager>,
64    context_id: HummockContextId,
65    compact_context_id: AtomicU32,
66    // used for hummock replay to avoid collision with existing sst files
67    sst_offset: u64,
68}
69
70impl MockHummockMetaClient {
71    pub fn new(
72        hummock_manager: Arc<HummockManager>,
73        context_id: HummockContextId,
74    ) -> MockHummockMetaClient {
75        MockHummockMetaClient {
76            hummock_manager,
77            context_id,
78            compact_context_id: AtomicU32::new(context_id.as_raw_id()),
79            sst_offset: 0,
80        }
81    }
82
83    pub fn with_sst_offset(
84        hummock_manager: Arc<HummockManager>,
85        context_id: HummockContextId,
86        sst_offset: u64,
87    ) -> Self {
88        Self {
89            hummock_manager,
90            context_id,
91            compact_context_id: AtomicU32::new(context_id.as_raw_id()),
92            sst_offset,
93        }
94    }
95
96    pub async fn get_compact_task(&self) -> Option<CompactTask> {
97        self.hummock_manager
98            .get_compact_task(
99                self.compact_context_id.load(Ordering::Acquire).into(),
100                StaticCompactionGroupId::StateDefault,
101                &mut *default_compaction_selector(),
102            )
103            .await
104            .unwrap_or(None)
105    }
106
107    pub fn context_id(&self) -> HummockContextId {
108        self.context_id
109    }
110}
111
112fn mock_err(error: super::error::Error) -> RpcError {
113    anyhow!(error).context("mock error").into()
114}
115
116#[async_trait]
117impl HummockMetaClient for MockHummockMetaClient {
118    async fn unpin_version_before(&self, unpin_version_before: HummockVersionId) -> Result<()> {
119        self.hummock_manager
120            .unpin_version_before(self.context_id, unpin_version_before)
121            .await
122            .map_err(mock_err)
123    }
124
125    async fn get_current_version(&self) -> Result<HummockVersion> {
126        Ok(self.hummock_manager.get_current_version().await)
127    }
128
129    async fn get_new_object_ids(&self, number: u32) -> Result<ObjectIdRange> {
130        fail_point!("get_new_sst_ids_err", |_| Err(anyhow!(
131            "failpoint get_new_sst_ids_err"
132        )
133        .into()));
134        self.hummock_manager
135            .get_new_object_ids(number)
136            .await
137            .map_err(mock_err)
138            .map(|range| ObjectIdRange {
139                start_id: range.start_id + self.sst_offset,
140                end_id: range.end_id + self.sst_offset,
141            })
142    }
143
144    async fn commit_epoch_with_change_log(
145        &self,
146        epoch: HummockEpoch,
147        sync_result: SyncResult,
148        change_log_info: Option<HummockMetaClientChangeLogInfo>,
149    ) -> Result<()> {
150        let version: HummockVersion = self.hummock_manager.get_current_version().await;
151        let table_ids = version
152            .state_table_info
153            .info()
154            .keys()
155            .copied()
156            .collect::<BTreeSet<_>>();
157
158        let commit_table_ids = sync_result
159            .uncommitted_ssts
160            .iter()
161            .flat_map(|sstable| sstable.sst_info.table_ids.clone())
162            .chain({
163                sync_result
164                    .old_value_ssts
165                    .iter()
166                    .flat_map(|sstable| sstable.sst_info.table_ids.clone())
167            })
168            .chain(sync_result.table_watermarks.keys().copied())
169            .chain(table_ids.iter().cloned())
170            .collect::<BTreeSet<_>>();
171
172        let new_table_fragment_infos = if commit_table_ids
173            .iter()
174            .all(|table_id| table_ids.contains(table_id))
175        {
176            vec![]
177        } else {
178            vec![NewTableFragmentInfo {
179                table_ids: commit_table_ids.iter().cloned().collect(),
180            }]
181        };
182
183        let sst_to_context = sync_result
184            .uncommitted_ssts
185            .iter()
186            .map(|LocalSstableInfo { sst_info, .. }| (sst_info.object_id, self.context_id))
187            .collect();
188        let new_table_watermark = sync_result.table_watermarks;
189        let table_change_log = match change_log_info {
190            Some(epochs) => {
191                assert_eq!(*epochs.last().expect("non-empty"), epoch);
192                build_table_change_log_delta(
193                    sync_result
194                        .old_value_ssts
195                        .into_iter()
196                        .map(|sst| sst.sst_info),
197                    sync_result.uncommitted_ssts.iter().map(|sst| &sst.sst_info),
198                    &epochs,
199                    commit_table_ids.iter().copied(),
200                )
201            }
202            None => Default::default(),
203        };
204
205        self.hummock_manager
206            .commit_epoch(CommitEpochInfo {
207                sstables: sync_result.uncommitted_ssts,
208                new_table_watermarks: new_table_watermark,
209                sst_to_context,
210                new_table_fragment_infos,
211                change_log_delta: table_change_log,
212                vector_index_delta: sync_result
213                    .vector_index_adds
214                    .into_iter()
215                    .map(|(table_id, adds)| (table_id, VectorIndexDelta::Adds(adds)))
216                    .collect(),
217                tables_to_commit: commit_table_ids
218                    .iter()
219                    .cloned()
220                    .map(|table_id| (table_id, epoch))
221                    .collect(),
222                table_change_log_progress: Vec::new(),
223                truncate_tables: HashSet::new(),
224            })
225            .await
226            .map_err(mock_err)?;
227        Ok(())
228    }
229
230    async fn trigger_manual_compaction(
231        &self,
232        _compaction_group_id: CompactionGroupId,
233        _table_id: JobId,
234        _level: u32,
235        _target_level: Option<u32>,
236        _sst_ids: Vec<HummockSstableId>,
237        _exclusive: bool,
238    ) -> Result<bool> {
239        todo!()
240    }
241
242    async fn trigger_full_gc(
243        &self,
244        _sst_retention_time_sec: u64,
245        _prefix: Option<String>,
246    ) -> Result<()> {
247        unimplemented!()
248    }
249
250    async fn subscribe_compaction_event(
251        &self,
252    ) -> Result<(
253        UnboundedSender<SubscribeCompactionEventRequest>,
254        BoxStream<'static, CompactionEventItem>,
255    )> {
256        let context_id = self
257            .hummock_manager
258            .metadata_manager()
259            .add_worker_node(
260                WorkerType::Compactor,
261                HostAddress {
262                    host: "compactor".to_owned(),
263                    port: 0,
264                },
265                Default::default(),
266                Default::default(),
267            )
268            .await
269            .unwrap();
270        let _compactor_rx = self
271            .hummock_manager
272            .compactor_manager
273            .clone()
274            .add_compactor(context_id);
275
276        let (request_sender, mut request_receiver) =
277            unbounded_channel::<SubscribeCompactionEventRequest>();
278
279        self.compact_context_id
280            .store(context_id.as_raw_id(), Ordering::Release);
281
282        let (task_tx, task_rx) = tokio::sync::mpsc::unbounded_channel();
283
284        let hummock_manager_compact = self.hummock_manager.clone();
285        let mut join_handle_vec = vec![];
286
287        let handle = tokio::spawn(async move {
288            loop {
289                let snapshot = hummock_manager_compact.compaction_state.snapshot();
290                let Some((groups, task_type)) = snapshot.pick_compaction_groups_and_type() else {
291                    break;
292                };
293
294                let group = groups[0];
295
296                if let TaskType::Ttl = task_type {
297                    match hummock_manager_compact
298                        .metadata_manager_ref()
299                        .get_all_table_options()
300                        .await
301                        .map_err(|err| Error::MetaStore(err.into()))
302                    {
303                        Ok(table_options) => {
304                            hummock_manager_compact.update_table_id_to_table_option(table_options);
305                        }
306                        Err(e) => {
307                            tracing::error!(error = %e.as_report(), "get_all_table_options fail");
308                        }
309                    }
310                }
311
312                let mut selector: Box<dyn CompactionSelector> = match task_type {
313                    compact_task::TaskType::Dynamic => default_compaction_selector(),
314                    compact_task::TaskType::SpaceReclaim => {
315                        Box::<SpaceReclaimCompactionSelector>::default()
316                    }
317
318                    _ => panic!("Error type when mock_hummock_meta_client subscribe_compact_tasks"),
319                };
320                if let Some(task) = hummock_manager_compact
321                    .get_compact_task(context_id, group, &mut *selector)
322                    .await
323                    .unwrap()
324                {
325                    let resp = SubscribeCompactionEventResponse {
326                        event: Some(ResponseEvent::CompactTask(task.into())),
327                        create_at: SystemTime::now()
328                            .duration_since(std::time::UNIX_EPOCH)
329                            .expect("Clock may have gone backwards")
330                            .as_millis() as u64,
331                    };
332
333                    let _ = task_tx.send(Ok(resp));
334                }
335            }
336        });
337
338        join_handle_vec.push(handle);
339
340        let hummock_manager_compact = self.hummock_manager.clone();
341        let report_handle = tokio::spawn(async move {
342            tracing::info!("report_handle start");
343            loop {
344                if let Some(item) = request_receiver.recv().await
345                    && let Event::ReportTask(ReportTask {
346                        task_id,
347                        task_status,
348                        sorted_output_ssts,
349                        table_stats_change,
350                        object_timestamps,
351                    }) = item.event.unwrap()
352                    && let Err(e) = hummock_manager_compact
353                        .report_compact_task(
354                            task_id,
355                            TaskStatus::try_from(task_status).unwrap(),
356                            sorted_output_ssts
357                                .into_iter()
358                                .map(SstableInfo::from)
359                                .collect_vec(),
360                            Some(table_stats_change),
361                            object_timestamps,
362                        )
363                        .await
364                {
365                    tracing::error!(error = %e.as_report(), "report compact_tack fail");
366                }
367            }
368        });
369
370        join_handle_vec.push(report_handle);
371
372        Ok((
373            request_sender,
374            Box::pin(CompactionEventItemStream {
375                inner: UnboundedReceiverStream::new(task_rx),
376                _handle: join_handle_vec,
377            }),
378        ))
379    }
380
381    async fn get_version_by_epoch(
382        &self,
383        _epoch: HummockEpoch,
384        _table_id: TableId,
385    ) -> Result<PbHummockVersion> {
386        unimplemented!()
387    }
388
389    async fn subscribe_iceberg_compaction_event(
390        &self,
391    ) -> Result<(
392        UnboundedSender<SubscribeIcebergCompactionEventRequest>,
393        BoxStream<'static, IcebergCompactionEventItem>,
394    )> {
395        unimplemented!()
396    }
397
398    async fn get_table_change_logs(
399        &self,
400        epoch_only: bool,
401        start_epoch_inclusive: Option<u64>,
402        end_epoch_inclusive: Option<u64>,
403        table_ids: Option<HashSet<TableId>>,
404        exclude_empty: bool,
405        limit: Option<u32>,
406    ) -> Result<TableChangeLogs> {
407        self.hummock_manager
408            .get_table_change_logs(
409                epoch_only,
410                start_epoch_inclusive,
411                end_epoch_inclusive,
412                table_ids,
413                exclude_empty,
414                limit,
415            )
416            .await
417            .map_err(mock_err)
418    }
419}
420
421impl MockHummockMetaClient {
422    pub fn hummock_manager_ref(&self) -> Arc<HummockManager> {
423        self.hummock_manager.clone()
424    }
425}
426
427pub struct CompactionEventItemStream {
428    inner: UnboundedReceiverStream<CompactionEventItem>,
429    _handle: Vec<JoinHandle<()>>,
430}
431
432impl Drop for CompactionEventItemStream {
433    fn drop(&mut self) {
434        self.inner.close();
435    }
436}
437
438impl Stream for CompactionEventItemStream {
439    type Item = CompactionEventItem;
440
441    fn poll_next(
442        mut self: std::pin::Pin<&mut Self>,
443        cx: &mut std::task::Context<'_>,
444    ) -> std::task::Poll<Option<Self::Item>> {
445        self.inner.poll_next_unpin(cx)
446    }
447}