Skip to main content

prolly/prolly/
gc.rs

1//! Garbage-collection planning for immutable prolly trees.
2//!
3//! The core [`Store`](crate::Store) trait does not require key listing, so the
4//! generic GC API works from explicit root and candidate sets:
5//!
6//! - mark live nodes from the trees an application wants to retain;
7//! - plan which caller-supplied candidate CIDs are unreachable;
8//! - optionally sweep those unreachable candidates.
9
10use super::blob::BlobRef;
11use super::cid::Cid;
12
13/// Live node set discovered from one or more retained tree roots.
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
15pub struct GcReachability {
16    /// Reachable content-addressed node CIDs, sorted by CID bytes.
17    pub live_cids: Vec<Cid>,
18    /// Number of reachable nodes.
19    pub live_nodes: usize,
20    /// Serialized byte weight of reachable nodes as encoded by the current
21    /// node serializer.
22    pub live_bytes: usize,
23    /// Number of reachable leaf nodes.
24    pub leaf_nodes: usize,
25    /// Number of reachable internal nodes.
26    pub internal_nodes: usize,
27}
28
29impl GcReachability {
30    /// Return reachable node CIDs in stable byte order.
31    pub fn cids(&self) -> &[Cid] {
32        &self.live_cids
33    }
34
35    /// Whether `cid` is reachable from the retained roots.
36    pub fn contains(&self, cid: &Cid) -> bool {
37        self.live_cids.iter().any(|probe| probe == cid)
38    }
39
40    /// Consume this report and return the reachable node CIDs.
41    pub fn into_cids(self) -> Vec<Cid> {
42        self.live_cids
43    }
44}
45
46/// Dry-run garbage-collection plan for an explicit candidate set.
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
48pub struct GcPlan {
49    /// Reachability report for the retained roots.
50    pub reachability: GcReachability,
51    /// Number of unique candidate CIDs inspected.
52    pub candidate_nodes: usize,
53    /// Unreachable candidate CIDs present in the store, sorted by CID bytes.
54    pub reclaimable_cids: Vec<Cid>,
55    /// Number of reclaimable candidate nodes.
56    pub reclaimable_nodes: usize,
57    /// Serialized bytes reclaimable from present unreachable candidates.
58    pub reclaimable_bytes: usize,
59    /// Candidate CIDs that were neither reachable nor present in the store.
60    pub missing_candidates: usize,
61}
62
63impl GcPlan {
64    /// Return reclaimable candidate CIDs in stable byte order.
65    pub fn reclaimable_cids(&self) -> &[Cid] {
66        &self.reclaimable_cids
67    }
68
69    /// Whether this plan would delete no nodes.
70    pub fn is_empty(&self) -> bool {
71        self.reclaimable_cids.is_empty()
72    }
73
74    /// Number of candidate nodes retained because they are still reachable.
75    pub fn retained_candidate_nodes(&self) -> usize {
76        self.candidate_nodes
77            .saturating_sub(self.reclaimable_nodes)
78            .saturating_sub(self.missing_candidates)
79    }
80}
81
82/// Result of sweeping a garbage-collection plan.
83#[derive(Clone, Debug, Default, PartialEq, Eq)]
84pub struct GcSweep {
85    /// Plan used to decide which candidates were unreachable.
86    pub plan: GcPlan,
87    /// Number of nodes deleted from the backing store.
88    pub deleted_nodes: usize,
89    /// Serialized bytes deleted from the backing store.
90    pub deleted_bytes: usize,
91}
92
93/// Live blob set discovered from one or more retained tree roots.
94#[derive(Clone, Debug, Default, PartialEq, Eq)]
95pub struct BlobGcReachability {
96    /// Reachable content-addressed blob references, sorted by CID bytes.
97    pub live_blobs: Vec<BlobRef>,
98    /// Number of unique reachable blobs.
99    pub live_blob_count: usize,
100    /// Total byte weight of unique reachable blobs.
101    pub live_blob_bytes: u64,
102    /// Number of reachable tree nodes scanned while marking blob references.
103    pub scanned_nodes: usize,
104    /// Number of reachable leaf values inspected while marking blob references.
105    pub scanned_values: usize,
106}
107
108impl BlobGcReachability {
109    /// Return reachable blob references in stable CID order.
110    pub fn blobs(&self) -> &[BlobRef] {
111        &self.live_blobs
112    }
113
114    /// Whether `reference` is reachable from the retained roots.
115    pub fn contains(&self, reference: &BlobRef) -> bool {
116        self.live_blobs
117            .iter()
118            .any(|probe| probe.cid == reference.cid)
119    }
120
121    /// Consume this report and return the reachable blob references.
122    pub fn into_blobs(self) -> Vec<BlobRef> {
123        self.live_blobs
124    }
125}
126
127/// Dry-run garbage-collection plan for offloaded blobs.
128#[derive(Clone, Debug, Default, PartialEq, Eq)]
129pub struct BlobGcPlan {
130    /// Reachability report for the retained roots.
131    pub reachability: BlobGcReachability,
132    /// Number of unique candidate blob CIDs inspected.
133    pub candidate_blobs: usize,
134    /// Unreachable candidate blobs present in the blob store, sorted by CID bytes.
135    pub reclaimable_blobs: Vec<BlobRef>,
136    /// Number of reclaimable candidate blobs.
137    pub reclaimable_blob_count: usize,
138    /// Bytes reclaimable from present unreachable candidates.
139    pub reclaimable_blob_bytes: u64,
140    /// Candidate blob CIDs that were neither reachable nor present in the blob
141    /// store.
142    pub missing_candidates: usize,
143}
144
145impl BlobGcPlan {
146    /// Return reclaimable blob references in stable CID order.
147    pub fn reclaimable_blobs(&self) -> &[BlobRef] {
148        &self.reclaimable_blobs
149    }
150
151    /// Whether this plan would delete no blobs.
152    pub fn is_empty(&self) -> bool {
153        self.reclaimable_blobs.is_empty()
154    }
155
156    /// Number of candidate blobs retained because they are still reachable.
157    pub fn retained_candidate_blobs(&self) -> usize {
158        self.candidate_blobs
159            .saturating_sub(self.reclaimable_blob_count)
160            .saturating_sub(self.missing_candidates)
161    }
162}
163
164/// Result of sweeping a blob garbage-collection plan.
165#[derive(Clone, Debug, Default, PartialEq, Eq)]
166pub struct BlobGcSweep {
167    /// Plan used to decide which candidate blobs were unreachable.
168    pub plan: BlobGcPlan,
169    /// Number of blobs deleted from the backing blob store.
170    pub deleted_blobs: usize,
171    /// Blob bytes deleted from the backing blob store.
172    pub deleted_blob_bytes: u64,
173}
174
175pub(crate) fn sort_cids(cids: &mut [Cid]) {
176    cids.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
177}
178
179pub(crate) fn sort_blob_refs(blobs: &mut [BlobRef]) {
180    blobs.sort_by(|left, right| left.cid.as_bytes().cmp(right.cid.as_bytes()));
181}