rails-brotli-cache 0.6.1 → 0.6.3
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/.github/workflows/ci.yml +1 -1
- data/Gemfile +1 -1
- data/README.md +3 -3
- data/Rakefile +2 -2
- data/benchmarks/Gemfile +7 -6
- data/benchmarks/main.rb +112 -11
- data/lib/rails-brotli-cache/store.rb +32 -20
- data/lib/rails-brotli-cache/version.rb +1 -1
- data/lib/rails-brotli-cache.rb +1 -2
- data/rails-brotli-cache.gemspec +13 -12
- data/spec/dummy/Gemfile +0 -1
- data/spec/dummy/config/application.rb +1 -1
- data/spec/dummy/config/environments/production.rb +4 -4
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +1 -1
- data/spec/rails-brotli-cache/cache_keys_spec.rb +4 -4
- data/spec/rails-brotli-cache/compatibility_spec.rb +17 -13
- data/spec/rails-brotli-cache/rails_store_spec.rb +2 -2
- data/spec/rails-brotli-cache/redis_spec.rb +3 -3
- data/spec/rails-brotli-cache/store_spec.rb +90 -5
- data/spec/spec_helper.rb +15 -16
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: deb7fb5b6df5533b08e76b59062ff0a1897822e74031108ae45efbff9612cc74
|
4
|
+
data.tar.gz: cbfe23f39e63c70e30d3ac1dc412d38ab9a0b725e6da63f6a724423774d6c99b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e28ac7994f28b176b086f80ae3bdd163a72c285a094d0b51bfa55ffee4f75b845414ed6a30487db60a158ad2e66795eae17ccde70a5f06cf4a1f84d0d5e7e3c
|
7
|
+
data.tar.gz: 2490251b4a3f29f0121132b18b127c23c152b07a7e187844fb061aacb76e448342de0cf8f1c5b20ea203371c7da9d6dc3b26fe4332f2f862c2bcf8726f316137
|
data/.github/workflows/ci.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -134,7 +134,7 @@ By default gem uses a Brotli compression, but you can customize the algorithm. Y
|
|
134
134
|
```ruby
|
135
135
|
config.cache_store = RailsBrotliCache::Store.new(
|
136
136
|
ActiveSupport::Cache::RedisCacheStore.new,
|
137
|
-
{ compressor_class:
|
137
|
+
{ compressor_class: ZSTDCompressor }
|
138
138
|
)
|
139
139
|
```
|
140
140
|
|
@@ -142,7 +142,7 @@ config.cache_store = RailsBrotliCache::Store.new(
|
|
142
142
|
|
143
143
|
class ZSTDCompressor
|
144
144
|
def self.deflate(payload)
|
145
|
-
::Zstd.compress(payload, 10)
|
145
|
+
::Zstd.compress(payload, level: 10)
|
146
146
|
end
|
147
147
|
|
148
148
|
def self.inflate(payload)
|
@@ -150,7 +150,7 @@ class ZSTDCompressor
|
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
153
|
-
Rails.cache.write('test-key', json, compressor_class:
|
153
|
+
Rails.cache.write('test-key', json, compressor_class: ZSTDCompressor)
|
154
154
|
```
|
155
155
|
|
156
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.
|
data/Rakefile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require
|
2
|
+
require "rspec/core/rake_task"
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
6
|
task default: :spec
|
7
7
|
|
8
|
-
desc
|
8
|
+
desc "Test all cache_stores"
|
9
9
|
task :test_all do
|
10
10
|
system("TEST_RAILS_CACHE_STORE=redis_cache_store bundle exec rspec spec && TEST_RAILS_CACHE_STORE=brotli_cache_store bundle exec rspec spec && TEST_RAILS_CACHE_STORE=mem_cache_store bundle exec rspec spec && bundle exec rspec spec")
|
11
11
|
end
|
data/benchmarks/Gemfile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
|
-
gem
|
4
|
-
gem
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
3
|
+
gem "brotli"
|
4
|
+
gem "rails-brotli-cache", path: "../"
|
5
|
+
gem "redis"
|
6
|
+
gem "dalli"
|
7
|
+
gem "zstd-ruby"
|
8
|
+
gem "lz4-ruby"
|
data/benchmarks/main.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/core_ext/hash"
|
3
|
+
require "net/http"
|
4
|
+
require "brotli"
|
5
|
+
require "rails-brotli-cache"
|
6
|
+
require "benchmark"
|
7
|
+
require "zstd-ruby"
|
8
|
+
require "lz4-ruby"
|
8
9
|
|
9
10
|
class ZSTDCompressor
|
10
11
|
def self.deflate(payload)
|
11
|
-
::Zstd.compress(payload, 10)
|
12
|
+
::Zstd.compress(payload, level: 10)
|
12
13
|
end
|
13
14
|
|
14
15
|
def self.inflate(payload)
|
@@ -16,21 +17,49 @@ class ZSTDCompressor
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
class LZ4Compressor
|
21
|
+
def self.deflate(payload)
|
22
|
+
::LZ4::compress(payload)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.inflate(payload)
|
26
|
+
::LZ4::uncompress(payload)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class LZ4HCCompressor
|
31
|
+
def self.deflate(payload)
|
32
|
+
::LZ4::compressHC(payload)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.inflate(payload)
|
36
|
+
::LZ4::uncompress(payload)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
19
40
|
memory_cache = ActiveSupport::Cache::MemoryStore.new(compress: true) # memory store does not use compression by default
|
20
41
|
brotli_memory_cache = RailsBrotliCache::Store.new(memory_cache)
|
21
42
|
zstd_memory_cache = RailsBrotliCache::Store.new(memory_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
43
|
+
lz4_memory_cache = RailsBrotliCache::Store.new(memory_cache, compressor_class: LZ4Compressor, prefix: "lz4-")
|
44
|
+
lz4hc_memory_cache = RailsBrotliCache::Store.new(memory_cache, compressor_class: LZ4HCCompressor, prefix: "lz4hc-")
|
22
45
|
|
23
46
|
redis_cache = ActiveSupport::Cache::RedisCacheStore.new
|
24
47
|
brotli_redis_cache = RailsBrotliCache::Store.new(redis_cache)
|
25
48
|
zstd_redis_cache = RailsBrotliCache::Store.new(redis_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
49
|
+
lz4_redis_cache = RailsBrotliCache::Store.new(redis_cache, compressor_class: LZ4Compressor, prefix: "lz4-")
|
50
|
+
lz4hc_redis_cache = RailsBrotliCache::Store.new(redis_cache, compressor_class: LZ4HCCompressor, prefix: "lz4hc-")
|
26
51
|
|
27
52
|
memcached_cache = ActiveSupport::Cache::MemCacheStore.new
|
28
53
|
brotli_memcached_cache = RailsBrotliCache::Store.new(memcached_cache)
|
29
54
|
zstd_memcached_cache = RailsBrotliCache::Store.new(memcached_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
55
|
+
lz4_memcached_cache = RailsBrotliCache::Store.new(memcached_cache, compressor_class: LZ4Compressor, prefix: "lz4-")
|
56
|
+
lz4hc_memcached_cache = RailsBrotliCache::Store.new(memcached_cache, compressor_class: LZ4HCCompressor, prefix: "lz4hc-")
|
30
57
|
|
31
|
-
file_cache = ActiveSupport::Cache::FileStore.new(
|
58
|
+
file_cache = ActiveSupport::Cache::FileStore.new("/tmp")
|
32
59
|
brotli_file_cache = RailsBrotliCache::Store.new(file_cache)
|
33
60
|
zstd_file_cache = RailsBrotliCache::Store.new(file_cache, compressor_class: ZSTDCompressor, prefix: "zs-")
|
61
|
+
lz4_file_cache = RailsBrotliCache::Store.new(file_cache, compressor_class: LZ4Compressor, prefix: "lz4-")
|
62
|
+
lz4hc_file_cache = RailsBrotliCache::Store.new(file_cache, compressor_class: LZ4HCCompressor, prefix: "lz4hc-")
|
34
63
|
|
35
64
|
json_uri = URI("https://raw.githubusercontent.com/pawurb/rails-brotli-cache/main/spec/fixtures/sample.json")
|
36
65
|
json = Net::HTTP.get(json_uri)
|
@@ -46,7 +75,7 @@ br_json_size = redis_cache.redis.with do |conn|
|
|
46
75
|
conn.get("br-json").size
|
47
76
|
end
|
48
77
|
puts "Brotli JSON size: #{br_json_size}"
|
49
|
-
puts "~#{((gzip_json_size - br_json_size).to_f / gzip_json_size.to_f * 100).round}%
|
78
|
+
puts "~#{((gzip_json_size - br_json_size).to_f / gzip_json_size.to_f * 100).round}% difference"
|
50
79
|
puts ""
|
51
80
|
|
52
81
|
zstd_redis_cache.write("json", json)
|
@@ -54,7 +83,23 @@ zs_json_size = redis_cache.redis.with do |conn|
|
|
54
83
|
conn.get("zs-json").size
|
55
84
|
end
|
56
85
|
puts "ZSTD JSON size: #{zs_json_size}"
|
57
|
-
puts "~#{((gzip_json_size - zs_json_size).to_f / gzip_json_size.to_f * 100).round}%
|
86
|
+
puts "~#{((gzip_json_size - zs_json_size).to_f / gzip_json_size.to_f * 100).round}% difference"
|
87
|
+
puts ""
|
88
|
+
|
89
|
+
lz4_redis_cache.write("json", json)
|
90
|
+
lz4_json_size = redis_cache.redis.with do |conn|
|
91
|
+
conn.get("lz4-json").size
|
92
|
+
end
|
93
|
+
puts "LZ4 JSON size: #{lz4_json_size}"
|
94
|
+
puts "~#{((gzip_json_size - lz4_json_size).to_f / gzip_json_size.to_f * 100).round}% difference"
|
95
|
+
puts ""
|
96
|
+
|
97
|
+
lz4hc_redis_cache.write("json", json)
|
98
|
+
lz4hc_json_size = redis_cache.redis.with do |conn|
|
99
|
+
conn.get("lz4hc-json").size
|
100
|
+
end
|
101
|
+
puts "LZ4HC JSON size: #{lz4hc_json_size}"
|
102
|
+
puts "~#{((gzip_json_size - lz4hc_json_size).to_f / gzip_json_size.to_f * 100).round}% difference"
|
58
103
|
puts ""
|
59
104
|
|
60
105
|
iterations = 100
|
@@ -81,6 +126,20 @@ Benchmark.bm do |x|
|
|
81
126
|
end
|
82
127
|
end
|
83
128
|
|
129
|
+
x.report("lz4_memory_cache") do
|
130
|
+
iterations.times do
|
131
|
+
lz4_memory_cache.write("test", json)
|
132
|
+
lz4_memory_cache.read("test")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
x.report("lz4hc_memory_cache") do
|
137
|
+
iterations.times do
|
138
|
+
lz4hc_memory_cache.write("test", json)
|
139
|
+
lz4hc_memory_cache.read("test")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
84
143
|
x.report("redis_cache") do
|
85
144
|
iterations.times do
|
86
145
|
redis_cache.write("test", json)
|
@@ -102,6 +161,20 @@ Benchmark.bm do |x|
|
|
102
161
|
end
|
103
162
|
end
|
104
163
|
|
164
|
+
x.report("lz4_redis_cache") do
|
165
|
+
iterations.times do
|
166
|
+
lz4_redis_cache.write("test", json)
|
167
|
+
lz4_redis_cache.read("test")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
x.report("lz4hc_redis_cache") do
|
172
|
+
iterations.times do
|
173
|
+
lz4hc_redis_cache.write("test", json)
|
174
|
+
lz4hc_redis_cache.read("test")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
105
178
|
x.report("memcached_cache") do
|
106
179
|
iterations.times do
|
107
180
|
memcached_cache.write("test", json)
|
@@ -123,6 +196,20 @@ Benchmark.bm do |x|
|
|
123
196
|
end
|
124
197
|
end
|
125
198
|
|
199
|
+
x.report("lz4_memcached_cache") do
|
200
|
+
iterations.times do
|
201
|
+
lz4_memcached_cache.write("test", json)
|
202
|
+
lz4_memcached_cache.read("test")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
x.report("lz4hc_memcached_cache") do
|
207
|
+
iterations.times do
|
208
|
+
lz4hc_memcached_cache.write("test", json)
|
209
|
+
lz4hc_memcached_cache.read("test")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
126
213
|
x.report("file_cache") do
|
127
214
|
iterations.times do
|
128
215
|
file_cache.write("test", json)
|
@@ -143,4 +230,18 @@ Benchmark.bm do |x|
|
|
143
230
|
zstd_file_cache.read("test")
|
144
231
|
end
|
145
232
|
end
|
233
|
+
|
234
|
+
x.report("lz4_file_cache") do
|
235
|
+
iterations.times do
|
236
|
+
lz4_file_cache.write("test", json)
|
237
|
+
lz4_file_cache.read("test")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
x.report("lz4hc_file_cache") do
|
242
|
+
iterations.times do
|
243
|
+
lz4hc_file_cache.write("test", json)
|
244
|
+
lz4hc_file_cache.read("test")
|
245
|
+
end
|
246
|
+
end
|
146
247
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/cache"
|
4
4
|
begin
|
5
|
-
require
|
5
|
+
require "brotli"
|
6
6
|
rescue LoadError
|
7
7
|
end
|
8
8
|
|
@@ -25,7 +25,7 @@ module RailsBrotliCache
|
|
25
25
|
DEFAULT_OPTIONS = {
|
26
26
|
compress_threshold: COMPRESS_THRESHOLD,
|
27
27
|
compress: true,
|
28
|
-
compressor_class: BrotliCompressor
|
28
|
+
compressor_class: BrotliCompressor,
|
29
29
|
}
|
30
30
|
|
31
31
|
attr_reader :core_store
|
@@ -33,10 +33,10 @@ module RailsBrotliCache
|
|
33
33
|
def initialize(core_store, options = {})
|
34
34
|
@core_store = core_store
|
35
35
|
@prefix = if options.key?(:prefix)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
options.fetch(:prefix)
|
37
|
+
else
|
38
|
+
"br-"
|
39
|
+
end
|
40
40
|
|
41
41
|
@init_options = options.reverse_merge(DEFAULT_OPTIONS)
|
42
42
|
end
|
@@ -87,9 +87,9 @@ module RailsBrotliCache
|
|
87
87
|
new_hash = hash.map do |key, val|
|
88
88
|
[
|
89
89
|
expanded_cache_key(key),
|
90
|
-
compressed(val, options)
|
90
|
+
compressed(val, options),
|
91
91
|
]
|
92
|
-
end
|
92
|
+
end.to_h
|
93
93
|
|
94
94
|
@core_store.write_multi(
|
95
95
|
new_hash,
|
@@ -107,18 +107,30 @@ module RailsBrotliCache
|
|
107
107
|
end.to_h
|
108
108
|
end
|
109
109
|
|
110
|
+
def delete_multi(names, options = nil)
|
111
|
+
options = (options || {}).reverse_merge(@init_options)
|
112
|
+
names = names.map { |name| expanded_cache_key(name) }
|
113
|
+
|
114
|
+
core_store.delete_multi(names, options)
|
115
|
+
end
|
116
|
+
|
110
117
|
def fetch_multi(*names)
|
111
118
|
options = names.extract_options!
|
112
|
-
|
119
|
+
expanded_names = names.map { |name| expanded_cache_key(name) }
|
113
120
|
options = options.reverse_merge(@init_options)
|
114
121
|
|
115
|
-
|
116
|
-
|
117
|
-
) do |name|
|
118
|
-
compressed(yield(source_cache_key(name)), options)
|
119
|
-
end.map do |key, val|
|
122
|
+
reads = core_store.send(:read_multi_entries, expanded_names, **options)
|
123
|
+
reads.map do |key, val|
|
120
124
|
[source_cache_key(key), uncompressed(val, options)]
|
121
125
|
end.to_h
|
126
|
+
|
127
|
+
writes = {}
|
128
|
+
ordered = names.index_with do |name|
|
129
|
+
reads.fetch(name) { writes[name] = yield(name) }
|
130
|
+
end
|
131
|
+
|
132
|
+
write_multi(writes)
|
133
|
+
ordered
|
122
134
|
end
|
123
135
|
|
124
136
|
def exist?(name, options = {})
|
@@ -170,11 +182,11 @@ module RailsBrotliCache
|
|
170
182
|
return payload if payload.is_a?(Integer)
|
171
183
|
|
172
184
|
serialized = if payload.start_with?(MARK_BR_COMPRESSED)
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
185
|
+
compressor = options.fetch(:compressor_class)
|
186
|
+
compressor.inflate(payload.byteslice(1..-1))
|
187
|
+
else
|
188
|
+
payload
|
189
|
+
end
|
178
190
|
|
179
191
|
Marshal.load(serialized)
|
180
192
|
end
|
data/lib/rails-brotli-cache.rb
CHANGED
data/rails-brotli-cache.gemspec
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "rails-brotli-cache/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name
|
8
|
-
gem.version
|
9
|
-
gem.authors
|
10
|
-
gem.email
|
11
|
-
gem.summary
|
12
|
-
gem.description
|
13
|
-
gem.homepage
|
14
|
-
gem.files
|
15
|
-
gem.test_files
|
7
|
+
gem.name = "rails-brotli-cache"
|
8
|
+
gem.version = RailsBrotliCache::VERSION
|
9
|
+
gem.authors = ["pawurb"]
|
10
|
+
gem.email = ["contact@pawelurbanek.com"]
|
11
|
+
gem.summary = %q{ Drop-in enhancement for Rails cache, offering better performance and compression with Brotli algorithm. }
|
12
|
+
gem.description = %q{ This gem reduces storage needed for Rails cache by using Brotli compression, which can produce outputs smaller by ~20% and offers better performance than Gzip. }
|
13
|
+
gem.homepage = "https://github.com/pawurb/rails-brotli-cache"
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = gem.files.grep(%r{^(spec)/})
|
16
16
|
gem.require_paths = ["lib"]
|
17
|
-
gem.license
|
17
|
+
gem.license = "MIT"
|
18
18
|
gem.add_dependency "activesupport"
|
19
19
|
gem.add_development_dependency "brotli"
|
20
20
|
gem.add_development_dependency "rspec"
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |gem|
|
|
24
24
|
gem.add_development_dependency "redis"
|
25
25
|
gem.add_development_dependency "dalli"
|
26
26
|
gem.add_development_dependency "byebug"
|
27
|
+
gem.add_development_dependency "rufo"
|
27
28
|
|
28
29
|
if gem.respond_to?(:metadata=)
|
29
30
|
gem.metadata = { "rubygems_mfa_required" => "true" }
|
data/spec/dummy/Gemfile
CHANGED
@@ -22,7 +22,7 @@ module Dummy
|
|
22
22
|
class Application < Rails::Application
|
23
23
|
# Initialize configuration defaults for originally generated Rails version.
|
24
24
|
|
25
|
-
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(
|
25
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
|
26
26
|
config.load_defaults 7.0
|
27
27
|
else
|
28
28
|
config.load_defaults 6.0
|
@@ -13,7 +13,7 @@ Rails.application.configure do
|
|
13
13
|
config.eager_load = true
|
14
14
|
|
15
15
|
# Full error reports are disabled and caching is turned on.
|
16
|
-
config.consider_all_requests_local
|
16
|
+
config.consider_all_requests_local = false
|
17
17
|
config.action_controller.perform_caching = true
|
18
18
|
|
19
19
|
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
@@ -45,7 +45,7 @@ Rails.application.configure do
|
|
45
45
|
config.log_level = :info
|
46
46
|
|
47
47
|
# Prepend all log lines with the following tags.
|
48
|
-
config.log_tags = [
|
48
|
+
config.log_tags = [:request_id]
|
49
49
|
|
50
50
|
# Use a different cache store in production.
|
51
51
|
# config.cache_store = :mem_cache_store
|
@@ -65,8 +65,8 @@ Rails.application.configure do
|
|
65
65
|
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
|
66
66
|
|
67
67
|
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
68
|
-
logger
|
68
|
+
logger = ActiveSupport::Logger.new(STDOUT)
|
69
69
|
logger.formatter = config.log_formatter
|
70
|
-
config.logger
|
70
|
+
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
71
71
|
end
|
72
72
|
end
|
@@ -19,11 +19,11 @@ Rails.application.configure do
|
|
19
19
|
# Configure public file server for tests with Cache-Control for performance.
|
20
20
|
config.public_file_server.enabled = true
|
21
21
|
config.public_file_server.headers = {
|
22
|
-
"Cache-Control" => "public, max-age=#{1.hour.to_i}"
|
22
|
+
"Cache-Control" => "public, max-age=#{1.hour.to_i}",
|
23
23
|
}
|
24
24
|
|
25
25
|
# Show full error reports and disable caching.
|
26
|
-
config.consider_all_requests_local
|
26
|
+
config.consider_all_requests_local = true
|
27
27
|
config.action_controller.perform_caching = false
|
28
28
|
config.cache_store = :null_store
|
29
29
|
|
@@ -4,5 +4,5 @@
|
|
4
4
|
# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
|
5
5
|
# notations and behaviors.
|
6
6
|
Rails.application.config.filter_parameters += [
|
7
|
-
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
7
|
+
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn,
|
8
8
|
]
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
|
-
return unless ENV[
|
5
|
+
return unless ENV["TEST_RAILS_CACHE_STORE"] == "redis_cache_store"
|
6
6
|
|
7
7
|
describe RailsBrotliCache do
|
8
8
|
class Post
|
@@ -60,7 +60,7 @@ describe RailsBrotliCache do
|
|
60
60
|
context "custom namespace string is not duplicated" do
|
61
61
|
let(:options) do
|
62
62
|
{
|
63
|
-
namespace: "myapp"
|
63
|
+
namespace: "myapp",
|
64
64
|
}
|
65
65
|
end
|
66
66
|
|
@@ -77,7 +77,7 @@ describe RailsBrotliCache do
|
|
77
77
|
context "custom namespace proc" do
|
78
78
|
let(:options) do
|
79
79
|
{
|
80
|
-
namespace: -> { "myapp" }
|
80
|
+
namespace: -> { "myapp" },
|
81
81
|
}
|
82
82
|
end
|
83
83
|
|
@@ -1,15 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RailsBrotliCache do
|
6
|
-
|
7
6
|
CACHE_STORE_TYPES = [
|
8
7
|
[ActiveSupport::Cache::MemoryStore.new, ActiveSupport::Cache::MemoryStore.new],
|
9
8
|
[ActiveSupport::Cache::RedisCacheStore.new, ActiveSupport::Cache::RedisCacheStore.new],
|
10
9
|
[ActiveSupport::Cache::MemCacheStore.new, ActiveSupport::Cache::MemCacheStore.new],
|
11
|
-
[ActiveSupport::Cache::FileStore.new(
|
12
|
-
[ActiveSupport::Cache::NullStore.new, ActiveSupport::Cache::NullStore.new]
|
10
|
+
[ActiveSupport::Cache::FileStore.new("./tmp"), ActiveSupport::Cache::FileStore.new("./tmp")],
|
11
|
+
[ActiveSupport::Cache::NullStore.new, ActiveSupport::Cache::NullStore.new],
|
13
12
|
]
|
14
13
|
|
15
14
|
CACHE_STORE_TYPES.each do |cache_store_types|
|
@@ -17,6 +16,11 @@ describe RailsBrotliCache do
|
|
17
16
|
SecureRandom.hex(2048)
|
18
17
|
end
|
19
18
|
|
19
|
+
after do
|
20
|
+
brotli_store.clear
|
21
|
+
standard_cache.clear
|
22
|
+
end
|
23
|
+
|
20
24
|
describe "Brotli cache has the same API as #{cache_store_types[0].class}" do
|
21
25
|
subject(:brotli_store) do
|
22
26
|
RailsBrotliCache::Store.new(cache_store_types[0])
|
@@ -36,15 +40,15 @@ describe RailsBrotliCache do
|
|
36
40
|
|
37
41
|
it "for #read and #write" do
|
38
42
|
int_val = 123
|
39
|
-
expect(brotli_store.write("int_val_key",
|
43
|
+
expect(brotli_store.write("int_val_key", int_val).class).to eq(standard_cache.write("int_val_key", int_val).class)
|
40
44
|
expect(brotli_store.read("int_val_key")).to eq(standard_cache.read("int_val_key"))
|
41
45
|
|
42
|
-
str_val =
|
43
|
-
expect(brotli_store.write("str_val_key",
|
46
|
+
str_val = big_enough_to_compress_value
|
47
|
+
expect(brotli_store.write("str_val_key", str_val).class).to eq(standard_cache.write("str_val_key", str_val).class)
|
44
48
|
expect(brotli_store.read("str_val_key")).to eq(standard_cache.read("str_val_key"))
|
45
49
|
|
46
50
|
complex_val = OpenStruct.new(a: 1, b: 2)
|
47
|
-
expect(brotli_store.write("complex_val_key",
|
51
|
+
expect(brotli_store.write("complex_val_key", complex_val).class).to eq(standard_cache.write("complex_val_key", complex_val).class)
|
48
52
|
expect(brotli_store.read("complex_val_key")).to eq(standard_cache.read("complex_val_key"))
|
49
53
|
end
|
50
54
|
|
@@ -66,8 +70,8 @@ describe RailsBrotliCache do
|
|
66
70
|
|
67
71
|
it "for #write_multi and #read_multi" do
|
68
72
|
values = {
|
69
|
-
"key_1" =>
|
70
|
-
"key_2" =>
|
73
|
+
"key_1" => big_enough_to_compress_value,
|
74
|
+
"key_2" => "val_2",
|
71
75
|
}
|
72
76
|
|
73
77
|
brotli_store.write_multi(values)
|
@@ -82,15 +86,15 @@ describe RailsBrotliCache do
|
|
82
86
|
it "for #fetch_multi" do
|
83
87
|
values = {
|
84
88
|
"key_1" => big_enough_to_compress_value,
|
85
|
-
"key_2" => "val_2"
|
89
|
+
"key_2" => "val_2",
|
86
90
|
}
|
87
91
|
|
88
92
|
brotli_store.fetch_multi("key_1", "key_2") do |key|
|
89
|
-
|
93
|
+
values[key]
|
90
94
|
end
|
91
95
|
|
92
96
|
standard_cache.fetch_multi("key_1", "key_2") do |key|
|
93
|
-
|
97
|
+
values[key]
|
94
98
|
end
|
95
99
|
|
96
100
|
expect(brotli_store.read("key_1")).to eq standard_cache.read("key_1")
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
|
-
return unless ENV[
|
5
|
+
return unless ENV["TEST_RAILS_CACHE_STORE"] == "brotli_cache_store"
|
6
6
|
|
7
7
|
describe RailsBrotliCache do
|
8
8
|
subject(:cache_store) do
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
|
-
return unless ENV[
|
5
|
+
return unless ENV["TEST_RAILS_CACHE_STORE"] == "redis_cache_store"
|
6
6
|
|
7
7
|
describe RailsBrotliCache do
|
8
8
|
let(:options) do
|
@@ -24,7 +24,7 @@ describe RailsBrotliCache do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
let(:json) do
|
27
|
-
File.read(
|
27
|
+
File.read("spec/fixtures/sample.json")
|
28
28
|
end
|
29
29
|
|
30
30
|
it "applies more efficient brotli compression" do
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "spec_helper"
|
4
4
|
|
5
5
|
describe RailsBrotliCache do
|
6
6
|
subject(:cache_store) do
|
@@ -89,7 +89,7 @@ describe RailsBrotliCache do
|
|
89
89
|
it "works" do
|
90
90
|
values = {
|
91
91
|
"key_1" => big_enough_to_compress_value,
|
92
|
-
"key_2" => 123
|
92
|
+
"key_2" => 123,
|
93
93
|
}
|
94
94
|
|
95
95
|
cache_store.write_multi(values, expires_in: 5.seconds)
|
@@ -107,13 +107,46 @@ describe RailsBrotliCache do
|
|
107
107
|
let(:keys) { %w[key_1 key_2] }
|
108
108
|
let(:response) do
|
109
109
|
{
|
110
|
-
|
111
|
-
|
110
|
+
"key_1" => big_enough_to_compress_value + "key_1",
|
111
|
+
"key_2" => big_enough_to_compress_value + "key_2",
|
112
112
|
}
|
113
113
|
end
|
114
114
|
|
115
|
-
it "works" do
|
115
|
+
it "works for store and reread" do
|
116
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
|
117
150
|
end
|
118
151
|
end
|
119
152
|
|
@@ -225,6 +258,58 @@ describe RailsBrotliCache do
|
|
225
258
|
end
|
226
259
|
end
|
227
260
|
|
261
|
+
describe "#delete_multi" do
|
262
|
+
it "removes multiple previously stored cache entries" do
|
263
|
+
cache_store.write("key_1", 1234)
|
264
|
+
cache_store.write("key_2", 5678)
|
265
|
+
cache_store.write("key_3", 9012)
|
266
|
+
|
267
|
+
expect(cache_store.read("key_1")).to eq 1234
|
268
|
+
expect(cache_store.read("key_2")).to eq 5678
|
269
|
+
expect(cache_store.read("key_3")).to eq 9012
|
270
|
+
|
271
|
+
cache_store.delete_multi(["key_1", "key_3"])
|
272
|
+
|
273
|
+
expect(cache_store.read("key_1")).to eq nil
|
274
|
+
expect(cache_store.read("key_2")).to eq 5678
|
275
|
+
expect(cache_store.read("key_3")).to eq nil
|
276
|
+
end
|
277
|
+
|
278
|
+
it "works with complex objects as cache keys" do
|
279
|
+
collection1 = [Post.new(id: 1), Post.new(id: 2)]
|
280
|
+
collection2 = [Post.new(id: 3), Post.new(id: 4)]
|
281
|
+
|
282
|
+
cache_store.write([:views, "controller/action", collection1], "fragment1")
|
283
|
+
cache_store.write([:views, "controller/action", collection2], "fragment2")
|
284
|
+
|
285
|
+
expect(cache_store.read([:views, "controller/action", collection1])).to eq "fragment1"
|
286
|
+
expect(cache_store.read([:views, "controller/action", collection2])).to eq "fragment2"
|
287
|
+
|
288
|
+
cache_store.delete_multi([[:views, "controller/action", collection1]])
|
289
|
+
|
290
|
+
expect(cache_store.read([:views, "controller/action", collection1])).to eq nil
|
291
|
+
expect(cache_store.read([:views, "controller/action", collection2])).to eq "fragment2"
|
292
|
+
end
|
293
|
+
|
294
|
+
it "handles empty array gracefully" do
|
295
|
+
cache_store.write("test-key", 1234)
|
296
|
+
expect(cache_store.read("test-key")).to eq 1234
|
297
|
+
|
298
|
+
cache_store.delete_multi([])
|
299
|
+
|
300
|
+
expect(cache_store.read("test-key")).to eq 1234
|
301
|
+
end
|
302
|
+
|
303
|
+
it "handles non-existent keys gracefully" do
|
304
|
+
cache_store.write("existing-key", 1234)
|
305
|
+
expect(cache_store.read("existing-key")).to eq 1234
|
306
|
+
|
307
|
+
cache_store.delete_multi(["existing-key", "non-existent-key"])
|
308
|
+
|
309
|
+
expect(cache_store.read("existing-key")).to eq nil
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
228
313
|
describe "#clear" do
|
229
314
|
it "clears the cache" do
|
230
315
|
expect(cache_store.write("test-key", 1234))
|
data/spec/spec_helper.rb
CHANGED
@@ -1,28 +1,27 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
3
|
require "active_support"
|
4
4
|
require "active_support/core_ext/hash"
|
5
|
-
require
|
5
|
+
require "redis"
|
6
6
|
|
7
|
-
require_relative
|
7
|
+
require_relative "../lib/rails-brotli-cache"
|
8
8
|
|
9
9
|
$redis = Redis.new
|
10
|
-
$test_rails_cache_store = if ENV[
|
11
|
-
|
12
|
-
elsif ENV[
|
13
|
-
|
14
|
-
elsif ENV[
|
15
|
-
|
16
|
-
else
|
17
|
-
|
18
|
-
end
|
10
|
+
$test_rails_cache_store = if ENV["TEST_RAILS_CACHE_STORE"] == "redis_cache_store"
|
11
|
+
ActiveSupport::Cache::RedisCacheStore.new(redis: $redis)
|
12
|
+
elsif ENV["TEST_RAILS_CACHE_STORE"] == "brotli_cache_store"
|
13
|
+
RailsBrotliCache::Store.new(ActiveSupport::Cache::MemoryStore.new)
|
14
|
+
elsif ENV["TEST_RAILS_CACHE_STORE"] == "memcache_cache_store"
|
15
|
+
ActiveSupport::Cache::ActiveSupport::Cache::MemCacheStore.new
|
16
|
+
else
|
17
|
+
ActiveSupport::Cache::MemoryStore.new
|
18
|
+
end
|
19
19
|
|
20
|
-
require_relative
|
21
|
-
ENV[
|
20
|
+
require_relative "../spec/dummy/config/environment"
|
21
|
+
ENV["RAILS_ROOT"] ||= "#{File.dirname(__FILE__)}../../../spec/dummy"
|
22
22
|
|
23
23
|
RSpec.configure do |config|
|
24
24
|
config.before(:each) do
|
25
25
|
Rails.cache.clear
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pawurb
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -136,6 +136,20 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rufo
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
139
153
|
description: " This gem reduces storage needed for Rails cache by using Brotli compression,
|
140
154
|
which can produce outputs smaller by ~20% and offers better performance than Gzip. "
|
141
155
|
email:
|
@@ -226,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
226
240
|
- !ruby/object:Gem::Version
|
227
241
|
version: '0'
|
228
242
|
requirements: []
|
229
|
-
rubygems_version: 3.
|
243
|
+
rubygems_version: 3.5.16
|
230
244
|
signing_key:
|
231
245
|
specification_version: 4
|
232
246
|
summary: Drop-in enhancement for Rails cache, offering better performance and compression
|