risingwave_storage/hummock/local_version/
pinned_version.rs1use std::collections::BTreeMap;
16use std::iter::empty;
17use std::ops::Deref;
18use std::sync::{Arc, LazyLock};
19use std::time::{Duration, Instant};
20
21use auto_enums::auto_enum;
22use risingwave_common::catalog::TableId;
23use risingwave_common::log::LogSuppressor;
24use risingwave_common::util::retry::exponential_backoff;
25use risingwave_hummock_sdk::level::{Level, Levels};
26use risingwave_hummock_sdk::version::HummockVersion;
27use risingwave_hummock_sdk::{CompactionGroupId, HummockVersionId, INVALID_VERSION_ID};
28use risingwave_rpc_client::HummockMetaClient;
29use thiserror_ext::AsReport;
30use tokio::sync::mpsc::error::TryRecvError;
31use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
32use tokio_retry::strategy::jitter;
33
34#[derive(Debug, Clone)]
35pub enum PinVersionAction {
36 Pin(HummockVersionId),
37 Unpin(HummockVersionId),
38}
39
40struct PinnedVersionGuard {
41 version_id: HummockVersionId,
42 pinned_version_manager_tx: UnboundedSender<PinVersionAction>,
43}
44
45impl PinnedVersionGuard {
46 fn new(
48 version_id: HummockVersionId,
49 pinned_version_manager_tx: UnboundedSender<PinVersionAction>,
50 ) -> Self {
51 if pinned_version_manager_tx
52 .send(PinVersionAction::Pin(version_id))
53 .is_err()
54 {
55 tracing::warn!("failed to send req pin version id{}", version_id);
56 }
57
58 Self {
59 version_id,
60 pinned_version_manager_tx,
61 }
62 }
63}
64
65impl Drop for PinnedVersionGuard {
66 fn drop(&mut self) {
67 if self
68 .pinned_version_manager_tx
69 .send(PinVersionAction::Unpin(self.version_id))
70 .is_err()
71 {
72 static LOG_SUPPRESSOR: LazyLock<LogSuppressor> =
73 LazyLock::new(|| LogSuppressor::per_second(1));
74 if let Ok(suppressed_count) = LOG_SUPPRESSOR.check() {
75 tracing::warn!(
76 suppressed_count,
77 version_id = %self.version_id,
78 "failed to send req unpin"
79 );
80 }
81 }
82 }
83}
84
85#[derive(Clone)]
86pub struct PinnedVersion {
87 version: Arc<HummockVersion>,
88 guard: Arc<PinnedVersionGuard>,
89}
90
91impl Deref for PinnedVersion {
92 type Target = HummockVersion;
93
94 fn deref(&self) -> &Self::Target {
95 &self.version
96 }
97}
98
99impl PinnedVersion {
100 pub fn new(
101 version: HummockVersion,
102 pinned_version_manager_tx: UnboundedSender<PinVersionAction>,
103 ) -> Self {
104 let version_id = version.id;
105 PinnedVersion {
106 guard: Arc::new(PinnedVersionGuard::new(
107 version_id,
108 pinned_version_manager_tx,
109 )),
110 version: Arc::new(version),
111 }
112 }
113
114 pub fn new_pin_version(&self, version: HummockVersion) -> Option<Self> {
115 assert!(
116 version.id >= self.version.id,
117 "pinning a older version {}. Current is {}",
118 version.id,
119 self.version.id
120 );
121 if version.id == self.version.id {
122 return None;
123 }
124 let version_id = version.id;
125 Some(PinnedVersion {
126 guard: Arc::new(PinnedVersionGuard::new(
127 version_id,
128 self.guard.pinned_version_manager_tx.clone(),
129 )),
130 version: Arc::new(version),
131 })
132 }
133
134 pub fn new_with_local_version(&self, version: HummockVersion) -> Option<Self> {
137 assert!(
138 version.id >= self.version.id,
139 "pinning a older version {}. Current is {}",
140 version.id,
141 self.version.id
142 );
143 if version.id == self.version.id {
144 return None;
145 }
146
147 let version_id = version.id;
148
149 Some(PinnedVersion {
150 guard: Arc::new(PinnedVersionGuard::new(
151 version_id,
152 self.guard.pinned_version_manager_tx.clone(),
153 )),
154 version: Arc::new(version),
155 })
156 }
157
158 pub fn id(&self) -> HummockVersionId {
159 self.version.id
160 }
161
162 pub fn is_valid(&self) -> bool {
163 self.version.id != INVALID_VERSION_ID
164 }
165
166 fn levels_by_compaction_groups_id(&self, compaction_group_id: CompactionGroupId) -> &Levels {
167 self.version
168 .levels
169 .get(&compaction_group_id)
170 .unwrap_or_else(|| {
171 panic!(
172 "levels for compaction group {} not found in version {}",
173 compaction_group_id,
174 self.id()
175 )
176 })
177 }
178
179 pub fn levels(&self, table_id: TableId) -> impl Iterator<Item = &Level> {
180 #[auto_enum(Iterator)]
181 match self.version.state_table_info.info().get(&table_id) {
182 Some(info) => {
183 let compaction_group_id = info.compaction_group_id;
184 let levels = self.levels_by_compaction_groups_id(compaction_group_id);
185 levels
186 .l0
187 .sub_levels
188 .iter()
189 .rev()
190 .chain(levels.levels.iter())
191 }
192 None => empty(),
193 }
194 }
195}
196
197pub(crate) async fn start_pinned_version_worker(
198 mut rx: UnboundedReceiver<PinVersionAction>,
199 hummock_meta_client: Arc<dyn HummockMetaClient>,
200 max_version_pinning_duration_sec: u64,
201) {
202 let min_execute_interval = Duration::from_millis(1000);
203 let max_retry_interval = Duration::from_secs(10);
204 let get_backoff_strategy =
205 || exponential_backoff(Duration::from_millis(10), 10, max_retry_interval).map(jitter);
206 let mut retry_backoff = get_backoff_strategy();
207 let mut min_execute_interval_tick = tokio::time::interval(min_execute_interval);
208 min_execute_interval_tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
209 let mut need_unpin = false;
210
211 let mut version_ids_in_use: BTreeMap<HummockVersionId, (usize, Instant)> = BTreeMap::new();
212 let max_version_pinning_duration_sec = Duration::from_secs(max_version_pinning_duration_sec);
213 loop {
215 min_execute_interval_tick.tick().await;
216 while version_ids_in_use.len() > 1
218 && let Some(e) = version_ids_in_use.first_entry()
219 {
220 if e.get().1.elapsed() < max_version_pinning_duration_sec {
221 break;
222 }
223 need_unpin = true;
224 e.remove();
225 }
226
227 let mut versions_to_unpin = vec![];
229 let inst = Instant::now();
230 'collect: loop {
231 match rx.try_recv() {
232 Ok(version_action) => match version_action {
233 PinVersionAction::Pin(version_id) => {
234 version_ids_in_use
235 .entry(version_id)
236 .and_modify(|e| {
237 e.0 += 1;
238 e.1 = inst;
239 })
240 .or_insert((1, inst));
241 }
242 PinVersionAction::Unpin(version_id) => {
243 versions_to_unpin.push(version_id);
244 }
245 },
246 Err(err) => match err {
247 TryRecvError::Empty => {
248 break 'collect;
249 }
250 TryRecvError::Disconnected => {
251 tracing::info!("Shutdown hummock unpin worker");
252 return;
253 }
254 },
255 }
256 }
257 if !versions_to_unpin.is_empty() {
258 need_unpin = true;
259 }
260 if !need_unpin {
261 continue;
262 }
263
264 for version in &versions_to_unpin {
265 match version_ids_in_use.get_mut(version) {
266 Some((counter, _)) => {
267 *counter -= 1;
268 if *counter == 0 {
269 version_ids_in_use.remove(version);
270 }
271 }
272 None => tracing::warn!(
273 "version {} to unpin does not exist, may already be unpinned due to expiration",
274 version
275 ),
276 }
277 }
278
279 match version_ids_in_use.first_entry() {
280 Some(unpin_before) => {
281 match hummock_meta_client
283 .unpin_version_before(*unpin_before.key())
284 .await
285 {
286 Ok(_) => {
287 versions_to_unpin.clear();
288 need_unpin = false;
289 retry_backoff = get_backoff_strategy();
290 }
291 Err(err) => {
292 let retry_after = retry_backoff.next().unwrap_or(max_retry_interval);
293 tracing::warn!(
294 error = %err.as_report(),
295 "Failed to unpin version. Will retry after about {} milliseconds",
296 retry_after.as_millis()
297 );
298 tokio::time::sleep(retry_after).await;
299 }
300 }
301 }
302 None => tracing::warn!("version_ids_in_use is empty!"),
303 }
304 }
305}