rails-brotli-cache 0.6.0 → 0.6.2
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/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
|