readthis 0.2.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -2
- data/CONTRIBUTING.md +14 -0
- data/Gemfile +6 -1
- data/README.md +79 -15
- data/benchmarks/memory.rb +11 -0
- data/benchmarks/multi.rb +52 -0
- data/lib/readthis/cache.rb +30 -52
- data/lib/readthis/expanders.rb +12 -7
- data/lib/readthis/notifications.rb +1 -1
- data/lib/readthis/version.rb +1 -1
- data/spec/readthis/cache_spec.rb +2 -2
- data/spec/readthis/expanders_spec.rb +14 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd2e3cdc15043b250cf043b45d05cc991297114b
|
4
|
+
data.tar.gz: 67ed2a3248bb47a61cef51f3d5af65c56a4d043b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71eed4853e2cbf4d7e1832a8399ff06ed9ce3fbb2aca120f6ff10ec60c61470dedd96b1096d712a7df47fd47a828f91b60cd7d70c80de87bbdbb7d4d771f2264
|
7
|
+
data.tar.gz: abc9ce7f90b7fd0d4be2c340781bf1087b3f9d34a431dfcee81b5125d0c839d82b7cf703e06e3ed061ad8f2df43f63944f7297370d8fc2631f7d3a9d46dab888
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
+
## v0.3.0 2014-12-01
|
2
|
+
|
3
|
+
- Added: Use `to_param` for key expansion, only when available. Makes it
|
4
|
+
possible to extract a key from any object when ActiveSupport is loaded.
|
5
|
+
- Added: Expand hashes as cache keys.
|
6
|
+
- Changed: Use `mget` for `read_multi`, faster and more synchronous than relying on
|
7
|
+
`pipelined`.
|
8
|
+
- Changed: Delimit compound objects with a slash rather than a colon.
|
9
|
+
|
1
10
|
## v0.2.0 2014-11-24
|
2
11
|
|
3
|
-
- Instrument all caching methods. Will use `ActiveSupport::Notifications`
|
12
|
+
- Added: Instrument all caching methods. Will use `ActiveSupport::Notifications`
|
4
13
|
if available, otherwise falls back to a polyfill.
|
5
|
-
- Expand objects with a `cache_key` method and arrays of strings or objects into
|
14
|
+
- Added: Expand objects with a `cache_key` method and arrays of strings or objects into
|
6
15
|
consistent naespaced keys.
|
7
16
|
|
8
17
|
## v0.1.0 2014-11-22
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Open an Issue
|
4
|
+
|
5
|
+
Let us know about bugs. Include your version of Readthis, Ruby, and the
|
6
|
+
environment you are using.
|
7
|
+
|
8
|
+
## Submit a Pull Request
|
9
|
+
|
10
|
+
1. Fork it ( https://github.com/sorentwo/readthis/fork )
|
11
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
12
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
13
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
14
|
+
5. Create a new Pull Request
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,58 @@
|
|
1
1
|
[](http://badge.fury.io/rb/readthis)
|
2
2
|
[](https://travis-ci.org/sorentwo/readthis)
|
3
|
+
[](https://codeclimate.com/github/sorentwo/readthis)
|
3
4
|
|
4
5
|
# Readthis
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
Readthis is a drop in replacement for any ActiveSupport compliant cache, but
|
8
|
+
emphasizes performance and simplicity. It takes some cues from Dalli (connection
|
9
|
+
pooling), the popular Memcache client. Below are some performance comparisons
|
10
|
+
against the only other notable redis cache implementation, `redis-activesupport`,
|
11
|
+
which has been abandoned and doesn't actually comply to Rails 4.2 cache store
|
12
|
+
behavior for `fetch_multi`.
|
8
13
|
|
9
|
-
|
14
|
+
## Footprint & Performance
|
15
|
+
|
16
|
+
Footprint compared to `redis-activesupport`:
|
17
|
+
|
18
|
+
```
|
19
|
+
# Total allocated objects after require
|
20
|
+
readthis: 19,964
|
21
|
+
redis-activesupport: 78,630
|
22
|
+
```
|
23
|
+
|
24
|
+
Performance compared to `dalli` and `redis-activesupport` for \*multi
|
25
|
+
operations:
|
26
|
+
|
27
|
+
```
|
28
|
+
Calculating -------------------------------------
|
29
|
+
readthis:read-multi 118.000 i/100ms
|
30
|
+
redisas:read-multi 94.000 i/100ms
|
31
|
+
dalli:read-multi 92.000 i/100ms
|
32
|
+
-------------------------------------------------
|
33
|
+
readthis:read-multi 1.206k (± 4.6%) i/s - 6.018k
|
34
|
+
redisas:read-multi 973.086 (± 4.4%) i/s - 4.888k
|
35
|
+
dalli:read-multi 949.348 (± 4.1%) i/s - 4.784k
|
36
|
+
|
37
|
+
Comparison:
|
38
|
+
readthis:read-multi: 1206.0 i/s
|
39
|
+
redisas:read-multi: 973.1 i/s - 1.24x slower
|
40
|
+
dalli:read-multi: 949.3 i/s - 1.27x slower
|
41
|
+
|
42
|
+
Calculating -------------------------------------
|
43
|
+
readthis:fetch-multi 114.000 i/100ms
|
44
|
+
redisas:fetch-multi 82.000 i/100ms
|
45
|
+
dalli:fetch-multi 97.000 i/100ms
|
46
|
+
-------------------------------------------------
|
47
|
+
readthis:fetch-multi 1.157k (± 5.0%) i/s - 5.814k
|
48
|
+
redisas:fetch-multi 829.211 (± 4.2%) i/s - 4.182k
|
49
|
+
dalli:fetch-multi 979.081 (± 3.8%) i/s - 4.947k
|
50
|
+
|
51
|
+
Comparison:
|
52
|
+
readthis:fetch-multi: 1157.2 i/s
|
53
|
+
dalli:fetch-multi: 979.1 i/s - 1.18x slower
|
54
|
+
redisas:fetch-multi: 829.2 i/s - 1.40x slower
|
55
|
+
```
|
10
56
|
|
11
57
|
## Installation
|
12
58
|
|
@@ -18,20 +64,38 @@ gem 'readthis'
|
|
18
64
|
|
19
65
|
## Usage
|
20
66
|
|
21
|
-
Use it the same way as any other [ActiveSupport::Cache::Store][store].
|
22
|
-
|
23
|
-
reasons):
|
67
|
+
Use it the same way as any other [ActiveSupport::Cache::Store][store]. Within a
|
68
|
+
rails environment config:
|
24
69
|
|
25
|
-
|
26
|
-
|
27
|
-
|
70
|
+
```ruby
|
71
|
+
config.cache_store = :readthis_store, ENV.fetch('REDIS_URL'), {
|
72
|
+
expires_in: 2.weeks,
|
73
|
+
namespace: 'cache'
|
74
|
+
}
|
75
|
+
```
|
76
|
+
|
77
|
+
Otherwise you can use it anywhere, without any reliance on ActiveSupport:
|
28
78
|
|
29
|
-
|
79
|
+
```ruby
|
80
|
+
require 'readthis'
|
30
81
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
82
|
+
cache = Readthis::Cache.new(ENV.fetch('REDIS_URL'), expires_in: 2.weeks)
|
83
|
+
```
|
84
|
+
|
85
|
+
You'll want to use a specific database for caching, just in case you need to
|
86
|
+
clear the cache entirely. Appending a number between 0 and 15 will specify the
|
87
|
+
redis database, which defaults to 0. For example, using database 2:
|
88
|
+
|
89
|
+
```
|
90
|
+
REDIS_URL=redis://localhost:6379/2
|
91
|
+
```
|
92
|
+
|
93
|
+
## Differences
|
94
|
+
|
95
|
+
Readthis supports all of standard cache methods except for the following:
|
96
|
+
|
97
|
+
* `cleanup` - redis does this with ttl for us already
|
98
|
+
* `delete_matched` - you really don't want to perform key matching operations
|
99
|
+
in redis. They are linear time and only support basic globbing.
|
36
100
|
|
37
101
|
[store]: http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler'; Bundler.setup
|
2
|
+
a = GC.stat(:total_allocated_object)
|
3
|
+
|
4
|
+
require 'readthis'
|
5
|
+
b = GC.stat(:total_allocated_object)
|
6
|
+
|
7
|
+
require 'redis-activesupport'
|
8
|
+
c = GC.stat(:total_allocated_object)
|
9
|
+
|
10
|
+
puts "readthis: #{b - a}"
|
11
|
+
puts "redis-activesupport: #{c - b}"
|
data/benchmarks/multi.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'benchmark/ips'
|
6
|
+
require 'dalli'
|
7
|
+
require 'redis-activesupport'
|
8
|
+
require 'active_support/cache/dalli_store'
|
9
|
+
require 'readthis'
|
10
|
+
|
11
|
+
redis_url = 'redis://localhost:6379/11'
|
12
|
+
dalli = ActiveSupport::Cache::DalliStore.new('localhost', namespace: 'da', pool_size: 5)
|
13
|
+
redisas = ActiveSupport::Cache::RedisStore.new(redis_url + '/ra')
|
14
|
+
readthis = Readthis::Cache.new(redis_url, namespace: 'rd', expires_in: 60)
|
15
|
+
|
16
|
+
('a'..'z').each do |key|
|
17
|
+
dalli.write(key, key * 1024)
|
18
|
+
readthis.write(key, key * 1024)
|
19
|
+
redisas.write(key, key * 1024)
|
20
|
+
end
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
x.report 'readthis:read-multi' do
|
24
|
+
readthis.read_multi(*('a'..'z'))
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report 'redisas:read-multi' do
|
28
|
+
redisas.read_multi(*('a'..'z'))
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report 'dalli:read-multi' do
|
32
|
+
dalli.read_multi(*('a'..'z'))
|
33
|
+
end
|
34
|
+
|
35
|
+
x.compare!
|
36
|
+
end
|
37
|
+
|
38
|
+
Benchmark.ips do |x|
|
39
|
+
x.report 'readthis:fetch-multi' do
|
40
|
+
readthis.fetch_multi(*('a'..'z')) { |_| 'missing' }
|
41
|
+
end
|
42
|
+
|
43
|
+
x.report 'redisas:fetch-multi' do
|
44
|
+
redisas.fetch_multi(*('a'..'z')) { |_| 'missing' }
|
45
|
+
end
|
46
|
+
|
47
|
+
x.report 'dalli:fetch-multi' do
|
48
|
+
dalli.fetch_multi(*('a'..'z')) { |_| 'missing' }
|
49
|
+
end
|
50
|
+
|
51
|
+
x.compare!
|
52
|
+
end
|
data/lib/readthis/cache.rb
CHANGED
@@ -33,10 +33,8 @@ module Readthis
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def read(key, options = {})
|
36
|
-
|
37
|
-
|
38
|
-
store.get(namespaced_key(key, merged_options(options)))
|
39
|
-
end
|
36
|
+
invoke(:read, key) do |store|
|
37
|
+
store.get(namespaced_key(key, merged_options(options)))
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
@@ -44,22 +42,18 @@ module Readthis
|
|
44
42
|
options = merged_options(options)
|
45
43
|
namespaced = namespaced_key(key, options)
|
46
44
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
store.set(namespaced, value)
|
53
|
-
end
|
45
|
+
invoke(:write, key) do |store|
|
46
|
+
if expiration = options[:expires_in]
|
47
|
+
store.setex(namespaced, expiration, value)
|
48
|
+
else
|
49
|
+
store.set(namespaced, value)
|
54
50
|
end
|
55
51
|
end
|
56
52
|
end
|
57
53
|
|
58
54
|
def delete(key, options = {})
|
59
|
-
|
60
|
-
|
61
|
-
store.del(namespaced_key(key, merged_options(options)))
|
62
|
-
end
|
55
|
+
invoke(:delete, key) do |store|
|
56
|
+
store.del(namespaced_key(key, merged_options(options)))
|
63
57
|
end
|
64
58
|
end
|
65
59
|
|
@@ -75,33 +69,23 @@ module Readthis
|
|
75
69
|
end
|
76
70
|
|
77
71
|
def increment(key, options = {})
|
78
|
-
|
79
|
-
|
80
|
-
store.incr(namespaced_key(key, merged_options(options)))
|
81
|
-
end
|
72
|
+
invoke(:incremenet, key) do |store|
|
73
|
+
store.incr(namespaced_key(key, merged_options(options)))
|
82
74
|
end
|
83
75
|
end
|
84
76
|
|
85
77
|
def decrement(key, options = {})
|
86
|
-
|
87
|
-
|
88
|
-
store.decr(namespaced_key(key, merged_options(options)))
|
89
|
-
end
|
78
|
+
invoke(:decrement, key) do |store|
|
79
|
+
store.decr(namespaced_key(key, merged_options(options)))
|
90
80
|
end
|
91
81
|
end
|
92
82
|
|
93
83
|
def read_multi(*keys)
|
94
84
|
options = merged_options(extract_options!(keys))
|
95
|
-
|
96
|
-
|
97
|
-
instrument(:read_multi, keys) do
|
98
|
-
with do |store|
|
99
|
-
results = store.pipelined do
|
100
|
-
keys.each { |key| store.get(namespaced_key(key, options)) }
|
101
|
-
end
|
102
|
-
end
|
85
|
+
mapping = keys.map { |key| namespaced_key(key, options) }
|
103
86
|
|
104
|
-
|
87
|
+
invoke(:read_multi, keys) do |store|
|
88
|
+
keys.zip(store.mget(mapping)).to_h
|
105
89
|
end
|
106
90
|
end
|
107
91
|
|
@@ -116,15 +100,13 @@ module Readthis
|
|
116
100
|
results = read_multi(*keys)
|
117
101
|
options = merged_options(extract_options!(keys))
|
118
102
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
results[key] = value
|
127
|
-
end
|
103
|
+
invoke(:fetch_multi, keys) do |store|
|
104
|
+
store.pipelined do
|
105
|
+
results.each do |key, value|
|
106
|
+
if value.nil?
|
107
|
+
value = yield key
|
108
|
+
write(key, value, options)
|
109
|
+
results[key] = value
|
128
110
|
end
|
129
111
|
end
|
130
112
|
end
|
@@ -134,19 +116,13 @@ module Readthis
|
|
134
116
|
end
|
135
117
|
|
136
118
|
def exist?(key, options = {})
|
137
|
-
|
138
|
-
|
139
|
-
store.exists(namespaced_key(key, merged_options(options)))
|
140
|
-
end
|
119
|
+
invoke(:exist?, key) do |store|
|
120
|
+
store.exists(namespaced_key(key, merged_options(options)))
|
141
121
|
end
|
142
122
|
end
|
143
123
|
|
144
124
|
def clear
|
145
|
-
|
146
|
-
with do |store|
|
147
|
-
store.flushdb
|
148
|
-
end
|
149
|
-
end
|
125
|
+
invoke(:clear, '*', &:flushdb)
|
150
126
|
end
|
151
127
|
|
152
128
|
private
|
@@ -158,8 +134,10 @@ module Readthis
|
|
158
134
|
self.class.notifications.instrument(name, key) { yield(payload) }
|
159
135
|
end
|
160
136
|
|
161
|
-
def
|
162
|
-
|
137
|
+
def invoke(operation, key, &block)
|
138
|
+
instrument(operation, key) do
|
139
|
+
pool.with(&block)
|
140
|
+
end
|
163
141
|
end
|
164
142
|
|
165
143
|
def extract_options!(array)
|
data/lib/readthis/expanders.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module Readthis
|
2
2
|
module Expanders
|
3
3
|
def self.expand(key, namespace = nil)
|
4
|
-
expanded =
|
5
|
-
key.cache_key
|
6
|
-
|
7
|
-
key.
|
8
|
-
|
9
|
-
key
|
10
|
-
|
4
|
+
expanded =
|
5
|
+
if key.respond_to?(:cache_key)
|
6
|
+
key.cache_key
|
7
|
+
elsif key.is_a?(Array)
|
8
|
+
key.flat_map { |elem| expand(elem) }.join('/')
|
9
|
+
elsif key.is_a?(Hash)
|
10
|
+
key.sort_by { |key, _| key.to_s }.map { |key, val| "#{key}=#{val}" }.join('/')
|
11
|
+
elsif key.respond_to?(:to_param)
|
12
|
+
key.to_param
|
13
|
+
else
|
14
|
+
key
|
15
|
+
end
|
11
16
|
|
12
17
|
namespace ? "#{namespace}:#{expanded}" : expanded
|
13
18
|
end
|
data/lib/readthis/version.rb
CHANGED
data/spec/readthis/cache_spec.rb
CHANGED
@@ -45,11 +45,11 @@ RSpec.describe Readthis::Cache do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'expands non-string keys' do
|
48
|
-
key_obj = double(cache_key: 'custom
|
48
|
+
key_obj = double(cache_key: 'custom')
|
49
49
|
|
50
50
|
cache.write(key_obj, 'some-value')
|
51
51
|
|
52
|
-
expect(cache.read('custom
|
52
|
+
expect(cache.read('custom')).to eq('some-value')
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -19,8 +19,20 @@ RSpec.describe Readthis::Expanders do
|
|
19
19
|
it 'expands an array of objects' do
|
20
20
|
object = double(cache_key: 'gamma')
|
21
21
|
|
22
|
-
expect(expand(['alpha', 'beta'])).to eq('alpha
|
23
|
-
expect(expand([object, object])).to eq('gamma
|
22
|
+
expect(expand(['alpha', 'beta'])).to eq('alpha/beta')
|
23
|
+
expect(expand([object, object])).to eq('gamma/gamma')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'expands the keys of a hash' do
|
27
|
+
keyhash = { 'beta' => 2, alpha: 1 }
|
28
|
+
|
29
|
+
expect(expand(keyhash)).to eq('alpha=1/beta=2')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'uses the to_param method if available' do
|
33
|
+
object = double(to_param: 'thing')
|
34
|
+
|
35
|
+
expect(expand(object)).to eq('thing')
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: readthis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Parker Selbert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -91,10 +91,13 @@ files:
|
|
91
91
|
- ".rspec"
|
92
92
|
- ".travis.yml"
|
93
93
|
- CHANGELOG.md
|
94
|
+
- CONTRIBUTING.md
|
94
95
|
- Gemfile
|
95
96
|
- LICENSE.txt
|
96
97
|
- README.md
|
97
98
|
- Rakefile
|
99
|
+
- benchmarks/memory.rb
|
100
|
+
- benchmarks/multi.rb
|
98
101
|
- bin/rspec
|
99
102
|
- lib/active_support/cache/readthis_store.rb
|
100
103
|
- lib/readthis.rb
|