consistent-hashing 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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