rails-brotli-cache 0.6.0 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -2
- data/benchmarks/Gemfile +2 -0
- data/benchmarks/main.rb +61 -2
- data/lib/rails-brotli-cache/store.rb +12 -5
- data/lib/rails-brotli-cache/version.rb +1 -1
- data/spec/rails-brotli-cache/compatibility_spec.rb +18 -10
- data/spec/rails-brotli-cache/store_spec.rb +49 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6267cfdccacd5e402e500f1df681e16e8d0be371718e499d0abde30c64822dbd
|
4
|
+
data.tar.gz: 4648a2053526200a231c8cd36632715700a6ee197f672431ed2de1c150e036eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dfdbc83794444e9be00962b4b5f6470f5c8803340cabbe3535f7143c87cc3bb21af6340d226e8b4b647cf6fd02a1318376daf98f4c50baa4707c70bcacca463
|
7
|
+
data.tar.gz: a8baf1bb1409642f3ea7e6251970a9fb8fbf1b3bbf6dade72a77f288d24de3f58fa2d00b36f24b4c82908a8f6e510564f9265b106297dcc1ddd7500b9c4f7306
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ You can check out [this blog post](https://pawelurbanek.com/rails-brotli-cache)
|
|
9
9
|
`Gemfile`
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem 'brotli' #
|
12
|
+
gem 'brotli' # an optional dependency, other compressors are supported
|
13
13
|
gem 'rails-brotli-cache'
|
14
14
|
```
|
15
15
|
|
@@ -139,10 +139,21 @@ config.cache_store = RailsBrotliCache::Store.new(
|
|
139
139
|
```
|
140
140
|
|
141
141
|
```ruby
|
142
|
+
|
143
|
+
class ZSTDCompressor
|
144
|
+
def self.deflate(payload)
|
145
|
+
::Zstd.compress(payload, 10)
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.inflate(payload)
|
149
|
+
::Zstd.decompress(payload)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
142
153
|
Rails.cache.write('test-key', json, compressor_class: Snappy)
|
143
154
|
```
|
144
155
|
|
145
|
-
This config expects a class
|
156
|
+
This config expects a class that defines two methods, `inflate` and `deflate`. It allows to use, for example, a [ZSTD by Facebook](https://github.com/SpringMT/zstd-ruby), offering even better performance and compression.
|
146
157
|
|
147
158
|
## Testing
|
148
159
|
|
data/benchmarks/Gemfile
CHANGED
data/benchmarks/main.rb
CHANGED
@@ -1,31 +1,62 @@
|
|
1
1
|
require 'active_support'
|
2
2
|
require 'active_support/core_ext/hash'
|
3
3
|
require 'net/http'
|
4
|
+
require 'brotli'
|
4
5
|
require 'rails-brotli-cache'
|
5
6
|
require 'benchmark'
|
7
|
+
require 'zstd-ruby'
|
8
|
+
|
9
|
+
class ZSTDCompressor
|
10
|
+
def self.deflate(payload)
|
11
|
+
::Zstd.compress(payload, 10)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.inflate(payload)
|
15
|
+
::Zstd.decompress(payload)
|
16
|
+
end
|
17
|
+
end
|
6
18
|
|
7
19
|
memory_cache = ActiveSupport::Cache::MemoryStore.new(compress: true) # memory store does not use compression by default
|
8
20
|
brotli_memory_cache = RailsBrotliCache::Store.new(memory_cache)
|
21
|
+
zstd_memory_cache = RailsBrotliCache::Store.new(memory_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
22
|
+
|
9
23
|
redis_cache = ActiveSupport::Cache::RedisCacheStore.new
|
10
24
|
brotli_redis_cache = RailsBrotliCache::Store.new(redis_cache)
|
25
|
+
zstd_redis_cache = RailsBrotliCache::Store.new(redis_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
26
|
+
|
11
27
|
memcached_cache = ActiveSupport::Cache::MemCacheStore.new
|
12
28
|
brotli_memcached_cache = RailsBrotliCache::Store.new(memcached_cache)
|
29
|
+
zstd_memcached_cache = RailsBrotliCache::Store.new(memcached_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
30
|
+
|
13
31
|
file_cache = ActiveSupport::Cache::FileStore.new('/tmp')
|
14
32
|
brotli_file_cache = RailsBrotliCache::Store.new(file_cache)
|
33
|
+
zstd_file_cache = RailsBrotliCache::Store.new(file_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
15
34
|
|
16
35
|
json_uri = URI("https://raw.githubusercontent.com/pawurb/rails-brotli-cache/main/spec/fixtures/sample.json")
|
17
36
|
json = Net::HTTP.get(json_uri)
|
18
37
|
|
19
38
|
puts "Uncompressed JSON size: #{json.size}"
|
20
39
|
redis_cache.write("gz-json", json)
|
21
|
-
gzip_json_size = redis_cache.redis.
|
40
|
+
gzip_json_size = redis_cache.redis.with do |conn|
|
41
|
+
conn.get("gz-json").size
|
42
|
+
end
|
22
43
|
puts "Gzip JSON size: #{gzip_json_size}"
|
23
44
|
brotli_redis_cache.write("json", json)
|
24
|
-
br_json_size = redis_cache.redis.
|
45
|
+
br_json_size = redis_cache.redis.with do |conn|
|
46
|
+
conn.get("br-json").size
|
47
|
+
end
|
25
48
|
puts "Brotli JSON size: #{br_json_size}"
|
26
49
|
puts "~#{((gzip_json_size - br_json_size).to_f / gzip_json_size.to_f * 100).round}% improvment"
|
27
50
|
puts ""
|
28
51
|
|
52
|
+
zstd_redis_cache.write("json", json)
|
53
|
+
zs_json_size = redis_cache.redis.with do |conn|
|
54
|
+
conn.get("zs-json").size
|
55
|
+
end
|
56
|
+
puts "ZSTD JSON size: #{zs_json_size}"
|
57
|
+
puts "~#{((gzip_json_size - zs_json_size).to_f / gzip_json_size.to_f * 100).round}% improvment"
|
58
|
+
puts ""
|
59
|
+
|
29
60
|
iterations = 100
|
30
61
|
|
31
62
|
Benchmark.bm do |x|
|
@@ -43,6 +74,13 @@ Benchmark.bm do |x|
|
|
43
74
|
end
|
44
75
|
end
|
45
76
|
|
77
|
+
x.report("zstd_memory_cache") do
|
78
|
+
iterations.times do
|
79
|
+
zstd_memory_cache.write("test", json)
|
80
|
+
zstd_memory_cache.read("test")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
46
84
|
x.report("redis_cache") do
|
47
85
|
iterations.times do
|
48
86
|
redis_cache.write("test", json)
|
@@ -57,6 +95,13 @@ Benchmark.bm do |x|
|
|
57
95
|
end
|
58
96
|
end
|
59
97
|
|
98
|
+
x.report("zstd_redis_cache") do
|
99
|
+
iterations.times do
|
100
|
+
zstd_redis_cache.write("test", json)
|
101
|
+
zstd_redis_cache.read("test")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
60
105
|
x.report("memcached_cache") do
|
61
106
|
iterations.times do
|
62
107
|
memcached_cache.write("test", json)
|
@@ -71,6 +116,13 @@ Benchmark.bm do |x|
|
|
71
116
|
end
|
72
117
|
end
|
73
118
|
|
119
|
+
x.report("zstd_memcached_cache") do
|
120
|
+
iterations.times do
|
121
|
+
zstd_memcached_cache.write("test", json)
|
122
|
+
zstd_memcached_cache.read("test")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
74
126
|
x.report("file_cache") do
|
75
127
|
iterations.times do
|
76
128
|
file_cache.write("test", json)
|
@@ -84,4 +136,11 @@ Benchmark.bm do |x|
|
|
84
136
|
brotli_file_cache.read("test")
|
85
137
|
end
|
86
138
|
end
|
139
|
+
|
140
|
+
x.report("zstd_file_cache") do
|
141
|
+
iterations.times do
|
142
|
+
zstd_file_cache.write("test", json)
|
143
|
+
zstd_file_cache.read("test")
|
144
|
+
end
|
145
|
+
end
|
87
146
|
end
|
@@ -109,14 +109,21 @@ module RailsBrotliCache
|
|
109
109
|
|
110
110
|
def fetch_multi(*names)
|
111
111
|
options = names.extract_options!
|
112
|
-
|
112
|
+
expanded_names = names.map { |name| expanded_cache_key(name) }
|
113
113
|
options = options.reverse_merge(@init_options)
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
reads = core_store.send(:read_multi_entries, expanded_names, **options)
|
116
|
+
reads.map do |key, val|
|
117
|
+
[source_cache_key(key), uncompressed(val, options)]
|
118
|
+
end.to_h
|
119
|
+
|
120
|
+
writes = {}
|
121
|
+
ordered = names.index_with do |name|
|
122
|
+
reads.fetch(name) { writes[name] = yield(name) }
|
119
123
|
end
|
124
|
+
|
125
|
+
write_multi(writes)
|
126
|
+
ordered
|
120
127
|
end
|
121
128
|
|
122
129
|
def exist?(name, options = {})
|
@@ -13,6 +13,15 @@ describe RailsBrotliCache do
|
|
13
13
|
]
|
14
14
|
|
15
15
|
CACHE_STORE_TYPES.each do |cache_store_types|
|
16
|
+
let(:big_enough_to_compress_value) do
|
17
|
+
SecureRandom.hex(2048)
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
brotli_store.clear
|
22
|
+
standard_cache.clear
|
23
|
+
end
|
24
|
+
|
16
25
|
describe "Brotli cache has the same API as #{cache_store_types[0].class}" do
|
17
26
|
subject(:brotli_store) do
|
18
27
|
RailsBrotliCache::Store.new(cache_store_types[0])
|
@@ -35,12 +44,12 @@ describe RailsBrotliCache do
|
|
35
44
|
expect(brotli_store.write("int_val_key", int_val).class).to eq(standard_cache.write("int_val_key", int_val).class)
|
36
45
|
expect(brotli_store.read("int_val_key")).to eq(standard_cache.read("int_val_key"))
|
37
46
|
|
38
|
-
str_val =
|
39
|
-
expect(brotli_store.write("str_val_key",
|
47
|
+
str_val = big_enough_to_compress_value
|
48
|
+
expect(brotli_store.write("str_val_key", str_val).class).to eq(standard_cache.write("str_val_key", str_val).class)
|
40
49
|
expect(brotli_store.read("str_val_key")).to eq(standard_cache.read("str_val_key"))
|
41
50
|
|
42
51
|
complex_val = OpenStruct.new(a: 1, b: 2)
|
43
|
-
expect(brotli_store.write("complex_val_key",
|
52
|
+
expect(brotli_store.write("complex_val_key", complex_val).class).to eq(standard_cache.write("complex_val_key", complex_val).class)
|
44
53
|
expect(brotli_store.read("complex_val_key")).to eq(standard_cache.read("complex_val_key"))
|
45
54
|
end
|
46
55
|
|
@@ -55,15 +64,14 @@ describe RailsBrotliCache do
|
|
55
64
|
end
|
56
65
|
|
57
66
|
it "for #fetch" do
|
58
|
-
|
59
|
-
expect(brotli_store.fetch("val_key") {
|
60
|
-
expect(brotli_store.fetch("val_key", force: true) { val }).to eq(standard_cache.fetch("val_key", force: true) { val })
|
67
|
+
expect(brotli_store.fetch("val_key") { big_enough_to_compress_value }).to eq(standard_cache.fetch("val_key") { big_enough_to_compress_value })
|
68
|
+
expect(brotli_store.fetch("val_key", force: true) { big_enough_to_compress_value }).to eq(standard_cache.fetch("val_key", force: true) { big_enough_to_compress_value })
|
61
69
|
expect(brotli_store.fetch("val_key")).to eq(standard_cache.fetch("val_key"))
|
62
70
|
end
|
63
71
|
|
64
72
|
it "for #write_multi and #read_multi" do
|
65
73
|
values = {
|
66
|
-
"key_1" =>
|
74
|
+
"key_1" => big_enough_to_compress_value,
|
67
75
|
"key_2" => "val_2"
|
68
76
|
}
|
69
77
|
|
@@ -78,16 +86,16 @@ describe RailsBrotliCache do
|
|
78
86
|
|
79
87
|
it "for #fetch_multi" do
|
80
88
|
values = {
|
81
|
-
"key_1" =>
|
89
|
+
"key_1" => big_enough_to_compress_value,
|
82
90
|
"key_2" => "val_2"
|
83
91
|
}
|
84
92
|
|
85
93
|
brotli_store.fetch_multi("key_1", "key_2") do |key|
|
86
|
-
|
94
|
+
values[key]
|
87
95
|
end
|
88
96
|
|
89
97
|
standard_cache.fetch_multi("key_1", "key_2") do |key|
|
90
|
-
|
98
|
+
values[key]
|
91
99
|
end
|
92
100
|
|
93
101
|
expect(brotli_store.read("key_1")).to eq standard_cache.read("key_1")
|
@@ -98,9 +98,55 @@ describe RailsBrotliCache do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
describe "fetch_multi" do
|
101
|
-
|
102
|
-
cache_store.fetch_multi(
|
103
|
-
|
101
|
+
subject do
|
102
|
+
cache_store.fetch_multi(*keys) do |key|
|
103
|
+
big_enough_to_compress_value + key
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
let(:keys) { %w[key_1 key_2] }
|
108
|
+
let(:response) do
|
109
|
+
{
|
110
|
+
'key_1' => big_enough_to_compress_value + 'key_1',
|
111
|
+
'key_2' => big_enough_to_compress_value + 'key_2'
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works for store and reread" do
|
116
|
+
expect(subject).to eq response
|
117
|
+
|
118
|
+
expect(cache_store.fetch_multi(*keys) do |key|
|
119
|
+
big_enough_to_compress_value + key
|
120
|
+
end).to eq response
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'with a complex object that responds to #cache_key' do
|
124
|
+
subject do
|
125
|
+
cache_store.fetch_multi(*keys) do |key|
|
126
|
+
big_enough_to_compress_value + key.id
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
let(:keys) do
|
131
|
+
[
|
132
|
+
OpenStruct.new(cache_key: 'key_1', id: '12345'),
|
133
|
+
OpenStruct.new(cache_key: 'key_2', id: '54321')
|
134
|
+
]
|
135
|
+
end
|
136
|
+
let(:response) do
|
137
|
+
{
|
138
|
+
keys[0] => big_enough_to_compress_value + '12345',
|
139
|
+
keys[1] => big_enough_to_compress_value + '54321'
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
it "works for store and reread" do
|
144
|
+
expect(subject).to eq response
|
145
|
+
|
146
|
+
expect(cache_store.fetch_multi(*keys) do |key|
|
147
|
+
big_enough_to_compress_value + key.id
|
148
|
+
end).to eq response
|
149
|
+
end
|
104
150
|
end
|
105
151
|
end
|
106
152
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-brotli-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -226,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
226
226
|
- !ruby/object:Gem::Version
|
227
227
|
version: '0'
|
228
228
|
requirements: []
|
229
|
-
rubygems_version: 3.
|
229
|
+
rubygems_version: 3.3.7
|
230
230
|
signing_key:
|
231
231
|
specification_version: 4
|
232
232
|
summary: Drop-in enhancement for Rails cache, offering better performance and compression
|