readthis 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/readthis.svg)](http://badge.fury.io/rb/readthis)
|
2
2
|
[![Build Status](https://travis-ci.org/sorentwo/readthis.svg?branch=master)](https://travis-ci.org/sorentwo/readthis)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/sorentwo/readthis/badges/gpa.svg)](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
|