large_object_store 1.3.4 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7330eb4955ee45e4ff4fcad4580a6fcd2f55ad48
4
- data.tar.gz: e61edd740a283e28ec308600a271a095d199a44b
2
+ SHA256:
3
+ metadata.gz: e72d3b11da68461fa95e282ad1f8727c0c397f87602a01557754a438c111f88d
4
+ data.tar.gz: a79ab553fd00bb63b89c584dda277c4faba58ce82d4095a7a73a16407d4995ca
5
5
  SHA512:
6
- metadata.gz: 5a9e62798f1fcc24a298e5d36cf8896c99aed6d3070a0d62dfec72a2c2279949acf003030ed5bbdcd9bd442b30eef9fb56cb9d44b98821f1365024383345403c
7
- data.tar.gz: dfc7717490b507a35ad4020aca46d3dd07dc1d2d773b1b6245ea4da1d06d61de78e4f85420cff6fa47e3d8f112f6cb70b4e57211b6be94fbceec099e2c66cf29
6
+ metadata.gz: b2eab6d7bf39128dda4096fc96dc583128e08d0b08acbce8ea879611be48c03024aacc2f0cb55d27827988ee024c1d291495f8c85bdeb12ba55becd34a634407
7
+ data.tar.gz: b991e5e5337333eeb39daade26edad8957819f8de768f2632df09f77873d9f351f5913d7dac19d5595115d3624369c88a8ddc9fd90a6967028db522221cadd96
data/Readme.md CHANGED
@@ -26,6 +26,19 @@ store.write("a" * 1000, compress: true, compress_limit: 100) # compress when gre
26
26
  store.write("a" * 1000, raw: true) # store as string to avoid marshaling overhead
