tokyo_store 0.3.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/.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