1use std::time::Duration;
16
17use serde::{Deserialize, Serialize};
18use snafu::ensure;
19
20use crate::error::{self, Result};
21
22#[allow(unused)]
24pub(crate) const TICKER_INTERVAL: Duration = Duration::from_secs(60 * 5);
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28#[serde(default)]
29pub struct SoftDropGcOptions {
30 pub enable: bool,
32 #[serde(with = "humantime_serde")]
34 pub retention: Duration,
35}
36
37impl Default for SoftDropGcOptions {
38 fn default() -> Self {
39 Self {
40 enable: false,
41 retention: Duration::from_days(7),
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(default)]
51pub struct GcSchedulerOptions {
52 pub enable: bool,
56 pub experimental_soft_drop: SoftDropGcOptions,
58 pub max_concurrent_tables: usize,
60 pub max_retries_per_region: usize,
62 pub region_gc_concurrency: usize,
64 #[serde(with = "humantime_serde")]
66 pub retry_backoff_duration: Duration,
67 pub min_region_size_threshold: u64,
69 pub sst_count_weight: f64,
71 pub file_removed_count_weight: f64,
73 #[serde(with = "humantime_serde")]
75 pub gc_cooldown_period: Duration,
76 pub regions_per_table_threshold: usize,
78 #[serde(with = "humantime_serde")]
80 pub mailbox_timeout: Duration,
81 #[serde(with = "humantime_serde")]
86 pub full_file_listing_interval: Duration,
87 #[serde(with = "humantime_serde")]
91 pub tracker_cleanup_interval: Duration,
92}
93
94impl Default for GcSchedulerOptions {
95 fn default() -> Self {
96 Self {
97 enable: false,
98 experimental_soft_drop: SoftDropGcOptions::default(),
99 max_concurrent_tables: 10,
100 max_retries_per_region: 3,
101 retry_backoff_duration: Duration::from_secs(5),
102 region_gc_concurrency: 16,
103 min_region_size_threshold: 100 * 1024 * 1024, sst_count_weight: 0.5, file_removed_count_weight: 1.0, gc_cooldown_period: Duration::from_secs(60 * 5), regions_per_table_threshold: 20, mailbox_timeout: Duration::from_secs(60), full_file_listing_interval: Duration::from_secs(60 * 60 * 24),
111 tracker_cleanup_interval: Duration::from_secs(60 * 60 * 6),
113 }
114 }
115}
116
117impl GcSchedulerOptions {
118 pub fn validate(&self) -> Result<()> {
120 #[cfg(not(feature = "enterprise"))]
121 ensure!(
122 !self.experimental_soft_drop.enable,
123 error::InvalidArgumentsSnafu {
124 err_msg: "gc.experimental_soft_drop.enable is only available in GreptimeDB Enterprise Edition",
125 }
126 );
127
128 ensure!(
129 !self.experimental_soft_drop.enable || self.enable,
130 error::InvalidArgumentsSnafu {
131 err_msg: "gc.enable must be true when soft drop is enabled",
132 }
133 );
134
135 if !self.enable {
136 return Ok(());
137 }
138
139 if self.experimental_soft_drop.enable {
140 ensure!(
141 self.experimental_soft_drop.retention.as_millis() > 0,
142 error::InvalidArgumentsSnafu {
143 err_msg: "soft drop retention must be at least 1ms",
144 }
145 );
146 ensure!(
147 self.experimental_soft_drop.retention.as_millis() <= i64::MAX as u128,
148 error::InvalidArgumentsSnafu {
149 err_msg: "soft drop retention must fit in an i64 millisecond value",
150 }
151 );
152 }
153
154 ensure!(
155 self.max_concurrent_tables > 0,
156 error::InvalidArgumentsSnafu {
157 err_msg: "max_concurrent_tables must be greater than 0",
158 }
159 );
160
161 ensure!(
162 self.max_retries_per_region > 0,
163 error::InvalidArgumentsSnafu {
164 err_msg: "max_retries_per_region must be greater than 0",
165 }
166 );
167
168 ensure!(
169 self.region_gc_concurrency > 0,
170 error::InvalidArgumentsSnafu {
171 err_msg: "region_gc_concurrency must be greater than 0",
172 }
173 );
174
175 ensure!(
176 !self.retry_backoff_duration.is_zero(),
177 error::InvalidArgumentsSnafu {
178 err_msg: "retry_backoff_duration must be greater than 0",
179 }
180 );
181
182 ensure!(
183 self.sst_count_weight >= 0.0,
184 error::InvalidArgumentsSnafu {
185 err_msg: "sst_count_weight must be non-negative",
186 }
187 );
188
189 ensure!(
190 self.file_removed_count_weight >= 0.0,
191 error::InvalidArgumentsSnafu {
192 err_msg: "file_removal_rate_weight must be non-negative",
193 }
194 );
195
196 ensure!(
197 !self.gc_cooldown_period.is_zero(),
198 error::InvalidArgumentsSnafu {
199 err_msg: "gc_cooldown_period must be greater than 0",
200 }
201 );
202
203 ensure!(
204 self.regions_per_table_threshold > 0,
205 error::InvalidArgumentsSnafu {
206 err_msg: "regions_per_table_threshold must be greater than 0",
207 }
208 );
209
210 ensure!(
211 !self.mailbox_timeout.is_zero(),
212 error::InvalidArgumentsSnafu {
213 err_msg: "mailbox_timeout must be greater than 0",
214 }
215 );
216
217 ensure!(
218 !self.full_file_listing_interval.is_zero(),
219 error::InvalidArgumentsSnafu {
220 err_msg: "full_file_listing_interval must be greater than 0",
221 }
222 );
223
224 ensure!(
225 !self.tracker_cleanup_interval.is_zero(),
226 error::InvalidArgumentsSnafu {
227 err_msg: "tracker_cleanup_interval must be greater than 0",
228 }
229 );
230
231 Ok(())
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_soft_drop_defaults() {
241 let options = GcSchedulerOptions::default();
242
243 assert!(!options.experimental_soft_drop.enable);
244 assert_eq!(
245 Duration::from_days(7),
246 options.experimental_soft_drop.retention
247 );
248 }
249
250 #[cfg(feature = "enterprise")]
251 #[test]
252 fn test_soft_drop_valid_when_gc_is_enabled() {
253 let options = GcSchedulerOptions {
254 enable: true,
255 experimental_soft_drop: SoftDropGcOptions {
256 enable: true,
257 retention: Duration::from_days(1),
258 },
259 ..Default::default()
260 };
261
262 assert!(options.validate().is_ok());
263 }
264
265 #[cfg(not(feature = "enterprise"))]
266 #[test]
267 fn test_soft_drop_rejected_in_non_enterprise_build() {
268 let options = GcSchedulerOptions {
269 enable: true,
270 experimental_soft_drop: SoftDropGcOptions {
271 enable: true,
272 retention: Duration::from_days(1),
273 },
274 ..Default::default()
275 };
276
277 let err = options.validate().unwrap_err();
278 assert!(err.to_string().contains("Enterprise Edition"));
279 }
280
281 #[cfg(feature = "enterprise")]
282 #[test]
283 fn test_soft_drop_requires_gc() {
284 let options = GcSchedulerOptions {
285 experimental_soft_drop: SoftDropGcOptions {
286 enable: true,
287 retention: Duration::from_days(1),
288 },
289 ..Default::default()
290 };
291
292 assert!(options.validate().is_err());
293 }
294
295 #[cfg(feature = "enterprise")]
296 #[test]
297 fn test_soft_drop_retention_must_be_positive() {
298 let options = GcSchedulerOptions {
299 enable: true,
300 experimental_soft_drop: SoftDropGcOptions {
301 enable: true,
302 retention: Duration::ZERO,
303 },
304 ..Default::default()
305 };
306
307 assert!(options.validate().is_err());
308 }
309
310 #[cfg(feature = "enterprise")]
311 #[test]
312 fn test_soft_drop_retention_must_be_at_least_one_millisecond() {
313 let options = GcSchedulerOptions {
314 enable: true,
315 experimental_soft_drop: SoftDropGcOptions {
316 enable: true,
317 retention: Duration::from_micros(999),
318 },
319 ..Default::default()
320 };
321
322 assert!(options.validate().is_err());
323 }
324
325 #[cfg(feature = "enterprise")]
326 #[test]
327 fn test_soft_drop_retention_must_fit_i64_millis() {
328 let options = GcSchedulerOptions {
329 enable: true,
330 experimental_soft_drop: SoftDropGcOptions {
331 enable: true,
332 retention: Duration::from_millis(i64::MAX as u64 + 1),
333 },
334 ..Default::default()
335 };
336
337 assert!(options.validate().is_err());
338 }
339}