Skip to content

Commit 034e2f2

Browse files
committed
MB-56581: Allow tuning of default reserved and hard limit
Add the ability to tune the reserved and hard limit being used without having to replace the static configuration file. Change-Id: I2e4fb6a082e790d722f327951c49d15050a1f1c7 Reviewed-on: https://review.couchbase.org/c/kv_engine/+/190037 Tested-by: Build Bot <[email protected]> Reviewed-by: Michael Blow <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 1814069 commit 034e2f2

File tree

6 files changed

+161
-18
lines changed

6 files changed

+161
-18
lines changed

daemon/mcbp_executors.cc

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,9 +520,20 @@ static void set_node_throttle_properties_executor(Cookie& cookie) {
520520
return;
521521
}
522522

523-
auto json = nlohmann::json::parse(cookie.getHeader().getValueString());
523+
const cb::throttle::SetNodeThrottleLimitPayload limits =
524+
nlohmann::json::parse(cookie.getHeader().getValueString());
524525
auto& instance = cb::serverless::Config::instance();
525-
instance.nodeCapacity = json.value("capacity", 0ULL);
526+
if (limits.capacity) {
527+
instance.nodeCapacity = limits.capacity.value();
528+
}
529+
if (limits.default_throttle_reserved_units) {
530+
instance.defaultThrottleReservedUnits =
531+
limits.default_throttle_reserved_units.value();
532+
}
533+
if (limits.default_throttle_hard_limit) {
534+
instance.defaultThrottleHardLimit =
535+
limits.default_throttle_hard_limit.value();
536+
}
526537
cookie.sendResponse(cb::mcbp::Status::Success);
527538
}
528539

daemon/mcbp_validators.cc

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,16 +1698,13 @@ static Status set_node_throttle_properties_validator(Cookie& cookie) {
16981698
}
16991699

17001700
try {
1701-
auto json = nlohmann::json::parse(payload);
1702-
if (!json.contains("capacity") || !json["capacity"].is_number() ||
1703-
json.value("capacity", -1L) < 1) {
1704-
cookie.setErrorContext(
1705-
R"("capacity" must specified and greater than 0)");
1706-
return Status::Einval;
1707-
}
1701+
const cb::throttle::SetNodeThrottleLimitPayload limits =
1702+
nlohmann::json::parse(cookie.getHeader().getValueString());
1703+
(void)limits;
17081704
} catch (const std::exception& exception) {
1709-
cookie.setErrorContext(fmt::format("Failed to parse attached JSON: {}",
1710-
exception.what()));
1705+
cookie.setErrorContext(
1706+
fmt::format("Invalid payload for SetNodeThrottleProperties: {}",
1707+
exception.what()));
17111708
return Status::Einval;
17121709
}
17131710
return Status::Success;

docs/Throttling.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,19 @@ following syntax:
5656

5757
{
5858
"capacity" : 25000,
59+
"default_throttle_hard_limit" : 5000,
60+
"default_throttle_reserved_units" : 2500
5961
}
6062

61-
"capacity" is the number of units per second
63+
"capacity" is the number of units per second.
64+
65+
"default_throttle_hard_limit" is the hard limit of number of units per second.
66+
67+
"default_throttle_reserved_units" is the number of units per second reserved
68+
for the bucket.
69+
70+
Note that default_throttle_hard_limit and default_throttle_reserved_units will
71+
only affect buckets created after their value is set.
6272

6373
The "default" settings would be stored in
6474
`/etc/couchbase/kv/serverless/config.json`

