Skip to content
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

http: prefetch for upstreams #14143

Merged
merged 21 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/envoy/config/cluster/v3/cluster.proto
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ message Cluster {
// for example proxying HTTP/1.1 if keep-alive were false and each stream resulted in connection
// termination. It would likely be overkill for long lived connections, such as TCP proxying SMTP
// or regular HTTP/1.1 with keep-alive. For long lived traffic, a value of 1.05 would be more
// reasonable, where for every 100 connections, 5 pre-established connections would be in the queue
// reasonable, where for every 100 connections, 5 preconnected connections would be in the queue
// in case of unexpected disconnects where the connection could not be reused.
//
// If this value is not set, or set explicitly to one, Envoy will fetch as many connections
Expand Down
2 changes: 1 addition & 1 deletion api/envoy/config/cluster/v4alpha/cluster.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion generated_api_shadow/envoy/config/cluster/v3/cluster.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions include/envoy/common/conn_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ class Instance {
virtual Upstream::HostDescriptionConstSharedPtr host() const PURE;

/**
* Establishes an additional upstream connection, if existing connections do not meet both current
* and anticipated load.
* Creates an upstream connection, if existing connections do not meet both current and
* anticipated load.
*
* @return true if a connection was established, false otherwise.
* @return true if a connection was preconnected, false otherwise.
*/
virtual bool maybePreconnect(float preconnect_ratio) PURE;
};
Expand Down
23 changes: 9 additions & 14 deletions source/common/upstream/cluster_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -858,20 +858,15 @@ void ClusterManagerImpl::maybePreconnect(
for (int i = 0; i < 3; ++i) {
// Just as in ConnPoolImplBase::shouldCreateNewConnection, see if adding this one new connection
// would put the cluster over desired capacity. If so, stop preconnecting.
if ((state.pending_streams_ + 1 + state.active_streams_) * peekahead_ratio <=
(state.connecting_stream_capacity_ + state.active_streams_)) {
if ((state.connecting_stream_capacity_ + state.active_streams_) >
(state.pending_streams_ + 1 + state.active_streams_) * peekahead_ratio) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm feeling very dense, but I'm having a really hard time with this logic. Two things:

  1. We are mixing streams and connections. When you add one, do we need to be taking into account the number of streams per connection? It seems like there is math missing here but maybe I'm not understanding it (likely). (I have additional confusion about why we are adding +1 on this side of the equation vs. the other side, but maybe that will be more clear once we talk about connections vs. streams).
  2. Is it possible to lift this logic out into a small helper shared with the same logic in shouldCreateNewConnection and add a bunch more comments? I think that would help me. (Same question about +1 in that function)

return;
}
ConnectionPool::Instance* preconnect_pool = pick_preconnect_pool();
if (preconnect_pool) {
if (!preconnect_pool->maybePreconnect(peekahead_ratio)) {
// Given that the next preconnect pick may be entirely different, we could
// opt to try again even if the first preconnect fails. Err on the side of
// caution and wait for the next attempt.
return;
}
} else {
// If unable to find a preconnect pool, exit early.
if (!preconnect_pool || !preconnect_pool->maybePreconnect(peekahead_ratio)) {
// Given that the next preconnect pick may be entirely different, we could
// opt to try again even if the first preconnect fails. Err on the side of
// caution and wait for the next attempt.
return;
}
}
Expand All @@ -885,7 +880,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::httpConnPool(
auto ret = connPool(priority, protocol, context, false);

// Now see if another host should be preconnected.
// httpConnPoolForCluster is called immediately before a call for newStream. newStream doesn't
// httpConnPool is called immediately before a call for newStream. newStream doesn't
// have the load balancer context needed to make selection decisions so preconnecting must be
// performed here in anticipation of the new stream.
// TODO(alyssawilk) refactor to have one function call and return a pair, so this invariant is
Expand All @@ -902,7 +897,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::tcpConnPool(
// Select a host and create a connection pool for it if it does not already exist.
auto ret = tcpConnPool(priority, context, false);

// tcpConnPoolForCluster is called immediately before a call for newConnection. newConnection
// tcpConnPool is called immediately before a call for newConnection. newConnection
// doesn't have the load balancer context needed to make selection decisions so preconnecting must
// be performed here in anticipation of the new connection.
// TODO(alyssawilk) refactor to have one function call and return a pair, so this invariant is
Expand Down Expand Up @@ -1253,7 +1248,7 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::onHostHealthFailure(

if (host->cluster().features() &
ClusterInfo::Features::CLOSE_CONNECTIONS_ON_HOST_HEALTH_FAILURE) {
// Close non connection pool TCP connections obtained from tcpConnForCluster()
// Close non connection pool TCP connections obtained from tcpConn()
//
// TODO(jono): The only remaining user of the non-pooled connections seems to be the statsd
// TCP client. Perhaps it could be rewritten to use a connection pool, and this code deleted.
Expand Down
10 changes: 8 additions & 2 deletions source/common/upstream/load_balancer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ static const std::string RuntimeZoneEnabled = "upstream.zone_routing.enabled";
static const std::string RuntimeMinClusterSize = "upstream.zone_routing.min_cluster_size";
static const std::string RuntimePanicThreshold = "upstream.healthy_panic_threshold";

bool tooManyPreconnects(size_t num_preconnect_picks, uint32_t healthy_hosts) {
// Currently we only allow the number of preconnected connections to equal the
// number of healthy hosts.
return num_preconnect_picks >= healthy_hosts;
}

// Distributes load between priorities based on the per priority availability and the normalized
// total availability. Load is assigned to each priority according to how available each priority is
// adjusted for the normalized total availability.
Expand Down Expand Up @@ -780,7 +786,7 @@ void EdfLoadBalancerBase::refresh(uint32_t priority) {
}

HostConstSharedPtr EdfLoadBalancerBase::peekAnotherHost(LoadBalancerContext* context) {
if (stashed_random_.size() >= total_healthy_hosts_) {
if (tooManyPreconnects(stashed_random_.size(), total_healthy_hosts_)) {
return nullptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 1 the right max ratio of prefetched connections to healthy hosts? I imagine that when the number of endpoints is small it would be beneficial to set this ratio > 1.0, specially if host weights are not all equal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one isn't a ratio thing - we currently cap #prefetches to the number of healthy hosts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there plans to relax that restriction? It seems to get in the way of getting to a fixed number of connections in cases where the number of healthy upstreams is less than the desired number of connections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as needed. Right now I was aiming at the server side, where for low QPS you'd genreally prefetch a few, and for high qps you'd want per upstream but as we move towards mobile if we only have a single upstream per endpoint we'd want to have more than 1 prefetch.
I don't want it unlimited because it's wasteful and it acts as a useful upper bound.


Expand Down Expand Up @@ -869,7 +875,7 @@ HostConstSharedPtr LeastRequestLoadBalancer::unweightedHostPick(const HostVector
}

HostConstSharedPtr RandomLoadBalancer::peekAnotherHost(LoadBalancerContext* context) {
if (stashed_random_.size() >= total_healthy_hosts_) {
if (tooManyPreconnects(stashed_random_.size(), total_healthy_hosts_)) {
return nullptr;
}
return peekOrChoose(context, true);
Expand Down
2 changes: 1 addition & 1 deletion test/common/upstream/load_balancer_fuzz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ message LbAction {
// This updates the health flags of hosts at a certain priority level. The number of hosts in each priority level/in localities is static,
// as untrusted upstreams cannot change that, and can only change their health flags.
UpdateHealthFlags update_health_flags = 1;
// preconnects a host using the encapsulated specific load balancer.
// Preconnects a host using the encapsulated specific load balancer.
google.protobuf.Empty preconnect = 2;
// Chooses a host using the encapsulated specific load balancer.
google.protobuf.Empty choose_host = 3;
Expand Down