MediaWiki REL1_33
HashRingTest.php
Go to the documentation of this file.
1<?php
2
7class HashRingTest extends PHPUnit\Framework\TestCase {
8
9 use MediaWikiCoversValidator;
10
11 public function testHashRingSerialize() {
12 $map = [ 's1' => 3, 's2' => 10, 's3' => 2, 's4' => 10, 's5' => 2, 's6' => 3 ];
13 $ring = new HashRing( $map, 'md5' );
14
15 $serialized = serialize( $ring );
16 $ringRemade = unserialize( $serialized );
17
18 for ( $i = 0; $i < 100; $i++ ) {
19 $this->assertEquals(
20 $ring->getLocation( "hello$i" ),
21 $ringRemade->getLocation( "hello$i" ),
22 'Items placed at proper locations'
23 );
24 }
25 }
26
27 public function testHashRingMapping() {
28 // SHA-1 based and weighted
29 $ring = new HashRing(
30 [ 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3, 's7' => 0 ],
31 'sha1'
32 );
33
34 $this->assertEquals(
35 [ 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ],
36 $ring->getLocationWeights(),
37 'Normalized location weights'
38 );
39
40 $locations = [];
41 for ( $i = 0; $i < 25; $i++ ) {
42 $locations[ "hello$i"] = $ring->getLocation( "hello$i" );
43 }
44 $expectedLocations = [
45 "hello0" => "s4",
46 "hello1" => "s6",
47 "hello2" => "s3",
48 "hello3" => "s6",
49 "hello4" => "s6",
50 "hello5" => "s4",
51 "hello6" => "s3",
52 "hello7" => "s4",
53 "hello8" => "s3",
54 "hello9" => "s3",
55 "hello10" => "s3",
56 "hello11" => "s5",
57 "hello12" => "s4",
58 "hello13" => "s5",
59 "hello14" => "s2",
60 "hello15" => "s5",
61 "hello16" => "s6",
62 "hello17" => "s5",
63 "hello18" => "s1",
64 "hello19" => "s1",
65 "hello20" => "s6",
66 "hello21" => "s5",
67 "hello22" => "s3",
68 "hello23" => "s4",
69 "hello24" => "s1"
70 ];
71 $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
72
73 $locations = [];
74 for ( $i = 0; $i < 5; $i++ ) {
75 $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 );
76 }
77
78 $expectedLocations = [
79 "hello0" => [ "s4", "s5" ],
80 "hello1" => [ "s6", "s5" ],
81 "hello2" => [ "s3", "s1" ],
82 "hello3" => [ "s6", "s5" ],
83 "hello4" => [ "s6", "s3" ],
84 ];
85 $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
86 }
87
91 public function testHashRingRatios( $locations, $expectedHits ) {
92 $ring = new HashRing( $locations, 'whirlpool' );
93
94 $locationStats = array_fill_keys( array_keys( $locations ), 0 );
95 for ( $i = 0; $i < 10000; ++$i ) {
96 ++$locationStats[$ring->getLocation( "key-$i" )];
97 }
98 $this->assertEquals( $expectedHits, $locationStats );
99 }
100
101 public static function providor_getHashLocationWeights() {
102 return [
103 [
104 [ 'big' => 10, 'medium' => 5, 'small' => 1 ],
105 [ 'big' => 6037, 'medium' => 3314, 'small' => 649 ]
106 ]
107 ];
108 }
109
113 public function testHashRingRatios2( $locations, $expected ) {
114 $ring = new HashRing( $locations, 'sha1' );
115 $locationStats = array_fill_keys( array_keys( $locations ), 0 );
116 for ( $i = 0; $i < 1000; ++$i ) {
117 foreach ( $ring->getLocations( "key-$i", 3 ) as $location ) {
118 ++$locationStats[$location];
119 }
120 }
121 $this->assertEquals( $expected, $locationStats );
122 }
123
124 public static function providor_getHashLocationWeights2() {
125 return [
126 [
127 [ 'big1' => 10, 'big2' => 10, 'big3' => 10, 'small1' => 1, 'small2' => 1 ],
128 [ 'big1' => 929, 'big2' => 899, 'big3' => 887, 'small1' => 143, 'small2' => 142 ]
129 ]
130 ];
131 }
132
133 public function testHashRingEjection() {
134 $map = [ 's1' => 5, 's2' => 5, 's3' => 10, 's4' => 10, 's5' => 5, 's6' => 5 ];
135 $ring = new HashRing( $map, 'md5' );
136
137 $ring->ejectFromLiveRing( 's3', 30 );
138 $ring->ejectFromLiveRing( 's6', 15 );
139
140 $this->assertEquals(
141 [ 's1' => 5, 's2' => 5, 's4' => 10, 's5' => 5 ],
142 $ring->getLiveLocationWeights(),
143 'Live location weights'
144 );
145
146 for ( $i = 0; $i < 100; ++$i ) {
147 $key = "key-$i";
148
149 $this->assertNotEquals( 's3', $ring->getLiveLocation( $key ), 'ejected' );
150 $this->assertNotEquals( 's6', $ring->getLiveLocation( $key ), 'ejected' );
151
152 if ( !in_array( $ring->getLocation( $key ), [ 's3', 's6' ], true ) ) {
153 $this->assertEquals(
154 $ring->getLocation( $key ),
155 $ring->getLiveLocation( $key ),
156 "Live ring otherwise matches (#$i)"
157 );
158 $this->assertEquals(
159 $ring->getLocations( $key, 1 ),
160 $ring->getLiveLocations( $key, 1 ),
161 "Live ring otherwise matches (#$i)"
162 );
163 }
164 }
165 }
166
167 public function testHashRingCollision() {
168 $ring1 = new HashRing( [ 0 => 1, 6497 => 1 ] );
169 $ring2 = new HashRing( [ 6497 => 1, 0 => 1 ] );
170
171 for ( $i = 0; $i < 100; ++$i ) {
172 $this->assertEquals( $ring1->getLocation( $i ), $ring2->getLocation( $i ) );
173 }
174 }
175
176 public function testHashRingKetamaMode() {
177 // Same as https://github.com/RJ/ketama/blob/master/ketama.servers
178 $map = [
179 '10.0.1.1:11211' => 600,
180 '10.0.1.2:11211' => 300,
181 '10.0.1.3:11211' => 200,
182 '10.0.1.4:11211' => 350,
183 '10.0.1.5:11211' => 1000,
184 '10.0.1.6:11211' => 800,
185 '10.0.1.7:11211' => 950,
186 '10.0.1.8:11211' => 100
187 ];
188 $ring = new HashRing( $map, 'md5' );
189 $wrapper = \Wikimedia\TestingAccessWrapper::newFromObject( $ring );
190
191 $ketama_test = function ( $count ) use ( $wrapper ) {
192 $baseRing = $wrapper->baseRing;
193
194 $lines = [];
195 for ( $key = 0; $key < $count; ++$key ) {
196 $location = $wrapper->getLocation( $key );
197
198 $itemPos = $wrapper->getItemPosition( $key );
199 $nodeIndex = $wrapper->findNodeIndexForPosition( $itemPos, $baseRing );
200 $nodePos = $baseRing[$nodeIndex][HashRing::KEY_POS];
201
202 $lines[] = sprintf( "%u %u %s\n", $itemPos, $nodePos, $location );
203 }
204
205 return "\n" . implode( '', $lines );
206 };
207
208 // Known correct values generated from C code:
209 // https://github.com/RJ/ketama/blob/master/libketama/ketama_test.c
210 $expected = <<<EOT
211
2122216742351 2217271743 10.0.1.1:11211
213943901380 949045552 10.0.1.5:11211
2142373066440 2374693370 10.0.1.6:11211
2152127088620 2130338203 10.0.1.6:11211
2162046197672 2051996197 10.0.1.7:11211
2172134629092 2135172435 10.0.1.1:11211
218470382870 472541453 10.0.1.7:11211
2191608782991 1609789509 10.0.1.3:11211
2202516119753 2520092206 10.0.1.2:11211
2213465331781 3466294492 10.0.1.4:11211
2221749342675 1753760600 10.0.1.5:11211
2231136464485 1137779711 10.0.1.1:11211
2243620997826 3621580689 10.0.1.7:11211
225283385029 285581365 10.0.1.6:11211
2262300818346 2302165654 10.0.1.5:11211
2272132603803 2134614475 10.0.1.8:11211
2282962705863 2969767984 10.0.1.2:11211
229786427760 786565633 10.0.1.5:11211
2304095887727 4096760944 10.0.1.6:11211
2312906459679 2906987515 10.0.1.6:11211
232137884056 138922607 10.0.1.4:11211
23381549628 82491298 10.0.1.6:11211
2343530020790 3530525869 10.0.1.6:11211
2354231817527 4234960467 10.0.1.7:11211
2362011099423 2014738083 10.0.1.7:11211
237107620750 120968799 10.0.1.6:11211
2383979113294 3981926993 10.0.1.4:11211
239273671938 276355738 10.0.1.4:11211
2404032816947 4033300359 10.0.1.5:11211
241464234862 466093615 10.0.1.1:11211
2423007059764 3007671127 10.0.1.5:11211
243542337729 542491760 10.0.1.7:11211
2444040385635 4044064727 10.0.1.5:11211
2453319802648 3320661601 10.0.1.7:11211
2461032153571 1035085391 10.0.1.1:11211
2473543939100 3545608820 10.0.1.5:11211
2483876899353 3885324049 10.0.1.2:11211
2493771318181 3773259708 10.0.1.8:11211
2503457906597 3459285639 10.0.1.5:11211
2513028975062 3031083168 10.0.1.7:11211
252244467158 250943416 10.0.1.5:11211
2531604785716 1609789509 10.0.1.3:11211
2543905343649 3905751132 10.0.1.1:11211
2551713497623 1725056963 10.0.1.5:11211
2561668356087 1668827816 10.0.1.5:11211
2573427369836 3438933308 10.0.1.1:11211
2582515850457 2520092206 10.0.1.2:11211
2593886138983 3887390208 10.0.1.1:11211
2604019334756 4023153300 10.0.1.8:11211
2611170561012 1170785765 10.0.1.7:11211
2621841809344 1848425105 10.0.1.6:11211
263973223976 973369204 10.0.1.1:11211
264358093210 359562433 10.0.1.6:11211
265378350808 380841931 10.0.1.5:11211
2664008477862 4012085095 10.0.1.7:11211
2671027226549 1028630030 10.0.1.6:11211
2682386583967 2387706118 10.0.1.1:11211
269522892146 524831677 10.0.1.7:11211
2703779194982 3788912803 10.0.1.5:11211
2713764731657 3771312500 10.0.1.7:11211
272184756999 187529415 10.0.1.6:11211
273838351231 845886003 10.0.1.3:11211
2742827220548 2828019973 10.0.1.6:11211
2753604721411 3607668249 10.0.1.6:11211
276472866282 475506254 10.0.1.5:11211
2772752268796 2754833471 10.0.1.5:11211
2781791464754 1795042583 10.0.1.7:11211
2793029359475 3031083168 10.0.1.7:11211
2803633378211 3639985542 10.0.1.6:11211
2813148267284 3149217023 10.0.1.6:11211
282163887996 166705043 10.0.1.7:11211
2833642803426 3649125922 10.0.1.7:11211
2843901799218 3902199881 10.0.1.7:11211
285418045394 425867331 10.0.1.6:11211
286346775981 348578169 10.0.1.6:11211
287368352208 372224616 10.0.1.7:11211
2882643711995 2644259911 10.0.1.5:11211
2892032983336 2033860601 10.0.1.6:11211
2903567842357 3572867530 10.0.1.2:11211
2911024982737 1028630030 10.0.1.6:11211
292933966832 938106828 10.0.1.7:11211
2932102520899 2103402846 10.0.1.7:11211
2943537205399 3538094881 10.0.1.7:11211
2952311233534 2314593262 10.0.1.1:11211
2962500514664 2503565236 10.0.1.7:11211
2971091958846 1093484995 10.0.1.6:11211
2983984972691 3987453644 10.0.1.1:11211
2992669994439 2670911201 10.0.1.4:11211
3002846111786 2846115813 10.0.1.5:11211
3011805010806 1808593732 10.0.1.8:11211
3021587024774 1587746378 10.0.1.5:11211
3033214549588 3215619351 10.0.1.2:11211
3041965214866 1970922428 10.0.1.7:11211
3051038671000 1040777775 10.0.1.7:11211
306820820468 823114475 10.0.1.6:11211
3072722835329 2723166435 10.0.1.5:11211
3081602053414 1604196066 10.0.1.5:11211
3091330835426 1335097278 10.0.1.5:11211
310556547565 557075710 10.0.1.4:11211
3112977587884 2978402952 10.0.1.1:11211
312
313EOT;
314
315 $this->assertEquals( $expected, $ketama_test( 100 ), 'Ketama mode (diff check)' );
316
317 // Hash of known correct values from C code
318 $this->assertEquals(
319 'd1a4912a80e4654ec2e4e462c8b911c6',
320 md5( $ketama_test( 1e3 ) ),
321 'Ketama mode (large, MD5 check)'
322 );
323
324 // Slower, full upstream MD5 check, manually verified 3/21/2018
325 // $this->assertEquals( '5672b131391f5aa2b280936aec1eea74', md5( $ketama_test( 1e6 ) ) );
326 }
327}
serialize()
unserialize( $serialized)
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
HashRing HashRing.
testHashRingRatios2( $locations, $expected)
providor_getHashLocationWeights2
testHashRingRatios( $locations, $expectedHits)
providor_getHashLocationWeights
static providor_getHashLocationWeights2()
static providor_getHashLocationWeights()
Convenience class for weighted consistent hash rings.
Definition HashRing.php:39
const KEY_POS
Definition HashRing.php:59
$lines
Definition router.php:61
foreach( $res as $row) $serialized