1use 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 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}