meta_srv/gc/
tracker.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16use std::time::Instant;
17
18use common_telemetry::info;
19use store_api::storage::RegionId;
20
21use crate::error::Result;
22use crate::gc::scheduler::GcScheduler;
23
24/// Tracks GC timing information for a region.
25#[derive(Debug, Clone)]
26pub(crate) struct RegionGcInfo {
27    /// Last time a regular GC was performed on this region.
28    pub(crate) last_gc_time: Instant,
29    /// Last time a full file listing GC was performed on this region.
30    pub(crate) last_full_listing_time: Option<Instant>,
31}
32
33impl RegionGcInfo {
34    pub(crate) fn new(last_gc_time: Instant) -> Self {
35        Self {
36            last_gc_time,
37            last_full_listing_time: None,
38        }
39    }
40}
41
42/// Tracks the last GC time for regions to implement cooldown.
43pub(crate) type RegionGcTracker = HashMap<RegionId, RegionGcInfo>;
44
45impl GcScheduler {
46    /// Clean up stale entries from the region GC tracker if enough time has passed.
47    /// This removes entries for regions that no longer exist in the current table routes.
48    pub(crate) async fn cleanup_tracker_if_needed(&self) -> Result<()> {
49        let mut last_cleanup = *self.last_tracker_cleanup.lock().await;
50        let now = Instant::now();
51
52        // Check if enough time has passed since last cleanup
53        if now.duration_since(last_cleanup) < self.config.tracker_cleanup_interval {
54            return Ok(());
55        }
56
57        info!("Starting region GC tracker cleanup");
58        let cleanup_start = Instant::now();
59
60        // Get all current region IDs from table routes
61        let table_to_region_stats = self.ctx.get_table_to_region_stats().await?;
62        let mut current_regions = HashSet::new();
63        for region_stats in table_to_region_stats.values() {
64            for region_stat in region_stats {
65                current_regions.insert(region_stat.id);
66            }
67        }
68
69        // Remove stale entries from tracker
70        let mut tracker = self.region_gc_tracker.lock().await;
71        let initial_count = tracker.len();
72        tracker.retain(|region_id, _| current_regions.contains(region_id));
73        let removed_count = initial_count - tracker.len();
74
75        *self.last_tracker_cleanup.lock().await = now;
76
77        info!(
78            "Completed region GC tracker cleanup: removed {} stale entries out of {} total (retained {}). Duration: {:?}",
79            removed_count,
80            initial_count,
81            tracker.len(),
82            cleanup_start.elapsed()
83        );
84
85        Ok(())
86    }
87
88    /// Determine if full file listing should be used for a region based on the last full listing time.
89    pub(crate) async fn should_use_full_listing(&self, region_id: RegionId) -> bool {
90        let gc_tracker = self.region_gc_tracker.lock().await;
91        let now = Instant::now();
92
93        if let Some(gc_info) = gc_tracker.get(&region_id) {
94            if let Some(last_full_listing) = gc_info.last_full_listing_time {
95                let elapsed = now.duration_since(last_full_listing);
96                elapsed >= self.config.full_file_listing_interval
97            } else {
98                // Never did full listing for this region, do it now
99                true
100            }
101        } else {
102            // First time GC for this region, do full listing
103            true
104        }
105    }
106
107    pub(crate) async fn update_full_listing_time(
108        &self,
109        region_id: RegionId,
110        did_full_listing: bool,
111    ) {
112        let mut gc_tracker = self.region_gc_tracker.lock().await;
113        let now = Instant::now();
114
115        gc_tracker
116            .entry(region_id)
117            .and_modify(|info| {
118                if did_full_listing {
119                    info.last_full_listing_time = Some(now);
120                }
121                info.last_gc_time = now;
122            })
123            .or_insert_with(|| RegionGcInfo {
124                last_gc_time: now,
125                // prevent need to full listing on the first GC
126                last_full_listing_time: Some(now),
127            });
128    }
129}