utilities/throttle_utilities.cc

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ std::size_t get_limit(const nlohmann::json& json,
4141
return std::numeric_limits<std::size_t>::max();
4242
}
4343
throw std::runtime_error(
44-
fmt::format("from_json(SetThrottleLimitPayload): {} must be "
45-
"set to \"{}\" if passed as a string",
44+
fmt::format("from_json(Set[Node]ThrottleLimitPayload): {} must "
45+
"be set to \"{}\" if passed as a string",
4646
key,
4747
unlimited));
4848
}
@@ -59,4 +59,51 @@ void from_json(const nlohmann::json& json, SetThrottleLimitPayload& object) {
5959
object.reserved = get_limit(json, "reserved");
6060
object.hard_limit = get_limit(json, "hard_limit");
6161
}
62+
63+
void to_json(nlohmann::json& json, const SetNodeThrottleLimitPayload& object) {
64+
if (object.capacity) {
65+
json["capacity"] = limit_to_json(object.capacity.value());
66+
}
67+
if (object.default_throttle_reserved_units) {
68+
json["default_throttle_reserved_units"] =
69+
limit_to_json(object.default_throttle_reserved_units.value());
70+
}
71+
if (object.default_throttle_hard_limit) {
72+
json["default_throttle_hard_limit"] =
73+
limit_to_json(object.default_throttle_hard_limit.value());
74+
}
75+
}
76+
77+
void from_json(const nlohmann::json& json,
78+
SetNodeThrottleLimitPayload& object) {
79+
if (json.contains("capacity")) {
80+
object.capacity = get_limit(json, "capacity");
81+
}
82+
83+
if (json.contains("default_throttle_reserved_units")) {
84+
object.default_throttle_reserved_units =
85+
get_limit(json, "default_throttle_reserved_units");
86+
}
87+
if (json.contains("default_throttle_hard_limit")) {
88+
object.default_throttle_hard_limit =
89+
get_limit(json, "default_throttle_hard_limit");
90+
}
91+
92+
if (object.default_throttle_hard_limit ||
93+
object.default_throttle_reserved_units) {
94+
if (!(object.default_throttle_hard_limit &&
95+
object.default_throttle_reserved_units)) {
96+
// both must be present
97+
throw std::invalid_argument(
98+
"both hard and reserved must be provided");
99+
}
100+
101+
// hard must be >= reserved
102+
if (object.default_throttle_hard_limit <
103+
object.default_throttle_reserved_units) {
104+
throw std::invalid_argument("hard must be >= reserved");
105+
}
106+
}
107+
}
108+
62109
} // namespace cb::throttle

utilities/throttle_utilities.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <nlohmann/json_fwd.hpp>
1414
#include <limits>
15+
#include <optional>
1516

1617
namespace cb::throttle {
1718

@@ -41,4 +42,17 @@ void to_json(nlohmann::json& json, const SetThrottleLimitPayload& object);
4142
/// Initialize the SetThrottleLimitPayload object from JSON
4243
void from_json(const nlohmann::json& json, SetThrottleLimitPayload& object);
4344

45+
/// The payload in the SetNodeThrottleLimit command
46+
struct SetNodeThrottleLimitPayload {
47+
std::optional<std::size_t> capacity;
48+
std::optional<std::size_t> default_throttle_reserved_units;
49+
std::optional<std::size_t> default_throttle_hard_limit;
50+
};
51+
52+
/// Convert the SetNodeThrottleLimitPayload to a JSON document
53+
void to_json(nlohmann::json& json, const SetNodeThrottleLimitPayload& object);
54+
55+
/// Initialize the SetNodeThrottleLimitPayload object from JSON
56+
void from_json(const nlohmann::json& json, SetNodeThrottleLimitPayload& object);
57+
4458
} // namespace cb::throttle

utilities/throttle_utilities_test.cc

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,94 @@
1414

1515
using namespace cb::throttle;
1616

