@@ -16,8 +16,8 @@ use std::collections::HashSet;
16
16
use std:: sync:: Arc ;
17
17
18
18
use chrono:: DateTime ;
19
- use chrono:: Days ;
20
19
use chrono:: Duration ;
20
+ use chrono:: TimeDelta ;
21
21
use chrono:: Utc ;
22
22
use databend_common_base:: base:: uuid:: Uuid ;
23
23
use databend_common_catalog:: table:: Table ;
@@ -32,6 +32,7 @@ use databend_common_storages_fuse::io::MetaReaders;
32
32
use databend_common_storages_fuse:: io:: SegmentsIO ;
33
33
use databend_common_storages_fuse:: io:: TableMetaLocationGenerator ;
34
34
use databend_common_storages_fuse:: FuseTable ;
35
+ use databend_common_storages_fuse:: RetentionPolicy ;
35
36
use databend_storages_common_cache:: CacheAccessor ;
36
37
use databend_storages_common_cache:: CacheManager ;
37
38
use databend_storages_common_cache:: LoadParams ;
@@ -89,57 +90,93 @@ pub async fn do_vacuum2(
89
90
let fuse_table = FuseTable :: try_from_table ( table) ?;
90
91
let start = std:: time:: Instant :: now ( ) ;
91
92
92
- let retention_period_in_days = if fuse_table. is_transient ( ) {
93
- 0
94
- } else {
95
- ctx. get_settings ( ) . get_data_retention_time_in_days ( ) ?
96
- } ;
93
+ let retention_policy = fuse_table. get_data_retention_policy ( ctx. as_ref ( ) ) ?;
97
94
98
- let is_vacuum_all = retention_period_in_days == 0 ;
95
+ // By default, do not vacuum all the historical snapshots.
96
+ let mut is_vacuum_all = false ;
97
+ let mut respect_flash_back_with_lvt = None ;
99
98
100
- let Some ( lvt) = set_lvt ( fuse_table, ctx. as_ref ( ) , retention_period_in_days) . await ? else {
101
- return Ok ( vec ! [ ] ) ;
102
- } ;
99
+ let snapshots_before_lvt = match retention_policy {
100
+ RetentionPolicy :: ByTimePeriod ( delta_duration) => {
101
+ info ! ( "using by ByTimePeriod policy {:?}" , delta_duration) ;
102
+ let retention_period = if fuse_table. is_transient ( ) {
103
+ // For transient table, keep no history data
104
+ TimeDelta :: zero ( )
105
+ } else {
106
+ delta_duration
107
+ } ;
103
108
104
- ctx. set_status_info ( & format ! (
105
- "set lvt for table {} takes {:?}, lvt: {:?}" ,
106
- fuse_table. get_table_info( ) . desc,
107
- start. elapsed( ) ,
108
- lvt
109
- ) ) ;
109
+ // A zero retention period indicates that we should vacuum all the historical snapshots
110
+ is_vacuum_all = retention_period. is_zero ( ) ;
110
111
111
- let start = std:: time:: Instant :: now ( ) ;
112
- let snapshots_before_lvt = if is_vacuum_all {
113
- list_until_prefix (
114
- fuse_table,
115
- fuse_table
116
- . meta_location_generator ( )
117
- . snapshot_location_prefix ( ) ,
118
- fuse_table. snapshot_loc ( ) . unwrap ( ) . as_str ( ) ,
119
- true ,
120
- None ,
121
- )
122
- . await ?
123
- } else {
124
- list_until_timestamp (
125
- fuse_table,
126
- fuse_table
127
- . meta_location_generator ( )
128
- . snapshot_location_prefix ( ) ,
129
- lvt,
130
- true ,
131
- None ,
132
- )
133
- . await ?
112
+ let Some ( lvt) = set_lvt ( fuse_table, ctx. as_ref ( ) , retention_period) . await ? else {
113
+ return Ok ( vec ! [ ] ) ;
114
+ } ;
115
+
116
+ if respect_flash_back {
117
+ respect_flash_back_with_lvt = Some ( lvt) ;
118
+ }
119
+
120
+ ctx. set_status_info ( & format ! (
121
+ "set lvt for table {} takes {:?}, lvt: {:?}" ,
122
+ fuse_table. get_table_info( ) . desc,
123
+ start. elapsed( ) ,
124
+ lvt
125
+ ) ) ;
126
+
127
+ let snapshots_before_lvt =
128
+ collect_gc_candidates_by_retention_period ( fuse_table, lvt, is_vacuum_all) . await ?;
129
+ snapshots_before_lvt
130
+ }
131
+ RetentionPolicy :: ByNumOfSnapshotsToKeep ( num_snapshots_to_keep) => {
132
+ info ! (
133
+ "using by ByNumOfSnapshotsToKeep policy {:?}" ,
134
+ num_snapshots_to_keep
135
+ ) ;
136
+ // List the snapshot order by timestamp asc, till the current snapshot(inclusively).
137
+ let need_one_more = true ;
138
+ let mut snapshots = list_until_prefix (
139
+ fuse_table,
140
+ fuse_table
141
+ . meta_location_generator ( )
142
+ . snapshot_location_prefix ( ) ,
143
+ fuse_table. snapshot_loc ( ) . unwrap ( ) . as_str ( ) ,
144
+ need_one_more,
145
+ None ,
146
+ )
147
+ . await ?;
148
+
149
+ let len = snapshots. len ( ) ;
150
+ if len <= num_snapshots_to_keep {
151
+ // Only the current snapshot is there, done
152
+ return Ok ( vec ! [ ] ) ;
153
+ }
154
+ if num_snapshots_to_keep == 1 {
155
+ // Expecting only one snapshot left, which means that we can use the current snapshot
156
+ // as gc root, this flag will be propagated to the select_gc_root func later.
157
+ is_vacuum_all = true ;
158
+ }
159
+
160
+ // When selecting the GC root later, the last snapshot in `snapshots` (after truncation)
161
+ // is the candidate, but its commit status is uncertain, so its previous snapshot is used
162
+ // as the GC root instead (except in the is_vacuum_all case).
163
+
164
+ // Therefore, during snapshot truncation, we keep 2 extra snapshots;
165
+ // see `select_gc_root` for details.
166
+ let num_candidates = len - num_snapshots_to_keep + 2 ;
167
+ snapshots. truncate ( num_candidates) ;
168
+ snapshots
169
+ }
134
170
} ;
135
171
136
172
let elapsed = start. elapsed ( ) ;
137
173
ctx. set_status_info ( & format ! (
138
- "list snapshots before lvt for table {} takes {:?}, snapshots_dir: {:?}, lvt : {:?}, snapshots: {:?}" ,
174
+ "list snapshots for table {} takes {:?}, snapshots_dir: {:?}, snapshots: {:?}" ,
139
175
fuse_table. get_table_info( ) . desc,
140
176
elapsed,
141
- fuse_table. meta_location_generator( ) . snapshot_location_prefix( ) ,
142
- lvt,
177
+ fuse_table
178
+ . meta_location_generator( )
179
+ . snapshot_location_prefix( ) ,
143
180
slice_summary( & snapshots_before_lvt)
144
181
) ) ;
145
182
@@ -148,9 +185,8 @@ pub async fn do_vacuum2(
148
185
fuse_table,
149
186
& snapshots_before_lvt,
150
187
is_vacuum_all,
151
- respect_flash_back ,
188
+ respect_flash_back_with_lvt ,
152
189
ctx. clone ( ) . get_abort_checker ( ) ,
153
- lvt,
154
190
)
155
191
. await ?
156
192
else {
@@ -341,13 +377,45 @@ pub async fn do_vacuum2(
341
377
Ok ( files_to_gc)
342
378
}
343
379
380
+ async fn collect_gc_candidates_by_retention_period (
381
+ fuse_table : & FuseTable ,
382
+ lvt : DateTime < Utc > ,
383
+ is_vacuum_all : bool ,
384
+ ) -> Result < Vec < Entry > > {
385
+ let snapshots_before_lvt = if is_vacuum_all {
386
+ list_until_prefix (
387
+ fuse_table,
388
+ fuse_table
389
+ . meta_location_generator ( )
390
+ . snapshot_location_prefix ( ) ,
391
+ fuse_table. snapshot_loc ( ) . unwrap ( ) . as_str ( ) ,
392
+ true ,
393
+ None ,
394
+ )
395
+ . await ?
396
+ } else {
397
+ list_until_timestamp (
398
+ fuse_table,
399
+ fuse_table
400
+ . meta_location_generator ( )
401
+ . snapshot_location_prefix ( ) ,
402
+ lvt,
403
+ true ,
404
+ None ,
405
+ )
406
+ . await ?
407
+ } ;
408
+
409
+ Ok ( snapshots_before_lvt)
410
+ }
411
+
344
412
/// Try set lvt as min(latest_snapshot.timestamp, now - retention_time).
345
413
///
346
414
/// Return `None` means we stop vacuumming, but don't want to report error to user.
347
415
async fn set_lvt (
348
416
fuse_table : & FuseTable ,
349
417
ctx : & dyn TableContext ,
350
- retention : u64 ,
418
+ retention_period : TimeDelta ,
351
419
) -> Result < Option < DateTime < Utc > > > {
352
420
let Some ( latest_snapshot) = fuse_table. read_table_snapshot ( ) . await ? else {
353
421
info ! (
@@ -366,7 +434,7 @@ async fn set_lvt(
366
434
let cat = ctx. get_default_catalog ( ) ?;
367
435
// safe to unwrap, as we have checked the version is v5
368
436
let latest_ts = latest_snapshot. timestamp . unwrap ( ) ;
369
- let lvt_point_candidate = std:: cmp:: min ( Utc :: now ( ) - Days :: new ( retention ) , latest_ts) ;
437
+ let lvt_point_candidate = std:: cmp:: min ( Utc :: now ( ) - retention_period , latest_ts) ;
370
438
371
439
let lvt_point = cat
372
440
. set_table_lvt (
@@ -538,14 +606,13 @@ async fn select_gc_root(
538
606
fuse_table : & FuseTable ,
539
607
snapshots_before_lvt : & [ Entry ] ,
540
608
is_vacuum_all : bool ,
541
- respect_flash_back : bool ,
609
+ respect_flash_back : Option < DateTime < Utc > > ,
542
610
abort_checker : AbortChecker ,
543
- lvt : DateTime < Utc > ,
544
611
) -> Result < Option < ( Arc < TableSnapshot > , Vec < String > , DateTime < Utc > ) > > {
545
612
let gc_root_path = if is_vacuum_all {
546
613
// safe to unwrap, or we should have stopped vacuuming in set_lvt()
547
614
fuse_table. snapshot_loc ( ) . unwrap ( )
548
- } else if respect_flash_back {
615
+ } else if let Some ( lvt ) = respect_flash_back {
549
616
let latest_location = fuse_table. snapshot_loc ( ) . unwrap ( ) ;
550
617
let gc_root = fuse_table
551
618
. find ( latest_location, abort_checker, |snapshot| {
0 commit comments