Skip to content

Commit f1c166b

Browse files
committed
Speed up remove_stale_channels_and_tracking nontrivially
During startup, the lightning protocol forces us to fetch a ton of gossip for channels where there is a `channel_update` in only one direction. We then have to wait around a while until we can prune the crap cause we don't know when the gossip sync has completed. Sadly, doing a large prune via `remove_stale_channels_and_tracking` is somewhat slow. Removing a large portion of our graph currently takes a bit more than 7.5 seconds on an i9-14900K, which can ultimately ~hang a node with a few less GHz ~forever. The bulk of this time is in our `IndexedMap` removals, where we walk the entire `keys` `Vec` to remove the entry, then shift it down after removing. In the previous commit we shifted to a bulk removal model for channels, here we do the same for nodes. This reduces the same test to around 340 milliseconds on the same hardware.
1 parent cc028f4 commit f1c166b

File tree

2 files changed

+33
-4
lines changed

2 files changed

+33
-4
lines changed

lightning/src/routing/gossip.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ use crate::ln::types::ChannelId;
3737
use crate::routing::utxo::{self, UtxoLookup, UtxoResolver};
3838
use crate::types::features::{ChannelFeatures, InitFeatures, NodeFeatures};
3939
use crate::types::string::PrintableString;
40-
use crate::util::indexed_map::{Entry as IndexedMapEntry, IndexedMap};
40+
use crate::util::indexed_map::{
41+
Entry as IndexedMapEntry, IndexedMap, OccupiedEntry as IndexedMapOccupiedEntry,
42+
};
4143
use crate::util::logger::{Level, Logger};
4244
use crate::util::scid_utils::{block_from_scid, scid_from_parts, MAX_SCID_BLOCK};
4345
use crate::util::ser::{MaybeReadable, Readable, ReadableArgs, RequiredWrapper, Writeable, Writer};
@@ -2387,13 +2389,18 @@ where
23872389
if !scids_to_remove.is_empty() {
23882390
let mut nodes = self.nodes.write().unwrap();
23892391
let mut removed_channels_lck = self.removed_channels.lock().unwrap();
2392+
let mut removed_node_counters = self.removed_node_counters.lock().unwrap();
23902393

23912394
let channels_removed_bulk = channels.remove_fetch_bulk(&scids_to_remove);
23922395
removed_channels_lck.reserve(channels_removed_bulk.len());
2396+
let mut nodes_to_remove = hash_set_with_capacity(channels_removed_bulk.len());
23932397
for (scid, info) in channels_removed_bulk {
2394-
self.remove_channel_in_nodes(&mut nodes, &info, scid);
2398+
self.remove_channel_in_nodes_callback(&mut nodes, &info, scid, |e| {
2399+
nodes_to_remove.insert(*e.key());
2400+
});
23952401
removed_channels_lck.insert(scid, Some(current_time_unix));
23962402
}
2403+
nodes.remove_bulk(&nodes_to_remove);
23972404
}
23982405

23992406
let should_keep_tracking = |time: &mut Option<u64>| {
@@ -2632,16 +2639,17 @@ where
26322639
Ok(())
26332640
}
26342641

2635-
fn remove_channel_in_nodes(
2642+
fn remove_channel_in_nodes_callback<RM: FnMut(IndexedMapOccupiedEntry<NodeId, NodeInfo>)>(
26362643
&self, nodes: &mut IndexedMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64,
2644+
mut remove_node: RM,
26372645
) {
26382646
macro_rules! remove_from_node {
26392647
($node_id: expr) => {
26402648
if let IndexedMapEntry::Occupied(mut entry) = nodes.entry($node_id) {
26412649
entry.get_mut().channels.retain(|chan_id| short_channel_id != *chan_id);
26422650
if entry.get().channels.is_empty() {
26432651
self.removed_node_counters.lock().unwrap().push(entry.get().node_counter);
2644-
entry.remove_entry();
2652+
remove_node(entry);
26452653
}
26462654
} else {
26472655
panic!(
@@ -2654,6 +2662,14 @@ where
26542662
remove_from_node!(chan.node_one);
26552663
remove_from_node!(chan.node_two);
26562664
}
2665+
2666+
fn remove_channel_in_nodes(
2667+
&self, nodes: &mut IndexedMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64,
2668+
) {
2669+
self.remove_channel_in_nodes_callback(nodes, chan, short_channel_id, |e| {
2670+
e.remove_entry();
2671+
});
2672+
}
26572673
}
26582674

26592675
impl ReadOnlyNetworkGraph<'_> {

lightning/src/util/indexed_map.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ impl<K: Clone + Hash + Ord, V> IndexedMap<K, V> {
8484
res
8585
}
8686

87+
/// Removes elements with the given `keys` in bulk.
88+
pub fn remove_bulk(&mut self, keys: &HashSet<K>) {
89+
for key in keys.iter() {
90+
self.map.remove(key);
91+
}
92+
self.keys.retain(|k| !keys.contains(k));
93+
}
94+
8795
/// Inserts the given `key`/`value` pair into the map, returning the element that was
8896
/// previously stored at the given `key`, if one exists.
8997
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
@@ -222,6 +230,11 @@ impl<'a, K: Hash + Ord, V> OccupiedEntry<'a, K, V> {
222230
res
223231
}
224232

233+
/// Get a reference to the key at the position described by this entry.
234+
pub fn key(&self) -> &K {
235+
self.underlying_entry.key()
236+
}
237+
225238
/// Get a reference to the value at the position described by this entry.
226239
pub fn get(&self) -> &V {
227240
self.underlying_entry.get()

0 commit comments

Comments
 (0)