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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38153c74bf09ef2a13760b3dea18a3a3e97790104b1af8a8d244ee104d0ad34e
4
- data.tar.gz: ed7760dd1e7373fa7e855f57ec7082a185f68b114e43bd051d29fb4be7619020
3
+ metadata.gz: 6267cfdccacd5e402e500f1df681e16e8d0be371718e499d0abde30c64822dbd
4
+ data.tar.gz: 4648a2053526200a231c8cd36632715700a6ee197f672431ed2de1c150e036eb
5
5
  SHA512:
6
- metadata.gz: dc9430106e033158f55f6421d42253a8aaec9b62833687538a289010e8b67b1ffc4a75ba0b71071e5925b7256d2d070b5930719ebd40d5a60f28dc6d7cd4cd19
7
- data.tar.gz: 47483feabfac21321eb6e4e6a0df6d5ffa8646261dbeb7b728897f3026f4deff699e682a4cf525f8cca79b824082173637baf9517d8e96c3cfd91aaf9b0b3291
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' # brotli is an optional dependency because other compressors are supported
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 which defines two class methods `inflate` and `deflate`. It allows you to instead use for example a [Google Snappy algorithm](https://github.com/miyucy/snappy) offering even better performance for the cost of worse compresion ratios. Optionally, you can define a custom class wrapping any compression library.
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
@@ -1,5 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'brotli'
3
4
  gem 'rails-brotli-cache', path: "../"
4
5
  gem 'redis'
5
6
  gem 'dalli'
7
+ gem 'zstd-ruby'
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.get("gz-json").size
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.get("br-json").size
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
- names = names.map { |name| expanded_cache_key(name) }
112
+ expanded_names = names.map { |name| expanded_cache_key(name) }
113
113
  options = options.reverse_merge(@init_options)
114
114
 
115
- @core_store.fetch_multi(
116
- *names, options.merge(compress: false)
117
- ) do |name|
118
- compressed(yield(name), options)
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 = {})
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsBrotliCache
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.2"
5
5
  end
@@ -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 = "str"
39
- expect(brotli_store.write("str_val_key", int_val).class).to eq(standard_cache.write("str_val_key", int_val).class)
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", int_val).class).to eq(standard_cache.write("complex_val_key", int_val).class)
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
- val = "123"
59
- expect(brotli_store.fetch("val_key") { val }).to eq(standard_cache.fetch("val_key") { val })
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" => "val_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" => "val_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
- "val_#{key.split('_').last}"
94
+ values[key]
87
95
  end
88
96
 
89
97
  standard_cache.fetch_multi("key_1", "key_2") do |key|
90
- "val_#{key.split('_').last}"
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
- it "works" do
102
- cache_store.fetch_multi("key_1", "key_2", expires_in: 5.seconds) { big_enough_to_compress_value }
103
- expect(cache_store.read("key_1")).to eq big_enough_to_compress_value
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.0
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-09 00:00:00.000000000 Z
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.4.10
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