Skip to content

Commit 47c9667

Browse files
authored
Merge pull request #208 from mfenniak/key-distribution
Fix FNV1a key poor distribution
2 parents ec873e7 + 3844c73 commit 47c9667

File tree

3 files changed

+138
-1
lines changed

3 files changed

+138
-1
lines changed

src/Enyim.Caching/FnvHash.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class FNV64 : System.Security.Cryptography.HashAlgorithm, IUIntHashAlgori
2323
public FNV64()
2424
{
2525
//base.HashSize = 64;
26+
Initialize();
2627
}
2728

2829
/// <summary>
@@ -109,6 +110,7 @@ public class FNV1 : HashAlgorithm, IUIntHashAlgorithm
109110
/// </summary>
110111
public FNV1()
111112
{
113+
Initialize();
112114
}
113115

114116
/// <summary>

src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ private static uint[] GenerateKeys(IMemcachedNode node, int numberOfKeys)
172172

173173
for (int i = 0; i < numberOfKeys; i++)
174174
{
175-
byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(String.Concat(address, "-", i)));
175+
byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(String.Concat(i, "-", address)));
176176

177177
for (int h = 0; h < PartCount; h++)
178178
{
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Net;
7+
using Enyim.Caching.Memcached;
8+
using Enyim.Caching.Memcached.Results;
9+
using Xunit;
10+
11+
namespace Enyim.Caching.Tests
12+
{
13+
public class DefaultNodeLocatorTest
14+
{
15+
[Fact]
16+
public void FNV1a()
17+
{
18+
var fnv = new Enyim.FNV1a();
19+
20+
// FNV1a test vectors:
21+
// http://www.isthe.com/chongo/src/fnv/test_fnv.c
22+
var testVectors = new List<Tuple<string, UInt32>>
23+
{
24+
new Tuple<string, uint>("",0x811c9dc5U),
25+
new Tuple<string, uint>("a",0xe40c292cU),
26+
new Tuple<string, uint>("b",0xe70c2de5U),
27+
new Tuple<string, uint>("c",0xe60c2c52U),
28+
new Tuple<string, uint>("d",0xe10c2473U),
29+
new Tuple<string, uint>("e",0xe00c22e0U),
30+
new Tuple<string, uint>("f",0xe30c2799U),
31+
new Tuple<string, uint>("fo",0x6222e842U),
32+
new Tuple<string, uint>("foo",0xa9f37ed7U),
33+
new Tuple<string, uint>("foob",0x3f5076efU),
34+
};
35+
36+
foreach (var testVector in testVectors)
37+
{
38+
byte[] data = fnv.ComputeHash(Encoding.ASCII.GetBytes(testVector.Item1));
39+
uint value = BitConverter.ToUInt32(data, 0);
40+
Assert.Equal(value, testVector.Item2);
41+
}
42+
}
43+
44+
[Fact]
45+
public void TestLocator()
46+
{
47+
String[] servers = new[]
48+
{
49+
"10.0.1.1:11211",
50+
"10.0.1.2:11211",
51+
"10.0.1.3:11211",
52+
"10.0.1.4:11211",
53+
"10.0.1.5:11211",
54+
"10.0.1.6:11211",
55+
"10.0.1.7:11211",
56+
"10.0.1.8:11211",
57+
};
58+
int[] serverCount = new int[servers.Length];
59+
60+
var nodes = servers.
61+
Select(s => new MockNode(new IPEndPoint(IPAddress.Parse(s.Substring(0, s.IndexOf(":"))), 11211))).
62+
Cast<IMemcachedNode>().
63+
ToList();
64+
65+
IMemcachedNodeLocator locator = new DefaultNodeLocator();
66+
locator.Initialize(nodes.ToList());
67+
68+
var keyCheckCount = 1000000;
69+
var expectedKeysPerServer = keyCheckCount / nodes.Count;
70+
71+
var random = new Random();
72+
for (int i = 0; i < keyCheckCount; i++)
73+
{
74+
var node = locator.Locate(random.NextDouble().ToString());
75+
for (int j = 0; j < nodes.Count; j++)
76+
{
77+
if (nodes[j] == node)
78+
{
79+
serverCount[j]++;
80+
break;
81+
}
82+
}
83+
}
84+
85+
double maxVariation = 0;
86+
for (int i = 0; i < serverCount.Length; i++)
87+
{
88+
var keysThisServer = serverCount[i];
89+
var variation = (double)Math.Abs(keysThisServer - expectedKeysPerServer) / expectedKeysPerServer;
90+
maxVariation = Math.Max(maxVariation, variation);
91+
Console.WriteLine("Expected about {0} keys per server; got {1} for server {2}; variation: {3:0.0%}", expectedKeysPerServer, keysThisServer, i, variation);
92+
}
93+
Assert.InRange(maxVariation, 0, 0.20); // variation expected to be less than 20%
94+
}
95+
}
96+
97+
class MockNode : IMemcachedNode
98+
{
99+
public MockNode(IPEndPoint endpoint)
100+
{
101+
this.EndPoint = endpoint;
102+
}
103+
104+
public EndPoint EndPoint { get; private set; }
105+
106+
public bool IsAlive => true;
107+
108+
public event Action<IMemcachedNode> Failed;
109+
110+
public void Dispose()
111+
{
112+
throw new NotImplementedException();
113+
}
114+
115+
public IOperationResult Execute(IOperation op)
116+
{
117+
throw new NotImplementedException();
118+
}
119+
120+
public Task<IOperationResult> ExecuteAsync(IOperation op)
121+
{
122+
throw new NotImplementedException();
123+
}
124+
125+
public Task<bool> ExecuteAsync(IOperation op, Action<bool> next)
126+
{
127+
throw new NotImplementedException();
128+
}
129+
130+
public bool Ping()
131+
{
132+
throw new NotImplementedException();
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)