readthis 0.4.0 → 0.5.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 +12 -0
- data/Gemfile +3 -0
- data/PERFORMANCE.md +30 -30
- data/README.md +22 -1
- data/benchmarks/marshalling.rb +37 -0
- data/lib/readthis/cache.rb +72 -49
- data/lib/readthis/entity.rb +51 -0
- data/lib/readthis/passthrough.rb +11 -0
- data/lib/readthis/version.rb +1 -1
- data/spec/readthis/cache_spec.rb +17 -20
- data/spec/readthis/entity_spec.rb +73 -0
- data/spec/readthis/passthrough_spec.rb +17 -0
- data/spec/spec_helper.rb +4 -0
- metadata +9 -5
- data/lib/readthis/compressor.rb +0 -41
- data/spec/readthis/compressor_spec.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1763594017ac9b87a099ca476d01c9f3823a0b0
|
4
|
+
data.tar.gz: 23e9363a5cc0c997c70f923f0ea21c50477843ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c3868ec4afab92154c4a9bc542de628224086760a6c8c5275d9ba83ede290b4800f209eb3c3b5349d11cd2f2f63e0aa5858e587308ac9d2988ecc6a35989511
|
7
|
+
data.tar.gz: 3ffd2cc9546da805453a27d5ca33a4a35525582eb7905973a45293e4093ed47c70ebe37b4e3fb8f16035074cb558de08e271b2de712b8f52e237a8f386db9ca3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## v0.5.0 2014-12-12
|
2
|
+
|
3
|
+
- Added: All read and write operations are marshalled to and from storage. This
|
4
|
+
allows hashes, arrays, etc. to be restored instead of always returning a
|
5
|
+
string. Unlike `ActiveSupport::Store::Entity`, no new objects are allocated
|
6
|
+
for each entity, reducing GC and improving performance.
|
7
|
+
- Fixed: Increment/Decrement interface was only accepting two params instead of
|
8
|
+
three. Now accepts `amount` as the second parameter.
|
9
|
+
- Changed: Increment/Decrement no longer use `incby` and `decby`, as they don't
|
10
|
+
work with marshalled values. This means they are not entirely atomic, so race
|
11
|
+
conditions are possible.
|
12
|
+
|
1
13
|
## v0.4.0 2014-12-11
|
2
14
|
|
3
15
|
- Added: Force the use of `hiredis` as the adapter. It is dramatically faster,
|
data/Gemfile
CHANGED
data/PERFORMANCE.md
CHANGED
@@ -19,55 +19,55 @@ Performance compared to `dalli` and `redis-activesupport`:
|
|
19
19
|
```
|
20
20
|
Raw Read Multi:
|
21
21
|
Calculating -------------------------------------
|
22
|
-
readthis:read-multi
|
23
|
-
redisas:read-multi
|
24
|
-
dalli:read-multi
|
22
|
+
readthis:read-multi 358.000 i/100ms
|
23
|
+
redisas:read-multi 94.000 i/100ms
|
24
|
+
dalli:read-multi 99.000 i/100ms
|
25
25
|
-------------------------------------------------
|
26
|
-
readthis:read-multi
|
27
|
-
redisas:read-multi
|
28
|
-
dalli:read-multi
|
26
|
+
readthis:read-multi 3.800k (± 2.3%) i/s - 19.332k
|
27
|
+
redisas:read-multi 962.199 (± 3.6%) i/s - 4.888k
|
28
|
+
dalli:read-multi 995.353 (± 1.1%) i/s - 5.049k
|
29
29
|
|
30
30
|
Comparison:
|
31
|
-
readthis:read-multi:
|
32
|
-
dalli:read-multi:
|
33
|
-
redisas:read-multi:
|
31
|
+
readthis:read-multi: 3799.8 i/s
|
32
|
+
dalli:read-multi: 995.4 i/s - 3.82x slower
|
33
|
+
redisas:read-multi: 962.2 i/s - 3.95x slower
|
34
34
|
|
35
35
|
Raw Fetch Multi:
|
36
36
|
Calculating -------------------------------------
|
37
|
-
readthis:fetch-multi
|
38
|
-
redisas:fetch-multi
|
39
|
-
dalli:fetch-multi
|
37
|
+
readthis:fetch-multi 336.000 i/100ms
|
38
|
+
redisas:fetch-multi 86.000 i/100ms
|
39
|
+
dalli:fetch-multi 102.000 i/100ms
|
40
40
|
-------------------------------------------------
|
41
|
-
readthis:fetch-multi
|
42
|
-
redisas:fetch-multi
|
43
|
-
dalli:fetch-multi 1.
|
41
|
+
readthis:fetch-multi 3.424k (± 2.6%) i/s - 17.136k
|
42
|
+
redisas:fetch-multi 874.803 (± 2.7%) i/s - 4.386k
|
43
|
+
dalli:fetch-multi 1.028k (± 1.2%) i/s - 5.202k
|
44
44
|
|
45
45
|
Comparison:
|
46
|
-
readthis:fetch-multi:
|
47
|
-
dalli:fetch-multi:
|
48
|
-
redisas:fetch-multi:
|
46
|
+
readthis:fetch-multi: 3424.2 i/s
|
47
|
+
dalli:fetch-multi: 1027.7 i/s - 3.33x slower
|
48
|
+
redisas:fetch-multi: 874.8 i/s - 3.91x slower
|
49
49
|
|
50
50
|
Compressed Writes:
|
51
51
|
Calculating -------------------------------------
|
52
|
-
readthis:write
|
53
|
-
dalli:write
|
52
|
+
readthis:write 924.000 i/100ms
|
53
|
+
dalli:write 903.000 i/100ms
|
54
54
|
-------------------------------------------------
|
55
|
-
readthis:write
|
56
|
-
dalli:write 9.
|
55
|
+
readthis:write 10.105k (± 4.9%) i/s - 50.820k
|
56
|
+
dalli:write 9.662k (± 1.6%) i/s - 48.762k
|
57
57
|
|
58
58
|
Comparison:
|
59
|
-
readthis:write:
|
60
|
-
dalli:write:
|
59
|
+
readthis:write: 10105.3 i/s
|
60
|
+
dalli:write: 9662.4 i/s - 1.05x slower
|
61
61
|
|
62
62
|
Compressed Read Multi:
|
63
63
|
Calculating -------------------------------------
|
64
|
-
readthis:read_multi
|
65
|
-
dalli:read_multi
|
64
|
+
readthis:read_multi 325.000 i/100ms
|
65
|
+
dalli:read_multi 100.000 i/100ms
|
66
66
|
-------------------------------------------------
|
67
|
-
readthis:read_multi
|
68
|
-
dalli:read_multi
|
67
|
+
readthis:read_multi 3.357k (± 2.3%) i/s - 16.900k
|
68
|
+
dalli:read_multi 1.014k (± 3.1%) i/s - 5.100k
|
69
69
|
|
70
70
|
Comparison:
|
71
|
-
readthis:read_multi:
|
72
|
-
dalli:read_multi:
|
71
|
+
readthis:read_multi: 3356.5 i/s
|
72
|
+
dalli:read_multi: 1014.1 i/s - 3.31x slower
|
73
73
|
```
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
[](http://badge.fury.io/rb/readthis)
|
2
2
|
[](https://travis-ci.org/sorentwo/readthis)
|
3
3
|
[](https://codeclimate.com/github/sorentwo/readthis)
|
4
|
+
[](https://coveralls.io/r/sorentwo/readthis?branch=master)
|
4
5
|
|
5
6
|
# Readthis
|
6
7
|
|
@@ -13,7 +14,7 @@ behavior for `fetch_multi`.
|
|
13
14
|
|
14
15
|
## Footprint & Performance
|
15
16
|
|
16
|
-
See [Performance]
|
17
|
+
See [Performance](PERFORMANCE.md)
|
17
18
|
|
18
19
|
## Installation
|
19
20
|
|
@@ -51,6 +52,8 @@ redis database, which defaults to 0. For example, using database 2:
|
|
51
52
|
REDIS_URL=redis://localhost:6379/2
|
52
53
|
```
|
53
54
|
|
55
|
+
### Compression
|
56
|
+
|
54
57
|
Compression can be enabled for all actions by passing the `compress` flag. By
|
55
58
|
default all values greater than 1024k will be compressed automatically. If there
|
56
59
|
is any content has not been stored with compression, or perhaps was compressed
|
@@ -65,6 +68,24 @@ config.cache_store = :readthis_store, ENV.fetch('REDIS_URL'), {
|
|
65
68
|
}
|
66
69
|
```
|
67
70
|
|
71
|
+
### Marshalling
|
72
|
+
|
73
|
+
Readthis uses Ruby's `Marshal` module for dumping and loading all values by
|
74
|
+
default. This isn't always the fastest option, depending on your use case it may
|
75
|
+
be desirable to use a faster but more flexible marshaller.
|
76
|
+
|
77
|
+
Use Oj for JSON marshalling, extremely fast, limited types:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Readthis::Cache.new(marshal: Oj)
|
81
|
+
```
|
82
|
+
|
83
|
+
If you don't mind everything being a string then use the Passthrough marshal:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
Readthis::Cache.new(marshal: Readthis::Passthrough)
|
87
|
+
```
|
88
|
+
|
68
89
|
## Differences
|
69
90
|
|
70
91
|
Readthis supports all of standard cache methods except for the following:
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'benchmark/ips'
|
6
|
+
require 'json'
|
7
|
+
require 'oj'
|
8
|
+
require 'readthis'
|
9
|
+
require 'readthis/passthrough'
|
10
|
+
|
11
|
+
REDIS_URL = 'redis://localhost:6379/11'
|
12
|
+
OPTIONS = { compressed: false }
|
13
|
+
|
14
|
+
readthis_pass = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Readthis::Passthrough))
|
15
|
+
readthis_oj = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Oj))
|
16
|
+
readthis_json = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: JSON))
|
17
|
+
readthis_ruby = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Marshal))
|
18
|
+
|
19
|
+
HASH = ('a'..'z').each_with_object({}) { |key, memo| memo[key] = key }
|
20
|
+
|
21
|
+
Benchmark.ips do |x|
|
22
|
+
x.report('pass:hash:dump') { readthis_oj.write('pass', HASH) }
|
23
|
+
x.report('oj:hash:dump') { readthis_oj.write('oj', HASH) }
|
24
|
+
x.report('json:hash:dump') { readthis_json.write('json', HASH) }
|
25
|
+
x.report('ruby:hash:dump') { readthis_ruby.write('ruby', HASH) }
|
26
|
+
|
27
|
+
x.compare!
|
28
|
+
end
|
29
|
+
|
30
|
+
Benchmark.ips do |x|
|
31
|
+
x.report('pass:hash:load') { readthis_oj.read('pass') }
|
32
|
+
x.report('oj:hash:load') { readthis_oj.read('oj') }
|
33
|
+
x.report('json:hash:load') { readthis_json.read('json') }
|
34
|
+
x.report('ruby:hash:load') { readthis_ruby.read('ruby') }
|
35
|
+
|
36
|
+
x.compare!
|
37
|
+
end
|
data/lib/readthis/cache.rb
CHANGED
@@ -1,19 +1,14 @@
|
|
1
|
-
require 'readthis/
|
1
|
+
require 'readthis/entity'
|
2
2
|
require 'readthis/expanders'
|
3
3
|
require 'readthis/notifications'
|
4
|
+
require 'readthis/passthrough'
|
4
5
|
require 'redis'
|
5
6
|
require 'hiredis'
|
6
7
|
require 'connection_pool'
|
7
8
|
|
8
9
|
module Readthis
|
9
10
|
class Cache
|
10
|
-
attr_reader :
|
11
|
-
:compression_threshold,
|
12
|
-
:expires_in,
|
13
|
-
:namespace,
|
14
|
-
:pool
|
15
|
-
|
16
|
-
alias_method :compress?, :compress
|
11
|
+
attr_reader :entity, :expires_in, :namespace, :pool
|
17
12
|
|
18
13
|
# Provide a class level lookup of the proper notifications module.
|
19
14
|
# Instrumention is expected to occur within applications that have
|
@@ -30,11 +25,14 @@ module Readthis
|
|
30
25
|
# Creates a new Readthis::Cache object with the given redis URL. The URL
|
31
26
|
# is parsed by the redis client directly.
|
32
27
|
#
|
33
|
-
# @param
|
34
|
-
# @option
|
35
|
-
# @option
|
36
|
-
# @option
|
37
|
-
# @option
|
28
|
+
# @param [String] A redis compliant url with necessary connection details
|
29
|
+
# @option [Boolean] :compress (false) Enable or disable automatic compression
|
30
|
+
# @option [Number] :compression_threshold (8k) The size a string must be for compression
|
31
|
+
# @option [Number] :expires_in The number of seconds until an entry expires
|
32
|
+
# @option [Module] :marshal (Marshal) Any module that responds to `dump` and `load`
|
33
|
+
# @option [String] :namespace Prefix used to namespace entries
|
34
|
+
# @option [Number] :pool_size (5) The number of threads in the pool
|
35
|
+
# @option [Number] :pool_timeout (5) How long before a thread times out
|
38
36
|
#
|
39
37
|
# @example Create a new cache instance
|
40
38
|
# Readthis::Cache.new('redis://localhost:6379/0', namespace: 'cache')
|
@@ -45,19 +43,35 @@ module Readthis
|
|
45
43
|
def initialize(url, options = {})
|
46
44
|
@expires_in = options.fetch(:expires_in, nil)
|
47
45
|
@namespace = options.fetch(:namespace, nil)
|
48
|
-
|
49
|
-
@
|
46
|
+
|
47
|
+
@entity = Readthis::Entity.new(
|
48
|
+
marshal: options.fetch(:marshal, Marshal),
|
49
|
+
compress: options.fetch(:compress, false),
|
50
|
+
threshold: options.fetch(:compression_threshold, 1024)
|
51
|
+
)
|
50
52
|
|
51
53
|
@pool = ConnectionPool.new(pool_options(options)) do
|
52
54
|
Redis.new(url: url, driver: :hiredis)
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
58
|
+
# Fetches data from the cache, using the given key. If there is data in
|
59
|
+
# the cache with the given key, then that data is returned. Otherwise, nil
|
60
|
+
# is returned.
|
61
|
+
#
|
62
|
+
# @param [String] Key for lookup
|
63
|
+
# @param [Hash] Optional overrides
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
#
|
67
|
+
# cache.read('missing') # => nil
|
68
|
+
# cache.read('matched') # => 'some value'
|
69
|
+
#
|
56
70
|
def read(key, options = {})
|
57
71
|
invoke(:read, key) do |store|
|
58
72
|
value = store.get(namespaced_key(key, merged_options(options)))
|
59
73
|
|
60
|
-
|
74
|
+
entity.load(value)
|
61
75
|
end
|
62
76
|
end
|
63
77
|
|
@@ -67,9 +81,9 @@ module Readthis
|
|
67
81
|
|
68
82
|
invoke(:write, key) do |store|
|
69
83
|
if expiration = options[:expires_in]
|
70
|
-
store.setex(namespaced, expiration,
|
84
|
+
store.setex(namespaced, expiration, entity.dump(value))
|
71
85
|
else
|
72
|
-
store.set(namespaced,
|
86
|
+
store.set(namespaced, entity.dump(value))
|
73
87
|
end
|
74
88
|
end
|
75
89
|
end
|
@@ -91,15 +105,45 @@ module Readthis
|
|
91
105
|
value
|
92
106
|
end
|
93
107
|
|
94
|
-
|
108
|
+
# Increment a key in the store.
|
109
|
+
#
|
110
|
+
# If the key doesn't exist it will be initialized at 0. If the key exists
|
111
|
+
# but it isn't a Fixnum it will be initialized at 0.
|
112
|
+
#
|
113
|
+
# @param [String] Key for lookup
|
114
|
+
# @param [Fixnum] Value to increment by
|
115
|
+
# @param [Hash] Optional overrides
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
#
|
119
|
+
# cache.increment('counter') # => 0
|
120
|
+
# cache.increment('counter') # => 1
|
121
|
+
# cache.increment('counter', 2) # => 3
|
122
|
+
#
|
123
|
+
def increment(key, amount = 1, options = {})
|
95
124
|
invoke(:incremenet, key) do |store|
|
96
|
-
|
125
|
+
alter(key, amount, options)
|
97
126
|
end
|
98
127
|
end
|
99
128
|
|
100
|
-
|
129
|
+
# Decrement a key in the store.
|
130
|
+
#
|
131
|
+
# If the key doesn't exist it will be initialized at 0. If the key exists
|
132
|
+
# but it isn't a Fixnum it will be initialized at 0.
|
133
|
+
#
|
134
|
+
# @param [String] Key for lookup
|
135
|
+
# @param [Fixnum] Value to decrement by
|
136
|
+
# @param [Hash] Optional overrides
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
#
|
140
|
+
# cache.write('counter', 20) # => 20
|
141
|
+
# cache.decrement('counter') # => 19
|
142
|
+
# cache.decrement('counter', 2) # => 17
|
143
|
+
#
|
144
|
+
def decrement(key, amount = 1, options = {})
|
101
145
|
invoke(:decrement, key) do |store|
|
102
|
-
|
146
|
+
alter(key, amount * -1, options)
|
103
147
|
end
|
104
148
|
end
|
105
149
|
|
@@ -108,7 +152,7 @@ module Readthis
|
|
108
152
|
mapping = keys.map { |key| namespaced_key(key, options) }
|
109
153
|
|
110
154
|
invoke(:read_multi, keys) do |store|
|
111
|
-
values =
|
155
|
+
values = store.mget(mapping).map { |value| entity.load(value) }
|
112
156
|
|
113
157
|
keys.zip(values).to_h
|
114
158
|
end
|
@@ -152,32 +196,11 @@ module Readthis
|
|
152
196
|
|
153
197
|
private
|
154
198
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def decompressed(value)
|
164
|
-
if compress?
|
165
|
-
compressor.decompress(value)
|
166
|
-
else
|
167
|
-
value
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def decompressed_multi(values)
|
172
|
-
if compress?
|
173
|
-
values.map { |value| compressor.decompress(value) }
|
174
|
-
else
|
175
|
-
values
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def compressor
|
180
|
-
@compressor ||= Readthis::Compressor.new(threshold: compression_threshold)
|
199
|
+
def alter(key, amount, options)
|
200
|
+
number = read(key, options)
|
201
|
+
delta = number.to_i + amount
|
202
|
+
write(key, delta, options)
|
203
|
+
delta
|
181
204
|
end
|
182
205
|
|
183
206
|
def instrument(operation, key)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module Readthis
|
4
|
+
class Entity
|
5
|
+
DEFAULT_THRESHOLD = 8 * 1024
|
6
|
+
|
7
|
+
attr_reader :marshal, :compression, :threshold
|
8
|
+
|
9
|
+
def initialize(marshal: Marshal, compress: false, threshold: DEFAULT_THRESHOLD)
|
10
|
+
@marshal = marshal
|
11
|
+
@compression = compress
|
12
|
+
@threshold = threshold
|
13
|
+
end
|
14
|
+
|
15
|
+
def dump(value)
|
16
|
+
return value if value.nil?
|
17
|
+
|
18
|
+
if compress?(value)
|
19
|
+
compress(value)
|
20
|
+
else
|
21
|
+
marshal.dump(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(value)
|
26
|
+
return value if value.nil?
|
27
|
+
|
28
|
+
if compress?(value)
|
29
|
+
decompress(value)
|
30
|
+
else
|
31
|
+
marshal.load(value)
|
32
|
+
end
|
33
|
+
rescue TypeError, Zlib::Error
|
34
|
+
value
|
35
|
+
end
|
36
|
+
|
37
|
+
def compress(value)
|
38
|
+
Zlib::Deflate.deflate(marshal.dump(value))
|
39
|
+
end
|
40
|
+
|
41
|
+
def decompress(value)
|
42
|
+
marshal.load(Zlib::Inflate.inflate(value))
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def compress?(value)
|
48
|
+
compression && value.bytesize >= threshold
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/readthis/version.rb
CHANGED
data/spec/readthis/cache_spec.rb
CHANGED
@@ -20,17 +20,6 @@ RSpec.describe Readthis::Cache do
|
|
20
20
|
|
21
21
|
expect(cache.expires_in).to eq(10)
|
22
22
|
end
|
23
|
-
|
24
|
-
it 'stores compression parameters' do
|
25
|
-
cache = Readthis::Cache.new(
|
26
|
-
url,
|
27
|
-
compress: true,
|
28
|
-
compression_threshold: 8
|
29
|
-
)
|
30
|
-
|
31
|
-
expect(cache.compress).to be_truthy
|
32
|
-
expect(cache.compression_threshold).to eq(8)
|
33
|
-
end
|
34
23
|
end
|
35
24
|
|
36
25
|
describe '#write' do
|
@@ -47,6 +36,14 @@ RSpec.describe Readthis::Cache do
|
|
47
36
|
expect(cache.read('some-key', namespace: 'cache')).to eq('some-value')
|
48
37
|
end
|
49
38
|
|
39
|
+
it 'roundtrips values as their original type' do
|
40
|
+
object = { a: 1, b: 2 }
|
41
|
+
|
42
|
+
cache.write('obj-key', object)
|
43
|
+
|
44
|
+
expect(cache.read('obj-key')).to eq(object)
|
45
|
+
end
|
46
|
+
|
50
47
|
it 'uses a custom expiration' do
|
51
48
|
cache.write('some-key', 'some-value', expires_in: 1)
|
52
49
|
|
@@ -70,7 +67,7 @@ RSpec.describe Readthis::Cache do
|
|
70
67
|
end
|
71
68
|
end
|
72
69
|
|
73
|
-
describe '
|
70
|
+
describe 'compression' do
|
74
71
|
it 'round trips entries when compression is enabled' do
|
75
72
|
com_cache = Readthis::Cache.new(url, compress: true, compression_threshold: 8)
|
76
73
|
raw_cache = Readthis::Cache.new(url)
|
@@ -129,11 +126,11 @@ RSpec.describe Readthis::Cache do
|
|
129
126
|
it 'maps multiple values to keys' do
|
130
127
|
cache.write('a', 1)
|
131
128
|
cache.write('b', 2)
|
132
|
-
cache.write('c', 3)
|
129
|
+
cache.write('c', '3')
|
133
130
|
|
134
131
|
expect(cache.read_multi('a', 'b', 'c')).to eq(
|
135
|
-
'a' =>
|
136
|
-
'b' =>
|
132
|
+
'a' => 1,
|
133
|
+
'b' => 2,
|
137
134
|
'c' => '3',
|
138
135
|
)
|
139
136
|
end
|
@@ -143,8 +140,8 @@ RSpec.describe Readthis::Cache do
|
|
143
140
|
cache.write('e', 2, namespace: 'cache')
|
144
141
|
|
145
142
|
expect(cache.read_multi('d', 'e', namespace: 'cache')).to eq(
|
146
|
-
'd' =>
|
147
|
-
'e' =>
|
143
|
+
'd' => 1,
|
144
|
+
'e' => 2,
|
148
145
|
)
|
149
146
|
end
|
150
147
|
end
|
@@ -157,9 +154,9 @@ RSpec.describe Readthis::Cache do
|
|
157
154
|
results = cache.fetch_multi('a', 'b', 'c') { |key| key + key }
|
158
155
|
|
159
156
|
expect(results).to eq(
|
160
|
-
'a' =>
|
157
|
+
'a' => 1,
|
161
158
|
'b' => 'bb',
|
162
|
-
'c' =>
|
159
|
+
'c' => 3,
|
163
160
|
)
|
164
161
|
end
|
165
162
|
end
|
@@ -188,7 +185,7 @@ RSpec.describe Readthis::Cache do
|
|
188
185
|
it 'atomically increases the stored integer' do
|
189
186
|
cache.write('counter', 10)
|
190
187
|
expect(cache.increment('counter')).to eq(11)
|
191
|
-
expect(cache.read('counter')).to eq(
|
188
|
+
expect(cache.read('counter')).to eq(11)
|
192
189
|
end
|
193
190
|
|
194
191
|
it 'defaults a missing key to 1' do
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'readthis/entity'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
RSpec.describe Readthis::Entity do
|
5
|
+
describe '#dump' do
|
6
|
+
it 'marshals the object as a ruby string' do
|
7
|
+
string = 'some string'
|
8
|
+
entity = Readthis::Entity.new
|
9
|
+
|
10
|
+
expect(entity.dump(string)).to eq(Marshal.dump(string))
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'marshals using a custom marshaller' do
|
14
|
+
string = 'some string'
|
15
|
+
entity = Readthis::Entity.new(marshal: JSON)
|
16
|
+
|
17
|
+
expect(entity.dump(string)).to eq(JSON.dump(string))
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'applies compression when enabled' do
|
21
|
+
string = 'a very large string, huge I tell you'
|
22
|
+
entity = Readthis::Entity.new(compress: true, threshold: 8)
|
23
|
+
dumped = Marshal.dump(string)
|
24
|
+
|
25
|
+
expect(entity.dump(string)).not_to eq(dumped)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not dump nil values' do
|
29
|
+
entity = Readthis::Entity.new
|
30
|
+
|
31
|
+
expect(entity.dump(nil)).to be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#load' do
|
36
|
+
it 'unmarshals a value' do
|
37
|
+
object = { a: 1, b: '2' }
|
38
|
+
dumped = Marshal.dump(object)
|
39
|
+
entity = Readthis::Entity.new
|
40
|
+
|
41
|
+
expect(entity.load(dumped)).to eq(object)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'uncompresses when compression is enabled' do
|
45
|
+
string = 'another one of those huge strings'
|
46
|
+
entity = Readthis::Entity.new(compress: true, threshold: 0)
|
47
|
+
dumped = Marshal.dump(string)
|
48
|
+
|
49
|
+
compressed = entity.compress(dumped)
|
50
|
+
|
51
|
+
expect(entity.load(compressed)).not_to eq(string)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not try to load a nil value' do
|
55
|
+
entity = Readthis::Entity.new
|
56
|
+
|
57
|
+
expect(entity.load(nil)).to be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'passes through the value when it fails to marshal' do
|
61
|
+
entity = Readthis::Entity.new
|
62
|
+
|
63
|
+
expect { entity.load('not marshalled') }.not_to raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'passes through the value when it fails to decompress' do
|
67
|
+
entity = Readthis::Entity.new(compress: true, threshold: 0)
|
68
|
+
dumped = Marshal.dump('some sizable string')
|
69
|
+
|
70
|
+
expect { entity.load(dumped) }.not_to raise_error
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'readthis/passthrough'
|
2
|
+
|
3
|
+
RSpec.describe Readthis::Passthrough do
|
4
|
+
describe '.load' do
|
5
|
+
it 'passes through the provided value' do
|
6
|
+
value = Object.new
|
7
|
+
expect(Readthis::Passthrough.load(value)).to eq(value)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.dump' do
|
12
|
+
it 'passes through the provided value' do
|
13
|
+
value = Object.new
|
14
|
+
expect(Readthis::Passthrough.dump(value)).to eq(value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
CHANGED
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.5.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-12-
|
11
|
+
date: 2014-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -112,21 +112,24 @@ files:
|
|
112
112
|
- README.md
|
113
113
|
- Rakefile
|
114
114
|
- benchmarks/compressed.rb
|
115
|
+
- benchmarks/marshalling.rb
|
115
116
|
- benchmarks/memory.rb
|
116
117
|
- benchmarks/multi.rb
|
117
118
|
- bin/rspec
|
118
119
|
- lib/active_support/cache/readthis_store.rb
|
119
120
|
- lib/readthis.rb
|
120
121
|
- lib/readthis/cache.rb
|
121
|
-
- lib/readthis/
|
122
|
+
- lib/readthis/entity.rb
|
122
123
|
- lib/readthis/expanders.rb
|
123
124
|
- lib/readthis/notifications.rb
|
125
|
+
- lib/readthis/passthrough.rb
|
124
126
|
- lib/readthis/version.rb
|
125
127
|
- readthis.gemspec
|
126
128
|
- spec/readthis/cache_spec.rb
|
127
|
-
- spec/readthis/
|
129
|
+
- spec/readthis/entity_spec.rb
|
128
130
|
- spec/readthis/expanders_spec.rb
|
129
131
|
- spec/readthis/notifications_spec.rb
|
132
|
+
- spec/readthis/passthrough_spec.rb
|
130
133
|
- spec/spec_helper.rb
|
131
134
|
homepage: https://github.com/sorentwo/readthis
|
132
135
|
licenses:
|
@@ -154,7 +157,8 @@ specification_version: 4
|
|
154
157
|
summary: Pooled active support compliant caching with redis
|
155
158
|
test_files:
|
156
159
|
- spec/readthis/cache_spec.rb
|
157
|
-
- spec/readthis/
|
160
|
+
- spec/readthis/entity_spec.rb
|
158
161
|
- spec/readthis/expanders_spec.rb
|
159
162
|
- spec/readthis/notifications_spec.rb
|
163
|
+
- spec/readthis/passthrough_spec.rb
|
160
164
|
- spec/spec_helper.rb
|
data/lib/readthis/compressor.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'zlib'
|
2
|
-
|
3
|
-
module Readthis
|
4
|
-
class Compressor
|
5
|
-
attr_reader :threshold
|
6
|
-
|
7
|
-
# Create a new Readthis::Compressor object that pivots on the provided
|
8
|
-
# threshold value.
|
9
|
-
#
|
10
|
-
# @param threshold [Number] the threshold size required for compression
|
11
|
-
def initialize(threshold: 1024)
|
12
|
-
@threshold = threshold
|
13
|
-
end
|
14
|
-
|
15
|
-
# Compress a value if its size is greater or equal to the current threshold.
|
16
|
-
#
|
17
|
-
# @param value [String] a string to compress
|
18
|
-
def compress(value)
|
19
|
-
if value.size >= threshold
|
20
|
-
Zlib::Deflate.deflate(value)
|
21
|
-
else
|
22
|
-
value
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Decompress a previously compressed object. It will attempt to decode a
|
27
|
-
# value regardless of whether it has been compressed, but will rescue
|
28
|
-
# decoding errors.
|
29
|
-
#
|
30
|
-
# @param value [String] a possibly compressed string to decompress
|
31
|
-
def decompress(value)
|
32
|
-
if value.size >= threshold
|
33
|
-
Zlib::Inflate.inflate(value)
|
34
|
-
else
|
35
|
-
value
|
36
|
-
end
|
37
|
-
rescue Zlib::Error
|
38
|
-
value
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'readthis/compressor'
|
2
|
-
|
3
|
-
RSpec.describe Readthis::Compressor do
|
4
|
-
describe '#compress' do
|
5
|
-
it 'compresses the input' do
|
6
|
-
compressor = Readthis::Compressor.new(threshold: 0)
|
7
|
-
input = 'aaa bbb ccc'
|
8
|
-
output = compressor.compress(input)
|
9
|
-
|
10
|
-
expect(input).not_to eq(output)
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'passes input below the threshold size through uncompressed' do
|
14
|
-
compressor = Readthis::Compressor.new(threshold: 1024)
|
15
|
-
input = 'abcdefg'
|
16
|
-
|
17
|
-
expect(compressor.compress(input)).to eq(input)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '#decompress' do
|
22
|
-
it 'decompresses compressed data' do
|
23
|
-
compressor = Readthis::Compressor.new(threshold: 0)
|
24
|
-
input = 'aaa bbb ccc'
|
25
|
-
compressed = compressor.compress(input)
|
26
|
-
decompressed = compressor.decompress(compressed)
|
27
|
-
|
28
|
-
expect(decompressed).to eq(input)
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'passes through decompression failures' do
|
32
|
-
compressor = Readthis::Compressor.new(threshold: 0)
|
33
|
-
input = 'abcdefg'
|
34
|
-
|
35
|
-
expect(compressor.decompress(input)).to eq(input)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|