Skip to main content

max / makenotwork

4.9 KB · 155 lines History Blame Raw
1 //! Retention: deletes records older than the configured window from every table
2 //! and reports per-table counts.
3
4 use super::{Result, SqlitePool};
5 use tracing::instrument;
6
7 /// Prune result with counts for each table.
8 pub struct PruneResult {
9 pub health: u64,
10 pub tests: u64,
11 pub test_details: u64,
12 pub heartbeats: u64,
13 pub alerts: u64,
14 pub tls: u64,
15 pub incidents: u64,
16 pub routes: u64,
17 pub dns: u64,
18 pub whois: u64,
19 pub backups: u64,
20 pub systemd: u64,
21 pub ca_bundle: u64,
22 pub synckit_fleet: u64,
23 }
24
25 /// Delete records older than `days` from all tables.
26 /// Only closed incidents (with a non-NULL `ended_at`) are pruned.
27 #[instrument(skip_all)]
28 pub async fn prune_old_records(pool: &SqlitePool, days: i64) -> Result<PruneResult> {
29 // Guard: days <= 0 would set cutoff to now (or the future), deleting
30 // everything. Treat this as a no-op instead.
31 if days <= 0 {
32 return Ok(PruneResult {
33 health: 0,
34 tests: 0,
35 test_details: 0,
36 heartbeats: 0,
37 alerts: 0,
38 tls: 0,
39 incidents: 0,
40 routes: 0,
41 dns: 0,
42 whois: 0,
43 backups: 0,
44 systemd: 0,
45 ca_bundle: 0,
46 synckit_fleet: 0,
47 });
48 }
49
50 let cutoff = chrono::Utc::now() - chrono::Duration::days(days);
51 let cutoff_str = cutoff.to_rfc3339();
52
53 let health_result = sqlx::query("DELETE FROM health_checks WHERE checked_at < ?")
54 .bind(&cutoff_str)
55 .execute(pool)
56 .await?;
57
58 // test_details has ON DELETE CASCADE on run_id and the pool runs with
59 // foreign_keys=ON, so deleting the runs below already removes their details.
60 // Count them first: the orphan sweep afterwards can only ever report rows the
61 // cascade missed, which is why PruneResult.test_details always read ~0.
62 let cascaded_details = sqlx::query_as::<_, (i64,)>(
63 "SELECT COUNT(*) FROM test_details
64 WHERE run_id IN (SELECT id FROM test_runs WHERE started_at < ?)",
65 )
66 .bind(&cutoff_str)
67 .fetch_one(pool)
68 .await?;
69
70 let test_result = sqlx::query("DELETE FROM test_runs WHERE started_at < ?")
71 .bind(&cutoff_str)
72 .execute(pool)
73 .await?;
74
75 // Safety net for details orphaned some other way (a database written while
76 // foreign_keys was off). Normally zero.
77 let orphan_result =
78 sqlx::query("DELETE FROM test_details WHERE run_id NOT IN (SELECT id FROM test_runs)")
79 .execute(pool)
80 .await?;
81
82 let peer_hb_result = sqlx::query("DELETE FROM peer_heartbeats WHERE checked_at < ?")
83 .bind(&cutoff_str)
84 .execute(pool)
85 .await?;
86
87 let alerts_result = sqlx::query("DELETE FROM alerts WHERE sent_at < ?")
88 .bind(&cutoff_str)
89 .execute(pool)
90 .await?;
91
92 let tls_result = sqlx::query("DELETE FROM tls_checks WHERE checked_at < ?")
93 .bind(&cutoff_str)
94 .execute(pool)
95 .await?;
96
97 let incidents_result =
98 sqlx::query("DELETE FROM incidents WHERE ended_at IS NOT NULL AND ended_at < ?")
99 .bind(&cutoff_str)
100 .execute(pool)
101 .await?;
102
103 let routes_result = sqlx::query("DELETE FROM route_checks WHERE checked_at < ?")
104 .bind(&cutoff_str)
105 .execute(pool)
106 .await?;
107
108 let dns_result = sqlx::query("DELETE FROM dns_checks WHERE checked_at < ?")
109 .bind(&cutoff_str)
110 .execute(pool)
111 .await?;
112
113 let whois_result = sqlx::query("DELETE FROM whois_checks WHERE checked_at < ?")
114 .bind(&cutoff_str)
115 .execute(pool)
116 .await?;
117
118 let backups_result = sqlx::query("DELETE FROM backup_checks WHERE checked_at < ?")
119 .bind(&cutoff_str)
120 .execute(pool)
121 .await?;
122
123 let systemd_result = sqlx::query("DELETE FROM systemd_checks WHERE checked_at < ?")
124 .bind(&cutoff_str)
125 .execute(pool)
126 .await?;
127
128 let ca_bundle_result = sqlx::query("DELETE FROM ca_bundle_checks WHERE checked_at < ?")
129 .bind(&cutoff_str)
130 .execute(pool)
131 .await?;
132
133 let synckit_fleet_result = sqlx::query("DELETE FROM synckit_fleet_checks WHERE checked_at < ?")
134 .bind(&cutoff_str)
135 .execute(pool)
136 .await?;
137
138 Ok(PruneResult {
139 health: health_result.rows_affected(),
140 tests: test_result.rows_affected(),
141 test_details: cascaded_details.0 as u64 + orphan_result.rows_affected(),
142 heartbeats: peer_hb_result.rows_affected(),
143 alerts: alerts_result.rows_affected(),
144 tls: tls_result.rows_affected(),
145 incidents: incidents_result.rows_affected(),
146 routes: routes_result.rows_affected(),
147 dns: dns_result.rows_affected(),
148 whois: whois_result.rows_affected(),
149 backups: backups_result.rows_affected(),
150 systemd: systemd_result.rows_affected(),
151 ca_bundle: ca_bundle_result.rows_affected(),
152 synckit_fleet: synckit_fleet_result.rows_affected(),
153 })
154 }
155