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 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