consistent-hashing 0.0.1 → 0.1.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/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.1.0 / 2012-04-15
2
+
3
+ * introduced the VirtualPoint class
4
+ * nodes can now consist of arbitrary data
5
+ * added methods to get all nodes and all virtual points on the hash ring
6
+
1
7
  == 0.0.1 / 2012-04-15
2
8
 
3
9
  * Birthday!
data/README.md CHANGED
@@ -7,11 +7,14 @@ Features
7
7
  --------
8
8
 
9
9
  * set number of replicas to create multiple virtual points in the ring for each node
10
+ * nodes can be arbitrary data (e.g. a Memcache client instance)
10
11
 
11
12
  Examples
12
13
  --------
13
14
 
14
15
  ```ruby
16
+ require 'consistent_hashing'
17
+
15
18
  ring = ConsistentHashing::Ring.new
16
19
  ring << "192.168.1.101" << "192.168.1.102"
17
20
 
@@ -21,6 +24,9 @@ ring.delete("192.168.1.101")
21
24
  # after removing 192.168.1.101, all keys previously mapped to it move clockwise to
22
25
  # the next node
23
26
  ring.node_for("foobar") # => 192.168.1.102
27
+
28
+ ring.nodes # => ["192.168.1.101", "192.168.1.102"]
29
+ ring.points # => [#<ConsistentHashing::VirtualPoint>, #<ConsistentHashing::VirtualPoint>, ...]
24
30
  ```
25
31
 
26
32
  Install
@@ -8,6 +8,7 @@ module ConsistentHashing
8
8
  # Internal: loads all necessary lib files
9
9
  #
10
10
  def self.load_lib
11
+ require File.join(LIBPATH, 'consistent_hashing', 'virtual_point')
11
12
  require File.join(LIBPATH, 'consistent_hashing', 'ring')
12
13
  end
13
14
  end
@@ -1,8 +1,11 @@
1
1
  require 'digest/md5'
2
+ require 'set'
2
3
 
3
4
  module ConsistentHashing
5
+
6
+ # Public: the hash ring containing all configured nodes
7
+ #
4
8
  class Ring
5
- attr_reader :ring
6
9
 
7
10
  # Public: returns a new ring object
8
11
  def initialize(nodes = [], replicas = 3)
@@ -27,7 +30,7 @@ module ConsistentHashing
27
30
  # generate the key of this (virtual) point in the hash
28
31
  key = hash_key(node, i)
29
32
 
30
- @ring[key] = node
33
+ @ring[key] = VirtualPoint.new(node, key)
31
34
  @sorted_keys << key
32
35
  end
33
36
 
@@ -50,19 +53,41 @@ module ConsistentHashing
50
53
  self
51
54
  end
52
55
 
53
- # Public: gets the node for an arbitrary key
56
+ # Public: gets the point for an arbitrary key
54
57
  #
55
58
  #
56
- def node_for(key)
57
- return [nil, 0] if @ring.empty?
59
+ def point_for(key)
60
+ return nil if @ring.empty?
58
61
 
59
62
  key = hash_key(key)
60
63
 
61
64
  @sorted_keys.each do |i|
62
- return [@ring[i], i] if key <= i
65
+ return @ring[i] if key <= i
63
66
  end
64
67
 
65
- [@ring[@sorted_keys[0]], 0]
68
+ @ring[@sorted_keys[0]]
69
+ end
70
+
71
+ # Public: gets the node where to store the key
72
+ #
73
+ # Returns: the node Object
74
+ def node_for(key)
75
+ point_for(key).node
76
+ end
77
+
78
+ # Public: get all nodes in the ring
79
+ #
80
+ # Returns: an Array of the nodes in the ring
81
+ def nodes
82
+ nodes = points.map { |point| point.node }
83
+ nodes.uniq
84
+ end
85
+
86
+ # Public: gets all points in the ring
87
+ #
88
+ # Returns: an Array of the points in the ring
89
+ def points
90
+ @ring.map { |point| point[1] }
66
91
  end
67
92
 
68
93
  protected
