diff --git a/veilid-core/src/routing_table/bucket.rs b/veilid-core/src/routing_table/bucket.rs index 78323ed3..1b819d7d 100644 --- a/veilid-core/src/routing_table/bucket.rs +++ b/veilid-core/src/routing_table/bucket.rs @@ -115,7 +115,11 @@ impl Bucket { self.entries.iter() } - pub(super) fn kick(&mut self, bucket_depth: usize) -> Option> { + pub(super) fn kick( + &mut self, + bucket_depth: usize, + closest_nodes: &BTreeSet, + ) -> Option> { // Get number of entries to attempt to purge from bucket let bucket_len = self.entries.len(); @@ -167,6 +171,11 @@ impl Bucket { continue; } + // if this entry is one of our N closest entries, don't drop it + if closest_nodes.contains(&entry.0) { + continue; + } + // if no references, lets evict it dead_node_ids.insert(entry.0); } diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index b8f8c829..ef8a5cb4 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -358,9 +358,10 @@ impl RoutingTableInner { "Starting routing table buckets purge. Table currently has {} nodes", self.bucket_entry_count() ); + let closest_nodes = BTreeSet::new(); for ck in VALID_CRYPTO_KINDS { for bucket in self.buckets.get_mut(&ck).unwrap().iter_mut() { - bucket.kick(0); + bucket.kick(0, &closest_nodes); } } self.all_entries.remove_expired(); @@ -396,11 +397,11 @@ impl RoutingTableInner { /// Attempt to settle buckets and remove entries down to the desired number /// which may not be possible due extant NodeRefs - pub fn kick_bucket(&mut self, bucket_index: BucketIndex) { + pub fn kick_bucket(&mut self, bucket_index: BucketIndex, closest_nodes: &BTreeSet) { let bucket = self.get_bucket_mut(bucket_index); let bucket_depth = Self::bucket_depth(bucket_index); - if let Some(_dead_node_ids) = bucket.kick(bucket_depth) { + if let Some(_dead_node_ids) = bucket.kick(bucket_depth, closest_nodes) { // Remove expired entries self.all_entries.remove_expired(); @@ -1252,7 +1253,7 @@ impl RoutingTableInner { } } -fn make_closest_noderef_sort( +pub(crate) fn make_closest_noderef_sort( crypto: Crypto, node_id: TypedKey, ) -> impl Fn(&NodeRefLocked, &NodeRefLocked) -> core::cmp::Ordering { @@ -1280,3 +1281,19 @@ fn make_closest_noderef_sort( }) } } + +pub(crate) fn make_closest_node_id_sort( + crypto: Crypto, + node_id: TypedKey, +) -> impl Fn(&CryptoKey, &CryptoKey) -> core::cmp::Ordering { + let kind = node_id.kind; + // Get cryptoversion to check distance with + let vcrypto = crypto.get(kind).unwrap(); + + move |a: &CryptoKey, b: &CryptoKey| -> core::cmp::Ordering { + // distance is the next metric, closer nodes first + let da = vcrypto.distance(a, &node_id.value); + let db = vcrypto.distance(b, &node_id.value); + da.cmp(&db) + } +} diff --git a/veilid-core/src/routing_table/tasks/kick_buckets.rs b/veilid-core/src/routing_table/tasks/kick_buckets.rs index 186e9ebb..b6f0bed9 100644 --- a/veilid-core/src/routing_table/tasks/kick_buckets.rs +++ b/veilid-core/src/routing_table/tasks/kick_buckets.rs @@ -1,5 +1,11 @@ use super::*; +/// How many 'reliable' nodes closest to our own node id to keep +const KEEP_N_CLOSEST_RELIABLE_ENTRIES_COUNT: usize = 16; + +/// How many 'unreliable' nodes closest to our own node id to keep +const KEEP_N_CLOSEST_UNRELIABLE_ENTRIES_COUNT: usize = 8; + impl RoutingTable { // Kick the queued buckets in the routing table to free dead nodes if necessary // Attempts to keep the size of the routing table down to the bucket depth @@ -15,8 +21,59 @@ impl RoutingTable { .into_iter() .collect(); let mut inner = self.inner.write(); + + // Get our closest nodes for each crypto kind + let mut closest_nodes_by_kind = BTreeMap::>::new(); + + for kind in VALID_CRYPTO_KINDS { + let our_node_id = self.node_id(kind); + let Some(buckets) = inner.buckets.get(&kind) else { + continue; + }; + let sort = make_closest_node_id_sort(self.crypto(), our_node_id); + + let mut closest_nodes = BTreeSet::::new(); + let mut closest_unreliable_count = 0usize; + let mut closest_reliable_count = 0usize; + + // Iterate buckets backward, sort entries by closest distance first + 'outer: for bucket in buckets.iter().rev() { + let mut entries = bucket.entries().collect::>(); + entries.sort_by(|a, b| sort(a.0, b.0)); + for (key, entry) in entries { + let state = entry.with(&inner, |_rti, e| e.state(cur_ts)); + match state { + BucketEntryState::Dead => { + // Do nothing with dead entries + } + BucketEntryState::Unreliable => { + // Add to closest unreliable nodes list + if closest_unreliable_count < KEEP_N_CLOSEST_UNRELIABLE_ENTRIES_COUNT { + closest_nodes.insert(*key); + closest_unreliable_count += 1; + } + } + BucketEntryState::Reliable => { + // Add to closest reliable nodes list + if closest_reliable_count < KEEP_N_CLOSEST_RELIABLE_ENTRIES_COUNT { + closest_nodes.insert(*key); + closest_reliable_count += 1; + } + } + } + if closest_unreliable_count == KEEP_N_CLOSEST_UNRELIABLE_ENTRIES_COUNT + && closest_reliable_count == KEEP_N_CLOSEST_RELIABLE_ENTRIES_COUNT + { + break 'outer; + } + } + } + + closest_nodes_by_kind.insert(kind, closest_nodes); + } + for bucket_index in kick_queue { - inner.kick_bucket(bucket_index) + inner.kick_bucket(bucket_index, &closest_nodes_by_kind[&bucket_index.0]); } Ok(()) }