1use std::collections::{BTreeMap, HashMap};
16use std::ops::{Deref, DerefMut};
17use std::sync::Arc;
18use std::sync::atomic::AtomicBool;
19
20use anyhow::anyhow;
21use bytes::Bytes;
22use futures::FutureExt;
23use itertools::Itertools;
24use parking_lot::lock_api::RwLock;
25use risingwave_common::catalog::{TableId, TableOption};
26use risingwave_common::monitor::MonitoredRwLock;
27use risingwave_common::system_param::reader::SystemParamsRead;
28use risingwave_hummock_sdk::change_log::TableChangeLog;
29use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta};
30use risingwave_hummock_sdk::{
31 CompactionGroupId, HummockCompactionTaskId, HummockContextId, HummockVersionId,
32 version_archive_dir, version_checkpoint_path,
33};
34use risingwave_meta_model::{
35 compaction_status, compaction_task, hummock_pinned_version, hummock_version_delta,
36 hummock_version_stats,
37};
38use risingwave_pb::hummock::compact_task::TaskStatus;
39use risingwave_pb::hummock::{
40 HummockVersionStats, PbCompactionGroupInfo, SubscribeCompactionEventRequest,
41};
42use table_write_throughput_statistic::TableWriteThroughputStatisticManager;
43use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
44use tokio::sync::{Mutex, Semaphore, oneshot};
45use tonic::Streaming;
46
47use crate::MetaResult;
48use crate::hummock::CompactorManagerRef;
49use crate::hummock::compaction::CompactStatus;
50use crate::hummock::error::Result;
51use crate::hummock::manager::checkpoint::HummockVersionCheckpoint;
52use crate::hummock::manager::context::ContextInfo;
53use crate::hummock::manager::gc::{FullGcState, GcManager};
54use crate::hummock::manager::sequence::PrefetchedSequence;
55use crate::hummock::model::ext::{compaction_task_model_to_assignment, to_table_change_log};
56use crate::manager::{MetaSrvEnv, MetadataManager};
57use crate::model::{ClusterId, MetadataModelError};
58use crate::rpc::metrics::MetaMetrics;
59
60mod context;
61mod gc;
62mod tests;
63mod versioning;
64pub use context::HummockVersionSafePoint;
65use versioning::*;
66pub(crate) mod checkpoint;
67mod commit_epoch;
68mod compaction;
69pub mod sequence;
70pub mod table_write_throughput_statistic;
71pub mod time_travel;
72mod timer_task;
73mod transaction;
74mod utils;
75mod worker;
76
77pub use commit_epoch::{CommitEpochInfo, NewTableFragmentInfo};
78pub use compaction::compaction_event_loop::*;
79use compaction::*;
80pub use compaction::{GroupState, GroupStateValidator, ManualCompactionTriggerResult};
81pub(crate) use utils::*;
82
83struct TableCommittedEpochNotifiers {
84 txs: HashMap<TableId, Vec<UnboundedSender<u64>>>,
85}
86
87impl TableCommittedEpochNotifiers {
88 fn notify_deltas(&mut self, deltas: &[HummockVersionDelta]) {
89 self.txs.retain(|table_id, txs| {
90 let mut is_dropped = false;
91 let mut committed_epoch = None;
92 for delta in deltas {
93 if delta.removed_table_ids.contains(table_id) {
94 is_dropped = true;
95 break;
96 }
97 if let Some(info) = delta.state_table_info_delta.get(table_id) {
98 committed_epoch = Some(info.committed_epoch);
99 }
100 }
101 if is_dropped {
102 false
103 } else if let Some(committed_epoch) = committed_epoch {
104 txs.retain(|tx| tx.send(committed_epoch).is_ok());
105 !txs.is_empty()
106 } else {
107 true
108 }
109 })
110 }
111}
112
113#[derive(Clone, Debug)]
114struct CompactionTaskReportResult {
115 task_id: HummockCompactionTaskId,
116
117 task_status: TaskStatus,
118 reported: bool,
119}
120
121struct CompactionTaskReportNotifiers {
122 txs: HashMap<HummockCompactionTaskId, Vec<oneshot::Sender<CompactionTaskReportResult>>>,
123}
124
125impl CompactionTaskReportNotifiers {
126 fn register(
127 &mut self,
128 task_id: HummockCompactionTaskId,
129 tx: oneshot::Sender<CompactionTaskReportResult>,
130 ) {
131 self.txs.entry(task_id).or_default().push(tx);
132 }
133
134 fn remove(&mut self, task_id: HummockCompactionTaskId) {
135 self.txs.remove(&task_id);
136 }
137
138 fn notify(&mut self, result: CompactionTaskReportResult) {
139 if let Some(txs) = self.txs.remove(&result.task_id) {
140 for tx in txs {
141 let _ = tx.send(result.clone());
142 }
143 }
144 }
145}
146pub struct HummockManager {
152 pub env: MetaSrvEnv,
153
154 metadata_manager: MetadataManager,
155 compaction: MonitoredRwLock<Compaction>,
159 versioning: MonitoredRwLock<Versioning>,
160 compaction_group_manager: MonitoredRwLock<CompactionGroupManager>,
162 context_info: MonitoredRwLock<ContextInfo>,
163
164 pub metrics: Arc<MetaMetrics>,
165
166 pub compactor_manager: CompactorManagerRef,
167 pub iceberg_compactor_manager: Arc<IcebergCompactorManager>,
168 event_sender: HummockManagerEventSender,
169 object_store: ObjectStoreRef,
170 version_checkpoint_path: String,
171 version_archive_dir: String,
172 pause_version_checkpoint: AtomicBool,
173 table_write_throughput_statistic_manager:
174 parking_lot::RwLock<TableWriteThroughputStatisticManager>,
175 table_committed_epoch_notifiers: parking_lot::Mutex<TableCommittedEpochNotifiers>,
176 compaction_task_report_notifiers: parking_lot::Mutex<CompactionTaskReportNotifiers>,
177 version_stat_tx: UnboundedSender<Arc<HummockVersion>>,
178
179 compactor_streams_change_tx:
183 UnboundedSender<(HummockContextId, Streaming<SubscribeCompactionEventRequest>)>,
184
185 pub compaction_state: CompactionState,
188 full_gc_state: Arc<FullGcState>,
189 now: Mutex<u64>,
190 inflight_time_travel_query: Semaphore,
191 gc_manager: GcManager,
192
193 prefetched_compaction_task_ids: PrefetchedSequence,
195 table_id_to_table_option: parking_lot::RwLock<HashMap<TableId, TableOption>>,
196}
197
198pub type HummockManagerRef = Arc<HummockManager>;
199
200use risingwave_object_store::object::{ObjectError, ObjectStoreRef, build_remote_object_store};
201use risingwave_pb::catalog::Table;
202
203use super::IcebergCompactorManager;
204use crate::controller::SqlMetaStore;
205use crate::hummock::manager::compaction_group_manager::CompactionGroupManager;
206use crate::hummock::manager::worker::HummockManagerEventSender;
207
208impl HummockManager {
209 pub async fn new(
210 env: MetaSrvEnv,
211 metadata_manager: MetadataManager,
212 metrics: Arc<MetaMetrics>,
213 compactor_manager: CompactorManagerRef,
214 compactor_streams_change_tx: UnboundedSender<(
215 HummockContextId,
216 Streaming<SubscribeCompactionEventRequest>,
217 )>,
218 ) -> Result<HummockManagerRef> {
219 let compaction_group_manager = CompactionGroupManager::new(&env).await?;
220 Self::new_impl(
221 env,
222 metadata_manager,
223 metrics,
224 compactor_manager,
225 compaction_group_manager,
226 compactor_streams_change_tx,
227 )
228 .await
229 }
230
231 #[cfg(any(test, feature = "test"))]
232 pub(super) async fn with_config(
233 env: MetaSrvEnv,
234 cluster_controller: crate::controller::cluster::ClusterControllerRef,
235 catalog_controller: crate::controller::catalog::CatalogControllerRef,
236 metrics: Arc<MetaMetrics>,
237 compactor_manager: CompactorManagerRef,
238 config: risingwave_pb::hummock::CompactionConfig,
239 compactor_streams_change_tx: UnboundedSender<(
240 HummockContextId,
241 Streaming<SubscribeCompactionEventRequest>,
242 )>,
243 ) -> HummockManagerRef {
244 let compaction_group_manager = CompactionGroupManager::new_with_config(&env, config)
245 .await
246 .unwrap();
247 let metadata_manager = MetadataManager::new(cluster_controller, catalog_controller);
248 Self::new_impl(
249 env,
250 metadata_manager,
251 metrics,
252 compactor_manager,
253 compaction_group_manager,
254 compactor_streams_change_tx,
255 )
256 .await
257 .unwrap()
258 }
259
260 async fn new_impl(
261 env: MetaSrvEnv,
262 metadata_manager: MetadataManager,
263 metrics: Arc<MetaMetrics>,
264 compactor_manager: CompactorManagerRef,
265 compaction_group_manager: CompactionGroupManager,
266 compactor_streams_change_tx: UnboundedSender<(
267 HummockContextId,
268 Streaming<SubscribeCompactionEventRequest>,
269 )>,
270 ) -> Result<HummockManagerRef> {
271 let sys_params = env.system_params_reader().await;
272 let state_store_url = sys_params.state_store();
273
274 let state_store_dir: &str = sys_params.data_directory();
275 let use_new_object_prefix_strategy: bool = sys_params.use_new_object_prefix_strategy();
276 let deterministic_mode = env.opts.compaction_deterministic_test;
277 let mut object_store_config = env.opts.object_store_config.clone();
278 object_store_config.set_atomic_write_dir();
281 let object_store = Arc::new(
282 build_remote_object_store(
283 state_store_url.strip_prefix("hummock+").unwrap_or("memory"),
284 metrics.object_store_metric.clone(),
285 "Version Checkpoint",
286 Arc::new(object_store_config),
287 )
288 .await,
289 );
290 if !deterministic_mode {
294 write_exclusive_cluster_id(
295 state_store_dir,
296 env.cluster_id().clone(),
297 object_store.clone(),
298 )
299 .await?;
300
301 if let risingwave_object_store::object::ObjectStoreImpl::S3(s3) = object_store.as_ref()
303 && !env.opts.do_not_config_object_storage_lifecycle
304 {
305 let is_bucket_expiration_configured =
306 s3.inner().configure_bucket_lifecycle(state_store_dir).await;
307 if is_bucket_expiration_configured {
308 return Err(ObjectError::internal("Cluster cannot start with object expiration configured for bucket because RisingWave data will be lost when object expiration kicks in.
309 Please disable object expiration and restart the cluster.")
310 .into());
311 }
312 }
313 }
314 let version_checkpoint_path = version_checkpoint_path(state_store_dir);
315 let version_archive_dir = version_archive_dir(state_store_dir);
316 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
317 let (version_stat_tx, mut version_stat_rx) = tokio::sync::mpsc::unbounded_channel();
318 let inflight_time_travel_query = env.opts.max_inflight_time_travel_query;
319 let gc_manager = GcManager::new(
320 object_store.clone(),
321 state_store_dir,
322 use_new_object_prefix_strategy,
323 );
324
325 let max_table_statistic_expired_time = std::cmp::max(
326 env.opts.table_stat_throuput_window_seconds_for_split,
327 env.opts.table_stat_throuput_window_seconds_for_merge,
328 ) as i64;
329
330 let iceberg_compactor_manager = Arc::new(IcebergCompactorManager::new());
331
332 let instance = HummockManager {
333 env,
334 versioning: MonitoredRwLock::new(
335 metrics.hummock_manager_lock_time.clone(),
336 metrics.hummock_manager_real_process_time.clone(),
337 Default::default(),
338 "hummock_manager::versioning",
339 ),
340 compaction: MonitoredRwLock::new(
341 metrics.hummock_manager_lock_time.clone(),
342 metrics.hummock_manager_real_process_time.clone(),
343 Default::default(),
344 "hummock_manager::compaction",
345 ),
346 compaction_group_manager: MonitoredRwLock::new(
347 metrics.hummock_manager_lock_time.clone(),
348 metrics.hummock_manager_real_process_time.clone(),
349 compaction_group_manager,
350 "hummock_manager::compaction_group_manager",
351 ),
352 context_info: MonitoredRwLock::new(
353 metrics.hummock_manager_lock_time.clone(),
354 metrics.hummock_manager_real_process_time.clone(),
355 Default::default(),
356 "hummock_manager::context_info",
357 ),
358 metrics,
359 metadata_manager,
360 compactor_manager,
361 iceberg_compactor_manager,
362 event_sender: tx,
363 object_store,
364 version_checkpoint_path,
365 version_archive_dir,
366 pause_version_checkpoint: AtomicBool::new(false),
367 table_write_throughput_statistic_manager: parking_lot::RwLock::new(
368 TableWriteThroughputStatisticManager::new(max_table_statistic_expired_time),
369 ),
370 table_committed_epoch_notifiers: parking_lot::Mutex::new(
371 TableCommittedEpochNotifiers {
372 txs: Default::default(),
373 },
374 ),
375 compaction_task_report_notifiers: parking_lot::Mutex::new(
376 CompactionTaskReportNotifiers {
377 txs: Default::default(),
378 },
379 ),
380 version_stat_tx,
381 compactor_streams_change_tx,
382 compaction_state: CompactionState::new(),
383 full_gc_state: FullGcState::new().into(),
384 now: Mutex::new(0),
385 inflight_time_travel_query: Semaphore::new(inflight_time_travel_query as usize),
386 gc_manager,
387 prefetched_compaction_task_ids: PrefetchedSequence::new(),
388 table_id_to_table_option: RwLock::new(HashMap::new()),
389 };
390 let instance = Arc::new(instance);
391 let version_stat_metrics = instance.metrics.clone();
392 tokio::spawn(async move {
393 while let Some(mut version) = version_stat_rx.recv().await {
394 while let Some(Some(next_version)) = version_stat_rx.recv().now_or_never() {
395 version = next_version;
396 }
397 transaction::trigger_version_stat(&version_stat_metrics, version.as_ref());
398 }
399 });
400 instance.init_time_travel_state().await?;
401 instance.load_meta_store_state().await?;
402 instance.may_fill_backward_table_change_logs().await?;
404 instance.start_worker(rx);
405 instance.release_invalid_contexts().await?;
406 instance.release_meta_context().await?;
408 Ok(instance)
409 }
410
411 fn meta_store_ref(&self) -> &SqlMetaStore {
412 self.env.meta_store_ref()
413 }
414
415 async fn load_meta_store_state(&self) -> Result<()> {
417 let now = self.load_now().await?;
418 *self.now.lock().await = now.unwrap_or(0);
419
420 let mut compaction_guard = self.compaction.write().await;
421 let mut versioning_guard = self.versioning.write().await;
422 let mut context_info_guard = self.context_info.write().await;
423 self.load_meta_store_state_impl(
424 &mut compaction_guard,
425 &mut versioning_guard,
426 &mut context_info_guard,
427 )
428 .await
429 }
430
431 async fn load_meta_store_state_impl(
433 &self,
434 compaction_guard: &mut Compaction,
435 versioning_guard: &mut Versioning,
436 context_info: &mut ContextInfo,
437 ) -> Result<()> {
438 use sea_orm::EntityTrait;
439 let meta_store = self.meta_store_ref();
440 let compaction_statuses: BTreeMap<CompactionGroupId, CompactStatus> =
441 compaction_status::Entity::find()
442 .all(&meta_store.conn)
443 .await
444 .map_err(MetadataModelError::from)?
445 .into_iter()
446 .map(|m| (m.compaction_group_id as CompactionGroupId, m.into()))
447 .collect();
448 if !compaction_statuses.is_empty() {
449 compaction_guard.compaction_statuses = compaction_statuses;
450 }
451
452 compaction_guard.compact_task_assignment = compaction_task::Entity::find()
453 .all(&meta_store.conn)
454 .await
455 .map_err(MetadataModelError::from)?
456 .into_iter()
457 .map(|m| {
458 (
459 m.id as HummockCompactionTaskId,
460 compaction_task_model_to_assignment(m),
461 )
462 })
463 .collect();
464
465 let hummock_version_deltas: BTreeMap<HummockVersionId, HummockVersionDelta> =
466 hummock_version_delta::Entity::find()
467 .all(&meta_store.conn)
468 .await
469 .map_err(MetadataModelError::from)?
470 .into_iter()
471 .map(|m| {
472 (
473 m.id,
474 HummockVersionDelta::from_persisted_protobuf_owned(m.into()),
475 )
476 })
477 .collect();
478
479 let checkpoint = self.try_read_checkpoint().await?;
480 let mut redo_state = if let Some(c) = checkpoint {
481 versioning_guard.checkpoint = c;
482 versioning_guard.checkpoint.version.as_ref().clone()
483 } else {
484 let default_compaction_config = self
485 .compaction_group_manager
486 .read()
487 .await
488 .default_compaction_config();
489 let checkpoint_version = HummockVersion::create_init_version(default_compaction_config);
490 tracing::info!("init hummock version checkpoint");
491 versioning_guard.checkpoint = HummockVersionCheckpoint {
492 version: Arc::new(checkpoint_version.clone()),
493 stale_objects: Default::default(),
494 };
495 self.write_checkpoint(&versioning_guard.checkpoint).await?;
496 checkpoint_version
497 };
498 let mut applied_delta_count = 0;
499 let total_to_apply = hummock_version_deltas.range(redo_state.id + 1..).count();
500 tracing::info!(
501 total_delta = hummock_version_deltas.len(),
502 total_to_apply,
503 "Start redo Hummock version."
504 );
505 for version_delta in hummock_version_deltas
506 .range(redo_state.id + 1..)
507 .map(|(_, v)| v)
508 {
509 assert_eq!(
510 version_delta.prev_id, redo_state.id,
511 "delta prev_id {}, redo state id {}",
512 version_delta.prev_id, redo_state.id
513 );
514 redo_state.apply_version_delta(version_delta);
515 redo_state.apply_table_change_log_delta_backward_compatibility(version_delta);
517 applied_delta_count += 1;
518 if applied_delta_count % 1000 == 0 {
519 tracing::info!("Redo progress {applied_delta_count}/{total_to_apply}.");
520 }
521 }
522 tracing::info!("Finish redo Hummock version.");
523 let pruned_stale_table_id_count = redo_state.prune_stale_table_ids_from_ssts();
524 if pruned_stale_table_id_count > 0 {
525 tracing::warn!(
526 pruned_stale_table_id_count,
527 version_id = ?redo_state.id,
528 "Pruned stale table ids from recovered Hummock SST metadata."
529 );
530 }
531 versioning_guard.version_stats = hummock_version_stats::Entity::find()
532 .one(&meta_store.conn)
533 .await
534 .map_err(MetadataModelError::from)?
535 .map(HummockVersionStats::from)
536 .unwrap_or_else(|| HummockVersionStats {
537 hummock_version_id: 0.into(),
539 ..Default::default()
540 });
541
542 versioning_guard.current_version = Arc::new(redo_state);
543 versioning_guard.hummock_version_deltas = hummock_version_deltas;
544 versioning_guard.table_change_log =
545 risingwave_meta_model::hummock_table_change_log::Entity::find()
546 .all(&self.env.meta_store_ref().conn)
547 .await
548 .map_err(MetadataModelError::from)?
549 .into_iter()
550 .map(|m| (m.table_id, to_table_change_log(m)))
551 .into_group_map()
552 .into_iter()
553 .map(|(table_id, unordered_change_logs)| {
554 (
555 table_id,
556 TableChangeLog::new(
557 unordered_change_logs
558 .into_iter()
559 .sorted_by_key(|l| l.checkpoint_epoch),
560 ),
561 )
562 })
563 .collect();
564
565 context_info.pinned_versions = hummock_pinned_version::Entity::find()
566 .all(&meta_store.conn)
567 .await
568 .map_err(MetadataModelError::from)?
569 .into_iter()
570 .map(|m| (m.context_id as HummockContextId, m.into()))
571 .collect();
572
573 self.initial_compaction_group_config_after_load(
574 versioning_guard,
575 self.compaction_group_manager.write().await.deref_mut(),
576 )
577 .await?;
578
579 Ok(())
580 }
581
582 pub fn init_metadata_for_version_replay(
583 &self,
584 _table_catalogs: Vec<Table>,
585 _compaction_groups: Vec<PbCompactionGroupInfo>,
586 ) -> Result<()> {
587 unimplemented!("kv meta store is deprecated");
588 }
589
590 #[cfg(any(test, feature = "test"))]
594 pub async fn replay_version_delta(
595 &self,
596 mut version_delta: HummockVersionDelta,
597 ) -> Result<(HummockVersion, Vec<CompactionGroupId>)> {
598 let mut versioning_guard = self.versioning.write().await;
599 version_delta.id = versioning_guard.current_version.next_version_id();
601 version_delta.prev_id = versioning_guard.current_version.id;
602 let mut version_new = versioning_guard.current_version.as_ref().clone();
603 version_new.apply_version_delta(&version_delta);
604
605 let compaction_group_ids = version_delta.group_deltas.keys().cloned().collect();
606 versioning_guard.current_version = Arc::new(version_new.clone());
607 Ok((version_new, compaction_group_ids))
608 }
609
610 pub async fn disable_commit_epoch(&self) -> Arc<HummockVersion> {
611 let mut versioning_guard = self.versioning.write().await;
612 versioning_guard.disable_commit_epochs = true;
613 versioning_guard.current_version.clone()
614 }
615
616 pub fn metadata_manager(&self) -> &MetadataManager {
617 &self.metadata_manager
618 }
619
620 pub fn object_store_media_type(&self) -> &'static str {
621 self.object_store.media_type()
622 }
623
624 pub fn update_table_id_to_table_option(
625 &self,
626 new_table_id_to_table_option: HashMap<TableId, TableOption>,
627 ) {
628 *self.table_id_to_table_option.write() = new_table_id_to_table_option;
629 }
630
631 pub fn metadata_manager_ref(&self) -> &MetadataManager {
632 &self.metadata_manager
633 }
634
635 pub async fn subscribe_table_committed_epoch(
636 &self,
637 table_id: TableId,
638 ) -> MetaResult<(u64, UnboundedReceiver<u64>)> {
639 let version = self.versioning.read().await;
640 if let Some(epoch) = version.current_version.table_committed_epoch(table_id) {
641 let (tx, rx) = unbounded_channel();
642 self.table_committed_epoch_notifiers
643 .lock()
644 .txs
645 .entry(table_id)
646 .or_default()
647 .push(tx);
648 Ok((epoch, rx))
649 } else {
650 Err(anyhow!("table {} not exist", table_id).into())
651 }
652 }
653}
654
655async fn write_exclusive_cluster_id(
656 state_store_dir: &str,
657 cluster_id: ClusterId,
658 object_store: ObjectStoreRef,
659) -> Result<()> {
660 const CLUSTER_ID_DIR: &str = "cluster_id";
661 const CLUSTER_ID_NAME: &str = "0";
662 let cluster_id_dir = format!("{}/{}/", state_store_dir, CLUSTER_ID_DIR);
663 let cluster_id_full_path = format!("{}{}", cluster_id_dir, CLUSTER_ID_NAME);
664 tracing::info!("try reading cluster_id");
665 match object_store.read(&cluster_id_full_path, ..).await {
666 Ok(stored_cluster_id) => {
667 let stored_cluster_id = String::from_utf8(stored_cluster_id.to_vec()).unwrap();
668 if cluster_id.deref() == stored_cluster_id {
669 return Ok(());
670 }
671
672 Err(ObjectError::internal(format!(
673 "Data directory is already used by another cluster with id {:?}, path {}.",
674 stored_cluster_id, cluster_id_full_path,
675 ))
676 .into())
677 }
678 Err(e) => {
679 if e.is_object_not_found_error() {
680 tracing::info!("cluster_id not found, writing cluster_id");
681 object_store
682 .upload(&cluster_id_full_path, Bytes::from(String::from(cluster_id)))
683 .await?;
684 return Ok(());
685 }
686 Err(e.into())
687 }
688 }
689}