readthis 0.4.0 → 0.5.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 +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
|
[![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
3
|
[![Code Climate](https://codeclimate.com/github/sorentwo/readthis/badges/gpa.svg)](https://codeclimate.com/github/sorentwo/readthis)
|
4
|
+
[![Coverage Status](https://img.shields.io/coveralls/sorentwo/readthis.svg)](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
|