consistent-hashing 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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