17-
TEST(ThrottleLimitPayload, AllDefault) {
17+
TEST(SetThrottleLimitPayload, AllDefault) {
1818
SetThrottleLimitPayload limits = nlohmann::json::parse("{}");
1919
EXPECT_EQ(std::numeric_limits<std::size_t>::max(), limits.reserved);
2020
EXPECT_EQ(std::numeric_limits<std::size_t>::max(), limits.hard_limit);
2121
}
2222

23-
TEST(ThrottleLimitPayload, NumericValues) {
23+
TEST(SetThrottleLimitPayload, NumericValues) {
2424
SetThrottleLimitPayload limits =
2525
nlohmann::json::parse(R"({"reserved": 1, "hard_limit": 2})");
2626
EXPECT_EQ(1, limits.reserved);
2727
EXPECT_EQ(2, limits.hard_limit);
2828
}
2929

30-
TEST(ThrottleLimitPayload, StringValues) {
30+
TEST(SetThrottleLimitPayload, StringValues) {
3131
SetThrottleLimitPayload limits = nlohmann::json::parse(
3232
R"({"reserved": 1, "hard_limit": "unlimited"})");
3333
EXPECT_EQ(1, limits.reserved);
3434
EXPECT_EQ(std::numeric_limits<std::size_t>::max(), limits.hard_limit);
3535
}
3636

37-
TEST(ThrottleLimitPayload, ToJson) {
37+
TEST(SetThrottleLimitPayload, ToJson) {
3838
nlohmann::json json = SetThrottleLimitPayload();
3939
EXPECT_EQ(R"({"hard_limit":"unlimited","reserved":"unlimited"})",
4040
json.dump());
4141
json = SetThrottleLimitPayload(1, 2);
4242
EXPECT_EQ(R"({"hard_limit":2,"reserved":1})", json.dump());
4343
}
44+
45+
TEST(SetNodeThrottleLimitPayload, AllDefault) {
46+
SetNodeThrottleLimitPayload limits = nlohmann::json::parse("{}");
47+
EXPECT_FALSE(limits.capacity);
48+
EXPECT_FALSE(limits.default_throttle_reserved_units);
49+
EXPECT_FALSE(limits.default_throttle_hard_limit);
50+
}
51+
52+
TEST(SetNodeThrottleLimitPayload, Capacity) {
53+
SetNodeThrottleLimitPayload limits =
54+
nlohmann::json::parse(R"({"capacity":1})");
55+
EXPECT_TRUE(limits.capacity);
56+
EXPECT_EQ(1, limits.capacity.value());
57+
EXPECT_FALSE(limits.default_throttle_reserved_units);
58+
EXPECT_FALSE(limits.default_throttle_hard_limit);
59+
60+
limits = nlohmann::json::parse(R"({"capacity":0})");
61+
EXPECT_TRUE(limits.capacity);
62+
EXPECT_EQ(0, limits.capacity.value());
63+
64+
limits = nlohmann::json::parse(R"({"capacity":"unlimited"})");
65+
EXPECT_TRUE(limits.capacity);
66+
EXPECT_EQ(std::numeric_limits<std::size_t>::max(), limits.capacity.value());
67+
}
68+
69+
TEST(SetNodeThrottleLimitPayload, DefaultThrottle) {
70+
SetNodeThrottleLimitPayload limits =
71+
nlohmann::json{{"default_throttle_reserved_units", 10},
72+
{"default_throttle_hard_limit", 25}};
73+
EXPECT_FALSE(limits.capacity);
74+
EXPECT_TRUE(limits.default_throttle_reserved_units);
75+
EXPECT_TRUE(limits.default_throttle_hard_limit);
76+
EXPECT_EQ(10, limits.default_throttle_reserved_units.value());
77+
EXPECT_EQ(25, limits.default_throttle_hard_limit.value());
78+
79+
// They may be equal
80+
limits = nlohmann::json{{"default_throttle_reserved_units", 40},
81+
{"default_throttle_hard_limit", 40}};
82+
EXPECT_FALSE(limits.capacity);
83+
EXPECT_TRUE(limits.default_throttle_reserved_units);
84+
EXPECT_TRUE(limits.default_throttle_hard_limit);
85+
EXPECT_EQ(40, limits.default_throttle_reserved_units.value());
86+
EXPECT_EQ(40, limits.default_throttle_hard_limit.value());
87+
88+
// They may be string
89+
limits = nlohmann::json{{"default_throttle_reserved_units", "unlimited"},
90+
{"default_throttle_hard_limit", "unlimited"}};
91+
EXPECT_FALSE(limits.capacity);
92+
EXPECT_TRUE(limits.default_throttle_reserved_units);
93+
EXPECT_TRUE(limits.default_throttle_hard_limit);
94+
EXPECT_EQ(std::numeric_limits<std::size_t>::max(),
95+
limits.default_throttle_reserved_units.value());
96+
EXPECT_EQ(std::numeric_limits<std::size_t>::max(),
97+
limits.default_throttle_hard_limit.value());
98+
99+
// but reserved cannot exceed hard
100+
try {
101+
limits =
102+
nlohmann::json{{"default_throttle_reserved_units", "unlimited"},
103+
{"default_throttle_hard_limit", 100}};
104+
FAIL() << "reserved must be less or equal to hard limit";
105+
} catch (const std::exception&) {
106+
}
107+
}

0 commit comments

Comments
 (0)