mongocached 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/Benchmark.md +54 -0
  2. data/Gemfile +25 -0
  3. data/HISTORY.md +1 -0
  4. data/README.md +14 -0
  5. data/Rakefile +17 -0
  6. data/lib/mongocached.rb +203 -0
  7. metadata +104 -0
data/Benchmark.md ADDED
@@ -0,0 +1,54 @@
1
+ ### Ruby 1.9.3p194
2
+
3
+ mo = mongocached / me = memcached
4
+
5
+ #### small string * 100000
6
+ <pre>
7
+ user system total real
8
+ mo set 5.060000 0.470000 5.530000 ( 5.538689)
9
+ me set 1.400000 1.310000 2.710000 ( 4.065908)
10
+ mo get 10.130000 1.110000 11.240000 ( 12.415902)
11
+ me get 1.360000 1.340000 2.700000 ( 3.768052)
12
+ </pre>
13
+
14
+
15
+ #### large hash * 100000
16
+ <pre>
17
+ user system total real
18
+ mo set 39.010000 1.170000 40.180000 ( 40.314892)
19
+ me set 15.510000 2.340000 17.850000 ( 20.471940)
20
+ mo get 39.740000 3.350000 43.090000 ( 47.473688)
21
+ me get 14.950000 1.420000 16.370000 ( 17.187145)
22
+ </pre>
23
+
24
+ #### large hash * 100000
25
+ <pre>
26
+ user system total real
27
+ mo set 40.650000 1.180000 41.830000 ( 42.026839)
28
+ me set 16.590000 2.060000 18.650000 ( 20.909275)
29
+ mo get 49.400000 5.650000 55.050000 ( 63.141785)
30
+ me get 11.760000 1.810000 13.570000 ( 14.814693)
31
+ </pre>
32
+
33
+ ### Ruby 1.9.2p318
34
+
35
+ mo = mongocached / me = memcached
36
+
37
+ #### small string * 100000
38
+ <pre>
39
+ user system total real
40
+ mo set 4.360000 0.470000 4.830000 ( 4.849431)
41
+ me set 1.950000 1.840000 3.790000 ( 5.901345)
42
+ mo get 9.580000 1.130000 10.710000 ( 12.000498)
43
+ me get 1.300000 1.390000 2.690000 ( 3.707633)
44
+ </pre>
45
+
46
+
47
+ #### large hash * 100000
48
+ <pre>
49
+ user system total real
50
+ mo set 39.770000 1.130000 40.900000 ( 41.075236)
51
+ me set 15.880000 2.440000 18.320000 ( 20.905582)
52
+ mo get 38.660000 3.590000 42.250000 ( 46.763608)
53
+ me get 11.900000 1.450000 13.350000 ( 14.215215)
54
+ </pre>
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ source :rubygems
2
+
3
+ gem 'mongo'
4
+ gem 'bson_ext'
5
+
6
+ group :development, :test do
7
+ gem 'rake'
8
+ end
9
+
10
+ group :development do
11
+ gem 'rdoc'
12
+ end
13
+
14
+ group :benchmark do
15
+ # for benchmark only
16
+ # requires sudo apt-get install libsasl2-dev
17
+ gem 'memcached'
18
+ end
19
+
20
+ group :test do
21
+ gem 'rspec'
22
+ gem 'simplecov', :require => false
23
+ end
24
+
25
+
data/HISTORY.md ADDED
@@ -0,0 +1 @@
1
+
data/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # Mongocached
2
+
3
+ Cache using mongo, like memcached.
4
+
5
+ ## Why?
6
+
7
+ Because disk space is cheaper.
8
+
9
+ ## Links
10
+
11
+ #### [API Docs](http://rubyops.github.com/mongocached/doc/)
12
+ #### [Coverage](http://rubyops.github.com/mongocached/coverage/)
13
+ #### [Benchmarks](https://github.com/rubyops/mongocached/blob/master/Benchmark.md)
14
+
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
7
+
8
+
9
+ task :benchmark do
10
+ load './spec/benchmarks.rb'
11
+ end
12
+
13
+ task :package do
14
+ puts %x{ gem build diskcached.gemspec && (mkdir ./pkg; mv *.gem ./pkg/ ) }
15
+ end
16
+
17
+ # vim: syntax=ruby
@@ -0,0 +1,203 @@
1
+ require 'mongo'
2
+ # @author Joshua P. Mervine <joshua@mervine.net>
3
+ class Mongocached
4
+ # version for gem
5
+ VERSION = '1.0.0'
6
+
7
+ @cleanup_last = nil
8
+ @ensure_indexes = false
9
+
10
+ # initialize object
11
+ def initialize options={}
12
+ @options = defaults.merge! options
13
+
14
+ unless @options[:lifetime].nil?
15
+ @cleanup_last = Time.now
16
+ @options[:cleanup_life] = ( @options[:lifetime] < 1800 ? @options[:lifetime] : 1800 )
17
+ else
18
+ @options[:cleanup_auto] = false
19
+ end
20
+
21
+ @last = nil
22
+
23
+ flush_expired!
24
+ end
25
+
26
+ def defaults
27
+ {
28
+ #cache_id_prefix: nil,
29
+ lifetime: 3600,
30
+ automatic_serialization: true,
31
+ host: 'localhost',
32
+ port: '27017',
33
+ dbname: 'mongocached',
34
+ collection: 'cache',
35
+ cleanup_auto: true,
36
+ cleanup_life: 1800,
37
+ config: {}
38
+ }
39
+ end
40
+
41
+ # expire cache
42
+ def delete id
43
+ @last = nil
44
+ collection.remove(_id: id)
45
+ end
46
+ alias :remove :delete
47
+
48
+ # delete all caches
49
+ def flush
50
+ collection.drop
51
+ end
52
+ alias :clean :flush
53
+
54
+ # flush expired caches if cleanup hasn't been run recently
55
+ def flush_expired
56
+ if !@cleanup_last.nil? && !@options[:cleanup_life].nil? && (@cleanup_last+@options[:cleanup_life]) < Time.now
57
+ flush_expired!
58
+ end
59
+ end
60
+
61
+ # flush expired caches, ingoring when garbage collection was last run
62
+ # update indexes
63
+ def flush_expired!
64
+ # commenting out forking until I'm sure it's necessary
65
+ # and won't cause zombie processes
66
+ #gcpid = Process.fork do
67
+ collection.remove(expires: {'$lt' => Time.now})
68
+ unless @ensure_indexes
69
+ collection.ensure_index([[:tags, 1]])
70
+ collection.ensure_index([[:expires, -1]])
71
+ end
72
+ @cleanup_last = Time.now
73
+ #end
74
+ #Process.detach(gcpid)
75
+ end
76
+ alias :clean_expired :flush_expired!
77
+
78
+ # create or read cache
79
+ # - creates cache if it doesn't exist
80
+ # - reads cache if it exists
81
+ def save id, tags = [], ttl = @options[:lifetime]
82
+ begin
83
+ doc = collection.find_one(_id: id, expires: { '$gt' => Time.now })
84
+ return deserialize(doc['data']) unless doc.nil?
85
+
86
+ data = Proc.new { yield }.call
87
+ collection.save({
88
+ _id: id,
89
+ data: serialize(data),
90
+ tags: tags,
91
+ expires: calc_expires(ttl)
92
+ })
93
+ return data
94
+
95
+ rescue LocalJumpError
96
+ # when nothing is passed to yield and it's called
97
+ return nil
98
+ end
99
+ end
100
+ alias :cache :save
101
+
102
+ # set cache
103
+ # - creates cache if it doesn't exist
104
+ # - updates cache if it does exist
105
+ def set id, data, tags = [], ttl = @options[:lifetime]
106
+ collection.save({
107
+ _id: id,
108
+ data: serialize(data),
109
+ tags: tags,
110
+ expires: calc_expires(ttl)
111
+ })
112
+ flush_expired if @options[:cleanup_auto]
113
+ true
114
+ end
115
+ alias :replace :set # for memcached compatability
116
+
117
+ # add cache
118
+ # - creates cache if it doesn't exist
119
+ # - return false if it does
120
+ def add id, data, tags = [], ttl = @options[:lifetime]
121
+ begin
122
+ collection.insert({
123
+ _id: id,
124
+ data: serialize(data),
125
+ tags: tags,
126
+ expires: calc_expires(ttl)
127
+ }, safe: true)
128
+ rescue Mongo::OperationFailure
129
+ flush_expired if @options[:cleanup_auto]
130
+ return false
131
+ end
132
+ flush_expired if @options[:cleanup_auto]
133
+ return true
134
+ end
135
+
136
+ # get cache
137
+ # - reads cache if it exists and isn't expired or raises Diskcache::NotFound
138
+ # - if passed an Array returns only items which exist and aren't expired, it raises Diskcache::NotFound if none are available
139
+ def get id
140
+ flush_expired if @options[:cleanup_auto]
141
+ if id.is_a? Array
142
+ # TODO: more mongo'y way to do this, perhaps a map/reduce?
143
+ # STORE.find({ _id: {'$in' => ['no1', 'no2', 'no3']}})
144
+ hash = {}
145
+ id.each do |i|
146
+ doc = collection.find_one(_id: i)
147
+ if !doc.nil? && doc['expires'] > Time.now
148
+ hash[i] = deserialize(doc['data'])
149
+ end
150
+ end
151
+ return hash unless hash.empty?
152
+ else
153
+ doc = collection.find_one(_id: id)
154
+ if !doc.nil? && doc['expires'] > Time.now
155
+ return deserialize(doc['data'])
156
+ end
157
+ end
158
+ raise Mongocached::NotFound
159
+ end
160
+ alias :load :get
161
+
162
+ private
163
+ def serialize data
164
+ if @options[:automatic_serialization]
165
+ Marshal::dump(data)
166
+ else
167
+ data
168
+ end
169
+ end
170
+
171
+ def deserialize data
172
+ if @options[:automatic_serialization]
173
+ Marshal::load(data)
174
+ else
175
+ data
176
+ end
177
+ end
178
+
179
+ def calc_expires ttl = @options[:lifetime]
180
+ return nil if ttl.nil?
181
+ Time.now+ttl
182
+ end
183
+
184
+ def collection
185
+ @collection ||= db[@options[:collection]]
186
+ end
187
+
188
+ def db
189
+ @db ||= connection[@options[:dbname]]
190
+ end
191
+
192
+ def connection
193
+ @connection ||= connect
194
+ end
195
+
196
+ def connect
197
+ ::Mongo::Connection.new(@options[:host], @options[:port], @options[:config])
198
+ end
199
+
200
+ class NotFound < Exception
201
+ end
202
+
203
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongocached
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joshua Mervine
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !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: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: simplecov
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdoc
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Simple mongodb based cache for things like Sinatra and Rails which is
63
+ implemented much like Memcached in hopes that in some cases they're interchangeable.
64
+ It's based off 'diskcached'. This gem should be considered 'alpha'!
65
+ email:
66
+ - joshua@mervine.net
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - lib/mongocached.rb
72
+ - README.md
73
+ - HISTORY.md
74
+ - Benchmark.md
75
+ - Gemfile
76
+ - Rakefile
77
+ homepage: http://mongocached.rubyops.net/
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ segments:
90
+ - 0
91
+ hash: -4487074619354158587
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: 1.3.6
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.24
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Simple disk cache
104
+ test_files: []