-
-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Load balancer #425
base: client-transport-improvements
Are you sure you want to change the base?
Load balancer #425
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really cool feature! My main concern is the complexity of the most important functions. I'd also like to see more explicit .expect()
messages that explain underlying assumptions.
// probe upstreams with a queue length of zero. If the queue length | ||
// is non-zero then the upstream recently got work and does not need | ||
// to be probed. | ||
if conn_rt_len > 1 && random::<f64>() < PROBE_P { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to use rand::Rng::gen_bool()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gen_bool is a method. Is it worth first selecting a random number generator to be able to invoke the method? Or is there a shortcut?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the shortest is
use rand::{thread_rng, Rng};
thread_rng().gen_bool(PROBE_P)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that better than the existing code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's for you and @bal-e to fight over :)
I don't mind either way. I just wanted to see what it would become.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gen_bool()
does the same thing you're doing right now, @Philip-NLnetLabs, it's just more explicit. There's no performance change or anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If gen_bool would be a function, just like random I would agree. But now suddenly we introduce a lot more machinery. It doesn't actually improve the code. But now the reader has to know what thread_rng is and if that is the right choice.
If is even worse, the reader now has to read the description of gen_bool to figure out what is does and when it panics.
I want code to be as simple as possible. And in this case the change seems increase code complexity not decrease it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree with "the reader now has to read the description of gen_bool to figure out what is does and when it panics." gen_bool
is a far more descriptive name ("generate boolean") than the current expression. You are implying that readers would understand random
better than they would understand gen_bool
. What about random
's panics? Which random number generator does it use? What range of numbers does it produce? Those are all non-obvious questions that the use of random
raises here.
let mut tmp_conn_rt = conn_rt.clone(); | ||
|
||
// Remove entries that exceed the QPS limit. Loop | ||
// backward to efficiently remove them. | ||
for i in (0..tmp_conn_rt.len()).rev() { | ||
// Fill-in current queue length. | ||
tmp_conn_rt[i].queue_length = Arc::strong_count( | ||
&conn_stats[i].queue_length_plus_one, | ||
) - 1; | ||
if let Some(max_burst) = conn_stats[i].max_burst { | ||
if conn_stats[i].burst_start.elapsed() | ||
> conn_stats[i].burst_interval | ||
{ | ||
conn_stats[i].burst_start = Instant::now(); | ||
conn_stats[i].burst = 0; | ||
} | ||
if conn_stats[i].burst > max_burst { | ||
tmp_conn_rt.swap_remove(i); | ||
} | ||
} else { | ||
// No limit. | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of cloning the whole Vec
and then filtering out of it, why not use .filter().collect()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would require two loops: one to update queue_length, burst_start and burst, and another for the filter. Do you think it is worth it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating those parameters can be done in forward order too, right? Then you can do them using .map()
or .inspect()
in the same iterator chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. The current loop updates conn_stats. How would that work with map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it only updates conn_stats
at corresponding indices. You can .zip(&mut conn_stats)
or use .enumerate()
to get the right index to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the full code:
let tmp_conn_rt: Vec<_> = conn_rt
.iter()
.cloned()
.zip(&mut conn_stats)
.filter_map(|(mut conn, stats)| {
// Fill-in current queue length.
conn.queue_length = Arc::strong_count(&stats.queue_length_plus_one) - 1;
if let Some(max_burst) = stats.max_burst {
if stats.burst_start.elapsed() > stats.burst_interval {
stats.burst_start = Instant::now();
stats.burst = 0;
}
if stats.burst > max_burst {
// Filter out the connection.
return None;
}
}
Some(conn)
})
.collect();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this is more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more efficient and more idiomatic. Readers don't need to consider how entries get filtered using swap_remove()
, nor do they have to think about the backward iteration. Instead of indexing conn_stats
, we just naturally get the right element from it to work with.
Load balancer for client transports