-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Introduce Least Request LB active request bias config #11252
Changes from 2 commits
a1b52fd
762e61b
4cf5f19
0a2b1fc
6f9b89c
6864b50
25f7c98
f4b2da3
e96d52d
a5d7782
cfca8f7
ce291d6
b487a5e
d7cf64c
3fc99ea
9be22f0
f2d8924
71fcad8
2690778
be7c000
a4e7b39
1010883
a6a285d
2676928
100c3db
cf06a76
eb1b857
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
#pragma once | ||
|
||
#include <cmath> | ||
#include <cstdint> | ||
#include <queue> | ||
#include <set> | ||
|
@@ -16,6 +17,9 @@ | |
namespace Envoy { | ||
namespace Upstream { | ||
|
||
static const std::string RuntimeLeastRequestsActiveRequestsExponent = | ||
"upstream.least_requests.active_requests_exponent"; | ||
|
||
// Priority levels and localities are considered overprovisioned with this factor. | ||
static constexpr uint32_t kDefaultOverProvisioningFactor = 140; | ||
|
||
|
@@ -365,7 +369,9 @@ class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase { | |
std::unique_ptr<EdfScheduler<const Host>> edf_; | ||
}; | ||
|
||
void initialize(); | ||
virtual void initialize(); | ||
|
||
virtual void refresh(uint32_t priority); | ||
|
||
// Seed to allow us to desynchronize load balancers across a fleet. If we don't | ||
// do this, multiple Envoys that receive an update at the same time (or even | ||
|
@@ -375,7 +381,6 @@ class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase { | |
const uint64_t seed_; | ||
|
||
private: | ||
void refresh(uint32_t priority); | ||
virtual void refreshHostSource(const HostsSource& source) PURE; | ||
virtual double hostWeight(const Host& host) PURE; | ||
virtual HostConstSharedPtr unweightedHostPick(const HostVector& hosts_to_use, | ||
|
@@ -437,7 +442,8 @@ class RoundRobinLoadBalancer : public EdfLoadBalancerBase { | |
* The benefit of the Maglev table is at the expense of resolution, memory usage is capped. | ||
* Additionally, the Maglev table can be shared amongst all threads. | ||
*/ | ||
class LeastRequestLoadBalancer : public EdfLoadBalancerBase { | ||
class LeastRequestLoadBalancer : public EdfLoadBalancerBase, | ||
Logger::Loggable<Logger::Id::upstream> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if you save some bytes by using ENVOY_LOG_MISC here rather than adding a second inheritance here, or by putting the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tried using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one quick point about that: the EXPECT_MEMORY_EQ macro will be a no-op on Mac. It only runs on linux release builds. There's a bazel flag controlling an ifdef whether to enable that check. EXPECT_MEMORY_LE will run on Mac if tcmalloc is being used, but the memory checking for non-canonical platforms is much looser, and so you might not notice a benefit from dropping the multiple inheritance on Mac. |
||
public: | ||
LeastRequestLoadBalancer( | ||
const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, | ||
|
@@ -454,6 +460,13 @@ class LeastRequestLoadBalancer : public EdfLoadBalancerBase { | |
initialize(); | ||
} | ||
|
||
protected: | ||
void refresh(uint32_t priority) override { | ||
active_requests_exponent_ = | ||
runtime_.snapshot().getDouble(RuntimeLeastRequestsActiveRequestsExponent, 1.0); | ||
EdfLoadBalancerBase::refresh(priority); | ||
} | ||
|
||
private: | ||
void refreshHostSource(const HostsSource&) override {} | ||
double hostWeight(const Host& host) override { | ||
|
@@ -465,11 +478,26 @@ class LeastRequestLoadBalancer : public EdfLoadBalancerBase { | |
// be the only/best way of doing this. Essentially, it makes weight and active requests equally | ||
// important. Are they equally important in practice? There is no right answer here and we might | ||
// want to iterate on this as we gain more experience. | ||
return static_cast<double>(host.weight()) / (host.stats().rq_active_.value() + 1); | ||
const double weight = static_cast<double>(host.weight()) / | ||
std::pow(host.stats().rq_active_.value() + 1, active_requests_exponent_); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For perf reasons I would probably check if 1.0 and then branch and avoid the pow call. |
||
|
||
ENVOY_LOG(debug, "cluster={} address={} active_requests_exponent={} weight={}", | ||
host.cluster().name(), host.address()->asString(), active_requests_exponent_, weight); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. definitely trace. I would also probably remove this in the final patch. |
||
|
||
return weight; | ||
} | ||
HostConstSharedPtr unweightedHostPick(const HostVector& hosts_to_use, | ||
const HostsSource& source) override; | ||
|
||
const uint32_t choice_count_; | ||
|
||
// When hosts have different weights, the host weight is calculated as: | ||
// | ||
// host_weight = (configured_weight / active_requests^k). k is configured via runtime and its | ||
// value is cached to avoid having to do a runtime lookup each time a host weight is generated. | ||
// | ||
// The cached value is refreshed in `LeastRequestLoadBalancer::refresh(uint32_t priority)`. | ||
double active_requests_exponent_; | ||
}; | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
#include "test/common/upstream/utility.h" | ||
#include "test/mocks/runtime/mocks.h" | ||
#include "test/mocks/upstream/mocks.h" | ||
#include "test/test_common/test_runtime.h" | ||
|
||
#include "gmock/gmock.h" | ||
#include "gtest/gtest.h" | ||
|
@@ -1532,6 +1533,30 @@ TEST_P(LeastRequestLoadBalancerTest, WeightImbalance) { | |
EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); | ||
} | ||
|
||
TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithCustomExponent) { | ||
EXPECT_CALL(runtime_.snapshot_, | ||
getDouble("upstream.least_requests.active_requests_exponent", 1.0)) | ||
.WillRepeatedly(Return(0.0)); | ||
|
||
hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", 1), | ||
makeTestHost(info_, "tcp://127.0.0.1:81", 2)}; | ||
stats_.max_host_weight_.set(2UL); | ||
|
||
hostSet().hosts_ = hostSet().healthy_hosts_; | ||
hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. | ||
|
||
EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); | ||
|
||
// We should see 2:1 ratio for hosts[1] to hosts[0], regardless of the active request count. | ||
hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised this works. Are the stats affecting the outstanding requests perceived by the LB when the exponent is non-zero? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the active rq stat is used to calculate the effective weights when the exponent is > 0: https://github.com/envoyproxy/envoy/pull/11252/files/0a2b1fcdad5daeef1136ef7c2208f356d3fcf05f#diff-e746c5f817e7b535220c275b9dea666bR494 |
||
EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); | ||
EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); | ||
EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); | ||
EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); | ||
EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); | ||
EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); | ||
} | ||
|
||
TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceCallbacks) { | ||
hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", 1), | ||
makeTestHost(info_, "tcp://127.0.0.1:81", 2)}; | ||
|
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.
absl::string_view to avoid static fiasco.