chimera 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.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +57 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +114 -0
  5. data/Rakefile +30 -0
  6. data/doc/NOTES +11 -0
  7. data/doc/examples/config.yml +16 -0
  8. data/doc/redis6379.conf +132 -0
  9. data/lib/chimera.rb +33 -0
  10. data/lib/chimera/associations.rb +146 -0
  11. data/lib/chimera/attributes.rb +52 -0
  12. data/lib/chimera/base.rb +95 -0
  13. data/lib/chimera/config.rb +9 -0
  14. data/lib/chimera/error.rb +12 -0
  15. data/lib/chimera/finders.rb +49 -0
  16. data/lib/chimera/geo_indexes.rb +76 -0
  17. data/lib/chimera/indexes.rb +177 -0
  18. data/lib/chimera/persistence.rb +70 -0
  19. data/lib/chimera/redis_objects.rb +345 -0
  20. data/lib/redis.rb +373 -0
  21. data/lib/redis/counter.rb +94 -0
  22. data/lib/redis/dist_redis.rb +149 -0
  23. data/lib/redis/hash_ring.rb +135 -0
  24. data/lib/redis/helpers/core_commands.rb +46 -0
  25. data/lib/redis/helpers/serialize.rb +25 -0
  26. data/lib/redis/list.rb +122 -0
  27. data/lib/redis/lock.rb +83 -0
  28. data/lib/redis/objects.rb +100 -0
  29. data/lib/redis/objects/counters.rb +132 -0
  30. data/lib/redis/objects/lists.rb +45 -0
  31. data/lib/redis/objects/locks.rb +71 -0
  32. data/lib/redis/objects/sets.rb +46 -0
  33. data/lib/redis/objects/values.rb +56 -0
  34. data/lib/redis/pipeline.rb +21 -0
  35. data/lib/redis/set.rb +156 -0
  36. data/lib/redis/value.rb +35 -0
  37. data/lib/riak_raw.rb +100 -0
  38. data/lib/typhoeus.rb +55 -0
  39. data/lib/typhoeus/.gitignore +1 -0
  40. data/lib/typhoeus/easy.rb +253 -0
  41. data/lib/typhoeus/filter.rb +28 -0
  42. data/lib/typhoeus/hydra.rb +210 -0
  43. data/lib/typhoeus/multi.rb +34 -0
  44. data/lib/typhoeus/remote.rb +306 -0
  45. data/lib/typhoeus/remote_method.rb +108 -0
  46. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  47. data/lib/typhoeus/request.rb +124 -0
  48. data/lib/typhoeus/response.rb +39 -0
  49. data/lib/typhoeus/service.rb +20 -0
  50. data/script/console +10 -0
  51. data/script/destroy +14 -0
  52. data/script/generate +14 -0
  53. data/test/models.rb +49 -0
  54. data/test/test_chimera.rb +238 -0
  55. data/test/test_helper.rb +7 -0
  56. metadata +243 -0
