consistent-hashing 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  consistent-hashing
2
2
  ==================
3
3
 
4
- A generic implementation of the Consistent Hashing algorithm.
4
+ A generic implementation of the Consistent Hashing algorithm using an AVL tree.
5
5
 
6
6
  Features
7
7
  --------
8
8
 
9
9
  * set number of replicas to create multiple virtual points in the ring for each node
10
10
  * nodes can be arbitrary data (e.g. a Memcache client instance)
11
+ * fast performance through using an AVL tree internally
11
12
 
12
13
  Examples
13
14
  --------
data/Rakefile CHANGED
@@ -15,4 +15,5 @@ Bones {
15
15
  email 'liebler.dominik@googlemail.com'
16
16
  url 'https://github.com/domnikl/consistent-hashing'
17
17
  ignore_file '.gitignore'
18
+ depend_on 'avl_tree'
18
19
  }
@@ -0,0 +1,42 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), %w{.. lib}))
2
+
3
+ require 'benchmark'
4
+ require 'ipaddr'
5
+ require 'consistent_hashing'
6
+
7
+ def ip(offset)
8
+ address = IPAddr.new('10.0.0.0').to_i + offset
9
+ [24, 16, 8, 0].collect {|b| (address >> b) & 255}.join('.')
10
+ end
11
+
12
+ def rand_client_id()
13
+ (rand * 1_000_000).to_int
14
+ end
15
+
16
+ def benchmark_insertions_lookups()
17
+ # The initial ring implementation using a combination of hash and sorted list
18
+ # had the following results when benchmarked:
19
+ # user system total real
20
+ # Insertions: 1.260000 0.000000 1.260000 ( 1.259346)
21
+ # Look ups: 20.080000 0.020000 20.100000 ( 20.111773)
22
+ #
23
+ # The ring implementation using an AVLTree has the following results
24
+ # when benchmarked on the same system:
25
+ # user system total real
26
+ # Insertions: 0.060000 0.000000 0.060000 ( 0.062302)
27
+ # Look ups: 1.020000 0.000000 1.020000 ( 1.028172)
28
+ #
29
+ # The performance improvement is ~20x for both insertions and lookups.
30
+
31
+ Benchmark.bm(10) do |x|
32
+ ring = ConsistentHashing::Ring.new(replicas: 200)
33
+ x.report("Insertions:") {for i in 1..1_000; ring << ip(i); end}
34
+ x.report("Look ups: ") do
35
+ for i in 1..100_000
36
+ ring.point_for(rand_client_id)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ benchmark_insertions_lookups
@@ -10,7 +10,8 @@ module ConsistentHashing
10
10
  def self.load_lib
11
11
  require File.join(LIBPATH, 'consistent_hashing', 'virtual_point')
12
12
  require File.join(LIBPATH, 'consistent_hashing', 'ring')
13
+ require File.join(LIBPATH, 'consistent_hashing', 'avl_tree')
13
14
  end
14
15
  end
15
16
 
16
- ConsistentHashing.load_lib
17
+ ConsistentHashing.load_lib
@@ -0,0 +1,49 @@
1
+ require 'avl_tree'
2
+
3
+ module ConsistentHashing
4
+ class AVLTree < ::AVLTree
5
+
6
+ def minimum_pair()
7
+ # Return the key with the smallest key value.
8
+ return nil if @root.empty?
9
+
10
+ current_node = @root
11
+ while not current_node.left.empty?
12
+ current_node = current_node.left
13
+ end
14
+
15
+ [current_node.key, current_node.value]
16
+ end
17
+
18
+ def next_gte_pair(key)
19
+ # Returns the key/value pair with a key that follows the provided key in
20
+ # sorted order.
21
+ node = next_gte_node(@root, key)
22
+ [node.key, node.value] if not node.empty?
23
+ end
24
+
25
+ protected
26
+
27
+ def next_gte_node(node, key)
28
+ return AVLTree::Node::EMPTY if node.empty?
29
+
30
+ if key < node.key
31
+ # The current key qualifies as after the provided key. However, we need
32
+ # to check the tree on the left to see if there's a key in there also
33
+ # greater than the provided key but less than the current key.
34
+ after = next_gte_node(node.left, key)
35
+ after = node if after.empty?
36
+ elsif key > node.key
37
+ # The current key will not be after the provided key, but something
38
+ # in the right branch maybe. Check the right branch for the first key
39
+ # larger than our value.
40
+ after = next_gte_node(node.right, key)
41
+ elsif node.key == key
42
+ # An exact match qualifies as the next largest node.
43
+ after = node
44
+ end
45
+
46
+ return after
47
+ end
48
+ end
49
+ end
@@ -10,8 +10,7 @@ module ConsistentHashing
10
10
  # Public: returns a new ring object
