rails-brotli-cache 0.1.1 → 0.2.1
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 +60 -34
- data/Rakefile +1 -1
- data/benchmarks/Gemfile +3 -0
- data/benchmarks/main.rb +69 -0
- data/lib/rails-brotli-cache/store.rb +87 -0
- data/lib/rails-brotli-cache/version.rb +1 -1
- data/lib/rails-brotli-cache.rb +1 -67
- data/spec/dummy/config/environments/development.rb +1 -1
- data/spec/rails-brotli-cache/rails_store_spec.rb +76 -0
- data/spec/rails-brotli-cache/redis_spec.rb +17 -15
- data/spec/rails-brotli-cache/store_spec.rb +78 -0
- data/spec/spec_helper.rb +11 -0
- metadata +9 -4
- data/spec/rails-brotli-cache/common_spec.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44c290253b45a487daecc85b45026bd7d3f140644095c7f15cfb67dcee1299b8
|
4
|
+
data.tar.gz: d37323f26dda367401403714eb094dfd6a0e3274b7d88cb81eb2d5380ce91979
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 537fe80385fbc5f8d339b56f789aaeefc45e2589456e2fb522afdcb3656af6254951654a0524b5136ad3cb25fb23c092ea6bc027377c39ed9f52645886a94fc0
|
7
|
+
data.tar.gz: a6057e3a65505003ee849e9f0a38a12963a78468c32275ca0e1d70ead490ddba7663d2a1d65b4359ca7fb626b27e15fc8552d77d9d35c353bbdf77f933fdb277
|
data/README.md
CHANGED
@@ -1,78 +1,104 @@
|
|
1
1
|
# Rails Brotli Cache [](https://badge.fury.io/rb/rails-brotli-cache) [](https://circleci.com/gh/pawurb/rails-brotli-cache)
|
2
2
|
|
3
|
-
This gem enables support for compressing Ruby on Rails cache entries using the [Brotli compression algorithm](https://github.com/google/brotli).
|
3
|
+
This gem enables support for compressing Ruby on Rails cache entries using the [Brotli compression algorithm](https://github.com/google/brotli). `RailsBrotliCache` offers better compression and faster performance compared to the default `Rails.cache` regardless of the underlying data store.
|
4
4
|
|
5
5
|
**The gem is currently in an early stage of development. Ideas on how to improve it and PRs are welcome.**
|
6
6
|
|
7
7
|
## Benchmarks
|
8
8
|
|
9
|
-
Brotli
|
9
|
+
Brotli cache works as a proxy layer wrapping the underlying cache data store.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
redis_cache = ActiveSupport::Cache::RedisCacheStore.new
|
13
|
+
brotli_redis_cache = RailsBrotliCache::Store.new(redis_cache)
|
14
|
+
```
|
10
15
|
|
11
16
|
**~25%** better compression of a sample JSON object:
|
12
17
|
|
13
18
|
```ruby
|
14
|
-
json = File.read("sample.json") # sample
|
15
|
-
json.size # =>
|
16
|
-
|
17
|
-
|
19
|
+
json = File.read("sample.json") # sample 435kb JSON text
|
20
|
+
json.size # => 435662
|
21
|
+
redis_cache.write("json", json)
|
22
|
+
brotli_redis_cache.write("json", json)
|
18
23
|
|
19
24
|
## Check the size of cache entry stored in Redis
|
20
|
-
$redis.get("json").size # =>
|
21
|
-
$redis.get("br-json").size # =>
|
25
|
+
$redis.get("json").size # => 31698
|
26
|
+
$redis.get("br-json").size # => 24058
|
22
27
|
```
|
23
28
|
|
24
29
|
**~20%** better compression of a sample ActiveRecord objects array:
|
25
30
|
|
26
31
|
```ruby
|
27
32
|
users = User.limit(100).to_a # 100 ActiveRecord objects
|
28
|
-
|
29
|
-
|
33
|
+
redis_cache.write("users", users)
|
34
|
+
brotli_redis_cache.write("users", users)
|
30
35
|
$redis.get("users").size # => 12331
|
31
36
|
$redis.get("br-users").size # => 10299
|
32
37
|
```
|
33
38
|
|
34
|
-
|
35
39
|
**~25%** faster performance for reading/writing a larger JSON file:
|
36
40
|
|
37
41
|
```ruby
|
38
|
-
json = File.read("sample.json") # sample
|
42
|
+
json = File.read("sample.json") # sample ~1mb JSON text
|
39
43
|
|
40
44
|
Benchmark.bm do |x|
|
41
|
-
x.report("
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
x.report("redis_cache") do
|
46
|
+
100.times do
|
47
|
+
redis_cache.write("test", json)
|
48
|
+
redis_cache.read("test")
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
|
-
x.report("
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
x.report("brotli_redis_cache") do
|
53
|
+
100.times do
|
54
|
+
brotli_redis_cache.write("test", json)
|
55
|
+
brotli_redis_cache.read("test")
|
52
56
|
end
|
53
57
|
end
|
58
|
+
|
59
|
+
# ...
|
54
60
|
end
|
55
61
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
62
|
+
# memory_cache 2.081221 0.051615 2.132836 ( 2.132877)
|
63
|
+
# brotli_memory_cache 1.134411 0.032996 1.167407 ( 1.167418)
|
64
|
+
# redis_cache 1.782225 0.049936 1.832161 ( 2.523317)
|
65
|
+
# brotli_redis_cache 1.218365 0.051084 1.269449 ( 1.850894)
|
66
|
+
# memcached_cache 1.766268 0.045351 1.811619 ( 2.504233)
|
67
|
+
# brotli_memcached_cache 1.194646 0.051750 1.246396 ( 1.752982)
|
68
|
+
```
|
69
|
+
|
70
|
+
Regardless of the underlying data store, Brotli cache offers between 20%-40% performance improvment.
|
71
|
+
|
72
|
+
You can run the benchmarks yourself by executing:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
cp docker-compose.yml.sample docker-compose.yml
|
76
|
+
docker compose up -d
|
77
|
+
cd benchmarks
|
78
|
+
bundle install
|
79
|
+
ruby main.rb
|
59
80
|
```
|
60
81
|
|
61
|
-
##
|
82
|
+
## Configuration
|
62
83
|
|
63
|
-
|
84
|
+
Gem works as a drop-in replacement for a standard Rails cache store. Here's how you can configure it with different store types:
|
64
85
|
|
65
|
-
|
86
|
+
```ruby
|
87
|
+
config.cache_store = RailsBrotliCache::Store.new(
|
88
|
+
ActiveSupport::Cache::RedisCacheStore.new(redis: $redis)
|
89
|
+
)
|
90
|
+
```
|
66
91
|
|
67
92
|
```ruby
|
68
|
-
RailsBrotliCache.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
RailsBrotliCache.write("test-key", 123, expires_in: 5.seconds)
|
73
|
-
sleep 6
|
74
|
-
RailsBrotliCache.read("test-key") # => nil
|
93
|
+
config.cache_store = RailsBrotliCache::Store.new(
|
94
|
+
ActiveSupport::Cache::MemoryStore.new
|
95
|
+
)
|
96
|
+
```
|
75
97
|
|
98
|
+
```ruby
|
99
|
+
config.cache_store = RailsBrotliCache::Store.new(
|
100
|
+
ActiveSupport::Cache::MemCacheStore.new("localhost:11211")
|
101
|
+
)
|
76
102
|
```
|
77
103
|
|
78
104
|
Gem appends `br-` to the cache key names to prevent conflicts with previously saved cache entries. You can disable this behaviour by adding the following initializer file:
|
@@ -80,7 +106,7 @@ Gem appends `br-` to the cache key names to prevent conflicts with previously sa
|
|
80
106
|
`app/config/initializers/rails-brotli-cache.rb`
|
81
107
|
|
82
108
|
```ruby
|
83
|
-
|
109
|
+
Rails.cache.disable_prefix!
|
84
110
|
```
|
85
111
|
|
86
112
|
## Testing
|
data/Rakefile
CHANGED
@@ -7,5 +7,5 @@ task default: :spec
|
|
7
7
|
|
8
8
|
desc 'Test all cache_stores'
|
9
9
|
task :test_all do
|
10
|
-
system("
|
10
|
+
system("RAILS_CACHE_STORE=redis_cache_store bundle exec rspec spec/ && RAILS_CACHE_STORE=brotli_cache_store bundle exec rspec spec/ && bundle exec rspec spec/")
|
11
11
|
end
|
data/benchmarks/Gemfile
ADDED
data/benchmarks/main.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rails-brotli-cache'
|
4
|
+
|
5
|
+
memory_cache = ActiveSupport::Cache::MemoryStore.new(compress: true) # memory store does not use compression by default
|
6
|
+
brotli_memory_cache = RailsBrotliCache::Store.new(memory_cache)
|
7
|
+
redis_cache = ActiveSupport::Cache::RedisCacheStore.new
|
8
|
+
brotli_redis_cache = RailsBrotliCache::Store.new(redis_cache)
|
9
|
+
memcached_cache = ActiveSupport::Cache::MemCacheStore.new
|
10
|
+
brotli_memcached_cache = RailsBrotliCache::Store.new(memcached_cache)
|
11
|
+
|
12
|
+
json_uri = URI("https://raw.githubusercontent.com/pawurb/rails-brotli-cache/main/spec/fixtures/sample.json")
|
13
|
+
json = Net::HTTP.get(json_uri)
|
14
|
+
|
15
|
+
puts "Uncompressed JSON size: #{json.size}"
|
16
|
+
redis_cache.write("gz-json", json)
|
17
|
+
gzip_json_size = redis_cache.redis.get("gz-json").size
|
18
|
+
puts "Gzip JSON size: #{gzip_json_size}"
|
19
|
+
brotli_redis_cache.write("json", json)
|
20
|
+
br_json_size = redis_cache.redis.get("br-json").size
|
21
|
+
puts "Brotli JSON size: #{br_json_size}"
|
22
|
+
puts "~#{((gzip_json_size - br_json_size).to_f / gzip_json_size.to_f * 100).round}% improvment"
|
23
|
+
puts ""
|
24
|
+
|
25
|
+
iterations = 100
|
26
|
+
|
27
|
+
Benchmark.bm do |x|
|
28
|
+
x.report("memory_cache") do
|
29
|
+
iterations.times do
|
30
|
+
memory_cache.write("test", json)
|
31
|
+
memory_cache.read("test")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report("brotli_memory_cache") do
|
36
|
+
iterations.times do
|
37
|
+
brotli_memory_cache.write("test", json)
|
38
|
+
brotli_memory_cache.read("test")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
x.report("redis_cache") do
|
43
|
+
iterations.times do
|
44
|
+
redis_cache.write("test", json)
|
45
|
+
redis_cache.read("test")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
x.report("brotli_redis_cache") do
|
50
|
+
iterations.times do
|
51
|
+
brotli_redis_cache.write("test", json)
|
52
|
+
brotli_redis_cache.read("test")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
x.report("memcached_cache") do
|
57
|
+
iterations.times do
|
58
|
+
memcached_cache.write("test", json)
|
59
|
+
memcached_cache.read("test")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
x.report("brotli_memcached_cache") do
|
64
|
+
iterations.times do
|
65
|
+
brotli_memcached_cache.write("test", json)
|
66
|
+
brotli_memcached_cache.read("test")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/cache'
|
4
|
+
require 'brotli'
|
5
|
+
|
6
|
+
module RailsBrotliCache
|
7
|
+
class Store < ::ActiveSupport::Cache::Store
|
8
|
+
COMPRESS_THRESHOLD = ENV.fetch("BR_CACHE_COMPRESS_THRESHOLD", 1).to_f * 1024.0
|
9
|
+
COMPRESS_QUALITY = ENV.fetch("BR_CACHE_COMPRESS_QUALITY", 5).to_i
|
10
|
+
MARK_BR_COMPRESSED = "\x02".b
|
11
|
+
|
12
|
+
def initialize(core_store, options = {})
|
13
|
+
@core_store = core_store
|
14
|
+
@prefix = "br-"
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch(name, options = nil, &block)
|
18
|
+
value = read(name, options)
|
19
|
+
|
20
|
+
if value.present? && !options&.fetch(:force, false) == true
|
21
|
+
return value
|
22
|
+
end
|
23
|
+
|
24
|
+
if block_given?
|
25
|
+
value = block.call
|
26
|
+
write(name, value, options)
|
27
|
+
|
28
|
+
value
|
29
|
+
elsif options && options[:force]
|
30
|
+
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
|
31
|
+
else
|
32
|
+
read(name, options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(name, value, options = nil)
|
37
|
+
serialized = Marshal.dump(value)
|
38
|
+
|
39
|
+
payload = if serialized.bytesize >= COMPRESS_THRESHOLD
|
40
|
+
MARK_BR_COMPRESSED + ::Brotli.deflate(serialized, quality: COMPRESS_QUALITY)
|
41
|
+
else
|
42
|
+
serialized
|
43
|
+
end
|
44
|
+
|
45
|
+
@core_store.write(
|
46
|
+
cache_key(name),
|
47
|
+
payload,
|
48
|
+
(options || {}).merge(compress: false)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def read(name, options = nil)
|
53
|
+
payload = @core_store.read(
|
54
|
+
cache_key(name),
|
55
|
+
options
|
56
|
+
)
|
57
|
+
|
58
|
+
return nil unless payload.present?
|
59
|
+
|
60
|
+
serialized = if payload.start_with?(MARK_BR_COMPRESSED)
|
61
|
+
::Brotli.inflate(payload.byteslice(1..-1))
|
62
|
+
else
|
63
|
+
payload
|
64
|
+
end
|
65
|
+
|
66
|
+
Marshal.load(serialized)
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete(name, options = nil)
|
70
|
+
@core_store.delete(cache_key(name), options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear(options = nil)
|
74
|
+
@core_store.clear
|
75
|
+
end
|
76
|
+
|
77
|
+
def disable_prefix!
|
78
|
+
@prefix = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def cache_key(name)
|
84
|
+
"#{@prefix}#{name}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/rails-brotli-cache.rb
CHANGED
@@ -1,73 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rails-brotli-cache/
|
4
|
-
require 'brotli'
|
3
|
+
require 'rails-brotli-cache/store'
|
5
4
|
|
6
5
|
module RailsBrotliCache
|
7
|
-
COMPRESS_THRESHOLD = ENV.fetch("BR_CACHE_COMPRESS_THRESHOLD", 1).to_f * 1024.0
|
8
|
-
COMPRESS_QUALITY = ENV.fetch("BR_CACHE_COMPRESS_QUALITY", 5).to_i
|
9
|
-
MARK_BR_COMPRESSED = "\x02".b
|
10
|
-
@@prefix = "br-"
|
11
|
-
|
12
|
-
def self.fetch(name, options = nil, &block)
|
13
|
-
value = read(name, options)
|
14
|
-
return value if value.present?
|
15
|
-
|
16
|
-
if block_given?
|
17
|
-
value = block.call
|
18
|
-
write(name, value, options)
|
19
|
-
|
20
|
-
value
|
21
|
-
elsif options && options[:force]
|
22
|
-
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
|
23
|
-
else
|
24
|
-
read(name, options)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.write(name, value, options = nil)
|
29
|
-
serialized = Marshal.dump(value)
|
30
|
-
|
31
|
-
payload = if serialized.bytesize >= COMPRESS_THRESHOLD
|
32
|
-
MARK_BR_COMPRESSED + ::Brotli.deflate(serialized, quality: COMPRESS_QUALITY)
|
33
|
-
else
|
34
|
-
serialized
|
35
|
-
end
|
36
|
-
|
37
|
-
Rails.cache.write(
|
38
|
-
cache_key(name),
|
39
|
-
payload,
|
40
|
-
(options || {}).merge(compress: false)
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.read(name, options = nil)
|
45
|
-
payload = Rails.cache.read(
|
46
|
-
cache_key(name),
|
47
|
-
options
|
48
|
-
)
|
49
|
-
|
50
|
-
return nil unless payload.present?
|
51
|
-
|
52
|
-
serialized = if payload.start_with?(MARK_BR_COMPRESSED)
|
53
|
-
::Brotli.inflate(payload.byteslice(1..-1))
|
54
|
-
else
|
55
|
-
payload
|
56
|
-
end
|
57
|
-
|
58
|
-
Marshal.load(serialized)
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.delete(name, options = nil)
|
62
|
-
Rails.cache.delete(cache_key(name), options)
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.disable_prefix!
|
66
|
-
@@prefix = nil
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.cache_key(name)
|
70
|
-
"#{@@prefix}#{name}"
|
71
|
-
end
|
72
6
|
end
|
73
7
|
|
@@ -19,7 +19,7 @@ Rails.application.configure do
|
|
19
19
|
|
20
20
|
# Enable/disable caching. By default caching is disabled.
|
21
21
|
# Run rails dev:cache to toggle caching.
|
22
|
-
config.cache_store =
|
22
|
+
config.cache_store = $rails_cache_store
|
23
23
|
|
24
24
|
# Print deprecation notices to the Rails logger.
|
25
25
|
config.active_support.deprecation = :log
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
return unless ENV['RAILS_CACHE_STORE'] == 'brotli_cache_store'
|
6
|
+
|
7
|
+
describe RailsBrotliCache do
|
8
|
+
subject(:cache_store) do
|
9
|
+
Rails.cache
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets the correct Rails.cache store" do
|
13
|
+
expect(Rails.cache.class).to eq RailsBrotliCache::Store
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#fetch" do
|
17
|
+
it "stores value in the configured Rails.cache with a prefix" do
|
18
|
+
cache_store.fetch("test-key") { 123 }
|
19
|
+
expect(cache_store.read("test-key")).to eq 123
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns nil for missing entries if block is not provided" do
|
23
|
+
expect(cache_store.fetch("missing-key")).to eq nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "executes block only once" do
|
27
|
+
counter = 0
|
28
|
+
cache_store.fetch("forced-key") { counter += 1 }
|
29
|
+
cache_store.fetch("forced-key") { counter += 1 }
|
30
|
+
expect(cache_store.read("forced-key")).to eq 1
|
31
|
+
end
|
32
|
+
|
33
|
+
context "{ force: true }" do
|
34
|
+
it "raises an error if block is not provided" do
|
35
|
+
expect {
|
36
|
+
cache_store.fetch("missing-key", force: true)
|
37
|
+
}.to raise_error(ArgumentError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "always refreshes cached entry if block is provided" do
|
41
|
+
counter = 0
|
42
|
+
cache_store.fetch("forced-key", force: true) { counter += 1 }
|
43
|
+
cache_store.fetch("forced-key", force: true) { counter += 1 }
|
44
|
+
expect(cache_store.read("forced-key")).to eq 2
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#read and #write" do
|
50
|
+
it "reads values stored in Rails cache with a prefix" do
|
51
|
+
expect(cache_store.read("test-key")).to eq nil
|
52
|
+
expect(cache_store.write("test-key", 1234))
|
53
|
+
expect(cache_store.read("test-key")).to eq 1234
|
54
|
+
end
|
55
|
+
|
56
|
+
context "payloads smaller then 1kb" do
|
57
|
+
before do
|
58
|
+
expect(Brotli).not_to receive(:deflate)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not apply compression" do
|
62
|
+
cache_store.write("test-key", 123)
|
63
|
+
expect(cache_store.read("test-key")).to eq 123
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#delete" do
|
69
|
+
it "removes the previously stored cache entry" do
|
70
|
+
expect(cache_store.write("test-key", 1234))
|
71
|
+
expect(cache_store.read("test-key")).to eq 1234
|
72
|
+
cache_store.delete("test-key")
|
73
|
+
expect(cache_store.read("test-key")).to eq nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
-
require 'redis'
|
5
4
|
|
6
|
-
return unless ENV[
|
7
|
-
$redis = Redis.new
|
5
|
+
return unless ENV['RAILS_CACHE_STORE'] == 'redis_cache_store'
|
8
6
|
|
9
7
|
describe RailsBrotliCache do
|
10
|
-
|
11
|
-
|
8
|
+
subject(:cache_store) do
|
9
|
+
RailsBrotliCache::Store.new(
|
10
|
+
ActiveSupport::Cache::RedisCacheStore.new(redis: $redis)
|
11
|
+
)
|
12
12
|
end
|
13
13
|
|
14
14
|
describe "#fetch" do
|
15
15
|
it "stores value in the configured redis cache store" do
|
16
|
-
|
16
|
+
cache_store.fetch("test-key") { 123 }
|
17
17
|
expect($redis.get("br-test-key")).to be_present
|
18
18
|
end
|
19
19
|
end
|
@@ -24,18 +24,20 @@ describe RailsBrotliCache do
|
|
24
24
|
|
25
25
|
it "applies more efficient brotli compression" do
|
26
26
|
Rails.cache.write("gz-test-key", json)
|
27
|
-
|
27
|
+
cache_store.write("test-key", json)
|
28
28
|
expect($redis.get("gz-test-key").size > $redis.get("br-test-key").size).to eq true
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
expect(
|
31
|
+
describe "disable_prefix!" do
|
32
|
+
it "saves brotli cache entries without `br-` prefix" do
|
33
|
+
cache_store.fetch("test-key") { 123 }
|
34
|
+
expect($redis.get("test-key")).to eq nil
|
35
|
+
expect($redis.get("br-test-key")).to be_present
|
36
|
+
cache_store.disable_prefix!
|
37
|
+
cache_store.fetch("test-key-2") { 123 }
|
38
|
+
expect($redis.get("br-test-key-2")).to eq nil
|
39
|
+
expect($redis.get("test-key-2")).to be_present
|
40
|
+
cache_store.instance_variable_set(:@prefix, "br-")
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe RailsBrotliCache do
|
6
|
+
subject(:cache_store) do
|
7
|
+
RailsBrotliCache::Store.new(ActiveSupport::Cache::MemoryStore.new)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#fetch" do
|
11
|
+
it "stores value in the configured Rails.cache with a prefix" do
|
12
|
+
cache_store.fetch("test-key") { 123 }
|
13
|
+
expect(cache_store.read("test-key")).to eq 123
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns nil for missing entries if block is not provided" do
|
17
|
+
expect(cache_store.fetch("missing-key")).to eq nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "executes block only once" do
|
21
|
+
counter = 0
|
22
|
+
cache_store.fetch("forced-key") { counter += 1 }
|
23
|
+
cache_store.fetch("forced-key") { counter += 1 }
|
24
|
+
expect(cache_store.read("forced-key")).to eq 1
|
25
|
+
end
|
26
|
+
|
27
|
+
context "{ force: true }" do
|
28
|
+
it "raises an error if block is not provided" do
|
29
|
+
expect {
|
30
|
+
cache_store.fetch("missing-key", force: true)
|
31
|
+
}.to raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "always refreshes cached entry if block is provided" do
|
35
|
+
counter = 0
|
36
|
+
cache_store.fetch("forced-key", force: true) { counter += 1 }
|
37
|
+
cache_store.fetch("forced-key", force: true) { counter += 1 }
|
38
|
+
expect(cache_store.read("forced-key")).to eq 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#read and #write" do
|
44
|
+
it "reads values stored in Rails cache with a prefix" do
|
45
|
+
expect(cache_store.read("test-key")).to eq nil
|
46
|
+
expect(cache_store.write("test-key", 1234))
|
47
|
+
expect(cache_store.read("test-key")).to eq 1234
|
48
|
+
end
|
49
|
+
|
50
|
+
context "payloads smaller then 1kb" do
|
51
|
+
before do
|
52
|
+
expect(Brotli).not_to receive(:deflate)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "does not apply compression" do
|
56
|
+
cache_store.write("test-key", 123)
|
57
|
+
expect(cache_store.read("test-key")).to eq 123
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#delete" do
|
63
|
+
it "removes the previously stored cache entry" do
|
64
|
+
expect(cache_store.write("test-key", 1234))
|
65
|
+
expect(cache_store.read("test-key")).to eq 1234
|
66
|
+
cache_store.delete("test-key")
|
67
|
+
expect(cache_store.read("test-key")).to eq nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "disable_prefix!" do
|
72
|
+
it "saves brotli cache entries without `br-` prefix" do
|
73
|
+
cache_store.disable_prefix!
|
74
|
+
cache_store.fetch("test-key") { 123 }
|
75
|
+
cache_store.instance_variable_set(:@prefix, "br-")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'rails'
|
4
|
+
require 'redis'
|
4
5
|
|
5
6
|
require_relative '../lib/rails-brotli-cache'
|
7
|
+
|
8
|
+
$redis = Redis.new
|
9
|
+
$rails_cache_store = if ENV['RAILS_CACHE_STORE'] == 'redis_cache_store'
|
10
|
+
ActiveSupport::Cache::RedisCacheStore.new(redis: $redis)
|
11
|
+
elsif ENV['RAILS_CACHE_STORE'] == 'brotli_cache_store'
|
12
|
+
RailsBrotliCache::Store.new(ActiveSupport::Cache::MemoryStore.new)
|
13
|
+
else
|
14
|
+
ActiveSupport::Cache::MemoryStore.new
|
15
|
+
end
|
16
|
+
|
6
17
|
require_relative '../spec/dummy/config/environment'
|
7
18
|
ENV['RAILS_ROOT'] ||= "#{File.dirname(__FILE__)}../../../spec/dummy"
|
8
19
|
|
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.2.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-05-
|
11
|
+
date: 2023-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -108,8 +108,11 @@ files:
|
|
108
108
|
- LICENSE.txt
|
109
109
|
- README.md
|
110
110
|
- Rakefile
|
111
|
+
- benchmarks/Gemfile
|
112
|
+
- benchmarks/main.rb
|
111
113
|
- docker-compose.yml.sample
|
112
114
|
- lib/rails-brotli-cache.rb
|
115
|
+
- lib/rails-brotli-cache/store.rb
|
113
116
|
- lib/rails-brotli-cache/version.rb
|
114
117
|
- rails-brotli-cache.gemspec
|
115
118
|
- spec/dummy/Gemfile
|
@@ -155,8 +158,9 @@ files:
|
|
155
158
|
- spec/dummy/public/robots.txt
|
156
159
|
- spec/dummy/vendor/.keep
|
157
160
|
- spec/fixtures/sample.json
|
158
|
-
- spec/rails-brotli-cache/
|
161
|
+
- spec/rails-brotli-cache/rails_store_spec.rb
|
159
162
|
- spec/rails-brotli-cache/redis_spec.rb
|
163
|
+
- spec/rails-brotli-cache/store_spec.rb
|
160
164
|
- spec/spec_helper.rb
|
161
165
|
homepage: https://github.com/pawurb/rails-brotli-cache
|
162
166
|
licenses:
|
@@ -225,6 +229,7 @@ test_files:
|
|
225
229
|
- spec/dummy/public/robots.txt
|
226
230
|
- spec/dummy/vendor/.keep
|
227
231
|
- spec/fixtures/sample.json
|
228
|
-
- spec/rails-brotli-cache/
|
232
|
+
- spec/rails-brotli-cache/rails_store_spec.rb
|
229
233
|
- spec/rails-brotli-cache/redis_spec.rb
|
234
|
+
- spec/rails-brotli-cache/store_spec.rb
|
230
235
|
- spec/spec_helper.rb
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe RailsBrotliCache do
|
6
|
-
context "without prepending Rails.cache" do
|
7
|
-
describe "#fetch" do
|
8
|
-
it "stores value in the configured Rails.cache with a prefix" do
|
9
|
-
RailsBrotliCache.fetch("test-key") { 123 }
|
10
|
-
expect(RailsBrotliCache.read("test-key")).to eq 123
|
11
|
-
expect(Rails.cache.read("br-test-key")).to be_present
|
12
|
-
end
|
13
|
-
|
14
|
-
it "returns nil for missing entries if block is not provided" do
|
15
|
-
expect(RailsBrotliCache.fetch("missing-key")).to eq nil
|
16
|
-
end
|
17
|
-
|
18
|
-
context "{ force: true }" do
|
19
|
-
it "raises an error if block is not provided" do
|
20
|
-
expect {
|
21
|
-
RailsBrotliCache.fetch("missing-key", force: true)
|
22
|
-
}.to raise_error(ArgumentError)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "always refreshes cached entry if block is provided" do
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "#read and #write" do
|
32
|
-
it "reads values stored in Rails cache with a prefix" do
|
33
|
-
expect(RailsBrotliCache.read("test-key")).to eq nil
|
34
|
-
expect(RailsBrotliCache.write("test-key", 1234))
|
35
|
-
expect(RailsBrotliCache.read("test-key")).to eq 1234
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "#delete" do
|
40
|
-
it "removes the previously stored cache entry" do
|
41
|
-
expect(RailsBrotliCache.write("test-key", 1234))
|
42
|
-
expect(RailsBrotliCache.read("test-key")).to eq 1234
|
43
|
-
RailsBrotliCache.delete("test-key")
|
44
|
-
expect(RailsBrotliCache.read("test-key")).to eq nil
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe "disable_prefix!" do
|
49
|
-
it "saves brotli cache entries without `br-` prefix" do
|
50
|
-
RailsBrotliCache.disable_prefix!
|
51
|
-
RailsBrotliCache.fetch("test-key") { 123 }
|
52
|
-
expect(Rails.cache.read("br-test-key")).to eq nil
|
53
|
-
expect(Rails.cache.read("test-key")).to be_present
|
54
|
-
RailsBrotliCache.class_variable_set(:@@prefix, "br-")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|