omq-zstd 0.4.3 → 0.4.5
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 +15 -9
- data/RFC.md +22 -17
- data/lib/omq/transport/zstd_tcp/codec.rb +8 -3
- data/lib/omq/transport/zstd_tcp/transport.rb +52 -7
- data/lib/omq/zstd/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4bd678bf2436ab0d01597aaff7d536957432abcd571c36b21b6d9d82eff12061
|
|
4
|
+
data.tar.gz: df584bef85cac3e3e031990bb511675bdb9b4aca50fc9975f869666d26154e47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83b6c89aec844c6c14d5afd2161173c3ef9aa915a821d5a05c78610637994fdd68a8d240ef05494136f02c7e4ec27363c5da48f8392dd017d5b728da53114a58
|
|
7
|
+
data.tar.gz: 87c79b3d3a07f4c6e42f823af146be14ab2c91770c3df221a7372e6c4554130fe17d5a4eebf66e9a9ec27b45ee581916ff3eb32362d10d1e21ba5c390d5373d2
|
data/README.md
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
Experimental Zstandard-compressed TCP transport for
|
|
8
8
|
[OMQ](https://github.com/zeromq/omq.rb).
|
|
9
9
|
|
|
10
|
-
Use [`omq-lz4`](../omq-lz4) for
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
cases where zstd ratio matters more than transport simplicity.
|
|
10
|
+
Use [`omq-lz4`](../omq-lz4) for CPU- or memory-sensitive compressed TCP
|
|
11
|
+
work. This gem remains for research, comparison, OMQ.rs parity, and cases
|
|
12
|
+
where zstd ratio matters more than transport simplicity.
|
|
14
13
|
|
|
15
14
|
Pick `zstd+tcp://` instead of `tcp://` and every message part on the wire is
|
|
16
15
|
compressed per-part with [Zstandard](https://github.com/facebook/zstd).
|
|
@@ -80,11 +79,17 @@ The sender ships the dictionary to the receiver in-band as a one-shot
|
|
|
80
79
|
single-part message prefixed with the dictionary sentinel
|
|
81
80
|
(`37 A4 30 EC`), so the receiver does not need a copy on disk.
|
|
82
81
|
|
|
83
|
-
**Auto-trained dictionary** (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
**Auto-trained dictionary** (opt-in):
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
push.connect("zstd+tcp://127.0.0.1:5555", auto_dict: true)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The sender collects up to 1000 samples or 100 KiB (whichever hits first),
|
|
89
|
+
skipping samples larger than 2048 bytes. It trains a 2 KiB dictionary, ships
|
|
90
|
+
it inline, and switches to dictionary mode. Until then, payloads are compressed
|
|
91
|
+
without a dictionary or sent plaintext when below the threshold. Automatic
|
|
92
|
+
dictionary training is off by default.
|
|
88
93
|
|
|
89
94
|
### Compression thresholds
|
|
90
95
|
|
|
@@ -152,6 +157,7 @@ transport layer. They are not delivered to the application.
|
|
|
152
157
|
| Train max bytes | 100 KiB |
|
|
153
158
|
| Train max sample length | 2048 B |
|
|
154
159
|
| Dictionary capacity | 2 KiB |
|
|
160
|
+
| Auto dictionary training | Off by default |
|
|
155
161
|
|
|
156
162
|
## When to use it
|
|
157
163
|
|
data/RFC.md
CHANGED
|
@@ -265,21 +265,26 @@ exceeds the limit MUST be rejected before decoder invocation.
|
|
|
265
265
|
### 7.1 Dictionary message format
|
|
266
266
|
|
|
267
267
|
A dictionary is shipped as a **single-part ZMTP message** (no MORE flag)
|
|
268
|
-
whose body
|
|
268
|
+
whose body is exactly a Zstandard dictionary blob in ZDICT format. ZDICT
|
|
269
|
+
blobs begin with the dictionary magic `37 A4 30 EC`; this transport uses
|
|
270
|
+
those first 4 bytes as the dictionary sentinel:
|
|
269
271
|
|
|
270
272
|
```
|
|
271
273
|
+------------------+------------------------+
|
|
272
|
-
| 37 A4 30 EC |
|
|
273
|
-
| (4 bytes) | (D bytes)
|
|
274
|
+
| 37 A4 30 EC | rest of ZDICT blob |
|
|
275
|
+
| (4 bytes) | (D - 4 bytes) |
|
|
274
276
|
+------------------+------------------------+
|
|
275
277
|
```
|
|
276
278
|
|
|
277
|
-
The sentinel `37 A4 30 EC` is
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
The sentinel `37 A4 30 EC` is the standard Zstandard dictionary magic
|
|
280
|
+
(`DICT_MAGIC`, little-endian). It is intentionally reused as this
|
|
281
|
+
transport's dictionary discriminator because a ZDICT blob already carries
|
|
282
|
+
it and it does not collide with the Zstandard frame magic or the
|
|
283
|
+
uncompressed sentinel.
|
|
280
284
|
|
|
281
|
-
The
|
|
282
|
-
to the Zstandard decoder's
|
|
285
|
+
The complete `D` bytes, including the leading `37 A4 30 EC` magic, are
|
|
286
|
+
the raw dictionary as it should be passed to the Zstandard decoder's
|
|
287
|
+
dictionary-load operation.
|
|
283
288
|
|
|
284
289
|
### 7.2 Constraints
|
|
285
290
|
|
|
@@ -287,9 +292,10 @@ to the Zstandard decoder's dictionary-load operation.
|
|
|
287
292
|
not set on the frame header). A dictionary sentinel in a multipart
|
|
288
293
|
message's non-final or non-only part is a protocol error.
|
|
289
294
|
|
|
290
|
-
- A dictionary message MUST NOT exceed **8 KiB** total
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
- A dictionary message MUST NOT exceed **8 KiB** total. Since the
|
|
296
|
+
message body is exactly the ZDICT blob, this is also the maximum
|
|
297
|
+
dictionary size. A receiver that receives a dictionary message larger
|
|
298
|
+
than 8 KiB MUST close the connection.
|
|
293
299
|
|
|
294
300
|
- A sender MUST send at most **one** dictionary message per direction
|
|
295
301
|
per connection. A receiver that receives a second dictionary message
|
|
@@ -305,9 +311,9 @@ to the Zstandard decoder's dictionary-load operation.
|
|
|
305
311
|
When the receiver encounters a dictionary part:
|
|
306
312
|
|
|
307
313
|
1. Validate the constraints in Sec. 7.2.
|
|
308
|
-
2.
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
2. Install the complete message body as the decompression dictionary for
|
|
315
|
+
this connection. The leading `37 A4 30 EC` bytes are part of the ZDICT
|
|
316
|
+
blob and MUST NOT be stripped.
|
|
311
317
|
4. Discard the message. It is not delivered to the application.
|
|
312
318
|
|
|
313
319
|
If all parts of a ZMTP message are dictionary parts (which is always
|
|
@@ -361,9 +367,8 @@ support auto-training:
|
|
|
361
367
|
| Max sample length | 2048 bytes |
|
|
362
368
|
| Training algorithm | FastCOVER |
|
|
363
369
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
dictionary instead.
|
|
370
|
+
Auto-training SHOULD be disabled by default. Applications MAY enable it
|
|
371
|
+
explicitly or supply an out-of-band dictionary instead.
|
|
367
372
|
|
|
368
373
|
### 7.6 Dictionary ID
|
|
369
374
|
|
|
@@ -22,9 +22,14 @@ module OMQ
|
|
|
22
22
|
attr_reader :send_dict_bytes, :max_message_size
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def initialize(level:, dict: nil, max_message_size: nil)
|
|
25
|
+
def initialize(level:, dict: nil, max_message_size: nil, auto_dict: nil)
|
|
26
26
|
@level = level
|
|
27
27
|
@max_message_size = max_message_size
|
|
28
|
+
@dict_capacity = if auto_dict.is_a?(Hash) && auto_dict[:capacity]
|
|
29
|
+
auto_dict[:capacity]
|
|
30
|
+
else
|
|
31
|
+
DICT_CAPACITY
|
|
32
|
+
end
|
|
28
33
|
|
|
29
34
|
# Start with a no-dict FrameCodec. Once a dict is configured
|
|
30
35
|
# (either via the `dict:` kwarg or via auto-training), this is
|
|
@@ -34,7 +39,7 @@ module OMQ
|
|
|
34
39
|
@send_codec = Zrip::FrameCodec.new(level: @level)
|
|
35
40
|
@send_dict_bytes = nil
|
|
36
41
|
|
|
37
|
-
@training = dict.nil?
|
|
42
|
+
@training = !!auto_dict && dict.nil?
|
|
38
43
|
@train_samples = []
|
|
39
44
|
@train_bytes = 0
|
|
40
45
|
|
|
@@ -102,7 +107,7 @@ module OMQ
|
|
|
102
107
|
return unless @train_samples.size >= TRAIN_MAX_SAMPLES ||
|
|
103
108
|
@train_bytes >= TRAIN_MAX_BYTES
|
|
104
109
|
|
|
105
|
-
trainer = Zrip::DictTrainer.new(
|
|
110
|
+
trainer = Zrip::DictTrainer.new(@dict_capacity)
|
|
106
111
|
@train_samples.each { |s| trainer.add_sample(s) }
|
|
107
112
|
trained_bytes = trainer.train
|
|
108
113
|
|
|
@@ -24,10 +24,18 @@ module OMQ
|
|
|
24
24
|
# @param engine [Engine]
|
|
25
25
|
# @param level [Integer] Zstd compression level
|
|
26
26
|
# @param dict [String, nil] user-supplied dictionary bytes
|
|
27
|
+
# @param auto_dict [true, Hash, nil] enable automatic dictionary
|
|
28
|
+
# training. Pass `true` for defaults or `{ capacity: N }`.
|
|
27
29
|
# @return [Listener]
|
|
28
30
|
#
|
|
29
|
-
def listener(endpoint, engine, level: -3, dict: nil, **)
|
|
30
|
-
|
|
31
|
+
def listener(endpoint, engine, level: -3, dict: nil, auto_dict: nil, **)
|
|
32
|
+
validate_auto_dict!(auto_dict, dict)
|
|
33
|
+
codec = codec_for(
|
|
34
|
+
engine,
|
|
35
|
+
level: level,
|
|
36
|
+
dict: dict,
|
|
37
|
+
auto_dict: normalize_auto_dict(auto_dict),
|
|
38
|
+
)
|
|
31
39
|
|
|
32
40
|
host, port = parse_endpoint(endpoint)
|
|
33
41
|
host = normalize_bind_host(host)
|
|
@@ -52,10 +60,18 @@ module OMQ
|
|
|
52
60
|
# @param engine [Engine]
|
|
53
61
|
# @param level [Integer] Zstd compression level
|
|
54
62
|
# @param dict [String, nil] user-supplied dictionary bytes
|
|
63
|
+
# @param auto_dict [true, Hash, nil] enable automatic dictionary
|
|
64
|
+
# training. Pass `true` for defaults or `{ capacity: N }`.
|
|
55
65
|
# @return [Dialer]
|
|
56
66
|
#
|
|
57
|
-
def dialer(endpoint, engine, level: -3, dict: nil, **)
|
|
58
|
-
|
|
67
|
+
def dialer(endpoint, engine, level: -3, dict: nil, auto_dict: nil, **)
|
|
68
|
+
validate_auto_dict!(auto_dict, dict)
|
|
69
|
+
codec = codec_for(
|
|
70
|
+
engine,
|
|
71
|
+
level: level,
|
|
72
|
+
dict: dict,
|
|
73
|
+
auto_dict: normalize_auto_dict(auto_dict),
|
|
74
|
+
)
|
|
59
75
|
Dialer.new(endpoint, engine, codec)
|
|
60
76
|
end
|
|
61
77
|
|
|
@@ -107,12 +123,41 @@ module OMQ
|
|
|
107
123
|
# @param dict [String, nil]
|
|
108
124
|
# @return [Codec]
|
|
109
125
|
#
|
|
110
|
-
def
|
|
111
|
-
|
|
126
|
+
def validate_auto_dict!(auto_dict, dict)
|
|
127
|
+
return unless auto_dict
|
|
128
|
+
|
|
129
|
+
if dict
|
|
130
|
+
raise ArgumentError, "cannot combine auto_dict: and dict:"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
return if auto_dict == true
|
|
134
|
+
|
|
135
|
+
unless auto_dict.is_a?(Hash)
|
|
136
|
+
raise TypeError, "auto_dict: must be true or a Hash; got #{auto_dict.class}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
cap = auto_dict[:capacity]
|
|
140
|
+
if cap && (cap < 1 || cap > Codec::MAX_DICT_SIZE)
|
|
141
|
+
raise ArgumentError,
|
|
142
|
+
"auto_dict capacity #{cap} out of range [1, #{Codec::MAX_DICT_SIZE}]"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def normalize_auto_dict(auto_dict)
|
|
148
|
+
return unless auto_dict
|
|
149
|
+
return { capacity: Codec::DICT_CAPACITY }.freeze if auto_dict == true
|
|
150
|
+
|
|
151
|
+
{ capacity: auto_dict[:capacity] || Codec::DICT_CAPACITY }.freeze
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def codec_for(engine, level:, dict:, auto_dict:)
|
|
156
|
+
@codecs[engine] ||= Codec.new level: level, dict: dict, auto_dict: auto_dict,
|
|
112
157
|
max_message_size: engine.options.max_message_size
|
|
113
158
|
|
|
114
159
|
rescue Ractor::IsolationError
|
|
115
|
-
Codec.new level: level, dict: dict,
|
|
160
|
+
Codec.new level: level, dict: dict, auto_dict: auto_dict,
|
|
116
161
|
max_message_size: engine.options.max_message_size
|
|
117
162
|
end
|
|
118
163
|
end
|
data/lib/omq/zstd/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omq-zstd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.2.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.2.0
|
|
40
40
|
description: Experimental zstd+tcp:// endpoint support for OMQ with per-frame Zstd
|
|
41
41
|
compression, bounded decompression, in-band dictionary shipping, and sender-side
|
|
42
42
|
dictionary training.
|