mongocached 1.0.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.
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: []