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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fcea64306ec7603681d7b3350e7cdf2d22d43487be062e79cc91f51f64aacfb
4
- data.tar.gz: ac5d8d88deb3f90ccf97be757d384612274f4d742b042b79bc949a47ea7d0d3c
3
+ metadata.gz: 5a3ed977d7fa2b57910a209fd3db1ca683634a9ccc2d299cd3dd0f95c0030d93
4
+ data.tar.gz: 4d439082a1142c335538c3ea3698cc629e6dc9fa406335386171d6457ad2a9e6
5
5
  SHA512:
6
- metadata.gz: 8e5a04f9ffdb46d130af145fa7025c544cc1709e7711cd92180b0408923667e61d70098efc4679c7b0d2617a3acde40b91c9ecaa1b5abb9e56cadc4a34cd8294
7
- data.tar.gz: 25ab87f733a74257a9b2d772d96a76be134b60804ce2796fa0a95710334eb0552f973fab4a7b04442f6d9cce8091da008c5ed17e6868e925b942fdc4dfefd0f6
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, `RZstd::Dictionary.train`, dict ID patching
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 `RZstd::Dictionary.train` raises, training is disabled permanently for the socket. No retry.
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
  [![Gem Version](https://img.shields.io/gem/v/omq-zstd?color=e9573f)](https://rubygems.org/gems/omq-zstd)
4
4
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
5
- [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.3-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org)
5
+ [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%204.0-CC342D?logo=ruby&logoColor=white)](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; only
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; fan-out compresses each part exactly once). Each accepted
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
- 1. Buffer plaintext samples from the first messages. Samples larger
344
- than **2048 bytes** SHOULD be skipped; dictionaries primarily
345
- benefit small frames.
346
- 2. When the buffer reaches **1000 samples** OR **100 KiB** of
347
- plaintext (whichever comes first), train a Zstandard dictionary from
348
- the buffered samples and discard the buffer.
349
- 3. The recommended dictionary capacity (training target size) is
350
- **2 KiB**.
351
- 4. Ship the trained dictionary via a dictionary message (Sec. 7.1) on
352
- every connection, before any compressed part that uses it.
353
- 5. Switch to dictionary-bound compression for all subsequent parts.
354
-
355
- If training fails (the sample set was too small or too uniform), the
356
- sender MUST stay in no-dictionary mode for the rest of the socket's
357
- lifetime. It MUST NOT retry training.
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 per rzstd 0.4,
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 = RZstd::FrameCodec.new(level: @level)
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
- begin
106
- trained = RZstd::Dictionary.train(@train_samples, capacity: DICT_CAPACITY)
107
- rescue RuntimeError
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
- patched = patch_auto_dict_id(trained.bytes)
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. rzstd 0.4 treats dict as a permanent
140
- # codec property, so install-time is always a fresh build.
141
- @send_codec = RZstd::FrameCodec.new(dict: bytes, level: @level)
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
- # rzstd 0.4: FrameCodec is the decoder. Starts no-dict; when a
19
- # dict shipment arrives on this direction, we build a fresh
20
- # dict-bound FrameCodec and replace this one. Level is a
21
- # compression parameter only — not consulted on decompress —
22
- # so we don't carry the send-side level through to recv.
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
- @recv_codec.decompress(wire, **decompress_opts)
132
- rescue RZstd::DecompressError => e
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 RZstd::MissingContentSizeError => e
134
+ rescue Zrip::MissingContentSizeError => e
135
135
  raise ProtocolError, "Zstd frame missing Frame_Content_Size (#{e.message})"
136
- rescue RZstd::OutputSizeLimitError => e
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
- # Replace the no-dict recv codec with a fresh dict-bound one;
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
 
@@ -9,7 +9,7 @@
9
9
  # push.connect("zstd+tcp://127.0.0.1:5555", level: -3)
10
10
 
11
11
  require "omq"
12
- require "rzstd"
12
+ require "zrip"
13
13
 
14
14
  require_relative "zstd_tcp/codec"
15
15
  require_relative "zstd_tcp/connection"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module Zstd
5
- VERSION = "0.4.2"
5
+ VERSION = "0.4.3"
6
6
  end
7
7
  end
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.2
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.23'
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.23'
25
+ version: '0.28'
26
26
  - !ruby/object:Gem::Dependency
27
- name: rzstd
27
+ name: zrip
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.4'
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: '0.4'
40
- description: Adds zstd+tcp:// endpoint support to OMQ with per-frame Zstd compression,
41
- bounded decompression, in-band dictionary shipping, and sender-side dictionary training.
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/paddor/omq-zstd
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: '3.3'
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.10
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: []