From 73ad1de2a4d77e10f77e4fc67cec8f5a540798a8 Mon Sep 17 00:00:00 2001 From: Yorgos Saslis Date: Thu, 17 Sep 2026 17:13:22 +0300 Subject: [PATCH] node/routing: Enforce routingMaxSize `limits.routingMaxSize` is documented as "Number of routing table entries before we start pruning", but the implementation diverges from this. DELETE FROM routing WHERE node <> ?1 AND rowid IN (SELECT rowid FROM routing WHERE timestamp < ?2 ORDER BY timestamp LIMIT ?3) `?2` is `now - routingMaxAge`. So if nothing is older than routingMaxAge, nothing is deleted, however far over routingMaxSize the table has grown. On a seed following a busy network, entries are refreshed by gossip faster than they age out, and the cap is never reachable. For example, on a permissive node with routingMaxSize=1000, routingMaxAge=7d, I observed: routing rows 94,254 older than routingMaxAge, ie. prunable 16 Dropping routingMaxAge to 1 day cleared the backlog the 7-day setting had accumulated -- from 94,254 down to 82,400 rows -- but then stopped, with 8 rows prunable and the table still ... 82x over the cap. The `routing` table size is therefore decided by arrival rate; the configured max size has no effect on it. Split the two evictions so each does what its name says: - size eviction removes the oldest entries down to `routingMaxSize`, regardless of age; - age eviction then removes anything still older than `routingMaxAge`. Size runs first: it takes the oldest rows, so the age pass only fires when the table was near its limit. --- crates/radicle/src/node/routing.rs | 33 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/radicle/src/node/routing.rs b/crates/radicle/src/node/routing.rs index f39db28c3..c2c5bf0f8 100644 --- a/crates/radicle/src/node/routing.rs +++ b/crates/radicle/src/node/routing.rs @@ -221,20 +221,33 @@ impl Store for Database { limit: Option, ignore: &NodeId, ) -> Result { - let limit: i64 = limit - .and_then(|limit| i64::try_from(limit).ok()) - .unwrap_or(i64::MAX); - let mut stmt = self.db.prepare( - "DELETE FROM routing - WHERE node <> ?1 AND rowid IN - (SELECT rowid FROM routing WHERE timestamp < ?2 ORDER BY timestamp LIMIT ?3)", - )?; + let mut pruned = 0; + + if let Some(limit) = limit { + let limit = i64::try_from(limit).unwrap_or(i64::MAX); + let mut stmt = self.db.prepare( + "DELETE FROM routing + WHERE node <> ?1 AND rowid IN + (SELECT rowid FROM routing + WHERE node <> ?1 + ORDER BY timestamp ASC + LIMIT ?2)", + )?; + stmt.bind((1, ignore))?; + stmt.bind((2, limit))?; + stmt.next()?; + pruned += self.db.change_count(); + } + + let mut stmt = self + .db + .prepare("DELETE FROM routing WHERE node <> ?1 AND timestamp < ?2")?; stmt.bind((1, ignore))?; stmt.bind((2, &oldest))?; - stmt.bind((3, limit))?; stmt.next()?; + pruned += self.db.change_count(); - Ok(self.db.change_count()) + Ok(pruned) } fn count(&self, id: &RepoId) -> Result {