rails-brotli-cache 0.5.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -3
- data/benchmarks/Gemfile +2 -0
- data/benchmarks/main.rb +64 -4
- data/lib/rails-brotli-cache/store.rb +8 -3
- data/lib/rails-brotli-cache/version.rb +1 -1
- data/rails-brotli-cache.gemspec +1 -1
- data/spec/rails-brotli-cache/compatibility_spec.rb +9 -6
- data/spec/rails-brotli-cache/store_spec.rb +15 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e109b9b356474fb8085b1cceaa892695de997465db4c90872556f76419da8f86
|
4
|
+
data.tar.gz: c8422310e9fed160052637a40dc7b1150b938c0f891b3b7b011eacdd11b8e4bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b435443fbf3a6a09baed87b124429e43dcd4d3fbd06cab175a15818b397285aeab96ad0694dc8a2f0e0d91c3478400ba0e3081045e93ca63e275e62626f0e0d
|
7
|
+
data.tar.gz: 95af20d56721fbe1e9118b29a0fa1f7dc3fe5825d8c7ee04d77b783b96b405d6b1e765f4d8b63e8668d9414ca1e716cc010e3201d7a93340d35251ebd966f9e8
|
data/README.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
-
# Rails Brotli Cache [![Gem Version](https://img.shields.io/gem/v/rails-brotli-cache)](https://badge.fury.io/rb/rails-brotli-cache) [![
|
1
|
+
# Rails Brotli Cache [![Gem Version](https://img.shields.io/gem/v/rails-brotli-cache)](https://badge.fury.io/rb/rails-brotli-cache) [![GH Actions](https://github.com/pawurb/rails-brotli-cache/actions/workflows/ci.yml/badge.svg)](https://github.com/pawurb/rails-brotli-cache/actions)
|
2
2
|
|
3
3
|
This gem enables support for compressing Ruby on Rails cache entries using the [Brotli compression algorithm](https://github.com/google/brotli). `RailsBrotliCache::Store` offers better compression and performance compared to the default `Rails.cache` Gzip, regardless of the underlying data store. The gem also allows specifying any custom compression algorithm instead of Brotli.
|
4
4
|
|
5
|
-
|
5
|
+
You can check out [this blog post](https://pawelurbanek.com/rails-brotli-cache) describing the gem in more detail.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
`Gemfile`
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'brotli' # an optional dependency, other compressors are supported
|
13
|
+
gem 'rails-brotli-cache'
|
14
|
+
```
|
6
15
|
|
7
16
|
## Benchmarks
|
8
17
|
|
@@ -130,10 +139,21 @@ config.cache_store = RailsBrotliCache::Store.new(
|
|
130
139
|
```
|
131
140
|
|
132
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
|
+
|
133
153
|
Rails.cache.write('test-key', json, compressor_class: Snappy)
|
134
154
|
```
|
135
155
|
|
136
|
-
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.
|
137
157
|
|
138
158
|
## Testing
|
139
159
|
|
data/benchmarks/Gemfile
CHANGED
data/benchmarks/main.rb
CHANGED
@@ -1,30 +1,62 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
3
|
require 'net/http'
|
4
|
+
require 'brotli'
|
4
5
|
require 'rails-brotli-cache'
|
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
|
5
18
|
|
6
19
|
memory_cache = ActiveSupport::Cache::MemoryStore.new(compress: true) # memory store does not use compression by default
|
7
20
|
brotli_memory_cache = RailsBrotliCache::Store.new(memory_cache)
|
21
|
+
zstd_memory_cache = RailsBrotliCache::Store.new(memory_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
22
|
+
|
8
23
|
redis_cache = ActiveSupport::Cache::RedisCacheStore.new
|
9
24
|
brotli_redis_cache = RailsBrotliCache::Store.new(redis_cache)
|
25
|
+
zstd_redis_cache = RailsBrotliCache::Store.new(redis_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
26
|
+
|
10
27
|
memcached_cache = ActiveSupport::Cache::MemCacheStore.new
|
11
28
|
brotli_memcached_cache = RailsBrotliCache::Store.new(memcached_cache)
|
29
|
+
zstd_memcached_cache = RailsBrotliCache::Store.new(memcached_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
30
|
+
|
12
31
|
file_cache = ActiveSupport::Cache::FileStore.new('/tmp')
|
13
32
|
brotli_file_cache = RailsBrotliCache::Store.new(file_cache)
|
33
|
+
zstd_file_cache = RailsBrotliCache::Store.new(file_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
14
34
|
|
15
35
|
json_uri = URI("https://raw.githubusercontent.com/pawurb/rails-brotli-cache/main/spec/fixtures/sample.json")
|
16
36
|
json = Net::HTTP.get(json_uri)
|
17
37
|
|
18
38
|
puts "Uncompressed JSON size: #{json.size}"
|
19
39
|
redis_cache.write("gz-json", json)
|
20
|
-
gzip_json_size = redis_cache.redis.
|
40
|
+
gzip_json_size = redis_cache.redis.with do |conn|
|
41
|
+
conn.get("gz-json").size
|
42
|
+
end
|
21
43
|
puts "Gzip JSON size: #{gzip_json_size}"
|
22
44
|
brotli_redis_cache.write("json", json)
|
23
|
-
br_json_size = redis_cache.redis.
|
45
|
+
br_json_size = redis_cache.redis.with do |conn|
|
46
|
+
conn.get("br-json").size
|
47
|
+
end
|
24
48
|
puts "Brotli JSON size: #{br_json_size}"
|
25
49
|
puts "~#{((gzip_json_size - br_json_size).to_f / gzip_json_size.to_f * 100).round}% improvment"
|
26
50
|
puts ""
|
27
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
|
+
|
28
60
|
iterations = 100
|
29
61
|
|
30
62
|
Benchmark.bm do |x|
|
@@ -42,6 +74,13 @@ Benchmark.bm do |x|
|
|
42
74
|
end
|
43
75
|
end
|
44
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
|
+
|
45
84
|
x.report("redis_cache") do
|
46
85
|
iterations.times do
|
47
86
|
redis_cache.write("test", json)
|
@@ -56,6 +95,13 @@ Benchmark.bm do |x|
|
|
56
95
|
end
|
57
96
|
end
|
58
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
|
+
|
59
105
|
x.report("memcached_cache") do
|
60
106
|
iterations.times do
|
61
107
|
memcached_cache.write("test", json)
|
@@ -70,6 +116,13 @@ Benchmark.bm do |x|
|
|
70
116
|
end
|
71
117
|
end
|
72
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
|
+
|
73
126
|
x.report("file_cache") do
|
74
127
|
iterations.times do
|
75
128
|
file_cache.write("test", json)
|
@@ -83,4 +136,11 @@ Benchmark.bm do |x|
|
|
83
136
|
brotli_file_cache.read("test")
|
84
137
|
end
|
85
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
|
86
146
|
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_support/cache'
|
4
|
-
|
4
|
+
begin
|
5
|
+
require 'brotli'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
5
8
|
|
6
9
|
module RailsBrotliCache
|
7
10
|
class Store < ::ActiveSupport::Cache::Store
|
@@ -112,8 +115,10 @@ module RailsBrotliCache
|
|
112
115
|
@core_store.fetch_multi(
|
113
116
|
*names, options.merge(compress: false)
|
114
117
|
) do |name|
|
115
|
-
compressed(yield(name), options)
|
116
|
-
end
|
118
|
+
compressed(yield(source_cache_key(name)), options)
|
119
|
+
end.map do |key, val|
|
120
|
+
[source_cache_key(key), uncompressed(val, options)]
|
121
|
+
end.to_h
|
117
122
|
end
|
118
123
|
|
119
124
|
def exist?(name, options = {})
|
data/rails-brotli-cache.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.license = "MIT"
|
18
18
|
gem.add_dependency "activesupport"
|
19
|
-
gem.
|
19
|
+
gem.add_development_dependency "brotli"
|
20
20
|
gem.add_development_dependency "rspec"
|
21
21
|
gem.add_development_dependency "railties"
|
22
22
|
gem.add_development_dependency "activemodel"
|
@@ -13,6 +13,10 @@ 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
|
+
|
16
20
|
describe "Brotli cache has the same API as #{cache_store_types[0].class}" do
|
17
21
|
subject(:brotli_store) do
|
18
22
|
RailsBrotliCache::Store.new(cache_store_types[0])
|
@@ -32,7 +36,7 @@ describe RailsBrotliCache do
|
|
32
36
|
|
33
37
|
it "for #read and #write" do
|
34
38
|
int_val = 123
|
35
|
-
expect(brotli_store.write("int_val_key",
|
39
|
+
expect(brotli_store.write("int_val_key", big_enough_to_compress_value).class).to eq(standard_cache.write("int_val_key", big_enough_to_compress_value).class)
|
36
40
|
expect(brotli_store.read("int_val_key")).to eq(standard_cache.read("int_val_key"))
|
37
41
|
|
38
42
|
str_val = "str"
|
@@ -55,16 +59,15 @@ describe RailsBrotliCache do
|
|
55
59
|
end
|
56
60
|
|
57
61
|
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 })
|
62
|
+
expect(brotli_store.fetch("val_key") { big_enough_to_compress_value }).to eq(standard_cache.fetch("val_key") { big_enough_to_compress_value })
|
63
|
+
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
64
|
expect(brotli_store.fetch("val_key")).to eq(standard_cache.fetch("val_key"))
|
62
65
|
end
|
63
66
|
|
64
67
|
it "for #write_multi and #read_multi" do
|
65
68
|
values = {
|
66
69
|
"key_1" => "val_1",
|
67
|
-
"key_2" =>
|
70
|
+
"key_2" => big_enough_to_compress_value
|
68
71
|
}
|
69
72
|
|
70
73
|
brotli_store.write_multi(values)
|
@@ -78,7 +81,7 @@ describe RailsBrotliCache do
|
|
78
81
|
|
79
82
|
it "for #fetch_multi" do
|
80
83
|
values = {
|
81
|
-
"key_1" =>
|
84
|
+
"key_1" => big_enough_to_compress_value,
|
82
85
|
"key_2" => "val_2"
|
83
86
|
}
|
84
87
|
|
@@ -98,9 +98,22 @@ describe RailsBrotliCache do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
describe "fetch_multi" do
|
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
|
+
|
101
115
|
it "works" do
|
102
|
-
|
103
|
-
expect(cache_store.read("key_1")).to eq big_enough_to_compress_value
|
116
|
+
expect(subject).to eq response
|
104
117
|
end
|
105
118
|
end
|
106
119
|
|
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.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
|
-
type: :
|
34
|
+
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
@@ -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
|