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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 513263f9ac89cdc3d8bfc354404778e49902460b
4
- data.tar.gz: 525ab985b197507e19c000bcb6dc21683ba068d9
3
+ metadata.gz: dd2e3cdc15043b250cf043b45d05cc991297114b
4
+ data.tar.gz: 67ed2a3248bb47a61cef51f3d5af65c56a4d043b
5
5
  SHA512:
6
- metadata.gz: f9207fe0c1d9746337d17ac05a23acd4bfde8c9d943d1814daa7426160a39fb8d932ea2fd7c69e898097afdf0427eab59cfad320362053547d4e40b3f2a4d4ee
7
- data.tar.gz: 14ae7680da85fe6591cc6b623c9f13e2c11dd7905add114288d6ed4138490b4a63a67d0832d0069706246cac73f5fe9de14774f0316e4a4db933ed418e6c60aa
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
@@ -1,4 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in readthis.gemspec
4
3
  gemspec
4
+
5
+ group :benchmarking do
6
+ gem 'benchmark-ips'
7
+ gem 'dalli'
8
+ gem 'redis-activesupport', github: 'sorentwo/redis-activesupport'
9
+ end
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
- An ActiveSupport::Cache compatible redis based cache focused on speed,
7
- simplicity, and forced pooling.
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
- The only dependencies are `redis` and `connection_pool`.
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]. Readthis
22
- supports all of the standard cache methods except for the following (with
23
- reasons):
67
+ Use it the same way as any other [ActiveSupport::Cache::Store][store]. Within a
68
+ rails environment config:
24
69
 
25
- * `cleanup` - redis does this with ttl for us already
26
- * `delete_matched` - you really don't want to do perform key matching operations
27
- in redis. They are linear time and only support basic globbing.
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
- ## Contributing
79
+ ```ruby
80
+ require 'readthis'
30
81
 
31
- 1. Fork it ( https://github.com/[my-github-username]/readthis/fork )
32
- 2. Create your feature branch (`git checkout -b my-new-feature`)
33
- 3. Commit your changes (`git commit -am 'Add some feature'`)
34
- 4. Push to the branch (`git push origin my-new-feature`)
35
- 5. Create a new Pull Request
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}"
@@ -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
@@ -33,10 +33,8 @@ module Readthis
33
33
  end
34
34
 
35
35
  def read(key, options = {})
36
- instrument(:read, key) do
37
- with do |store|
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
- instrument(:write, key) do
48
- with do |store|
49
- if expiration = options[:expires_in]
50
- store.setex(namespaced, expiration, value)
51
- else
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
- instrument(:delete, key) do
60
- with do |store|
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
- instrument(:incremenet, key) do
79
- with do |store|
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
- instrument(:decrement, key) do
87
- with do |store|
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
- results = []
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
- keys.zip(results).to_h
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
- instrument(:fetch_multi, keys) do
120
- with do |store|
121
- store.pipelined do
122
- results.each do |key, value|
123
- if value.nil?
124
- value = yield key
125
- write(key, value, options)
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
- instrument(:exist?, key) do
138
- with do |store|
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
- instrument(:clear, '*') do
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 with(&block)
162
- pool.with(&block)
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)
@@ -1,13 +1,18 @@
1
1
  module Readthis
2
2
  module Expanders
3
3
  def self.expand(key, namespace = nil)
4
- expanded = if key.respond_to?(:cache_key)
5
- key.cache_key
6
- elsif key.is_a?(Array)
7
- key.flat_map { |elem| expand(elem) }.join(':')
8
- else
9
- key
10
- end
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
@@ -1,6 +1,6 @@
1
1
  module Readthis
2
2
  module Notifications
3
- def self.instrument(name, payload)
3
+ def self.instrument(_name, payload)
4
4
  yield(payload)
5
5
  end
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module Readthis
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -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-key')
48
+ key_obj = double(cache_key: 'custom')
49
49
 
50
50
  cache.write(key_obj, 'some-value')
51
51
 
52
- expect(cache.read('custom-key')).to eq('some-value')
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:beta')
23
- expect(expand([object, object])).to eq('gamma: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.2.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-25 00:00:00.000000000 Z
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