-
Notifications
You must be signed in to change notification settings - Fork 876
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
feat(server): SetConfig() for ClusterConfig. #1202
Changes from 4 commits
85184b2
0916eb8
768e83e
5f4da9b
530cbba
1be7e42
0740767
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 |
---|---|---|
|
@@ -3,36 +3,83 @@ | |
// | ||
|
||
#pragma once | ||
#include <absl/container/flat_hash_set.h> | ||
|
||
#include <absl/base/thread_annotations.h> | ||
|
||
#include <array> | ||
#include <optional> | ||
#include <string_view> | ||
#include <tuple> | ||
#include <vector> | ||
|
||
#include "src/core/fibers.h" | ||
|
||
namespace dfly { | ||
|
||
typedef uint16_t SlotId; | ||
using SlotId = uint16_t; | ||
|
||
class ClusterConfig { | ||
public: | ||
ClusterConfig(); | ||
struct Node { | ||
std::string id; | ||
std::string ip; | ||
uint16_t port = 0; | ||
}; | ||
|
||
struct SlotRange { | ||
SlotId start = 0; | ||
SlotId end = 0; | ||
}; | ||
|
||
struct ClusterShard { | ||
std::vector<SlotRange> slot_ranges; | ||
Node master; | ||
std::vector<Node> replicas; | ||
}; | ||
|
||
explicit ClusterConfig(std::string_view my_id); | ||
|
||
static SlotId KeySlot(std::string_view key); | ||
|
||
static bool IsClusterEnabled() { | ||
return cluster_enabled; | ||
} | ||
|
||
// If the key contains the {...} pattern, return only the part between { and } | ||
static std::string_view KeyTag(std::string_view key); | ||
|
||
// If key is in my slots ownership return true | ||
bool IsMySlot(SlotId id); | ||
bool IsMySlot(SlotId id) const; | ||
|
||
// Returns nodes that own `id`. Result will always have the first element set to be the master, | ||
// and the following 0 or more elements are the replicas. | ||
std::vector<Node> GetNodesForSlot(SlotId id) const; | ||
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. what is the flow we will need this api for? 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.
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. Removed per the new API |
||
|
||
// Returns the master configured for `id`. Returns a default-initialized `Node` if `SetConfig()` | ||
// was never completed successfully. | ||
Node GetMasterNodeForSlot(SlotId id) const; | ||
|
||
// Returns true if `new_config` is valid and internal state was changed. Returns false and changes | ||
// nothing otherwise. | ||
bool SetConfig(const std::vector<ClusterShard>& new_config); | ||
|
||
private: | ||
void AddSlots(); | ||
struct SlotOwner { | ||
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. In this implementation we are using *16384 space to save the data ,instead saving it only once as the config format, and we will need to have a logic to convert from the slot array to the config format again to be able to reply to the CLISTER SHARDS command. If you would create a member : In this implementation if we have one master and one replica, the data will be saved in the config_ member, the slot array will point to the config data. 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 guess that it's a matter of what we're optimizing for. The current optimization is aimed for fast "get owners for slot" questions, such as 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 we should optimise for "get owners for slot" as it is in the flow of client database queries. 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. As discussed offline, I kept 2 members and updated the code according to your suggestion. PTAL. |
||
Node master; | ||
std::vector<Node> replicas; | ||
bool owned_by_me = false; | ||
}; | ||
|
||
bool IsConfigValid(const std::vector<ClusterShard>& new_config); | ||
|
||
util::SharedMutex slots_mu_; | ||
absl::flat_hash_set<SlotId> owned_slots_; | ||
static bool cluster_enabled; | ||
static constexpr SlotId kMaxSlotNum = 0x3FFF; | ||
|
||
const std::string my_id_; | ||
|
||
mutable util::SharedMutex slots_mu_; | ||
|
||
// This array covers the whole range of possible slots. | ||
std::array<SlotOwner, kMaxSlotNum + 1> slots_ ABSL_GUARDED_BY(slots_mu_) = {}; | ||
}; | ||
|
||
} // namespace dfly |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,14 +4,31 @@ | |
|
||
#include "server/cluster/cluster_config.h" | ||
|
||
#include <gmock/gmock-matchers.h> | ||
|
||
#include "base/gtest.h" | ||
#include "base/logging.h" | ||
|
||
using namespace std; | ||
using testing::Pointwise; | ||
using Node = dfly::ClusterConfig::Node; | ||
|
||
namespace dfly { | ||
|
||
class ClusterConfigTest : public ::testing::Test {}; | ||
MATCHER(NodeMatches, "") { | ||
auto first = std::get<0>(arg); | ||
auto second = std::get<1>(arg); | ||
return first.id == second.id && first.ip == second.ip && first.port == second.port; | ||
} | ||
|
||
class ClusterConfigTest : public ::testing::Test { | ||
protected: | ||
static constexpr string_view kMyId = "my-id"; | ||
ClusterConfig config_{kMyId}; | ||
}; | ||
|
||
TEST_F(ClusterConfigTest, KeyTagTest) { | ||
std::string key = "{user1000}.following"; | ||
string key = "{user1000}.following"; | ||
ASSERT_EQ("user1000", ClusterConfig::KeyTag(key)); | ||
|
||
key = " foo{}{bar}"; | ||
|
@@ -27,4 +44,92 @@ TEST_F(ClusterConfigTest, KeyTagTest) { | |
ASSERT_EQ(key, ClusterConfig::KeyTag(key)); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigEmpty) { | ||
// Test that empty-initialization causes none of the slots to be owned locally. | ||
for (SlotId i : {0, 1, 10, 100, 1'000, 10'000, 16'000, 0x3FFF}) { | ||
EXPECT_FALSE(config_.IsMySlot(i)); | ||
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. please add a test where you check that IsMySlot is true and for some false 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. That's a good idea! |
||
} | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetInvalidEmpty) { | ||
// Test that empty config means all slots are owned locally. | ||
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. Update the comment |
||
EXPECT_FALSE(config_.SetConfig({})); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetInvalidMissingSlots) { | ||
EXPECT_FALSE(config_.SetConfig({{.slot_ranges = {{.start = 0, .end = 16000}}, | ||
.master = {.id = "other", .ip = "192.168.0.100", .port = 7000}, | ||
.replicas = {}}})); | ||
vector<Node> expected = {{}}; // 1 empty master, no replicas | ||
EXPECT_THAT(config_.GetNodesForSlot(0), Pointwise(NodeMatches(), expected)); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetInvalidDoubleBookedSlot) { | ||
EXPECT_FALSE(config_.SetConfig({{.slot_ranges = {{.start = 0, .end = 0x3FFF}}, | ||
.master = {.id = "other", .ip = "192.168.0.100", .port = 7000}, | ||
.replicas = {}}, | ||
{.slot_ranges = {{.start = 0, .end = 0}}, | ||
.master = {.id = "other2", .ip = "192.168.0.101", .port = 7001}, | ||
.replicas = {}}})); | ||
vector<Node> expected = {{}}; // 1 empty master, no replicas | ||
EXPECT_THAT(config_.GetNodesForSlot(0), Pointwise(NodeMatches(), expected)); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetOk) { | ||
EXPECT_TRUE(config_.SetConfig({{.slot_ranges = {{.start = 0, .end = 0x3FFF}}, | ||
.master = {.id = "other", .ip = "192.168.0.100", .port = 7000}, | ||
.replicas = {}}})); | ||
std::vector<Node> expected = { | ||
ClusterConfig::Node{.id = "other", .ip = "192.168.0.100", .port = 7000}}; | ||
EXPECT_THAT(config_.GetNodesForSlot(0), Pointwise(NodeMatches(), expected)); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetOkWithReplicas) { | ||
EXPECT_TRUE(config_.SetConfig( | ||
{{.slot_ranges = {{.start = 0, .end = 0x3FFF}}, | ||
.master = {.id = "other-master", .ip = "192.168.0.100", .port = 7000}, | ||
.replicas = {{.id = "other-replica", .ip = "192.168.0.101", .port = 7001}}}})); | ||
std::vector<Node> expected = { | ||
ClusterConfig::Node{.id = "other-master", .ip = "192.168.0.100", .port = 7000}, | ||
ClusterConfig::Node{.id = "other-replica", .ip = "192.168.0.101", .port = 7001}}; | ||
EXPECT_THAT(config_.GetNodesForSlot(0), Pointwise(NodeMatches(), expected)); | ||
} | ||
|
||
TEST_F(ClusterConfigTest, ConfigSetMultipleInstances) { | ||
EXPECT_TRUE(config_.SetConfig( | ||
{{.slot_ranges = {{.start = 0, .end = 5'000}}, | ||
.master = {.id = "other-master", .ip = "192.168.0.100", .port = 7000}, | ||
.replicas = {{.id = "other-replica", .ip = "192.168.0.101", .port = 7001}}}, | ||
{.slot_ranges = {{.start = 5'001, .end = 10'000}}, | ||
.master = {.id = "other-master2", .ip = "192.168.0.102", .port = 7002}, | ||
.replicas = {{.id = "other-replica2", .ip = "192.168.0.103", .port = 7003}}}, | ||
{.slot_ranges = {{.start = 10'001, .end = 0x3FFF}}, | ||
.master = {.id = "other-master3", .ip = "192.168.0.104", .port = 7004}, | ||
.replicas = {{.id = "other-replica3", .ip = "192.168.0.105", .port = 7005}}}})); | ||
{ | ||
std::vector<Node> expected = { | ||
ClusterConfig::Node{.id = "other-master", .ip = "192.168.0.100", .port = 7000}, | ||
ClusterConfig::Node{.id = "other-replica", .ip = "192.168.0.101", .port = 7001}}; | ||
for (int i = 0; i <= 5'000; ++i) { | ||
EXPECT_THAT(config_.GetNodesForSlot(i), Pointwise(NodeMatches(), expected)); | ||
} | ||
} | ||
{ | ||
std::vector<Node> expected = { | ||
ClusterConfig::Node{.id = "other-master2", .ip = "192.168.0.102", .port = 7002}, | ||
ClusterConfig::Node{.id = "other-replica2", .ip = "192.168.0.103", .port = 7003}}; | ||
for (int i = 5'001; i <= 10'000; ++i) { | ||
EXPECT_THAT(config_.GetNodesForSlot(i), Pointwise(NodeMatches(), expected)); | ||
} | ||
} | ||
{ | ||
std::vector<Node> expected = { | ||
ClusterConfig::Node{.id = "other-master3", .ip = "192.168.0.104", .port = 7004}, | ||
ClusterConfig::Node{.id = "other-replica3", .ip = "192.168.0.105", .port = 7005}}; | ||
for (int i = 10'001; i <= 0x3FFF; ++i) { | ||
EXPECT_THAT(config_.GetNodesForSlot(i), Pointwise(NodeMatches(), expected)); | ||
} | ||
} | ||
} | ||
|
||
} // namespace dfly |
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.
Please add log prints so it will be easier to understand if we have a problem with the config
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.
Oh, that's a great idea!