@@ -0,0 +1,94 @@
1
+ class Redis
2
+ #
3
+ # Class representing a Redis counter. This functions like a proxy class, in
4
+ # that you can say @object.counter_name to get the value and then
5
+ # @object.counter_name.increment to operate on it. You can use this
6
+ # directly, or you can use the counter :foo class method in your
7
+ # class to define a counter.
8
+ #
9
+ class Counter
10
+ require 'redis/helpers/core_commands'
11
+ include Redis::Helpers::CoreCommands
12
+
13
+ attr_reader :key, :options, :redis
14
+ def initialize(key, redis=$redis, options={})
15
+ @key = key
16
+ @redis = redis
17
+ @options = options
18
+ @options[:start] ||= 0
19
+ @redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
20
+ end
21
+
22
+ # Reset the counter to its starting value. Not atomic, so use with care.
23
+ # Normally only useful if you're discarding all sub-records associated
24
+ # with a parent and starting over (for example, restarting a game and
25
+ # disconnecting all players).
26
+ def reset(to=options[:start])
27
+ redis.set key, to.to_i
28
+ end
29
+
30
+ # Returns the current value of the counter. Normally just calling the
31
+ # counter will lazily fetch the value, and only update it if increment
32
+ # or decrement is called. This forces a network call to redis-server
33
+ # to get the current value.
34
+ def value
35
+ redis.get(key).to_i
36
+ end
37
+ alias_method :get, :value
38
+
39
+ # Increment the counter atomically and return the new value. If passed
40
+ # a block, that block will be evaluated with the new value of the counter
41
+ # as an argument. If the block returns nil or throws an exception, the
42
+ # counter will automatically be decremented to its previous value. This
43
+ # method is aliased as incr() for brevity.
44
+ def increment(by=1, &block)
45
+ val = redis.incr(key, by).to_i
46
+ block_given? ? rewindable_block(:decrement, val, &block) : val
47
+ end
48
+ alias_method :incr, :increment
49
+
50
+ # Decrement the counter atomically and return the new value. If passed
51
+ # a block, that block will be evaluated with the new value of the counter
52
+ # as an argument. If the block returns nil or throws an exception, the
53
+ # counter will automatically be incremented to its previous value. This
54
+ # method is aliased as incr() for brevity.
55
+ def decrement(by=1, &block)
56
+ val = redis.decr(key, by).to_i
57
+ block_given? ? rewindable_block(:increment, val, &block) : val
58
+ end
59
+ alias_method :decr, :decrement
60
+
61
+ ##
62
+ # Proxy methods to help make @object.counter == 10 work
63
+ def to_s; value.to_s; end
64
+ alias_method :to_str, :to_s
65
+ alias_method :to_i, :value
66
+ def nil?; value.nil? end
67
+
68
+ # Math ops
69
+ %w(== < > <= >=).each do |m|
70
+ class_eval <<-EndOverload
71
+ def #{m}(x)
72
+ value #{m} x
73
+ end
74
+ EndOverload
75
+ end
76
+
77
+ private
78
+
79
+ # Implements atomic increment/decrement blocks
80
+ def rewindable_block(rewind, value, &block)
81
+ raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
82
+ ret = nil
83
+ begin
84
+ ret = yield value
85
+ rescue
86
+ send(rewind)
87
+ raise
88
+ end
89
+ send(rewind) if ret.nil?
90
+ ret
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,149 @@
1
+ require 'redis'
2
+ require 'hash_ring'
3
+ class DistRedis
4
+ attr_reader :ring
5
+ def initialize(opts={})
6
+ hosts = []
7
+
8
+ db = opts[:db] || nil
9
+ timeout = opts[:timeout] || nil
10
+
11
+ raise Error, "No hosts given" unless opts[:hosts]
12
+
13
+ opts[:hosts].each do |h|
14
+ host, port = h.split(':')
15
+ hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout)
16
+ end
17
+
18
+ @ring = HashRing.new hosts
19
+ end
20
+
21
+ def node_for_key(key)
22
+ key = $1 if key =~ /\{(.*)?\}/
23
+ @ring.get_node(key)
24
+ end
25
+
26
+ def add_server(server)
27
+ server, port = server.split(':')
28
+ @ring.add_node Redis.new(:host => server, :port => port)
29
+ end
30
+
31
+ def method_missing(sym, *args, &blk)
32
+ if redis = node_for_key(args.first.to_s)
33
+ redis.send sym, *args, &blk
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ def keys(glob)
40
+ @ring.nodes.map do |red|
41
+ red.keys(glob)
42
+ end
43
+ end
44
+
45
+ def save
46
+ on_each_node :save
47
+ end
48
+
49
+ def bgsave
50
+ on_each_node :bgsave
51
+ end
52
+
53
+ def quit
54
+ on_each_node :quit
55
+ end
56
+
57
+ def flush_all
58
+ on_each_node :flush_all
59
+ end
60
+ alias_method :flushall, :flush_all
61
+
62
+ def flush_db
63
+ on_each_node :flush_db
64
+ end
65
+ alias_method :flushdb, :flush_db
66
+
67
+ def delete_cloud!
68
+ @ring.nodes.each do |red|
69
+ red.keys("*").each do |key|
70
+ red.delete key
71
+ end
72
+ end
73
+ end
74
+
75
+ def on_each_node(command, *args)
76
+ @ring.nodes.each do |red|
77
+ red.send(command, *args)
78
+ end
79
+ end
80
+
81
+ def mset()
82
+
83
+ end
84
+
85
+ def mget(*keyz)
86
+ results = {}
87
+ kbn = keys_by_node(keyz)
88
+ kbn.each do |node, node_keyz|
89
+ node.mapped_mget(*node_keyz).each do |k, v|
90
+ results[k] = v
91
+ end
92
+ end
93
+ keyz.flatten.map { |k| results[k] }
94
+ end
95
+
96
+ def keys_by_node(*keyz)
97
+ keyz.flatten.inject({}) do |kbn, k|
98
+ node = node_for_key(k)
99
+ next if kbn[node] && kbn[node].include?(k)
100
+ kbn[node] ||= []
101
+ kbn[node] << k
102
+ kbn
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+
109
+ if __FILE__ == $0
110
+
111
+ r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
112
+ r['urmom'] = 'urmom'
113
+ r['urdad'] = 'urdad'
114
+ r['urmom1'] = 'urmom1'
115
+ r['urdad1'] = 'urdad1'
116
+ r['urmom2'] = 'urmom2'
117
+ r['urdad2'] = 'urdad2'
118
+ r['urmom3'] = 'urmom3'
119
+ r['urdad3'] = 'urdad3'
120
+ p r['urmom']
121
+ p r['urdad']
122
+ p r['urmom1']
123
+ p r['urdad1']
124
+ p r['urmom2']
125
+ p r['urdad2']
126
+ p r['urmom3']
127
+ p r['urdad3']
128
+
129
+ r.push_tail 'listor', 'foo1'
130
+ r.push_tail 'listor', 'foo2'
131
+ r.push_tail 'listor', 'foo3'
132
+ r.push_tail 'listor', 'foo4'
133
+ r.push_tail 'listor', 'foo5'
134
+
135
+ p r.pop_tail('listor')
136
+ p r.pop_tail('listor')
137
+ p r.pop_tail('listor')
138
+ p r.pop_tail('listor')
139
+ p r.pop_tail('listor')
140
+
141
+ puts "key distribution:"
142
+
143
+ r.ring.nodes.each do |red|
144
+ p [red.port, red.keys("*")]
145
+ end
146
+ r.delete_cloud!
147
+ p r.keys('*')
148
+
149
+ end
@@ -0,0 +1,135 @@
1
+ require 'zlib'
2
+
3
+ class HashRing
4
+
5
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
6
+
7
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
8
+
9
+ # nodes is a list of objects that have a proper to_s representation.
10
+ # replicas indicates how many virtual points should be used pr. node,
11
+ # replicas are required to improve the distribution.
12
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
13
+ @replicas = replicas
14
+ @ring = {}
15
+ @nodes = []
16
+ @sorted_keys = []
17
+ nodes.each do |node|
18
+ add_node(node)
19
+ end
20
+ end
21
+
22
+ # Adds a `node` to the hash ring (including a number of replicas).
23
+ def add_node(node)
24
+ @nodes << node
25
+ @replicas.times do |i|
26
+ key = Zlib.crc32("#{node}:#{i}")
27
+ @ring[key] = node
28
+ @sorted_keys << key
29
+ end
30
+ @sorted_keys.sort!
31
+ end
32
+
33
+ def remove_node(node)
34
+ @nodes.reject!{|n| n.to_s == node.to_s}
35
+ @replicas.times do |i|
36
+ key = Zlib.crc32("#{node}:#{i}")
37
+ @ring.delete(key)
38
+ @sorted_keys.reject! {|k| k == key}
39
+ end
40
+ end
41
+
42
+ # get the node in the hash ring for this key
43
+ def get_node(key)
44
+ get_node_pos(key)[0]
45
+ end
46
+
47
+ def get_node_pos(key)
48
+ return [nil,nil] if @ring.size == 0
49
+ crc = Zlib.crc32(key)
50
+ idx = HashRing.binary_search(@sorted_keys, crc)
51
+ return [@ring[@sorted_keys[idx]], idx]
52
+ end
53
+
54
+ def iter_nodes(key)
55
+ return [nil,nil] if @ring.size == 0
56
+ node, pos = get_node_pos(key)
57
+ @sorted_keys[pos..-1].each do |k|
58
+ yield @ring[k]
59
+ end
60
+ end
61
+
62
+ class << self
63
+
64
+ # gem install RubyInline to use this code
65
+ # Native extension to perform the binary search within the hashring.
66
+ # There's a pure ruby version below so this is purely optional
67
+ # for performance. In testing 20k gets and sets, the native
68
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
69
+ begin
70
+ require 'inline'
71
+ inline do |builder|
72
+ builder.c <<-EOM
73
+ int binary_search(VALUE ary, unsigned int r) {
74
+ int upper = RARRAY_LEN(ary) - 1;
75
+ int lower = 0;
76
+ int idx = 0;
77
+
78
+ while (lower <= upper) {
79
+ idx = (lower + upper) / 2;
80
+
81
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
82
+ unsigned int l = NUM2UINT(continuumValue);
83
+ if (l == r) {
84
+ return idx;
85
+ }
86
+ else if (l > r) {
87
+ upper = idx - 1;
88
+ }
89
+ else {
90
+ lower = idx + 1;
91
+ }
92
+ }
93
+ if (upper < 0) {
94
+ upper = RARRAY_LEN(ary) - 1;
95
+ }
96
+ return upper;
97
+ }
98
+ EOM
99
+ end
100
+ rescue Exception => e
101
+ # Find the closest index in HashRing with value <= the given value
102
+ def binary_search(ary, value, &block)
103
+ upper = ary.size - 1
104
+ lower = 0
105
+ idx = 0
106
+
107
+ while(lower <= upper) do
108
+ idx = (lower + upper) / 2
109
+ comp = ary[idx] <=> value
110
+
111
+ if comp == 0
112
+ return idx
113
+ elsif comp > 0
114
+ upper = idx - 1
115
+ else
116
+ lower = idx + 1
117
+ end
118
+ end
119
+
120
+ if upper < 0
121
+ upper = ary.size - 1
122
+ end
123
+ return upper
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ # ring = HashRing.new ['server1', 'server2', 'server3']
132
+ # p ring
133
+ # #
134
+ # p ring.get_node "kjhjkjlkjlkkh"
135
+ #
@@ -0,0 +1,46 @@
1
+ class Redis
2
+ module Helpers
3
+ # These are core commands that all types share (rename, etc)
4
+ module CoreCommands
5
+ def exists?
6
+ redis.exists key
7
+ end
8
+
9
+ def delete
10
+ redis.del key
11
+ end
12
+ alias_method :del, :delete
13
+ alias_method :clear, :delete
14
+
15
+ def type
16
+ redis.type key
17
+ end
18
+
19
+ def rename(name, setkey=true)
20
+ dest = name.is_a?(self.class) ? name.key : name
21
+ ret = redis.rename key, dest
22
+ @key = dest if ret && setkey
23
+ ret
24
+ end
25
+
26
+ def renamenx(name, setkey=true)
27
+ dest = name.is_a?(self.class) ? name.key : name
28
+ ret = redis.renamenx key, dest
29
+ @key = dest if ret && setkey
30
+ ret
31
+ end
32
+
33
+ def expire(seconds)
34
+ redis.expire key, seconds
35
+ end
36
+
37
+ def expireat(unixtime)
38
+ redis.expire key, unixtime
39
+ end
40
+
41
+ def move(dbindex)
42
+ redis.move key, dbindex
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ class Redis
2
+ module Helpers
3
+ module Serialize
4
+ include Marshal
5
+
6
+ def to_redis(value)
7
+ case value
8
+ when String, Fixnum, Bignum, Float
9
+ value
10
+ else
11
+ dump(value)
12
+ end
13
+ end
14
+
15
+ def from_redis(value)
16
+ case value
17
+ when Array
18
+ value.collect{|v| from_redis(v)}
19
+ else
20
+ restore(value) rescue value
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end