diskcached 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -4
- data/README.md +135 -1
- data/Rakefile +18 -0
- data/lib/diskcached.rb +42 -33
- metadata +13 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2d3ce84b679e0539fce33f13ae46242030555375
|
4
|
+
data.tar.gz: 8d122fd674accaae92f1cbb3fc5ed9d9f024a34a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e4f49916e1134d3187f21434663ac638da6ed24bca20764bd0d553eee0af2eed666b42e84fb21be17393d9ef2d10a6093d7f81b0c422082e694c4277f6b37ab
|
7
|
+
data.tar.gz: 377874d6837768046cc7a2909a73659c2dc47d9211e6de678c6345db3210d844ff6742b450b164a1cf8633b1c8a96fdf1b08b25539e92f8b084b32dffdd4cacd
|
data/Gemfile
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
group :development, :test do
|
4
4
|
gem 'rake'
|
5
5
|
end
|
6
6
|
|
7
7
|
group :development do
|
8
|
-
gem '
|
8
|
+
gem 'yard'
|
9
|
+
gem 'redcarpet'
|
9
10
|
end
|
10
11
|
|
11
12
|
group :benchmark do
|
@@ -18,5 +19,3 @@ group :test do
|
|
18
19
|
gem 'rspec'
|
19
20
|
gem 'simplecov', :require => false
|
20
21
|
end
|
21
|
-
|
22
|
-
|
data/README.md
CHANGED
@@ -1,3 +1,137 @@
|
|
1
1
|
# Diskcached
|
2
2
|
|
3
|
-
|
3
|
+
> Simple disk cache for things like Sinatra which is implemented much like Memcached in hopes that in some cases they're interchangeable.
|
4
|
+
|
5
|
+
### [Documentation](http://rubyops.github.com/diskcached/doc/Diskcached.html) | [Coverage](http://rubyops.github.com/diskcached/coverage/index.html#_AllFiles) | [Benchmarks](https://github.com/rubyops/diskcached/wiki/Benchmark-Output)
|
6
|
+
|
7
|
+
## Introduction
|
8
|
+
|
9
|
+
I created Diskcached as a simple cacheing layer for things like html fragments and database calls. I thought about using [memcached](http://memcached.org/), but as the app I was working on was running on a single server, it seemed overkill. Additionally, I looked at using [rack-cache](http://rtomayko.github.com/rack-cache/), but I felt it was a bit more complex then I was looking for. So Diskcached was born (although it was origiionally released as "simple\_disk\_cache" -- for about 12 hours).
|
10
|
+
|
11
|
+
* To the comment: "I'm not clear how memcached on a single server is overkill."
|
12
|
+
> 1. In some cases -- e.g. Dreamhost shared hosting and Heroku (I believe) -- it is difficult, if not impossible to install memcached. This is for those situations.
|
13
|
+
> 1. In all cases, disk space is cheaper than memory. For example, when I used [myhosting.com](http://myhosting.com), which charges $1 per 20G of disk storage and $1 per 512MB of memory. So in my case, I use Diskcached instead of memcached and my memory foot print is ~300MB. While at the moment, I could very easily handle running memcached without running out of memory, using disk based cacheing allows me to scale much further before having to upgrade my hosting package. Additionally, if you [check out my blogs performance metrics](https://github.com/jmervine/ditty/wiki/Performance), you'll see that Diskcached brought me from ~140ms render times, to ~1ms render times, allowing me to scale even further.
|
14
|
+
|
15
|
+
* To the comment: "If you need memcache...then use it."
|
16
|
+
> * I totally agree!
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
:::shell
|
21
|
+
gem install diskcached
|
22
|
+
|
23
|
+
Or with [Bundler](http://mervine.net/tag/bundler):
|
24
|
+
|
25
|
+
:::ruby
|
26
|
+
source :rubygems
|
27
|
+
gem 'diskcached'
|
28
|
+
|
29
|
+
## Basic Usage
|
30
|
+
|
31
|
+
### Block Style
|
32
|
+
|
33
|
+
:::ruby
|
34
|
+
require 'diskcached'
|
35
|
+
@diskcache = Diskcached.new
|
36
|
+
|
37
|
+
result = @diskcache.cache('expensive_code') do
|
38
|
+
# some expensive code
|
39
|
+
end
|
40
|
+
|
41
|
+
puts result
|
42
|
+
|
43
|
+
The above will create the cache if it doesn't exist and cache the result of block and return it. If the cache exists and isn't expired, it will read from the cache and returnwhat's stored. This allows you to passively wrap code in a cache block and not worry about checking to see if it's valid or expired.
|
44
|
+
|
45
|
+
Also worth noting, it will return `nil` if something goes wrong.
|
46
|
+
|
47
|
+
### Memcached Style
|
48
|
+
|
49
|
+
Using Diskcached like this should allow for a "drag and drop" replacement of Memcached, should you so decide.
|
50
|
+
|
51
|
+
:::ruby
|
52
|
+
require 'diskcached'
|
53
|
+
@diskcache = Diskcached.new
|
54
|
+
|
55
|
+
begin
|
56
|
+
result = @diskcache.get('expensive_code')
|
57
|
+
rescue # Diskcached::NotFound # prevents easy replacement, but is safer.
|
58
|
+
result = run_expensive_code
|
59
|
+
@diskcache.set('expensive_code', result)
|
60
|
+
end
|
61
|
+
|
62
|
+
puts result
|
63
|
+
|
64
|
+
It's important to note that Diskcached is quite a bit simpler then Memcached and in some ways more forgiving. If Memcached compatability is really important, refer to Memcached docs as well as Diskcached docs when implementing your code.
|
65
|
+
|
66
|
+
## Benchmarks
|
67
|
+
|
68
|
+
### Comments
|
69
|
+
|
70
|
+
Diskcached wasn't designed to be a faster solution, just a simpler
|
71
|
+
one when compaired to Memcached. However, from these benchmarks,
|
72
|
+
it holds up will and even should provide slightly faster reads.
|
73
|
+
|
74
|
+
##### [Moved to 'Benchmark Output'](https://github.com/rubyops/diskcached/wiki/Benchmark-Output)
|
75
|
+
|
76
|
+
## Sinatra Application 'httperf' results.
|
77
|
+
|
78
|
+
On a development machine (Unicorn w/ 1 worker) I ran a series of [httperf](http://www.hpl.hp.com/research/linux/httperf/) tests to see how Diskcached ran in real world situations. You can [checkout the full output from multiple examples here](https://gist.github.com/3062334), but there's a taste...
|
79
|
+
|
80
|
+
Using the endpoint [http://mervine.net/](http://mervine.net/) on my dev server and hitting it 100,000 times --
|
81
|
+
|
82
|
+
### Code Example from Test
|
83
|
+
|
84
|
+
:::ruby
|
85
|
+
15 configure do
|
86
|
+
...
|
87
|
+
44 $diskcache = Diskcached.new(File.join(settings.root, 'cache'))
|
88
|
+
45 $diskcache.flush # ensure caches are empty on startup
|
89
|
+
46 end
|
90
|
+
...
|
91
|
+
58 before do
|
92
|
+
...
|
93
|
+
61 @cache_key = cache_sha(request.path_info)
|
94
|
+
62 end
|
95
|
+
...
|
96
|
+
231 get "/" do
|
97
|
+
232 begin
|
98
|
+
233 raise Diskcached::NotFound if authorized?
|
99
|
+
234 content = $diskcache.get(@cache_key)
|
100
|
+
235 logger.debug("reading index from cache") unless authorized?
|
101
|
+
236 rescue Diskcached::NotFound
|
102
|
+
237 logger.debug("storing index to cache") unless authorized?
|
103
|
+
238 content = haml(:index, :layout => choose_layout)
|
104
|
+
239 $diskcache.set(@cache_key, content) unless authorized?
|
105
|
+
240 end
|
106
|
+
241 content
|
107
|
+
242 end
|
108
|
+
|
109
|
+
### Test Results
|
110
|
+
|
111
|
+
:::shell
|
112
|
+
httperf --client=0/1 --server=localhost --port=9001 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=100000 --num-calls=1
|
113
|
+
httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETS
|
114
|
+
|
115
|
+
Maximum connect burst length: 1
|
116
|
+
|
117
|
+
Total: connections 100000 requests 100000 replies 100000 test-duration 744.646 s
|
118
|
+
|
119
|
+
Connection rate: 134.3 conn/s (7.4 ms/conn, <=1 concurrent connections)
|
120
|
+
Connection time [ms]: min 1.9 avg 7.4 max 398.8 median 4.5 stddev 10.5
|
121
|
+
Connection time [ms]: connect 0.1
|
122
|
+
Connection length [replies/conn]: 1.000
|
123
|
+
|
124
|
+
Request rate: 134.3 req/s (7.4 ms/req)
|
125
|
+
Request size [B]: 62.0
|
126
|
+
|
127
|
+
Reply rate [replies/s]: min 116.6 avg 134.3 max 147.2 stddev 6.1 (148 samples)
|
128
|
+
Reply time [ms]: response 6.9 transfer 0.5
|
129
|
+
Reply size [B]: header 216.0 content 105088.0 footer 0.0 (total 105304.0)
|
130
|
+
Reply status: 1xx=0 2xx=100000 3xx=0 4xx=0 5xx=0
|
131
|
+
|
132
|
+
CPU time [s]: user 287.88 system 115.60 (user 38.7% system 15.5% total 54.2%)
|
133
|
+
Net I/O: 13818.2 KB/s (113.2*10^6 bps)
|
134
|
+
|
135
|
+
Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
|
136
|
+
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
|
137
|
+
|
data/Rakefile
CHANGED
@@ -2,9 +2,27 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'rspec/core/rake_task'
|
5
|
+
require './lib/diskcached'
|
5
6
|
RSpec::Core::RakeTask.new(:spec)
|
6
7
|
task :default => :spec
|
7
8
|
|
9
|
+
desc "generate and update gh-pages"
|
10
|
+
task :pages do
|
11
|
+
system(" set -x; bundle exec rspec ") or abort
|
12
|
+
system(" set -x; bundle exec yardoc --protected ./lib/**/*.rb ") or abort
|
13
|
+
system(" set -x; rm -rf /tmp/doc /tmp/coverage ") or abort
|
14
|
+
system(" set -x; mv -v ./doc /tmp ") or abort
|
15
|
+
system(" set -x; mv -v ./coverage /tmp ") or abort
|
16
|
+
system(" set -x; git checkout gh-pages ") or abort
|
17
|
+
system(" set -x; rm -rf ./doc ./coverage ") or abort
|
18
|
+
system(" set -x; mv -v /tmp/doc . ") or abort
|
19
|
+
system(" set -x; mv -v /tmp/coverage . ") or abort
|
20
|
+
system(" set -x; git add . ") or abort
|
21
|
+
system(" set -x; git commit --all -m 'updating doc and coverage' ") or abort
|
22
|
+
system(" set -x; git checkout master ") or abort
|
23
|
+
puts "don't forget to run: git push origin gh-pages"
|
24
|
+
end
|
25
|
+
|
8
26
|
|
9
27
|
task :benchmark do
|
10
28
|
load './spec/benchmarks.rb'
|
data/lib/diskcached.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# @author Joshua P. Mervine <joshua@mervine.net>
|
2
2
|
class Diskcached
|
3
3
|
# version for gem
|
4
|
-
VERSION = '1.1.
|
4
|
+
VERSION = '1.1.1'
|
5
5
|
|
6
6
|
# disk location for cache store
|
7
7
|
attr_reader :store
|
8
|
-
|
8
|
+
|
9
9
|
# cache timeout
|
10
10
|
attr_reader :timeout
|
11
|
-
|
11
|
+
|
12
12
|
# time of last #garbage_collect
|
13
13
|
attr_reader :gc_last
|
14
14
|
|
@@ -26,17 +26,25 @@ class Diskcached
|
|
26
26
|
def initialize store="/tmp/cache", timeout=600, autogc=true
|
27
27
|
@store = store
|
28
28
|
@timeout = timeout
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
|
30
|
+
@gc_last, @gc_time = nil
|
31
|
+
|
32
|
+
# true or false, this will be ignored if @gc_last and @gc_time
|
33
|
+
# are nil
|
34
|
+
@gc_auto = autogc
|
35
|
+
|
36
|
+
unless timeout.nil?
|
37
|
+
send(:timeout=, timeout)
|
36
38
|
end
|
39
|
+
|
37
40
|
ensure_store_directory
|
38
41
|
end
|
39
42
|
|
43
|
+
def timeout=(t)
|
44
|
+
@gc_last = Time.now
|
45
|
+
@gc_time = t
|
46
|
+
end
|
47
|
+
|
40
48
|
# return true if cache with 'key' is expired
|
41
49
|
def expired? key
|
42
50
|
return false if timeout.nil?
|
@@ -52,14 +60,14 @@ class Diskcached
|
|
52
60
|
# expire (delete) all caches in #store directory
|
53
61
|
def flush
|
54
62
|
Dir[ File.join( store, '*.cache' ) ].each do |file|
|
55
|
-
File.delete(file)
|
63
|
+
File.delete(file)
|
56
64
|
end
|
57
65
|
end
|
58
66
|
|
59
67
|
# flush expired caches if garbage collection
|
60
68
|
# hasn't been run recently
|
61
|
-
def flush_expired
|
62
|
-
if gc_last && gc_time && gc_last+gc_time <= Time.now
|
69
|
+
def flush_expired
|
70
|
+
if gc_last && gc_time && gc_last+gc_time <= Time.now
|
63
71
|
flush_expired!
|
64
72
|
end
|
65
73
|
end
|
@@ -67,9 +75,9 @@ class Diskcached
|
|
67
75
|
# flash expired caches, ingoring when garbage
|
68
76
|
# collection was last run
|
69
77
|
def flush_expired!
|
70
|
-
Dir[ File.join( store, "*.cache" ) ].each do |f|
|
78
|
+
Dir[ File.join( store, "*.cache" ) ].each do |f|
|
71
79
|
if (File.mtime(f)+timeout) <= Time.now
|
72
|
-
File.delete(f)
|
80
|
+
File.delete(f)
|
73
81
|
end
|
74
82
|
end
|
75
83
|
@gc_last = Time.now
|
@@ -108,28 +116,29 @@ class Diskcached
|
|
108
116
|
alias :replace :set # for memcached compatability
|
109
117
|
|
110
118
|
# get cache with 'key'
|
111
|
-
# - reads cache if it exists and isn't expired
|
119
|
+
# - reads cache if it exists and isn't expired
|
112
120
|
# or raises Diskcache::NotFound
|
113
|
-
# - if 'key' is an Array returns only keys
|
121
|
+
# - if 'key' is an Array returns only keys
|
114
122
|
# which exist and aren't expired, it raises
|
115
123
|
# Diskcache::NotFound if none are available
|
116
124
|
def get key
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
hash[k] = Marshal::load(read_cache_file(k)) unless expired?(k)
|
122
|
-
end
|
123
|
-
flush_expired if gc_auto
|
124
|
-
return hash unless hash.empty?
|
125
|
-
else
|
126
|
-
flush_expired if gc_auto
|
127
|
-
return Marshal::load(read_cache_file(key)) unless expired?(key)
|
125
|
+
if key.is_a? Array
|
126
|
+
ret = {}
|
127
|
+
key.each do |k|
|
128
|
+
ret[k] = Marshal::load(read_cache_file(k)) unless expired?(k)
|
128
129
|
end
|
129
|
-
|
130
|
-
|
131
|
-
|
130
|
+
|
131
|
+
raise if ret.empty?
|
132
|
+
return ret
|
132
133
|
end
|
134
|
+
|
135
|
+
raise if expired?(key)
|
136
|
+
|
137
|
+
return Marshal::load(read_cache_file(key))
|
138
|
+
rescue
|
139
|
+
raise Diskcached::NotFound
|
140
|
+
ensure
|
141
|
+
flush_expired if gc_auto
|
133
142
|
end
|
134
143
|
|
135
144
|
# returns path to cache file with 'key'
|
@@ -151,12 +160,12 @@ class Diskcached
|
|
151
160
|
def read_cache_file key
|
152
161
|
f = File.open( cache_file(key), "r" )
|
153
162
|
f.flock(File::LOCK_SH)
|
154
|
-
out = f.read
|
163
|
+
out = f.read
|
155
164
|
f.close
|
156
165
|
return out
|
157
166
|
end
|
158
167
|
|
159
|
-
# returns mtime of cache file or nil if
|
168
|
+
# returns mtime of cache file or nil if
|
160
169
|
# file doesn't exist
|
161
170
|
def read_cache_mtime key
|
162
171
|
return nil unless File.exists?(cache_file(key))
|
metadata
CHANGED
@@ -1,62 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diskcached
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Joshua Mervine
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2015-04-16 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rspec
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: simplecov
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rdoc
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
description: Simple disk cache for things like Sinatra which is implemented much like
|
@@ -75,26 +68,25 @@ files:
|
|
75
68
|
- Rakefile
|
76
69
|
homepage: http://diskcached.rubyops.net/
|
77
70
|
licenses: []
|
71
|
+
metadata: {}
|
78
72
|
post_install_message:
|
79
73
|
rdoc_options: []
|
80
74
|
require_paths:
|
81
75
|
- lib
|
82
76
|
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
77
|
requirements:
|
85
|
-
- -
|
78
|
+
- - '>='
|
86
79
|
- !ruby/object:Gem::Version
|
87
80
|
version: '0'
|
88
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
82
|
requirements:
|
91
|
-
- -
|
83
|
+
- - '>='
|
92
84
|
- !ruby/object:Gem::Version
|
93
85
|
version: 1.3.6
|
94
86
|
requirements: []
|
95
87
|
rubyforge_project:
|
96
|
-
rubygems_version:
|
88
|
+
rubygems_version: 2.0.14
|
97
89
|
signing_key:
|
98
|
-
specification_version:
|
90
|
+
specification_version: 4
|
99
91
|
summary: Simple disk cache
|
100
92
|
test_files: []
|