generalizes sample-size in quic::ConnectionTable::prune_random (#31011)

Even if there are many connections with stake less than the
threshold_stake, prune_random might still reject if both randomly
sampled connections have stake bigger than the threshold. A bigger
sample-size will make this less likely (at the cost of more
computations):
https://github.com/solana-labs/solana/blob/2cbd5d6c9/streamer/src/nonblocking/quic.rs#L958-L985

In order to benchmark for an optimal sample-size, the commit generalizes
the sample-size and makes it configurable.
This commit is contained in:
behzad nouri 2023-04-03 13:33:12 +00:00 committed by GitHub
parent 8c860e9894
commit 3cb22458f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 29 additions and 35 deletions

View File

@ -28,6 +28,7 @@ use {
timing, timing,
}, },
std::{ std::{
iter::repeat_with,
net::{IpAddr, SocketAddr, UdpSocket}, net::{IpAddr, SocketAddr, UdpSocket},
sync::{ sync::{
atomic::{AtomicBool, AtomicU64, Ordering}, atomic::{AtomicBool, AtomicU64, Ordering},
@ -459,6 +460,7 @@ async fn setup_connection(
stats: Arc<StreamStats>, stats: Arc<StreamStats>,
wait_for_chunk_timeout: Duration, wait_for_chunk_timeout: Duration,
) { ) {
const PRUNE_RANDOM_SAMPLE_SIZE: usize = 2;
if let Ok(connecting_result) = timeout(QUIC_CONNECTION_HANDSHAKE_TIMEOUT, connecting).await { if let Ok(connecting_result) = timeout(QUIC_CONNECTION_HANDSHAKE_TIMEOUT, connecting).await {
if let Ok(new_connection) = connecting_result { if let Ok(new_connection) = connecting_result {
stats.total_new_connections.fetch_add(1, Ordering::Relaxed); stats.total_new_connections.fetch_add(1, Ordering::Relaxed);
@ -484,7 +486,8 @@ async fn setup_connection(
if params.stake > 0 { if params.stake > 0 {
let mut connection_table_l = staked_connection_table.lock().unwrap(); let mut connection_table_l = staked_connection_table.lock().unwrap();
if connection_table_l.total_size >= max_staked_connections { if connection_table_l.total_size >= max_staked_connections {
let num_pruned = connection_table_l.prune_random(params.stake); let num_pruned =
connection_table_l.prune_random(PRUNE_RANDOM_SAMPLE_SIZE, params.stake);
stats.num_evictions.fetch_add(num_pruned, Ordering::Relaxed); stats.num_evictions.fetch_add(num_pruned, Ordering::Relaxed);
} }
@ -948,39 +951,27 @@ impl ConnectionTable {
num_pruned num_pruned
} }
fn connection_stake(&self, index: usize) -> Option<u64> { // Randomly selects sample_size many connections, evicts the one with the
self.table // lowest stake, and returns the number of pruned connections.
.get_index(index) // If the stakes of all the sampled connections are higher than the
.and_then(|(_, connection_vec)| connection_vec.first()) // threshold_stake, rejects the pruning attempt, and returns 0.
.map(|connection| connection.stake) fn prune_random(&mut self, sample_size: usize, threshold_stake: u64) -> usize {
}
// Randomly select two connections, and evict the one with lower stake. If the stakes of both
// the connections are higher than the threshold_stake, reject the pruning attempt, and return 0.
fn prune_random(&mut self, threshold_stake: u64) -> usize {
let mut num_pruned = 0;
let mut rng = thread_rng(); let mut rng = thread_rng();
// The candidate1 and candidate2 could potentially be the same. If so, the stake of the candidate let num_pruned = std::iter::once(self.table.len())
// will be compared just against the threshold_stake. .filter(|&size| size > 0)
let candidate1 = rng.gen_range(0, self.table.len()); .flat_map(|size| repeat_with(move || rng.gen_range(0, size)))
let candidate2 = rng.gen_range(0, self.table.len()); .map(|index| {
let connection = self.table[index].first();
let candidate1_stake = self.connection_stake(candidate1).unwrap_or(0); let stake = connection.map(|connection| connection.stake);
let candidate2_stake = self.connection_stake(candidate2).unwrap_or(0); (index, stake)
})
if candidate1_stake < threshold_stake || candidate2_stake < threshold_stake { .take(sample_size)
let removed = if candidate1_stake < candidate2_stake { .min_by_key(|&(_, stake)| stake)
self.table.swap_remove_index(candidate1) .filter(|&(_, stake)| stake < Some(threshold_stake))
} else { .and_then(|(index, _)| self.table.swap_remove_index(index))
self.table.swap_remove_index(candidate2) .map(|(_, connections)| connections.len())
}; .unwrap_or_default();
self.total_size = self.total_size.saturating_sub(num_pruned);
if let Some((_, removed_value)) = removed {
self.total_size -= removed_value.len();
num_pruned += removed_value.len();
}
}
num_pruned num_pruned
} }
@ -1793,12 +1784,15 @@ pub mod test {
// Try pruninng with threshold stake less than all the entries in the table // Try pruninng with threshold stake less than all the entries in the table
// It should fail to prune (i.e. return 0 number of pruned entries) // It should fail to prune (i.e. return 0 number of pruned entries)
let pruned = table.prune_random(0); let pruned = table.prune_random(/*sample_size:*/ 2, /*threshold_stake:*/ 0);
assert_eq!(pruned, 0); assert_eq!(pruned, 0);
// Try pruninng with threshold stake higher than all the entries in the table // Try pruninng with threshold stake higher than all the entries in the table
// It should succeed to prune (i.e. return 1 number of pruned entries) // It should succeed to prune (i.e. return 1 number of pruned entries)
let pruned = table.prune_random(num_entries as u64 + 1); let pruned = table.prune_random(
2, // sample_size
num_entries as u64 + 1, // threshold_stake
);
assert_eq!(pruned, 1); assert_eq!(pruned, 1);
} }