11
11
  def initialize(nodes = [], replicas = 3)
12
12
  @replicas = replicas
13
- @sorted_keys = []
14
- @ring = Hash.new
13
+ @ring = AVLTree.new
15
14
 
16
15
  nodes.each { |node| add(node) }
17
16
  end
@@ -31,11 +30,8 @@ module ConsistentHashing
31
30
  key = hash_key(node, i)
32
31
 
33
32
  @ring[key] = VirtualPoint.new(node, key)
34
- @sorted_keys << key
35
33
  end
36
34
 
37
- @sorted_keys.sort!
38
-
39
35
  self
40
36
  end
41
37
  alias :<< :add
@@ -47,7 +43,6 @@ module ConsistentHashing
47
43
  key = hash_key(node, i)
48
44
 
49
45
  @ring.delete key
50
- @sorted_keys.delete key
51
46
  end
52
47
 
53
48
  self
@@ -58,14 +53,10 @@ module ConsistentHashing
58
53
  #
59
54
  def point_for(key)
60
55
  return nil if @ring.empty?
61
-
62
56
  key = hash_key(key)
63
-
64
- @sorted_keys.each do |i|
65
- return @ring[i] if key <= i
66
- end
67
-
68
- @ring[@sorted_keys[0]]
57
+ _, value = @ring.next_gte_pair(key)
58
+ _, value = @ring.minimum_pair unless value
59
+ value
69
60
  end
70
61
 
71
62
  # Public: gets the node where to store the key
@@ -100,4 +91,4 @@ module ConsistentHashing
100
91
  Digest::MD5.hexdigest(key.to_s)[0..16].hex
101
92
  end
102
93
  end
103
- end
94
+ end
@@ -0,0 +1,62 @@
1
+ require File.join(File.dirname(__FILE__), %w{ .. test_consistent_hashing})
2
+
3
+ class TestAVLTree < ConsistentHashing::TestCase
4
+ AVLTree = ConsistentHashing::AVLTree
5
+
6
+ def test_minimum_returns_nil_if_empty
7
+ tree = AVLTree.new
8
+ assert_nil tree.minimum_pair
9
+ end
10
+
11
+ def test_minimum_returns_root_node_if_only_node
12
+ tree = AVLTree.new
13
+ tree[0]= 1
14
+ assert_equal(tree.minimum_pair, [0, 1])
15
+ end
16
+
17
+ def test_minimum_returns_left_most_leaf
18
+ tree = AVLTree.new
19
+ 1.upto(10) do |i|
20
+ tree[i] = i.to_s
21
+ end
22
+
23
+ assert_equal(tree.minimum_pair, [1, "1"])
24
+ end
25
+
26
+ def test_next_gte_pair_returns_nil_if_empty
27
+ tree = AVLTree.new
28
+ assert_nil tree.next_gte_pair("some key")
29
+ end
30
+
31
+ def test_next_gte_pair_finds_exact_match_key
32
+ tree = AVLTree.new
33
+ 1.upto(10) do |i|
34
+ tree[i] = i.to_s
35
+ end
36
+
37
+ 1.upto(10) do |i|
38
+ assert_equal(tree.next_gte_pair(i), [i, i.to_s])
39
+ end
40
+ end
41
+
42
+ def test_next_gte_pair_finds_slightly_larger_key
43
+ tree = AVLTree.new
44
+ (2..11).step(2) do |i|
45
+ tree[i] = i.to_s
46
+ end
47
+
48
+ (1..10).step(2) do |i|
49
+ assert_equal(tree.next_gte_pair(i), [i + 1, (i + 1).to_s])
50
+ end
51
+ end
52
+
53
+ def test_next_gte_pair_returns_nil_if_no_larger_keys
54
+ tree = AVLTree.new
55
+ (2..11).step(2) do |i|
56
+ tree[i] = i.to_s
57
+ end
58
+
59
+ assert_nil tree.next_gte_pair(12)
60
+ end
61
+ end
62
+
@@ -51,34 +51,4 @@ class TestRing < ConsistentHashing::TestCase
51
51
  def test_point_for
