proxygen
RendezvousHashTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
10 #include <folly/Conv.h>
13 #include <map>
14 #include <vector>
15 
17 
18 using namespace proxygen;
19 
20 TEST(RendezvousHash, Consistency) {
21  RendezvousHash hashes;
22  std::vector<std::pair<std::string, uint64_t> > nodes;
23  for (int i = 0; i < 10; ++i) {
24  nodes.emplace_back(folly::to<std::string>("key", i), 1);
25  }
26  hashes.build(nodes);
27 
28  for (size_t rank = 0; rank < nodes.size() + 2; rank++) {
29  std::map<uint64_t, size_t> mapping;
30  for (int i = 0; i < 10000; ++i) {
31  mapping[i] = hashes.get(i, rank);
32  }
33 
34  FOR_EACH_KV(key, expected, mapping) {
35  EXPECT_EQ(expected, hashes.get(key, rank));
36  }
37  }
38 }
39 
40 TEST(RendezvousHash, ConsistencyWithNewNode) {
41  RendezvousHash hashes;
42  int numNodes = 10;
43  std::vector<std::pair<std::string, uint64_t> > nodes;
44  for (int i = 0; i < numNodes; ++i) {
45  nodes.emplace_back(folly::to<std::string>("key", i), 1);
46  }
47  hashes.build(nodes);
48  std::map<uint64_t, size_t> mapping;
49  for (uint64_t i = 0; i < 10000; ++i) {
50  mapping[i] = hashes.get(i);
51  }
52  hashes = RendezvousHash();
53  // Adding a new node and rebuild the hash
54  nodes.emplace_back(folly::to<std::string>("key", numNodes), 1);
55  hashes.build(nodes);
56  // traffic should only flow to the new node
57  FOR_EACH_KV (key, expected, mapping) {
58  size_t id = hashes.get(key);
59  EXPECT_TRUE(expected == id || numNodes == int(id));
60  }
61 }
62 
63 TEST(RendezvousHash, ConsistencyWithIncreasedWeight) {
64  RendezvousHash hashes;
65  int numNodes = 10;
66  std::vector<std::pair<std::string, uint64_t> > nodes;
67  for (int i = 0; i < numNodes; ++i) {
68  nodes.emplace_back(folly::to<std::string>("key", i), i);
69  }
70  hashes.build(nodes);
71 
72  std::map<uint64_t, size_t> mapping;
73  for (uint64_t i = 0; i < 10000; ++i) {
74  mapping[i] = hashes.get(i);
75  }
76 
77  // Increase the weight by 2
78  nodes.clear();
79  hashes = RendezvousHash();
80  for (int i = 0; i < numNodes; ++i) {
81  nodes.emplace_back(folly::to<std::string>("key", i), i*2);
82  }
83  hashes.build(nodes);
84 
85  // traffic shouldn't flow at all
86  FOR_EACH_KV (key, expected, mapping) {
87  EXPECT_EQ(expected, hashes.get(key));
88  }
89 }
90 
91 TEST(RendezvousHash, ConsistentFlowToIncreasedWeightNode) {
92  RendezvousHash hashes;
93  int numNodes = 10;
94  std::vector<std::pair<std::string, uint64_t> > nodes;
95  for (int i = 0; i < numNodes; ++i) {
96  nodes.emplace_back(folly::to<std::string>("key", i), i);
97  }
98  hashes.build(nodes);
99 
100  std::map<uint64_t, size_t> mapping;
101  for (uint64_t i = 0; i < 10000; ++i) {
102  mapping[i] = hashes.get(i);
103  }
104 
105  nodes.clear();
106  // Increase the weight for a single node
107  hashes = RendezvousHash();
108 
109  nodes.emplace_back(folly::to<std::string>("key", 0), 10);
110 
111  for (int i = 1; i < numNodes; ++i) {
112  nodes.emplace_back(folly::to<std::string>("key", i), i);
113  }
114  hashes.build(nodes);
115  // traffic should only flow to the first node
116  FOR_EACH_KV (key, expected, mapping) {
117  size_t id = hashes.get(key);
118  EXPECT_TRUE(expected == id || 0 == int(id));
119  }
120 }
121 
122 TEST(RendezvousHash, ConsistentFlowToDecreasedWeightNodes) {
123  RendezvousHash hashes;
124  int numNodes = 18;
125  std::vector<std::pair<std::string, uint64_t> > nodes;
126  for (int i = 0; i < numNodes; ++i) {
127  nodes.emplace_back(folly::to<std::string>("key", i), 100);
128  }
129  hashes.build(nodes);
130  std::map<uint64_t, size_t> mapping;
131  for (uint64_t i = 0; i < 10000; ++i) {
132  mapping[i] = hashes.get(i);
133  }
134 
135  nodes.clear();
136  hashes = RendezvousHash();
137 
138  // decrease the weights for 5 nodes
139  for (int i = 0; i < 5; ++i) {
140  nodes.emplace_back(folly::to<std::string>("key", i), 50);
141  }
142 
143  // keep the weights for the rest unchanged
144  for (int i = 5; i < numNodes; ++i) {
145  nodes.emplace_back(folly::to<std::string>("key", i), 100);
146  }
147 
148  hashes.build(nodes);
149  FOR_EACH_KV (key, expected, mapping) {
150  // traffic should only flow to nodes with decreased nodes
151  size_t id = hashes.get(key);
152  EXPECT_TRUE(expected == id || id >= 5);
153  }
154 }
155 
156 TEST(RendezvousHash, ConsistentFlowToDecreasedWeightNode) {
157  RendezvousHash hashes;
158  int numNodes = 10;
159  std::vector<std::pair<std::string, uint64_t> > nodes;
160  for (int i = 0; i < numNodes; ++i) {
161  nodes.emplace_back(folly::to<std::string>("key", i), i);
162  }
163  hashes.build(nodes);
164  std::map<uint64_t, size_t> mapping;
165  for (uint64_t i = 0; i < 10000; ++i) {
166  mapping[i] = hashes.get(i);
167  }
168 
169  // Increase the weight for a single node
170  nodes.clear();
171  hashes = RendezvousHash();
172 
173  for (int i = 0; i < numNodes - 1; ++i) {
174  nodes.emplace_back(folly::to<std::string>("key", i), i);
175  }
176 
177  // zero the weight of the last node
178  nodes.emplace_back(folly::to<std::string>("key", numNodes-1), 0);
179  hashes.build(nodes);
180  FOR_EACH_KV (key, expected, mapping) {
181  // traffic should only flow from the zero weight cluster to others
182  size_t id = hashes.get(key);
183  if (expected == (uint64_t)numNodes-1) {
184  EXPECT_TRUE(expected != id);
185  } else {
186  EXPECT_TRUE(expected == id);
187  }
188  }
189 }
190 
191 TEST(RendezvousHash, ConsistencyWithDecreasedWeight) {
192  RendezvousHash hashes;
193  int numNodes = 10;
194  std::vector<std::pair<std::string, uint64_t> > nodes;
195  for (int i = 0; i < numNodes; ++i) {
196  nodes.emplace_back(folly::to<std::string>("key", i), i*2);
197  }
198  hashes.build(nodes);
199  std::map<uint64_t, size_t> mapping;
200  for (uint64_t i = 0; i < 10000; ++i) {
201  mapping[i] = hashes.get(i);
202  }
203 
204  // Decrease the weight by 2
205  nodes.clear();
206  hashes = RendezvousHash();
207 
208  for (int i = 0; i < numNodes; ++i) {
209  nodes.emplace_back(folly::to<std::string>("key", i), i);
210  }
211  hashes.build(nodes);
212 
213  // traffic shouldn't flow at all
214  FOR_EACH_KV (key, expected, mapping) {
215  EXPECT_EQ(expected, hashes.get(key));
216  }
217 }
218 
219 TEST(ConsistentHashRing, DistributionAccuracy) {
220  std::vector<std::string> keys =
221  {"ash_proxy", "prn_proxy", "snc_proxy", "frc_proxy"};
222 
223  std::vector<std::vector<uint64_t>> weights = {
224  {248, 342, 2, 384},
225  {10, 10, 10, 10},
226  {25, 25, 10, 10},
227  {100, 10, 10, 1},
228  {100, 5, 5, 5},
229  {922337203685, 12395828300, 50192385101, 59293845010}
230  };
231 
232  for (auto& weight: weights) {
233  RendezvousHash hashes;
234  std::vector<std::pair<std::string, uint64_t> > nodes;
235  FOR_EACH_RANGE (i, 0, keys.size()) {
236  nodes.emplace_back(keys[i], weight[i]);
237  }
238  hashes.build(nodes);
239 
240  std::vector<uint64_t> distribution(keys.size());
241 
242  for (uint64_t i = 0; i < 21000; ++i) {
243  distribution[hashes.get(i)]++;
244  }
245 
246  uint64_t totalWeight = 0;
247 
248  for (auto& w: weight) {
249  totalWeight += w;
250  }
251 
252  double maxError = 0.0;
253  for (size_t i = 0; i < keys.size(); ++i) {
254  double expected = 100.0 * weight[i] / totalWeight;
255  double actual = 100.0 * distribution[i] / 21000;
256 
257  maxError = std::max(maxError, fabs(expected - actual));
258  }
259  // make sure the error rate is less than 1.0%
260  EXPECT_LE(maxError, 1.0);
261  }
262 }
263 
264 TEST(RendezvousHash, selectNUnweighted) {
265  RendezvousHash hashes;
266  std::vector<std::pair<std::string, uint64_t>> nodes;
267  int size = 100;
268  for (int i = 0; i < size; ++i) {
269  nodes.emplace_back(folly::to<std::string>("key", i), 1);
270  }
271  hashes.build(nodes);
272  auto seed = 91484253;
273 
274  // rank > size
275  auto select = hashes.selectNUnweighted(seed, size + 10);
276  EXPECT_EQ(select.size(), size);
277  for (int i = 0; i < size; i++) {
278  EXPECT_EQ(select[i], i);
279  }
280 
281  // check valid index in selection
282  int rank = size / 4;
283  select = hashes.selectNUnweighted(seed, rank);
284  EXPECT_EQ(select.size(), rank);
285  std::set<size_t> uniqueIndex;
286  for (auto index : select) {
287  EXPECT_EQ(uniqueIndex.count(index), 0);
288  uniqueIndex.insert(index);
289  EXPECT_LT(index, size);
290  }
291 
292  // change seed, check different selection
293  for (int i = 1; i < 100; i++) {
294  select = hashes.selectNUnweighted(seed + i, rank);
295  int different = 0;
296  for (auto index : select) {
297  if (uniqueIndex.count(index) == 0) {
298  different++;
299  }
300  }
301  EXPECT_GT(different, 0);
302  }
303 }
#define EXPECT_LE(val1, val2)
Definition: gtest.h:1928
#define FOR_EACH_KV(k, v, c)
Definition: Foreach.h:197
LogLevel max
Definition: LogLevel.cpp:31
static const int seed
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
void build(std::vector< std::pair< std::string, uint64_t >> &) override
size_t get(const uint64_t key, const size_t rank=0) const override
#define FOR_EACH_RANGE(i, begin, end)
Definition: Foreach.h:313
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
std::vector< size_t > selectNUnweighted(const uint64_t key, const size_t rank) const
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
TEST(RendezvousHash, Consistency)
#define EXPECT_LT(val1, val2)
Definition: gtest.h:1930
#define EXPECT_GT(val1, val2)
Definition: gtest.h:1934