27
27
  ```
28
28
 
29
+ zstd
30
+ ====
31
+
32
+ [zstd compression](https://engineering.fb.com/2016/08/31/core-data/smaller-and-faster-data-compression-with-zstandard/), a modern improvement over the venerable zlib compression algorithm, is supported by passing the `zstd` flag when writing items:
33
+
34
+ ```
35
+ store.write("a" * 10_000_000, compress: true, zstd: true)
36
+ ```
37
+
38
+ For backwards compatibility and to enable safe roll-out of the change in working systems, the `zstd` flag defaults to `false`.
39
+
40
+ zstd decompression is used when the zstd magic number is detected at the beginning of compressed data, so `zstd: true` does not need to be passed when reading/fetching items.
41
+
29
42
  Author
30
43
  ======
31
44
  [Ana Martinez](https://github.com/anamartinez)<br/>
@@ -33,4 +46,4 @@ acemacu@gmail.com<br/>
33
46
  [Michael Grosser](https://github.com/grosser)<br/>
34
47
  michael@grosser.it<br/>
35
48
  License: MIT<br/>
36
- [![Build Status](https://travis-ci.org/anamartinez/large_object_store.png)](https://travis-ci.org/anamartinez/large_object_store)
49
+ [![CI](https://github.com/anamartinez/large_object_store/actions/workflows/actions.yml/badge.svg)](https://github.com/anamartinez/large_object_store/actions/workflows/actions.yml)
@@ -1,3 +1,3 @@
1
1
  module LargeObjectStore
2
- VERSION = "1.3.4"
2
+ VERSION = "1.7.0"
3
3
  end
@@ -1,11 +1,12 @@
1
1
  require "large_object_store/version"
2
2
  require "zlib"
3
+ require "zstd-ruby"
3
4
  require "securerandom"
4
5
 
5
6
  module LargeObjectStore
6
7
  UUID_BYTES = 16
7
8
  UUID_SIZE = UUID_BYTES * 2
8
- CACHE_VERSION = 3
9
+ CACHE_VERSION = 4
9
10
  MAX_OBJECT_SIZE = 1024**2
10
11
  ITEM_HEADER_SIZE = 100
11
12
  DEFAULT_COMPRESS_LIMIT = 16*1024
@@ -13,35 +14,40 @@ module LargeObjectStore
13
14
  COMPRESSED = 1
14
15
  RAW = 2
15
16
  FLAG_RADIX = 32 # we can store 32 different states
17
+ ZSTD_MAGIC = "\x28\xB5\x2F\xFD".force_encoding('ASCII-8BIT')
18
+ ZSTD_COMPRESS_LEVEL = 3 # Default level recommended by zstd authors
16
19
 
17
- def self.wrap(store)
18
- RailsWrapper.new(store)
20
+ def self.wrap(*args)
21
+ RailsWrapper.new(*args)
22
+ end
23
+ class << self
24
+ ruby2_keywords :wrap if respond_to?(:ruby2_keywords, true)
19
25
  end
20
26
 
21
27
  class RailsWrapper
22
28
  attr_reader :store
23
29
 
24
- def initialize(store)
30
+ def initialize(store, serializer: Marshal, max_slice_size: MAX_OBJECT_SIZE)
25
31
  @store = store
32
+ @serializer = serializer
33
+ @max_slice_size = [max_slice_size, MAX_OBJECT_SIZE].min
34
+ @namespace = (store.respond_to?(:options) && store.options[:namespace]) || ""
26
35
  end
27
36
 
28
- def write(key, value, options = {})
37
+ def write(key, value, **options)
29
38
  options = options.dup
30
39
  value = serialize(value, options)
31
40
 
32
- # calculate slice size; note that key length is a factor because
33
- # the key is stored on the same slab page as the value
34
- slice_size = MAX_OBJECT_SIZE - ITEM_HEADER_SIZE - UUID_SIZE - key.bytesize
35
-
41
+ slice_size = safe_slice_size(key)
36
42
  # store number of pages
37
43
  pages = (value.size / slice_size.to_f).ceil
38
44
 
39
45
  if pages == 1
40
- !!@store.write(key(key, 0), value, options)
46
+ !!@store.write(key(key, 0), value, **options)
41
47
  else
42
48
  # store meta
43
49
  uuid = SecureRandom.hex(UUID_BYTES)
44
- return false unless @store.write(key(key, 0), [pages, uuid], options) # invalidates the old cache
50
+ return false unless @store.write(key(key, 0), [pages, uuid], **options) # invalidates the old cache
45
51
 
46
52
  # store object
47
53
  page = 1
@@ -49,7 +55,7 @@ module LargeObjectStore
49
55
  slice = value.slice!(0, slice_size)
50
56
  break if slice.size == 0
51
57
 
52
- return false unless @store.write(key(key, page), slice.prepend(uuid), options.merge(raw: true))
58
+ return false unless @store.write(key(key, page), slice.prepend(uuid), raw: true, **options)
53
59
  page += 1
54
60
  end
55
61
  true
@@ -83,11 +89,11 @@ module LargeObjectStore
83
89
  deserialize(data)
84
90
  end
85
91
 
86
- def fetch(key, options={})
92
+ def fetch(key, **options)
87
93
  value = read(key)
88
94
  return value unless value.nil?
89
95
  value = yield
90
- write(key, value, options)
96
+ write(key, value, **options)
91
97
  value
92
98
  end
93
99
 
@@ -101,6 +107,19 @@ module LargeObjectStore
101
107
 
102
108
  private
103
109
 
110
+ # calculate slice size; note that key length is a factor because
111
+ # the key is stored on the same slab page as the value
112
+ def safe_slice_size(key)
113
+ namespace_length = @namespace.empty? ? 0 : @namespace.size + 1
114
+ overhead = ITEM_HEADER_SIZE + UUID_SIZE + key.bytesize + namespace_length
115
+ slice_size = @max_slice_size - overhead
116
+ if slice_size <= 0
117
+ MAX_OBJECT_SIZE - overhead
118
+ else
119
+ slice_size
120
+ end
121
+ end
122
+
104
123
  # convert a object to a string
105
124
  # modifies options
106
125
  def serialize(value, options)
@@ -110,31 +129,56 @@ module LargeObjectStore
110
129
  flag |= RAW
111
130
  value = value.to_s
112
131
  else
113
- value = Marshal.dump(value)
132
+ value = @serializer.dump(value)
114
133
  end
115
134
 
116
135
  if compress?(value, options)
117
136
  flag |= COMPRESSED
118
- value = Zlib::Deflate.deflate(value)
137
+ value = compress(value, options)
119
138
  end
120
139
 
121
140
  value.prepend(flag.to_s(FLAG_RADIX))
122
141
  end
123
142
 
143
+ def compress(value, options)
144
+ if options[:zstd]
145
+ Zstd.compress(value, ZSTD_COMPRESS_LEVEL)
146
+ else
147
+ Zlib::Deflate.deflate(value)
148
+ end
149
+ end
150
+
151
+ def decompress(data)
152
+ if data.start_with?(ZSTD_MAGIC)
153
+ Zstd.decompress(data)
154
+ else
155
+ Zlib::Inflate.inflate(data)
156
+ end
157
+ end
158
+
124
159
  # opposite operations and order of serialize
125
160
  def deserialize(raw_data)
126
161
  data = raw_data.dup
127
162
  flag = data.slice!(0, 1).to_i(FLAG_RADIX)
128
- data = Zlib::Inflate.inflate(data) if flag & COMPRESSED == COMPRESSED
129
- data = Marshal.load(data) if flag & RAW != RAW
163
+ data = decompress(data) if flag & COMPRESSED == COMPRESSED
164
+ data = @serializer.load(data) if flag & RAW != RAW
130
165
  data
131
166
  end
132
167
 
133
168
  # Don't pass compression on to Rails, we're doing it ourselves.
134
169
  def compress?(value, options)
135
- return unless options.delete(:compress)
136
- compress_limit = options.delete(:compress_limit) || DEFAULT_COMPRESS_LIMIT
137
- value.bytesize > compress_limit
170
+ return unless options[:compress]
171
+
172
+ compress_limit = options[:compress_limit] || DEFAULT_COMPRESS_LIMIT
173
+ should_compress = value.bytesize > compress_limit
174
+
175
+ if should_compress
176
+ # Pass compress: false to Rails in case the default is true
177
+ options[:compress] = false
178
+ options.delete(:compress_limit)
179
+ end
180
+
181
+ should_compress
138
182
  end
139
183
 
140
184
  def key(key, i)
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: large_object_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.4
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ana Martinez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-27 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-01-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zstd-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.5
13
27
  description:
14
28
  email: acemacu@gmail.com
15
29
  executables: []
@@ -31,15 +45,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
45
  requirements:
32
46
  - - ">="
33
47
  - !ruby/object:Gem::Version
34
- version: '2.0'
48
+ version: '2.6'
35
49
  required_rubygems_version: !ruby/object:Gem::Requirement
36
50
  requirements:
37
51
  - - ">="
38
52
  - !ruby/object:Gem::Version
39
53
  version: '0'
40
54
  requirements: []
41
- rubyforge_project:
42
- rubygems_version: 2.6.14
55
+ rubygems_version: 3.5.3
43
56
  signing_key:
44
57
  specification_version: 4
45
58
  summary: Store large objects in memcache or others