risingwave_meta/manager/iceberg_compaction/
mod.rs1mod gc;
16mod manual;
17mod schedule;
18mod stream;
19#[cfg(test)]
20pub(crate) mod test_utils;
21
22use std::collections::{BTreeMap, HashMap, HashSet};
23use std::sync::Arc;
24use std::time::Duration;
25
26use anyhow::anyhow;
27use parking_lot::{Mutex, RwLock};
28use risingwave_common::id::WorkerId;
29use risingwave_connector::sink::SinkParam;
30use risingwave_connector::sink::catalog::{SinkCatalog, SinkId};
31use risingwave_connector::sink::iceberg::{ICEBERG_SINK, IcebergConfig};
32use risingwave_connector::source::UPSTREAM_SOURCE_KEY;
33use risingwave_pb::iceberg_compaction::SubscribeIcebergCompactionEventRequest;
34use risingwave_pb::id::IcebergCompactionTaskId;
35use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
36use tonic::Streaming;
37
38use super::MetaSrvEnv;
39use crate::MetaResult;
40use crate::barrier::BarrierScheduler;
41use crate::hummock::IcebergCompactorManagerRef;
42use crate::manager::MetadataManager;
43use crate::rpc::metrics::MetaMetrics;
44
45pub type IcebergCompactionManagerRef = Arc<IcebergCompactionManager>;
46
47pub(crate) type CompactorChangeTx =
48 UnboundedSender<(WorkerId, Streaming<SubscribeIcebergCompactionEventRequest>)>;
49
50pub(crate) type CompactorChangeRx =
51 UnboundedReceiver<(WorkerId, Streaming<SubscribeIcebergCompactionEventRequest>)>;
52
53type ManualCompactionWaiter = tokio::sync::oneshot::Sender<MetaResult<IcebergCompactionTaskId>>;
54
55use schedule::CompactionTrack;
56pub use schedule::IcebergCompactionScheduleStatus;
57
58pub struct IcebergCompactionManager {
59 pub env: MetaSrvEnv,
60 inner: Arc<RwLock<IcebergCompactionManagerInner>>,
61
62 metadata_manager: MetadataManager,
63 barrier_scheduler: BarrierScheduler,
64 pub iceberg_compactor_manager: IcebergCompactorManagerRef,
65
66 compactor_streams_change_tx: CompactorChangeTx,
67
68 orphan_file_cleanup_locks: Mutex<HashMap<SinkId, Arc<tokio::sync::Mutex<()>>>>,
69
70 pub metrics: Arc<MetaMetrics>,
71}
72
73struct IcebergCompactionManagerInner {
74 sink_schedules: HashMap<SinkId, CompactionTrack>,
75 sink_recovery: HashMap<SinkId, SinkRecoveryState>,
77 next_recovery_generation: u64,
79 snapshot_expiration_sink_ids: HashSet<SinkId>,
80 manifest_rewrite_sink_ids: HashSet<SinkId>,
81 orphan_file_cleanup_sink_ids: HashSet<SinkId>,
82 manual_compaction_waiters: HashMap<SinkId, ManualCompactionWaiter>,
83}
84
85#[derive(Default)]
86struct SinkRecoveryState {
87 generation: u64,
88 recovering: bool,
89}
90
91impl IcebergCompactionManagerInner {
92 fn next_recovery_generation(&mut self) -> u64 {
93 self.next_recovery_generation += 1;
94 self.next_recovery_generation
95 }
96
97 fn recovery_generation(&self, sink_id: SinkId) -> u64 {
98 self.sink_recovery
99 .get(&sink_id)
100 .map_or(0, |state| state.generation)
101 }
102
103 fn is_recovering(&self, sink_id: SinkId) -> bool {
104 self.sink_recovery
105 .get(&sink_id)
106 .is_some_and(|state| state.recovering)
107 }
108}
109
110impl IcebergCompactionManager {
111 fn report_timeout(&self) -> Duration {
112 Duration::from_secs(self.env.opts.iceberg_compaction_report_timeout_sec)
113 }
114
115 fn config_refresh_interval(&self) -> Duration {
116 Duration::from_secs(self.env.opts.iceberg_compaction_config_refresh_interval_sec)
117 }
118
119 pub fn build(
120 env: MetaSrvEnv,
121 metadata_manager: MetadataManager,
122 iceberg_compactor_manager: IcebergCompactorManagerRef,
123 metrics: Arc<MetaMetrics>,
124 barrier_scheduler: BarrierScheduler,
125 ) -> (Arc<Self>, CompactorChangeRx) {
126 let (compactor_streams_change_tx, compactor_streams_change_rx) =
127 tokio::sync::mpsc::unbounded_channel();
128 (
129 Arc::new(Self {
130 env,
131 inner: Arc::new(RwLock::new(IcebergCompactionManagerInner {
132 sink_schedules: HashMap::default(),
133 sink_recovery: HashMap::default(),
134 next_recovery_generation: 0,
135 snapshot_expiration_sink_ids: HashSet::default(),
136 manifest_rewrite_sink_ids: HashSet::default(),
137 orphan_file_cleanup_sink_ids: HashSet::default(),
138 manual_compaction_waiters: HashMap::default(),
139 })),
140 metadata_manager,
141 barrier_scheduler,
142 iceberg_compactor_manager,
143 compactor_streams_change_tx,
144 orphan_file_cleanup_locks: Mutex::new(HashMap::default()),
145 metrics,
146 }),
147 compactor_streams_change_rx,
148 )
149 }
150
151 async fn get_sink_param(&self, sink_id: SinkId) -> MetaResult<SinkParam> {
152 let prost_sink_catalog = self
153 .metadata_manager
154 .catalog_controller
155 .get_sink_by_id(sink_id)
156 .await?
157 .ok_or_else(|| anyhow!("Sink not found: {}", sink_id))?;
158 let sink_catalog = SinkCatalog::from(prost_sink_catalog);
159 let param = SinkParam::try_from_sink_catalog(sink_catalog)?;
160 Ok(param)
161 }
162
163 async fn load_iceberg_config(&self, sink_id: SinkId) -> MetaResult<IcebergConfig> {
164 let registration = self
165 .inner
166 .read()
167 .sink_schedules
168 .get(&sink_id)
169 .and_then(|track| track.pk_index_registration().cloned());
170 if let Some(registration) = registration {
171 if !registration.is_ready() {
172 return Err(anyhow!("iceberg pk-index sink {} is not ready", sink_id).into());
173 }
174 return Ok((*registration.config()).clone());
175 }
176 let sink_param = self.get_sink_param(sink_id).await?;
177 let iceberg_config = IcebergConfig::from_btreemap(sink_param.properties)?;
178 Ok(iceberg_config)
179 }
180
181 fn orphan_file_cleanup_lock(&self, sink_id: SinkId) -> Arc<tokio::sync::Mutex<()>> {
182 self.orphan_file_cleanup_locks
183 .lock()
184 .entry(sink_id)
185 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
186 .clone()
187 }
188}
189
190pub fn is_iceberg_sink(properties: &BTreeMap<String, String>) -> bool {
193 properties
194 .get(UPSTREAM_SOURCE_KEY)
195 .is_some_and(|connector| connector.eq_ignore_ascii_case(ICEBERG_SINK))
196}