omq-zstd 0.4.2 → 0.4.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/DESIGN.md +2 -2
- data/README.md +11 -4
- data/RFC.md +26 -17
- data/lib/omq/transport/zstd_tcp/codec.rb +11 -13
- data/lib/omq/transport/zstd_tcp/connection.rb +18 -14
- data/lib/omq/transport/zstd_tcp.rb +1 -1
- data/lib/omq/zstd/version.rb +1 -1
- metadata +13 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a3ed977d7fa2b57910a209fd3db1ca683634a9ccc2d299cd3dd0f95c0030d93
|
|
4
|
+
data.tar.gz: 4d439082a1142c335538c3ea3698cc629e6dc9fa406335386171d6457ad2a9e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a68d93c3c116a9fe508844058515f74733a674c6a8a085149b8bff2a8cfb089793509a5d3aed54cc56c3730ecd8386e6eb12ac500cad734e58d8976b6cc674bd
|
|
7
|
+
data.tar.gz: 9eb6751a7682a0ebefd9680b9c48f8a1f6c31041e2f276e87064054d6d7094145a8e6a4776579ea33b9d4c10eacd6d4ce6817c6c4cc3e0d4ec1401ecd659a01c
|
data/DESIGN.md
CHANGED
|
@@ -67,7 +67,7 @@ One Codec per Dialer or Listener. Shared across all connections of
|
|
|
67
67
|
that endpoint. Owns:
|
|
68
68
|
|
|
69
69
|
- **Compression**: `#compress_parts(parts)` with identity cache
|
|
70
|
-
- **Training**: sample collection, `
|
|
70
|
+
- **Training**: sample collection, `Zrip::DictTrainer`, dict ID patching
|
|
71
71
|
- **Send dict**: `#send_dict_bytes` — the trained or user-supplied dict bytes
|
|
72
72
|
|
|
73
73
|
### ZstdConnection (per-connection)
|
|
@@ -124,7 +124,7 @@ end
|
|
|
124
124
|
- **Sample size cap**: frames > 1024 bytes are skipped (dictionaries primarily benefit small frames)
|
|
125
125
|
- **Dict capacity**: 8 KiB (conservative; Zstd recommends ~100:1 sample-to-dict ratio)
|
|
126
126
|
- **Dict ID patching**: auto-trained dicts get a random ID in the user range (32768..2^31-1) to avoid collisions with Zstd's built-in dict IDs
|
|
127
|
-
- **Training failure**: if `
|
|
127
|
+
- **Training failure**: if `Zrip::DictTrainer#train` raises, training is disabled permanently for the socket. No retry.
|
|
128
128
|
|
|
129
129
|
## Frame dispatch
|
|
130
130
|
|
data/README.md
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/omq-zstd)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://www.ruby-lang.org)
|
|
6
|
+
|
|
7
|
+
Experimental Zstandard-compressed TCP transport for
|
|
8
|
+
[OMQ](https://github.com/zeromq/omq.rb).
|
|
9
|
+
|
|
10
|
+
Use [`omq-lz4`](../omq-lz4) for new compressed TCP work. OMQ.rs removed
|
|
11
|
+
`zstd+tcp://` to reduce transport complexity after lz4rip gained
|
|
12
|
+
dictionary training. This gem remains for research, comparison, and
|
|
13
|
+
cases where zstd ratio matters more than transport simplicity.
|
|
6
14
|
|
|
7
|
-
Zstandard-compressed TCP transport for [OMQ](https://github.com/paddor/omq).
|
|
8
15
|
Pick `zstd+tcp://` instead of `tcp://` and every message part on the wire is
|
|
9
16
|
compressed per-part with [Zstandard](https://github.com/facebook/zstd).
|
|
10
17
|
Compression is intrinsic to the transport: no negotiation, no socket option,
|
|
11
|
-
no payload changes. The ZMTP handshake runs over plain TCP
|
|
18
|
+
no payload changes. The ZMTP handshake runs over plain TCP. Only
|
|
12
19
|
post-handshake message parts are compressed.
|
|
13
20
|
|
|
14
21
|
See [RFC.md](RFC.md) for the wire-format specification and
|
|
@@ -170,7 +177,7 @@ It is **not** worth it for:
|
|
|
170
177
|
`require "omq/zstd"` registers the `zstd+tcp` scheme on
|
|
171
178
|
`OMQ::Engine.transports`. A `zstd+tcp` socket builds a per-engine
|
|
172
179
|
`Codec` (one Zstd dictionary instance shared across all the socket's
|
|
173
|
-
connections
|
|
180
|
+
connections. Fan-out compresses each part exactly once). Each accepted
|
|
174
181
|
or dialed TCP connection is wrapped in `ZstdConnection`, a
|
|
175
182
|
`SimpleDelegator` over the underlying ZMTP connection that intercepts
|
|
176
183
|
`#send_message` / `#write_message` / `#receive_message`. Message parts
|
data/RFC.md
CHANGED
|
@@ -338,23 +338,32 @@ in shared training; they use their own dictionary independently.
|
|
|
338
338
|
|
|
339
339
|
### 7.5 Automatic dictionary training
|
|
340
340
|
|
|
341
|
-
A sender MAY train a dictionary automatically from early traffic
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
341
|
+
A sender MAY train a dictionary automatically from early traffic.
|
|
342
|
+
The training algorithm, trigger condition, and parameters are
|
|
343
|
+
implementation choices. The following wire-level constraints MUST be
|
|
344
|
+
respected:
|
|
345
|
+
|
|
346
|
+
- A trained dictionary MUST NOT exceed the maximum dictionary size
|
|
347
|
+
(Sec. 7.2: 8 KiB).
|
|
348
|
+
- A sender MUST ship at most one dictionary per direction per
|
|
349
|
+
connection (Sec. 7.2).
|
|
350
|
+
- If training fails (the sample set was too small or too uniform),
|
|
351
|
+
the sender MUST stay in no-dictionary mode for the rest of the
|
|
352
|
+
socket's lifetime. It MUST NOT retry training.
|
|
353
|
+
|
|
354
|
+
The following parameters are RECOMMENDED for implementations that
|
|
355
|
+
support auto-training:
|
|
356
|
+
|
|
357
|
+
| Parameter | Recommended value |
|
|
358
|
+
|------------------------|------------------------------------------|
|
|
359
|
+
| Dictionary capacity | 2 KiB |
|
|
360
|
+
| Training trigger | 1000 samples OR 100 KiB (first reached) |
|
|
361
|
+
| Max sample length | 2048 bytes |
|
|
362
|
+
| Training algorithm | FastCOVER |
|
|
363
|
+
|
|
364
|
+
Whether auto-training is enabled by default is an implementation
|
|
365
|
+
choice. Applications MAY disable it or supply an out-of-band
|
|
366
|
+
dictionary instead.
|
|
358
367
|
|
|
359
368
|
### 7.6 Dictionary ID
|
|
360
369
|
|
|
@@ -28,10 +28,10 @@ module OMQ
|
|
|
28
28
|
|
|
29
29
|
# Start with a no-dict FrameCodec. Once a dict is configured
|
|
30
30
|
# (either via the `dict:` kwarg or via auto-training), this is
|
|
31
|
-
# replaced with a fresh dict-bound FrameCodec
|
|
31
|
+
# replaced with a fresh dict-bound FrameCodec. In Zrip, the
|
|
32
32
|
# dict is a permanent property of the codec, so swapping dicts
|
|
33
33
|
# means constructing a new one.
|
|
34
|
-
@send_codec =
|
|
34
|
+
@send_codec = Zrip::FrameCodec.new(level: @level)
|
|
35
35
|
@send_dict_bytes = nil
|
|
36
36
|
|
|
37
37
|
@training = dict.nil?
|
|
@@ -102,18 +102,16 @@ module OMQ
|
|
|
102
102
|
return unless @train_samples.size >= TRAIN_MAX_SAMPLES ||
|
|
103
103
|
@train_bytes >= TRAIN_MAX_BYTES
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@training = false
|
|
109
|
-
@train_samples = nil
|
|
110
|
-
return
|
|
111
|
-
end
|
|
105
|
+
trainer = Zrip::DictTrainer.new(DICT_CAPACITY)
|
|
106
|
+
@train_samples.each { |s| trainer.add_sample(s) }
|
|
107
|
+
trained_bytes = trainer.train
|
|
112
108
|
|
|
113
109
|
@training = false
|
|
114
110
|
@train_samples = nil
|
|
115
111
|
|
|
116
|
-
|
|
112
|
+
return if trained_bytes.empty?
|
|
113
|
+
|
|
114
|
+
patched = patch_auto_dict_id(trained_bytes)
|
|
117
115
|
install_send_dict(patched)
|
|
118
116
|
end
|
|
119
117
|
|
|
@@ -136,9 +134,9 @@ module OMQ
|
|
|
136
134
|
end
|
|
137
135
|
|
|
138
136
|
# Replace the no-dict send codec with a fresh dict-bound one;
|
|
139
|
-
# the old codec is GC'd.
|
|
140
|
-
#
|
|
141
|
-
@send_codec =
|
|
137
|
+
# the old codec is GC'd. Zrip treats dict as a permanent codec
|
|
138
|
+
# property, so install-time is always a fresh build.
|
|
139
|
+
@send_codec = Zrip::FrameCodec.new(dict: bytes, level: @level)
|
|
142
140
|
@send_dict_bytes = bytes
|
|
143
141
|
end
|
|
144
142
|
|
|
@@ -15,12 +15,11 @@ module OMQ
|
|
|
15
15
|
super(conn)
|
|
16
16
|
@codec = codec
|
|
17
17
|
@dict_shipped = false
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# dict-bound FrameCodec and replace this one.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@recv_codec = RZstd::FrameCodec.new
|
|
18
|
+
# FrameCodec is the decoder. Starts no-dict; when a dict
|
|
19
|
+
# shipment arrives on this direction, we build a fresh
|
|
20
|
+
# dict-bound FrameCodec and replace this one.
|
|
21
|
+
@recv_codec = Zrip::FrameCodec.new
|
|
22
|
+
@recv_no_dict_codec = @recv_codec
|
|
24
23
|
@recv_dict_bytes = nil
|
|
25
24
|
@last_wire_size_in = nil
|
|
26
25
|
end
|
|
@@ -128,16 +127,24 @@ module OMQ
|
|
|
128
127
|
|
|
129
128
|
decompress_opts = budget ? { max_output_size: budget } : {}
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
codec = frame_has_dict_id?(wire) ? @recv_codec : @recv_no_dict_codec
|
|
131
|
+
codec.decompress(wire, **decompress_opts)
|
|
132
|
+
rescue Zrip::DecompressError => e
|
|
133
133
|
raise ProtocolError, "decompression failed: #{e.message}"
|
|
134
|
-
rescue
|
|
134
|
+
rescue Zrip::MissingContentSizeError => e
|
|
135
135
|
raise ProtocolError, "Zstd frame missing Frame_Content_Size (#{e.message})"
|
|
136
|
-
rescue
|
|
136
|
+
rescue Zrip::OutputSizeLimitError => e
|
|
137
137
|
raise ProtocolError, "declared FCS exceeds limit (#{e.message})"
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def frame_has_dict_id?(wire)
|
|
142
|
+
return false if wire.bytesize < 5
|
|
143
|
+
|
|
144
|
+
(wire.getbyte(4) & 0x03) != 0
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
141
148
|
def install_recv_dict(wire)
|
|
142
149
|
if wire.bytesize < 8
|
|
143
150
|
raise ProtocolError, "dict frame too short"
|
|
@@ -147,10 +154,7 @@ module OMQ
|
|
|
147
154
|
raise ProtocolError, "dict exceeds #{Codec::MAX_DICT_SIZE} bytes"
|
|
148
155
|
end
|
|
149
156
|
|
|
150
|
-
|
|
151
|
-
# the old codec is GC'd (rzstd 0.4: dict is a permanent codec
|
|
152
|
-
# property).
|
|
153
|
-
@recv_codec = RZstd::FrameCodec.new(dict: wire.b)
|
|
157
|
+
@recv_codec = Zrip::FrameCodec.new(dict: wire.b)
|
|
154
158
|
@recv_dict_bytes = wire.b
|
|
155
159
|
end
|
|
156
160
|
|
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.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -15,30 +15,31 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
18
|
+
version: '0.28'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
25
|
+
version: '0.28'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: zrip
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 0.1.1
|
|
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:
|
|
40
|
-
description:
|
|
41
|
-
bounded decompression, in-band dictionary shipping, and sender-side
|
|
39
|
+
version: 0.1.1
|
|
40
|
+
description: Experimental zstd+tcp:// endpoint support for OMQ with per-frame Zstd
|
|
41
|
+
compression, bounded decompression, in-band dictionary shipping, and sender-side
|
|
42
|
+
dictionary training.
|
|
42
43
|
email:
|
|
43
44
|
- paddor@gmail.com
|
|
44
45
|
executables: []
|
|
@@ -55,7 +56,7 @@ files:
|
|
|
55
56
|
- lib/omq/transport/zstd_tcp/transport.rb
|
|
56
57
|
- lib/omq/zstd.rb
|
|
57
58
|
- lib/omq/zstd/version.rb
|
|
58
|
-
homepage: https://github.com/
|
|
59
|
+
homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-zstd
|
|
59
60
|
licenses:
|
|
60
61
|
- ISC
|
|
61
62
|
metadata: {}
|
|
@@ -66,14 +67,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
66
67
|
requirements:
|
|
67
68
|
- - ">="
|
|
68
69
|
- !ruby/object:Gem::Version
|
|
69
|
-
version: '
|
|
70
|
+
version: '4.0'
|
|
70
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
72
|
requirements:
|
|
72
73
|
- - ">="
|
|
73
74
|
- !ruby/object:Gem::Version
|
|
74
75
|
version: '0'
|
|
75
76
|
requirements: []
|
|
76
|
-
rubygems_version: 4.0.
|
|
77
|
+
rubygems_version: 4.0.16
|
|
77
78
|
specification_version: 4
|
|
78
|
-
summary: Zstd+TCP transport for OMQ
|
|
79
|
+
summary: Experimental Zstd+TCP transport for OMQ
|
|
79
80
|
test_files: []
|