hashafras 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/hashafras.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "hashafras/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hashafras"
7
+ s.version = Hashafras::VERSION
8
+ s.authors = ["Tim Ariyeh"]
9
+ s.email = ["tim.ariyeh@gmail.com"]
10
+ s.homepage = "https://github.com/timariyeh/hashafras"
11
+ s.summary = %q{Stupid-simple consistent hashing for Ruby}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "hashafras"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ end
@@ -0,0 +1,64 @@
1
+ require 'zlib'
2
+
3
+ module Hashafras
4
+ class Ring
5
+ DEFAULTS = {:replicas => 20}
6
+
7
+ attr_accessor :options
8
+
9
+ def initialize(options = {})
10
+ @options = DEFAULTS.merge(options)
11
+ end
12
+
13
+ def add_member(name, host)
14
+ options[:replicas].times do |t|
15
+ key = hash("#{name}_#{t}")
16
+ members[key] = host
17
+ member_positions << key
18
+ member_positions.sort!
19
+ end
20
+ end
21
+
22
+ def remove_member(name)
23
+ options[:replicas].times do |t|
24
+ key = hash("#{name}_#{t}")
25
+ members.delete(key)
26
+ member_positions.delete(key)
27
+ member_positions.compact!
28
+ end
29
+ end
30
+
31
+ def members
32
+ @members ||= {}
33
+ end
34
+
35
+ def member_positions
36
+ @member_positions ||= []
37
+ end
38
+
39
+ def hash(key)
40
+ Zlib.crc32("#{key}")
41
+ end
42
+
43
+ def find_host_for_key(key)
44
+ return nil if members.empty?
45
+ return members.first if members.size == 1
46
+
47
+ hash_value = hash(key)
48
+ return members[hash_value] if members[hash_value]
49
+
50
+ return find_nearest_member(hash_value)
51
+ end
52
+
53
+ def find_nearest_member(key)
54
+ member = nil
55
+ member_positions.each do |m_position|
56
+ if m_position > key
57
+ member = members[m_position]
58
+ break
59
+ end
60
+ end
61
+ member ||= members.first.last
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Hashafras
2
+ VERSION = "0.0.1"
3
+ end
data/lib/hashafras.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "hashafras/version"
2
+ require "hashafras/ring"
data/spec/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
data/spec/ring_spec.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require 'benchmark'
4
+ describe Hashafras::Ring do
5
+ context "has only one member" do
6
+ let(:ring){
7
+ Hashafras::Ring.new.tap do |o|
8
+ o.add_member("s1", "s1:80")
9
+ end
10
+ }
11
+ it "should always return the same member" do
12
+ ring.find_host_for_key("foo").should == "s1:80"
13
+ ring.find_host_for_key("bar").should == "s1:80"
14
+ ring.find_host_for_key("baz").should == "s1:80"
15
+ end
16
+ end
17
+
18
+ context "has many members" do
19
+ def members
20
+ @members ||= 10.times.inject([]) {|memo,obj| memo.push(:name => "s#{obj}", :host => "s#{obj}:80") }
21
+ end
22
+
23
+ def iterations
24
+ 1000
25
+ end
26
+ let(:ring){
27
+ Hashafras::Ring.new.tap do |o|
28
+ members.each do |s|
29
+ o.add_member(s[:name], s[:host])
30
+ end
31
+ end
32
+ }
33
+ it "should evenly distribute keys" do
34
+ results = {}
35
+ iterations.times do |key|
36
+ host = ring.find_host_for_key(key)
37
+ results[host] ||= 0
38
+ results[host] += 1
39
+ end
40
+ members.count.should == results.keys.count
41
+ max = results.max.last.to_f
42
+ min = results.min.last.to_f
43
+
44
+ (min / max).should > 0.8
45
+ end
46
+
47
+ context "when topology changes" do
48
+ def results
49
+ results = {}
50
+ iterations.times do |key|
51
+ host = ring.find_host_for_key(key)
52
+ results[key] = host
53
+ end
54
+ results
55
+ end
56
+
57
+ def result_diff(result1, result2)
58
+ result1.reject {|k,v| result2[k] == v}
59
+ end
60
+
61
+ it "should be minimally disruptive to the keyspace when nodes are added" do
62
+ original_results = results
63
+ ring.add_member("new_host", "new_host:80")
64
+ updated_results = results
65
+
66
+ diff = result_diff(original_results, updated_results)
67
+ diff.count.should <= (iterations / (members.count))
68
+ end
69
+
70
+ it "should be minimally disruptive to the keyspace when nodes are removed" do
71
+ original_results = results
72
+ ring.remove_member("new_host")
73
+ updated_results = results
74
+
75
+ diff = result_diff(original_results, updated_results)
76
+ diff.count.should <= (iterations / (members.count))
77
+ end
78
+ end
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hashafras
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tim Ariyeh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-05 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &8301720 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *8301720
25
+ description: ''
26
+ email:
27
+ - tim.ariyeh@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - Rakefile
35
+ - hashafras.gemspec
36
+ - lib/hashafras.rb
37
+ - lib/hashafras/ring.rb
38
+ - lib/hashafras/version.rb
39
+ - spec/.rspec
40
+ - spec/ring_spec.rb
41
+ homepage: https://github.com/timariyeh/hashafras
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project: hashafras
61
+ rubygems_version: 1.8.6
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Stupid-simple consistent hashing for Ruby
65
+ test_files: []