tokyo_store 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.tch
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcos Piccinini
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = TokyoStore
2
+
3
+ Rack based Tokyo stored (FAST!) Rails session store.
4
+
5
+ Code:: http://github.com/nofxx/tokyo_store
6
+ Demo:: http://github.com/nofxx/tokyo_webapps under rails/tokyo_store
7
+
8
+
9
+ == Require
10
+
11
+ Tokyo Cabinet and/or Tyrant:: http://tokyocabinet.sourceforge.net
12
+
13
+ Choose an adapter:
14
+
15
+ Tyrant - Pure Ruby:: http://tokyocabinet.sourceforge.net/tyrantrubydoc
16
+ Cabinet - C Bindings:: http://tokyocabinet.sourceforge.net/rubydoc
17
+ RufusTyrant - Rufus FFI:: http://github.com/jmettraux/rufus-tokyo
18
+
19
+
20
+ == Install
21
+
22
+ gem install nofxx-tokyo_store
23
+
24
+ Rails (enviroment.rb)
25
+
26
+ config.gem 'nofxx-tokyo_store', :lib => 'tokyo_store'
27
+ ActionController::Base.session_store = Rack::Session::<ADAPTER (Tyrant, Cabinet or RufusTyrant)>
28
+
29
+
30
+ == Tyrant
31
+
32
+ Start the server if you are using a Tyrant based adapter:
33
+
34
+ ttserver data.tch
35
+
36
+ Some rake tasks: http://gist.github.com/147413
37
+
38
+
39
+ Have fun!
40
+
41
+
42
+ == Thanks
43
+
44
+ Mikio Hirabayashi:: tokyo products
45
+ John Mettraux:: rufus-tokyo
46
+ Luca Guidi:: redis-store
47
+
48
+
49
+ == Copyright
50
+
51
+ Copyright (c) 2009 Marcos Piccinini. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "tokyo_store"
8
+ gem.summary = "Tokyo Tyrant rails session store"
9
+ gem.email = "x@nofxx.com"
10
+ gem.homepage = "http://github.com/nofxx/tokyo_store"
11
+ gem.authors = ["Marcos Piccinini"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+ task :default => :spec
31
+
32
+ require 'rake/rdoctask'
33
+ Rake::RDocTask.new do |rdoc|
34
+ if File.exist?('VERSION')
35
+ version = File.read('VERSION').chomp
36
+ else
37
+ version = ""
38
+ end
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "foo #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,134 @@
1
+ #
2
+ # Tokyo Store
3
+ #
4
+ # Benchmark vs MemCacheStore on memcached and tyrant
5
+ #
6
+ $: << File.join(File.dirname(__FILE__), "/../lib")
7
+ require 'rubygems'
8
+ require 'active_support'
9
+ require 'cache/tokyo_store'
10
+ require 'redis-store'
11
+ require 'cache/rails/redis_store'
12
+
13
+ P = "x" * 100
14
+ M = P * 10
15
+ G = M * 10
16
+ A = [P, M, G]
17
+ X = { :small => P, :medium => M, :big => G }
18
+ class User; attr_accessor :name, :info; end
19
+ u = User.new; u.name = P; u.info = G
20
+ O = u
21
+ T = ARGV[0].to_i || 10000
22
+
23
+ #TODO: Cabinet & memcached C bindings
24
+ TEST = {
25
+ "TokyoStore" => ActiveSupport::Cache.lookup_store(:tokyo_store, "localhost:1978"),
26
+ "MemTokyo" => ActiveSupport::Cache.lookup_store(:mem_cache_store, "localhost:1978"),
27
+ "RedisStore" => ActiveSupport::Cache.lookup_store(:redis_store, "localhost:6379"),
28
+ "MemCache" => ActiveSupport::Cache.lookup_store(:mem_cache_store, "localhost:11211"),
29
+ }
30
+
31
+ puts " Write"
32
+ puts "----------"
33
+ Benchmark.bmbm do |b|
34
+ TEST.each_pair do |k,s|
35
+ b.report("#{k} P") { T.times { |i| s.write i.to_s, P }}
36
+ b.report("#{k} M") { T.times { |i| s.write i.to_s, M }}
37
+ b.report("#{k} G") { T.times { |i| s.write i.to_s, G }}
38
+ b.report("#{k} Obj") { T.times { |i| s.write i.to_s, O }}
39
+ b.report("#{k} Hash") { T.times { |i| s.write i.to_s, X }}
40
+ b.report("#{k} Array") { T.times { |i| s.write i.to_s, A }}
41
+ b.report("#{k} Delete") { T.times { |i| s.delete i.to_s }}
42
+ b.report("#{k} +") { T.times { |i| s.increment i.to_s }}
43
+ b.report("#{k} -") { T.times { |i| s.decrement i.to_s }}
44
+ end
45
+ end
46
+
47
+ puts " Read"
48
+ puts "----------"
49
+ TEST.each { |p| 10_000.times { |i| p[1].write i.to_s, G }}
50
+ #TODO: implement read with diff data.
51
+ Benchmark.bmbm do |b|
52
+ TEST.each_pair do |k,s|
53
+ k = s.class.to_s.split("::")[-1]
54
+ b.report("#{k} Seq") { T.times { |i| s.read rand(i).to_s }}
55
+ b.report("#{k} Rand") { T.times { |i| s.read rand(i).to_s }}
56
+ b.report("#{k} Exist") { T.times { |i| s.exist? i.to_s }}
57
+ end
58
+ end
59
+
60
+ puts
61
+ thr = []
62
+ Benchmark.bmbm do |b|
63
+ TEST.each_pair do |k,s|
64
+ b.report("#{k} TW") { (T/2).times { |j| thr << Thread.new { (T/2).times { |i| s.write "#{j}-#{i}", X }}}; thr.each { |t| t.join }; thr = [] }
65
+ b.report("#{k} TR") { (T/2).times { |j| thr << Thread.new { (T/2).times { |i| s.read "#{j}-#{i}" }}}; thr.each { |t| t.join }; thr = [] }
66
+ end
67
+ end
68
+
69
+ __END__
70
+
71
+ *NOTE: Redis and Memcache support native expiration, Tokyo doesn't.
72
+ Wondering the impact of this feature written in ruby...
73
+
74
+ Core 2 Duo 8500 - 3.16Ghz
75
+ ------------------------------------------------------------
76
+ Write
77
+ -------------
78
+ TokyoStore# P 0.140000 0.090000 0.230000 ( 0.367587)
79
+ RedisStore# P 0.340000 0.090000 0.430000 ( 0.542441)
80
+ MemCacheD # P 0.570000 0.160000 0.730000 ( 0.829167)
81
+ MemCacheT # P 0.560000 0.170000 0.730000 ( 0.897084)
82
+
83
+ TokyoStore# M 0.170000 0.100000 0.270000 ( 0.448071)
84
+ RedisStore# M 0.360000 0.120000 0.480000 ( 0.641083)
85
+ MemCacheD # M 0.610000 0.140000 0.750000 ( 0.878559)
86
+ MemCacheT # M 0.630000 0.140000 0.770000 ( 0.951748)
87
+
88
+ TokyoStore# G 0.410000 0.090000 0.500000 ( 0.976746)
89
+ RedisStore# G 0.930000 0.170000 1.100000 ( 1.572024)
90
+ MemCacheD # G 1.000000 0.200000 1.200000 ( 1.429635)
91
+ MemCacheT # G 1.060000 0.170000 1.230000 ( 1.558731)
92
+
93
+ TokyoStore# D 0.220000 0.170000 0.390000 ( 0.707877)
94
+ RedisStore# D 0.240000 0.110000 0.350000 ( 0.474265)
95
+ MemCacheD # D 0.560000 0.110000 0.670000 ( 0.775865)
96
+ MemCacheT # D 0.560000 0.140000 0.700000 ( 0.860964)
97
+
98
+ TokyoStore# + 0.230000 0.190000 0.420000 ( 0.654690)
99
+ RedisStore# + 0.200000 0.120000 0.320000 ( 0.494333)
100
+ MemCacheD # + 0.570000 0.130000 0.700000 ( 0.801407)
101
+ MemCacheT # + 0.540000 0.190000 0.730000 ( 0.937882)
102
+
103
+ TokyoStore# - 0.200000 0.210000 0.410000 ( 0.669899)
104
+ RedisStore# - 0.240000 0.120000 0.360000 ( 0.511279)
105
+ MemCacheD # - 0.600000 0.100000 0.700000 ( 0.778212)
106
+ MemCacheT # - 0.600000 0.140000 0.740000 ( 0.983345)
107
+
108
+ Read
109
+ --------------
110
+ TokyoStore# Seq 0.440000 0.150000 0.590000 ( 0.858501)
111
+ RedisStore# Seq 0.660000 0.130000 0.790000 ( 1.039310)
112
+ MemCacheD # Seq 0.830000 0.220000 1.050000 ( 1.135630)
113
+ MemCacheT # Seq 1.210000 0.190000 1.400000 ( 1.754004)
114
+
115
+ TokyoStore# Rand 0.490000 0.200000 0.690000 ( 1.003466)
116
+ RedisStore# Rand 0.580000 0.210000 0.790000 ( 1.877162)
117
+ MemCacheD # Rand 0.870000 0.170000 1.040000 ( 1.135382)
118
+ MemCacheT # Rand 1.410000 0.230000 1.640000 ( 1.971372)
119
+
120
+ TokyoStore# Exist 0.460000 0.140000 0.600000 ( 0.845920)
121
+ RedisStore# Exist 0.550000 0.250000 0.800000 ( 1.658580)
122
+ MemCacheD # Exist 0.810000 0.220000 1.030000 ( 1.138310)
123
+ MemCacheT # Exist 1.150000 0.250000 1.400000 ( 1.760770)
124
+
125
+
126
+ Tokyo # W 0.510000 0.150000 0.660000 ( 1.023739)
127
+ Redis # W 0.540000 0.140000 0.680000 ( 1.032881)
128
+ MemCa # W 2.530000 0.210000 2.740000 ( 3.072881)
129
+ MemTo # W 2.600000 0.280000 2.880000 ( 3.344125)
130
+
131
+ Tokyo # R 0.680000 0.210000 0.890000 ( 2.547828)
132
+ Redis # R 0.710000 0.220000 0.930000 ( 2.599269)
133
+ MemCa # R 2.190000 0.240000 2.430000 ( 2.977408)
134
+ MemTo # R 3.210000 0.330000 3.540000 ( 4.298882)
@@ -0,0 +1,75 @@
1
+ #
2
+ # Tokyo Store
3
+ #
4
+ # Session Benchmark vs Redis Store
5
+ #
6
+ # TODO: memcache
7
+ #
8
+ $: << File.join(File.dirname(__FILE__), "/../lib")
9
+ require 'rubygems'
10
+ require 'benchmark'
11
+ require 'rack'
12
+ require 'tokyo_store'
13
+ require 'redis-store'
14
+ #S = [Rack::Session::Tokyo::DEFAULT_OPTIONS[:key], { :test => "foo"}]
15
+ T = ARGV[0].to_i || 10000
16
+
17
+ rack = lambda do |env|
18
+ env["rack.session"]["counter"] ||= 0
19
+ env["rack.session"]["counter"] += 1
20
+ Rack::Response.new(env["rack.session"].inspect).to_a
21
+ end
22
+
23
+ TEST = {
24
+ "Tokyo" => Rack::Session::Tokyo.new(rack),
25
+ "Redis" => Rack::Session::Redis.new(rack),
26
+ }
27
+ bar = "_" * 45
28
+
29
+ puts "\n#{bar} SET"
30
+ Benchmark.bmbm do |b|
31
+ TEST.each_pair do |n,s|
32
+ b.report(n) { T.times { Rack::MockRequest.new(s).get("/") }}
33
+ end
34
+ end
35
+
36
+ puts "\n#{bar} GET"
37
+ Benchmark.bmbm do |b|
38
+ TEST.each_pair do |n,s|
39
+ b.report(n) do T.times do
40
+ req = Rack::MockRequest.new(s)
41
+ cookie = req.get("/")["Set-Cookie"]
42
+ req.get("/", "HTTP_COOKIE" => cookie)
43
+ end end
44
+ end
45
+ end
46
+
47
+ puts "\n#{bar} EXIST"
48
+ Benchmark.bmbm do |b|
49
+ TEST.each_pair do |n,s|
50
+ b.report(n) do T.times do
51
+ Rack::MockRequest.new(s).get("/", "HTTP_COOKIE" => "rack.session=badbadcookie")
52
+ end end
53
+ end
54
+ end
55
+
56
+
57
+ __END__
58
+
59
+ *Note: Tokyo uses FFI adapter, while redis a pure ruby.
60
+ Both comparable to memcache pr too. Check the cache benchmark.
61
+
62
+ _____________________________________________ GET
63
+ Tokyo 6.310000 1.030000 7.340000 ( 8.511138)
64
+ Redis 7.300000 1.010000 8.310000 ( 9.325441)
65
+ ------------------------------- total: 15.650000sec
66
+
67
+ _____________________________________________ SET
68
+ Tokyo 3.340000 0.540000 3.880000 ( 4.562920)
69
+ Redis 3.960000 0.540000 4.500000 ( 5.030627)
70
+ -------------------------------- total: 8.380000sec
71
+
72
+ _____________________________________________ EXIST
73
+ Tokyo 4.090000 0.650000 4.740000 ( 5.537898)
74
+ Redis 4.700000 0.700000 5.400000 ( 6.061131)
75
+ ------------------------------- total: 10.140000sec
@@ -0,0 +1,139 @@
1
+ require 'rufus/tokyo/tyrant'
2
+ # require 'tokyocabinet'
3
+ module ActiveSupport
4
+ module Cache
5
+
6
+ # A cache store implementation which stores data in Tokyo Cabinet
7
+ #
8
+ # Special features:
9
+ # - Substring match
10
+ # - Clustering and load balancing. TODO
11
+ # - Time-based expiry support. TODO (Lua)
12
+ # - Per-request in memory cache for all communication with the Tokyo server(s).
13
+ class TokyoStore < Store
14
+
15
+ def self.build_tokyo(*store)
16
+ store = store.flatten
17
+ options = store.extract_options!
18
+ #TODO: multiple instances
19
+ store = store.empty? ? ["localhost", 1978] : store[0].split(":")
20
+
21
+ #TODO: Auto choice between tyrant ffi x tyrant pure ruby x cabinet C
22
+ # Tyrant FFI
23
+ Rufus::Tokyo::Tyrant.new(store[0], store[1].to_i)
24
+
25
+ # Cabinet C
26
+ #hdb = HDB.new
27
+ # if !hdb.open(store[0], HDB::OWRITER | HDB::OCREAT)
28
+ # ecode = hdb.ecode
29
+ # STDERR.printf("open error: %s\n", hdb.errmsg(ecode))
30
+ # end
31
+ # hdb
32
+ end
33
+
34
+ # Creates a new TokyoStore object, with the given tyrant server
35
+ # addresses. Each address is either a host name, or a host-with-port string
36
+ # in the form of "host_name:port". For example:
37
+ #
38
+ # ActiveSupport::Cache::TokyoStore.new("localhost", "server-downstairs.localnetwork:8229")
39
+ #
40
+ # If no addresses are specified, then TokyoStore will connect to
41
+ # localhost port 1978 (the default tyrant port).
42
+ def initialize(*store)
43
+ if store.first.respond_to?(:get)
44
+ @data = store.first
45
+ else
46
+ @data = self.class.build_tokyo(*store)
47
+ end
48
+
49
+ extend Strategy::LocalCache
50
+ end
51
+
52
+ # Reads multiple keys from the cache.
53
+ def read_multi(*keys)
54
+ #keys.inject({ }){ |h,k| h.merge({ k => read(k)}) }
55
+ @data.lget(keys).inject({ }) { |h, k| h.merge({ k[0] => Marshal.load(k[1])})} #
56
+ end
57
+
58
+ def read(key, options = nil) # :nodoc:
59
+ # TODO: benchmark [key] vs .get(key)
60
+ super
61
+ return nil unless val = @data[key]
62
+ val = Marshal.load(val) unless raw?(options)
63
+ val
64
+ # if str = @data.get(key)
65
+ # Marshal.load str
66
+ # else
67
+ # STDERR.printf("get error: %s\n", @data.errmsg(@data.ecode))
68
+ # end
69
+ # logger.error("TokyoError (#{e}): #{e.message}")
70
+ # nil
71
+ end
72
+
73
+ # Writes a value to the cache.
74
+ #
75
+ # Possible options:
76
+ # - +:unless_exist+ - set to true if you don't want to update the cache
77
+ # if the key is already set.
78
+ def write(key, value, options = nil)
79
+ super
80
+ method = options && options[:unless_exist] ? :add : :set
81
+ # will break the connection if you send it an integer
82
+ # in raw mode, so we convert it to a string to be sure it continues working.
83
+ value = raw?(options) ? value.to_s : Marshal.dump(value) # if value.instance_of? Hash
84
+
85
+ @data[key] = value
86
+ ###response = @data.put(key, value) || STDERR.printf("get error: %s\n", @data.errmsg(@data.ecode))#, expires_in(options), raw?(options))
87
+ # logger.error("TokyoError (#{e}): #{e.message}")
88
+ # false
89
+ end
90
+
91
+ def delete(key, options = nil) # :nodoc:
92
+ super
93
+ @data.delete(key) #= nil #, expires_in(options))
94
+ end
95
+
96
+ def exist?(key, options = nil) # :nodoc:
97
+ # Local cache is checked first?
98
+ !read(key, options).nil?
99
+ end
100
+
101
+ def increment(key, amount = 1) # :nodoc:
102
+ #NATIVE breaks...rufus integer prob?
103
+ # @data.incr(key, amount)
104
+ @data[key] = (@data[key].to_i + amount).to_s
105
+ end
106
+
107
+ def decrement(key, amount = 1) # :nodoc:
108
+ # @data.incr(key, -amount)
109
+ increment(key, -amount)
110
+ end
111
+
112
+ def delete_matched(matcher, options = nil) # :nodoc:
113
+ super
114
+ @data.delete_keys_with_prefix(matcher)
115
+ end
116
+
117
+ def clear
118
+ @data.clear
119
+ end
120
+
121
+ def stats
122
+ @data.stat
123
+ end
124
+
125
+ private
126
+ #TODO
127
+ # def expires_in(options)
128
+ # (options && options[:expires_in]) || 0
129
+ # end
130
+
131
+ def raw?(options)
132
+ options && options[:raw]
133
+ end
134
+
135
+
136
+ end
137
+
138
+ end
139
+ end