Skip to main content

max / makenotwork

4.2 KB · 131 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 }
21
22 /// Delete records older than `days` from all tables.
23 /// Only closed incidents (with a non-NULL `ended_at`) are pruned.
24 #[instrument(skip_all)]
25 pub async fn prune_old_records(pool: &SqlitePool, days: i64) -> Result<PruneResult> {
26 // Guard: days <= 0 would set cutoff to now (or the future), deleting
27 // everything. Treat this as a no-op instead.
28 if days <= 0 {
29 return Ok(PruneResult {
30 health: 0,
31 tests: 0,
32 test_details: 0,
33 heartbeats: 0,
34 alerts: 0,
35 tls: 0,
36 incidents: 0,
37 routes: 0,
38 dns: 0,
39 whois: 0,
40 backups: 0,
41 });
42 }
43
44 let cutoff = chrono::Utc::now() - chrono::Duration::days(days);
45 let cutoff_str = cutoff.to_rfc3339();
46
47 let health_result = sqlx::query("DELETE FROM health_checks WHERE checked_at < ?")
48 .bind(&cutoff_str)
49 .execute(pool)
50 .await?;
51
52 // test_details has ON DELETE CASCADE on run_id and the pool runs with
53 // foreign_keys=ON, so deleting the runs below already removes their details.
54 // Count them first: the orphan sweep afterwards can only ever report rows the
55 // cascade missed, which is why PruneResult.test_details always read ~0.
56 let cascaded_details = sqlx::query_as::<_, (i64,)>(
57 "SELECT COUNT(*) FROM test_details
58 WHERE run_id IN (SELECT id FROM test_runs WHERE started_at < ?)",
59 )
60 .bind(&cutoff_str)
61 .fetch_one(pool)
62 .await?;
63
64 let test_result = sqlx::query("DELETE FROM test_runs WHERE started_at < ?")
65 .bind(&cutoff_str)
66 .execute(pool)
67 .await?;
68
69 // Safety net for details orphaned some other way (a database written while
70 // foreign_keys was off). Normally zero.
71 let orphan_result =
72 sqlx::query("DELETE FROM test_details WHERE run_id NOT IN (SELECT id FROM test_runs)")
73 .execute(pool)
74 .await?;
75
76 let peer_hb_result = sqlx::query("DELETE FROM peer_heartbeats WHERE checked_at < ?")
77 .bind(&cutoff_str)
78 .execute(pool)
79 .await?;
80
81 let alerts_result = sqlx::query("DELETE FROM alerts WHERE sent_at < ?")
82 .bind(&cutoff_str)
83 .execute(pool)
84 .await?;
85
86 let tls_result = sqlx::query("DELETE FROM tls_checks WHERE checked_at < ?")
87 .bind(&cutoff_str)
88 .execute(pool)
89 .await?;
90
91 let incidents_result =
92 sqlx::query("DELETE FROM incidents WHERE ended_at IS NOT NULL AND ended_at < ?")
93 .bind(&cutoff_str)
94 .execute(pool)
95 .await?;
96
97 let routes_result = sqlx::query("DELETE FROM route_checks WHERE checked_at < ?")
98 .bind(&cutoff_str)
99 .execute(pool)
100 .await?;
101
102 let dns_result = sqlx::query("DELETE FROM dns_checks WHERE checked_at < ?")
103 .bind(&cutoff_str)
104 .execute(pool)
105 .await?;
106
107 let whois_result = sqlx::query("DELETE FROM whois_checks WHERE checked_at < ?")
108 .bind(&cutoff_str)
109 .execute(pool)
110 .await?;
111
112 let backups_result = sqlx::query("DELETE FROM backup_checks WHERE checked_at < ?")
113 .bind(&cutoff_str)
114 .execute(pool)
115 .await?;
116
117 Ok(PruneResult {
118 health: health_result.rows_affected(),
119 tests: test_result.rows_affected(),
120 test_details: cascaded_details.0 as u64 + orphan_result.rows_affected(),
121 heartbeats: peer_hb_result.rows_affected(),
122 alerts: alerts_result.rows_affected(),
123 tls: tls_result.rows_affected(),
124 incidents: incidents_result.rows_affected(),
125 routes: routes_result.rows_affected(),
126 dns: dns_result.rows_affected(),
127 whois: whois_result.rows_affected(),
128 backups: backups_result.rows_affected(),
129 })
130 }
131