52
52
  assert_equal "C", @ring.node_for(@examples["C"])
53
53
  end
54
-
55
- def test_arbitrary_node_data
56
- nodes = [
57
- {"host" => "192.168.1.101"},
58
- {"host" => "192.168.1.102"},
59
- {"host" => "192.168.1.103"}
60
- ]
61
-
62
- ring = ConsistentHashing::Ring.new(nodes)
63
- assert_equal 9, ring.length
64
-
65
- ring_nodes = ring.nodes
66
- assert_equal 3, ring_nodes.length
67
- assert_equal "192.168.1.102", ring_nodes[1]['host']
68
- end
69
-
70
- def test_points
71
- nodes = [
72
- {"host" => "192.168.1.101"},
73
- {"host" => "192.168.1.102"},
74
- {"host" => "192.168.1.103"}
75
- ]
76
-
77
- ring = ConsistentHashing::Ring.new(nodes)
78
- points = ring.points
79
-
80
- assert_equal 9, points.length
81
- assert_not_equal 0, points[0].index
82
- assert_equal "192.168.1.101", points[0].node['host']
83
- end
84
- end
54
+ end
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: consistent-hashing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-15 00:00:00.000000000 Z
12
+ date: 2012-09-22 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: avl_tree
16
+ requirement: &70236637210800 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70236637210800
14
25
  - !ruby/object:Gem::Dependency
15
26
  name: bones
16
- requirement: &70147185310840 !ruby/object:Gem::Requirement
27
+ requirement: &70236637209960 !ruby/object:Gem::Requirement
17
28
  none: false
18
29
  requirements:
19
30
  - - ! '>='
@@ -21,8 +32,9 @@ dependencies:
21
32
  version: 3.8.0
22
33
  type: :development
23
34
  prerelease: false
24
- version_requirements: *70147185310840
25
- description: A generic implementation of the Consistent Hashing algorithm.
35
+ version_requirements: *70236637209960
36
+ description: A generic implementation of the Consistent Hashing algorithm using an
37
+ AVL tree.
26
38
  email: liebler.dominik@googlemail.com
27
39
  executables: []
28
40
  extensions: []
@@ -33,9 +45,12 @@ files:
33
45
  - History.txt
34
46
  - README.md
35
47
  - Rakefile
48
+ - benchmark/benchmark.rb
36
49
  - lib/consistent_hashing.rb
50
+ - lib/consistent_hashing/avl_tree.rb
37
51
  - lib/consistent_hashing/ring.rb
38
52
  - lib/consistent_hashing/virtual_point.rb
53
+ - test/consistent_hashing/test_avl_tree.rb
39
54
  - test/consistent_hashing/test_ring.rb
40
55
  - test/consistent_hashing/test_virtual_point.rb
41
56
  - test/test_consistent_hashing.rb
@@ -65,8 +80,10 @@ rubyforge_project: consistent-hashing
65
80
  rubygems_version: 1.8.16
66
81
  signing_key:
67
82
  specification_version: 3
68
- summary: A generic implementation of the Consistent Hashing algorithm.
83
+ summary: A generic implementation of the Consistent Hashing algorithm using an AVL
84
+ tree.
69
85
  test_files:
86
+ - test/consistent_hashing/test_avl_tree.rb
70
87
  - test/consistent_hashing/test_ring.rb
71
88
  - test/consistent_hashing/test_virtual_point.rb
72
89
  - test/test_consistent_hashing.rb