@@ -0,0 +1,18 @@
1
+ module ConsistentHashing
2
+
3
+ # Public: represents a virtual point on the hash ring
4
+ #
5
+ class VirtualPoint
6
+ attr_reader :node, :index
7
+
8
+ def initialize(node, index)
9
+ @node = node
10
+ @index = index.to_i
11
+ end
12
+
13
+ # Public: set a new index for the virtual point. Useful if the point gets duplicated
14
+ def index=(index)
15
+ @index = index.to_i
16
+ end
17
+ end
18
+ end
@@ -26,23 +26,59 @@ class TestRing < ConsistentHashing::TestCase
26
26
  end
27
27
 
28
28
  def test_get_node
29
- assert_equal "A", @ring.node_for(@examples["A"])[0]
30
- assert_equal "B", @ring.node_for(@examples["B"])[0]
31
- assert_equal "C", @ring.node_for(@examples["C"])[0]
29
+ assert_equal "A", @ring.point_for(@examples["A"]).node
30
+ assert_equal "B", @ring.point_for(@examples["B"]).node
31
+ assert_equal "C", @ring.point_for(@examples["C"]).node
32
32
  end
33
33
 
34
34
  # should fall back to the first node, if key > last node
35
35
  def test_get_node_fallback_to_first
36
36
  ring = ConsistentHashing::Ring.new ["A"], 1
37
37
 
38
- assert_equal "A", ring.node_for(@examples["not_found"])[0]
39
- assert_equal 0, ring.node_for(@examples["not_found"])[1]
38
+ point = ring.point_for(@examples["not_found"])
39
+
40
+ assert_equal "A", point.node
41
+ assert_not_equal 0, point.index
40
42
  end
41
43
 
42
44
  # if I remove node C, all keys previously mapped to C should be moved clockwise to
43
45
  # the next node. That's a virtual point of B here
44
46
  def test_remove_node
45
47
  @ring.delete("C")
46
- assert_equal "B", @ring.node_for(@examples["C"])[0]
48
+ assert_equal "B", @ring.point_for(@examples["C"]).node
49
+ end
50
+
51
+ def test_point_for
52
+ assert_equal "C", @ring.node_for(@examples["C"])
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']
47
83
  end
48
84
  end
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), %w{ .. test_consistent_hashing})
2
+
3
+ class TestVirtualPoint < ConsistentHashing::TestCase
4
+ def setup
5
+ @node = {'host' => '192.168.1.101'}
6
+ @point = ConsistentHashing::VirtualPoint.new(@node, "1")
7
+ end
8
+
9
+ def test_init
10
+ assert_equal @node, @point.node
11
+ assert_equal 1, @point.index
12
+ end
13
+
14
+ def test_set_index
15
+ @point.index = "2"
16
+ assert_equal 2, @point.index
17
+ end
18
+ end
data/version.txt CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.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.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-04-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bones
16
- requirement: &70330988436160 !ruby/object:Gem::Requirement
16
+ requirement: &70147185310840 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 3.8.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70330988436160
24
+ version_requirements: *70147185310840
25
25
  description: A generic implementation of the Consistent Hashing algorithm.
26
26
  email: liebler.dominik@googlemail.com
27
27
  executables: []
@@ -35,7 +35,9 @@ files:
35
35
  - Rakefile
36
36
  - lib/consistent_hashing.rb
37
37
  - lib/consistent_hashing/ring.rb
38
+ - lib/consistent_hashing/virtual_point.rb
38
39
  - test/consistent_hashing/test_ring.rb
40
+ - test/consistent_hashing/test_virtual_point.rb
39
41
  - test/test_consistent_hashing.rb
40
42
  - version.txt
41
43
  homepage: https://github.com/domnikl/consistent-hashing
@@ -66,4 +68,5 @@ specification_version: 3
66
68
  summary: A generic implementation of the Consistent Hashing algorithm.
67
69
  test_files:
68
70
  - test/consistent_hashing/test_ring.rb
71
+ - test/consistent_hashing/test_virtual_point.rb
69
72
  - test/test_consistent_hashing.rb