dalli 3.2.8 → 5.0.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +249 -1
  3. data/Gemfile +16 -2
  4. data/README.md +82 -2
  5. data/lib/dalli/client.rb +286 -25
  6. data/lib/dalli/instrumentation.rb +143 -0
  7. data/lib/dalli/key_manager.rb +23 -8
  8. data/lib/dalli/options.rb +1 -1
  9. data/lib/dalli/pid_cache.rb +1 -1
  10. data/lib/dalli/pipelined_deleter.rb +82 -0
  11. data/lib/dalli/pipelined_getter.rb +44 -20
  12. data/lib/dalli/pipelined_setter.rb +87 -0
  13. data/lib/dalli/protocol/base.rb +94 -24
  14. data/lib/dalli/protocol/connection_manager.rb +23 -12
  15. data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +1 -1
  16. data/lib/dalli/protocol/meta.rb +173 -10
  17. data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +42 -10
  18. data/lib/dalli/protocol/response_buffer.rb +36 -12
  19. data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +72 -26
  20. data/lib/dalli/protocol/server_config_parser.rb +2 -2
  21. data/lib/dalli/protocol/string_marshaller.rb +65 -0
  22. data/lib/dalli/protocol/ttl_sanitizer.rb +1 -1
  23. data/lib/dalli/protocol/value_compressor.rb +2 -11
  24. data/lib/dalli/protocol/value_marshaller.rb +1 -1
  25. data/lib/dalli/protocol/value_serializer.rb +59 -40
  26. data/lib/dalli/protocol.rb +10 -0
  27. data/lib/dalli/ring.rb +2 -2
  28. data/lib/dalli/servers_arg_normalizer.rb +1 -1
  29. data/lib/dalli/socket.rb +74 -14
  30. data/lib/dalli/version.rb +2 -2
  31. data/lib/dalli.rb +12 -5
  32. data/lib/rack/session/dalli.rb +43 -8
  33. metadata +26 -17
  34. data/lib/dalli/protocol/binary/request_formatter.rb +0 -117
  35. data/lib/dalli/protocol/binary/response_header.rb +0 -36
  36. data/lib/dalli/protocol/binary/response_processor.rb +0 -239
  37. data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
  38. data/lib/dalli/protocol/binary.rb +0 -173
  39. data/lib/dalli/server.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7d8b3dd9e76a224d876f901dc44c3cf8016326209df3efc7fe522053a4a3c22
4
- data.tar.gz: 0ced058a6a170cadd4b74268de5417c4a1e700baf5767fc8f260db9477e2b735
3
+ metadata.gz: ae3cbae2603955279bb78b75682ac1f56105d5ec2b2e0d464ab15255ed23369b
4
+ data.tar.gz: 2e5a3960e638293cfdf5663ef959d1ab9690620e09f0151a844cdeb9424cbf80
5
5
  SHA512:
6
- metadata.gz: 3e316a4d60c3327cecec46a0a34c52536130199124035c375bf1933676d85fbf09e99a985ae44faf4e27b0fb1f8b8faef1656a812e6bdfc387406c5e18874461
7
- data.tar.gz: bce3e0e41c280e99889bea6835c1b2f701cbcb5e4f398203ae2b4d6ebc05cdc505ee5955365715f8e00441d69701ceac84de37a98eac8581d003d1ab411a33e9
6
+ metadata.gz: 243382482bc345bf982f2cb96fae28b974f37b2eee653a71c7f3a85518ef5acdbfda6f2fddacfa31da6252774000e11a1c96d3707d29808c636951d85ace17be
7
+ data.tar.gz: 5938608c26cd307e397cad4fdf6e2fda4519fdbd8df2b7d75352b691996fec4dca59632cc0053b8bf410612c8e6cada73ccad7baf5c000a183bd04bb196d056e
data/CHANGELOG.md CHANGED
@@ -1,9 +1,257 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
- Unreleased
4
+ 5.0.2
5
5
  ==========
6
6
 
7
+ Performance:
8
+
9
+ - Add single-server fast path for `get_multi`, `set_multi`, and `delete_multi` (#1077)
10
+ - When only one memcached server is configured, bypass the `Pipelined*` machinery (IO.select, response buffering, server grouping) and issue all quiet meta requests inline followed by a noop terminator
11
+ - `get_multi` shows ~1.5x improvement at 10 keys and ~1.75x at 100–500 keys compared to the `PipelinedGetter` path
12
+ - Thanks to Dan Mayer (Shopify) for this contribution
13
+
14
+ Development:
15
+
16
+ - Add `bin/benchmark_branch` script for benchmarking against the current branch
17
+
18
+ 5.0.1
19
+ ==========
20
+
21
+ Performance:
22
+
23
+ - Reduce object allocations in pipelined get response processing (#1072, #1078)
24
+ - Offset-based `ResponseBuffer`: track a read offset instead of slicing a new string after every parsed response; compact only when the consumed portion exceeds 4KB and more than half the buffer
25
+ - Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing
26
+ - Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
27
+ - `PipelinedGetter`: replace Hash-based socket-to-server mapping with linear scan (faster for typical 1-5 server counts); use `Process.clock_gettime(CLOCK_MONOTONIC)` instead of `Time.now`
28
+ - Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
29
+
30
+ Bug Fixes:
31
+
32
+ - Rescue `IOError` in connection manager `write`/`flush` methods (#1075)
33
+ - Prevents unhandled exceptions when a connection is closed mid-operation
34
+ - Thanks to Graham Cooper (Shopify) for this fix
35
+
36
+ Development:
37
+
38
+ - Add `rubocop-thread_safety` for detecting thread-safety issues (#1076)
39
+ - Add CONTRIBUTING.md with AI contribution policy (#1074)
40
+
41
+ 5.0.0
42
+ ==========
43
+
44
+ **Breaking Changes:**
45
+
46
+ - **Removed binary protocol** - The meta protocol is now the only supported protocol
47
+ - The `:protocol` option is no longer used
48
+ - Requires memcached 1.6+ (for meta protocol support)
49
+ - Users on older memcached versions must upgrade or stay on Dalli 4.x
50
+
51
+ - **Removed SASL authentication** - The meta protocol does not support authentication
52
+ - Use network-level security (firewall rules, VPN) or memcached's TLS support instead
53
+ - Users requiring SASL authentication must stay on Dalli 4.x with binary protocol
54
+
55
+ - **Ruby 3.3+ required** - Dropped support for Ruby 3.1 and 3.2
56
+ - Ruby 3.2 reached end-of-life in March 2026
57
+ - JRuby remains supported
58
+
59
+ Performance:
60
+
61
+ - **~7% read performance improvement** (CRuby only)
62
+ - Use native `IO#read` instead of custom `readfull` implementation
63
+ - Enabled by Ruby 3.3's `IO#timeout=` support
64
+ - JRuby continues to use `readfull` for compatibility
65
+
66
+ OpenTelemetry:
67
+
68
+ - Migrate to stable OTel semantic conventions (#1070)
69
+ - `db.system` renamed to `db.system.name`
70
+ - `db.operation` renamed to `db.operation.name`
71
+ - `server.address` now contains hostname only; `server.port` is a separate integer attribute
72
+ - `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
73
+ - Add `db.query.text` span attribute with configurable modes
74
+ - `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
75
+ - Add `peer.service` span attribute
76
+ - `:otel_peer_service` option for logical service naming
77
+
78
+ Internal:
79
+
80
+ - Simplified protocol directory structure: moved `lib/dalli/protocol/meta/*` to `lib/dalli/protocol/`
81
+ - Removed deprecated binary protocol files and SASL authentication code
82
+ - Removed `require 'set'` (autoloaded in Ruby 3.3+)
83
+
84
+ 4.3.3
85
+ ==========
86
+
87
+ Performance:
88
+
89
+ - Reduce object allocations in pipelined get response processing (#1072)
90
+ - Offset-based `ResponseBuffer`: track a read offset instead of slicing a new string after every parsed response; compact only when the consumed portion exceeds 4KB and more than half the buffer
91
+ - Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing in both binary and meta protocols
92
+ - Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
93
+ - `PipelinedGetter`: replace Hash-based socket-to-server mapping with linear scan (faster for typical 1-5 server counts); use `Process.clock_gettime(CLOCK_MONOTONIC)` instead of `Time.now`
94
+ - Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
95
+
96
+ Bug Fixes:
97
+
98
+ - Skip OTel integration tests when meta protocol is unavailable (#1072)
99
+
100
+ 4.3.2
101
+ ==========
102
+
103
+ OpenTelemetry:
104
+
105
+ - Migrate to stable OTel semantic conventions
106
+ - `db.system` renamed to `db.system.name`
107
+ - `db.operation` renamed to `db.operation.name`
108
+ - `server.address` now contains hostname only; `server.port` is a separate integer attribute
109
+ - `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
110
+ - Add `db.query.text` span attribute with configurable modes
111
+ - `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
112
+ - Add `peer.service` span attribute
113
+ - `:otel_peer_service` option for logical service naming
114
+
115
+ 4.3.1
116
+ ==========
117
+
118
+ Bug Fixes:
119
+
120
+ - Fix socket compatibility with gems that monkey-patch TCPSocket (#996, #1012)
121
+ - Gems like `socksify` and `resolv-replace` modify `TCPSocket#initialize`, breaking Ruby 3.0+'s `connect_timeout:` keyword argument
122
+ - Detection now uses parameter signature checking instead of gem-specific method detection
123
+ - Falls back to `Timeout.timeout` when monkey-patching is detected
124
+ - Detection result is cached for performance
125
+
126
+ - Fix network retry bug with `socket_max_failures: 0` (#1065)
127
+ - Previously, setting `socket_max_failures: 0` could still cause retries due to error handling
128
+ - Introduced `RetryableNetworkError` subclass to distinguish retryable vs non-retryable errors
129
+ - `down!` now raises non-retryable `NetworkError`, `reconnect!` raises `RetryableNetworkError`
130
+ - Thanks to Graham Cooper (Shopify) for this fix
131
+
132
+ - Fix "character class has duplicated range" Ruby warning (#1067)
133
+ - Fixed regex in `KeyManager::VALID_NAMESPACE_SEPARATORS` that caused warnings on newer Ruby versions
134
+ - Thanks to Hartley McGuire for this fix
135
+
136
+ Improvements:
137
+
138
+ - Add StrictWarnings test helper to catch Ruby warnings early (#1067)
139
+
140
+ - Use bulk attribute setter for OpenTelemetry spans (#1068)
141
+ - Reduces lock acquisitions when setting span attributes
142
+ - Thanks to Robert Laurin (Shopify) for this optimization
143
+
144
+ - Fix double recording of exceptions on OpenTelemetry spans (#1069)
145
+ - OpenTelemetry's `in_span` method already records exceptions and sets error status automatically
146
+ - Removed redundant explicit exception recording that caused exceptions to appear twice in traces
147
+ - Thanks to Robert Laurin (Shopify) for this fix
148
+
149
+ 4.3.0
150
+ ==========
151
+
152
+ New Features:
153
+
154
+ - Add `namespace_separator` option to customize the separator between namespace and key (#1019)
155
+ - Default is `:` for backward compatibility
156
+ - Must be a single non-alphanumeric character (e.g., `:`, `/`, `|`, `.`)
157
+ - Example: `Dalli::Client.new(servers, namespace: 'myapp', namespace_separator: '/')`
158
+
159
+ Bug Fixes:
160
+
161
+ - Fix architecture-dependent struct timeval packing for socket timeouts (#1034)
162
+ - Detects correct pack format for time_t and suseconds_t on each platform
163
+ - Fixes timeout issues on architectures with 64-bit time_t
164
+
165
+ - Fix get_multi hanging with large key counts (#776, #941)
166
+ - Add interleaved read/write for pipelined gets to prevent socket buffer deadlock
167
+ - For batches over 10,000 keys per server, requests are now sent in chunks
168
+
169
+ - **Breaking:** Enforce string-only values in raw mode (#1022)
170
+ - `set(key, nil, raw: true)` now raises `MarshalError` instead of storing `""`
171
+ - `set(key, 123, raw: true)` now raises `MarshalError` instead of storing `"123"`
172
+ - This matches the behavior of client-level `raw: true` mode
173
+ - To store counters, use string values: `set('counter', '0', raw: true)`
174
+
175
+ CI:
176
+
177
+ - Add TruffleRuby to CI test matrix (#988)
178
+
179
+ 4.2.0
180
+ ==========
181
+
182
+ Performance:
183
+
184
+ - Buffered I/O: Use `socket.sync = false` with explicit flush to reduce syscalls for pipelined operations
185
+ - get_multi optimizations: Use Set for O(1) server tracking lookups
186
+ - Raw mode optimization: Skip bitflags request in meta protocol when in raw mode (saves 2 bytes per request)
187
+
188
+ New Features:
189
+
190
+ - OpenTelemetry tracing support: Automatically instruments operations when OpenTelemetry SDK is present
191
+ - Zero overhead when OpenTelemetry is not loaded
192
+ - Traces `get`, `set`, `delete`, `get_multi`, `set_multi`, `delete_multi`, `get_with_metadata`, and `fetch_with_lock`
193
+ - Spans include `db.system: memcached` and `db.operation` attributes
194
+ - Single-key operations include `server.address` attribute
195
+ - Multi-key operations include `db.memcached.key_count` attribute
196
+ - `get_multi` spans include `db.memcached.hit_count` and `db.memcached.miss_count` for cache efficiency metrics
197
+ - Exceptions are automatically recorded on spans with error status
198
+
199
+ 4.1.0
200
+ ==========
201
+
202
+ New Features:
203
+
204
+ - Add `set_multi` for efficient bulk set operations using pipelined requests
205
+ - Add `delete_multi` for efficient bulk delete operations using pipelined requests
206
+ - Add `fetch_with_lock` for thundering herd protection using meta protocol's vivify/recache flags (requires memcached 1.6+)
207
+ - Add thundering herd protection support to meta protocol (requires memcached 1.6+):
208
+ - `N` (vivify) flag for creating stubs on cache miss
209
+ - `R` (recache) flag for winning recache race when TTL is below threshold
210
+ - Response flags `W` (won recache), `X` (stale), `Z` (lost race)
211
+ - `delete_stale` method for marking items as stale instead of deleting
212
+ - Add `get_with_metadata` for advanced cache operations with metadata retrieval (requires memcached 1.6+):
213
+ - Returns hash with `:value`, `:cas`, `:won_recache`, `:stale`, `:lost_recache`
214
+ - Optional `:return_hit_status` returns `:hit_before` (true/false for previous access)
215
+ - Optional `:return_last_access` returns `:last_access` (seconds since last access)
216
+ - Optional `:skip_lru_bump` prevents LRU update on access
217
+ - Optional `:vivify_ttl` and `:recache_ttl` for thundering herd protection
218
+
219
+ Deprecations:
220
+
221
+ - Binary protocol is deprecated and will be removed in Dalli 5.0. Use `protocol: :meta` instead (requires memcached 1.6+)
222
+ - SASL authentication is deprecated and will be removed in Dalli 5.0. Consider using network-level security or memcached's TLS support
223
+
224
+ 4.0.1
225
+ ==========
226
+
227
+ - Add `:raw` client option to skip serialization entirely, returning raw byte strings
228
+ - Handle `OpenSSL::SSL::SSLError` in connection manager
229
+
230
+ 4.0.0
231
+ ==========
232
+
233
+ BREAKING CHANGES:
234
+
235
+ - Require Ruby 3.1+ (dropped support for Ruby 2.6, 2.7, and 3.0)
236
+ - Removed `Dalli::Server` deprecated alias - use `Dalli::Protocol::Binary` instead
237
+ - Removed `:compression` option - use `:compress` instead
238
+ - Removed `close_on_fork` method - use `reconnect_on_fork` instead
239
+
240
+ Other changes:
241
+
242
+ - Add security warning when using default Marshal serializer (silence with `silence_marshal_warning: true`)
243
+ - Add defense-in-depth input validation for stats command arguments
244
+ - Add `string_fastpath` option to skip serialization for simple strings (byroot)
245
+ - Meta protocol set performance improvement (danmayer)
246
+ - Fix connection_pool 3.0 compatibility for Rack session store
247
+ - Fix session recovery after deletion (stengineering0)
248
+ - Fix cannot read response data included terminator `\r\n` when use meta protocol (matsubara0507)
249
+ - Support SERVER_ERROR response from Memcached as per the [memcached spec](https://github.com/memcached/memcached/blob/e43364402195c8e822bb8f88755a60ab8bbed62a/doc/protocol.txt#L172) (grcooper)
250
+ - Update Socket timeout handling to use Socket#timeout= when available (nickamorim)
251
+ - Serializer: reraise all .load errors as UnmarshalError (olleolleolle)
252
+ - Reconnect gracefully when a fork is detected instead of crashing (PatrickTulskie)
253
+ - Update CI to test against memcached 1.6.40
254
+
7
255
  3.2.8
8
256
  ==========
9
257
 
data/Gemfile CHANGED
@@ -5,17 +5,31 @@ source 'https://rubygems.org'
5
5
  gemspec
6
6
 
7
7
  group :development, :test do
8
+ gem 'benchmark'
9
+ gem 'cgi'
8
10
  gem 'connection_pool'
9
- gem 'minitest', '~> 5'
10
- gem 'rack', '~> 2.0', '>= 2.2.0'
11
+ gem 'debug' unless RUBY_PLATFORM == 'java'
12
+ if RUBY_VERSION >= '3.2'
13
+ gem 'minitest', '~> 6'
14
+ gem 'minitest-mock'
15
+ else
16
+ gem 'minitest', '~> 5'
17
+ end
18
+ gem 'rack', '~> 3'
19
+ gem 'rack-session'
11
20
  gem 'rake', '~> 13.0'
12
21
  gem 'rubocop'
13
22
  gem 'rubocop-minitest'
14
23
  gem 'rubocop-performance'
15
24
  gem 'rubocop-rake'
25
+ gem 'rubocop-thread_safety'
16
26
  gem 'simplecov'
17
27
  end
18
28
 
19
29
  group :test do
20
30
  gem 'ruby-prof', platform: :mri
31
+
32
+ # For socket compatibility testing (these gems monkey-patch TCPSocket)
33
+ gem 'resolv-replace', require: false
34
+ gem 'socksify', require: false
21
35
  end
data/README.md CHANGED
@@ -10,10 +10,90 @@ Dalli supports:
10
10
  * Fine-grained control of data serialization and compression
11
11
  * Thread-safe operation (either through use of a connection pool, or by using the Dalli client in threadsafe mode)
12
12
  * SSL/TLS connections to memcached
13
- * SASL authentication
13
+ * OpenTelemetry distributed tracing (automatic when SDK is present)
14
14
 
15
15
  The name is a variant of Salvador Dali for his famous painting [The Persistence of Memory](http://en.wikipedia.org/wiki/The_Persistence_of_Memory).
16
16
 
17
+ ## Requirements
18
+
19
+ * Ruby 3.3 or later (JRuby also supported)
20
+ * memcached 1.6 or later
21
+
22
+ ## Configuration Options
23
+
24
+ ### Namespace
25
+
26
+ Use namespaces to partition your cache and avoid key collisions between different applications or environments:
27
+
28
+ ```ruby
29
+ # All keys will be prefixed with "myapp:"
30
+ Dalli::Client.new('localhost:11211', namespace: 'myapp')
31
+
32
+ # Dynamic namespace using a Proc (evaluated on each operation)
33
+ Dalli::Client.new('localhost:11211', namespace: -> { "tenant:#{Thread.current[:tenant_id]}" })
34
+ ```
35
+
36
+ ### Namespace Separator
37
+
38
+ By default, the namespace and key are joined with a colon (`:`). You can customize this with the `namespace_separator` option:
39
+
40
+ ```ruby
41
+ # Keys will be prefixed with "myapp/" instead of "myapp:"
42
+ Dalli::Client.new('localhost:11211', namespace: 'myapp', namespace_separator: '/')
43
+ ```
44
+
45
+ The separator must be a single non-alphanumeric character. Valid examples: `:`, `/`, `|`, `.`, `-`, `_`, `#`
46
+
47
+ ## Security Note
48
+
49
+ By default, Dalli uses Ruby's Marshal for serialization. Deserializing untrusted data with Marshal can lead to remote code execution. If you cache user-controlled data, consider using a safer serializer:
50
+
51
+ ```ruby
52
+ Dalli::Client.new('localhost:11211', serializer: JSON)
53
+ ```
54
+
55
+ See the [5.0-Upgrade.md](5.0-Upgrade.md) guide for upgrade information.
56
+
57
+ ## OpenTelemetry Tracing
58
+
59
+ Dalli automatically instruments operations with [OpenTelemetry](https://opentelemetry.io/) when the SDK is present. No configuration is required - just add the OpenTelemetry gems to your application:
60
+
61
+ ```ruby
62
+ # Gemfile
63
+ gem 'opentelemetry-sdk'
64
+ gem 'opentelemetry-exporter-otlp' # or your preferred exporter
65
+ ```
66
+
67
+ When OpenTelemetry is loaded, Dalli creates spans for:
68
+ - Single key operations: `get`, `set`, `delete`, `add`, `replace`, `incr`, `decr`, etc.
69
+ - Multi-key operations: `get_multi`, `set_multi`, `delete_multi`
70
+ - Advanced operations: `get_with_metadata`, `fetch_with_lock`
71
+
72
+ ### Span Attributes
73
+
74
+ All spans include:
75
+ - `db.system`: `memcached`
76
+ - `db.operation`: The operation name (e.g., `get`, `set_multi`)
77
+
78
+ Single-key operations also include:
79
+ - `server.address`: The memcached server that handled the request (e.g., `localhost:11211`)
80
+
81
+ Multi-key operations include cache efficiency metrics:
82
+ - `db.memcached.key_count`: Number of keys in the request
83
+ - `db.memcached.hit_count`: Number of keys found (for `get_multi`)
84
+ - `db.memcached.miss_count`: Number of keys not found (for `get_multi`)
85
+
86
+ ### Error Handling
87
+
88
+ Exceptions are automatically recorded on spans with error status. When an operation fails:
89
+ 1. The exception is recorded on the span via `span.record_exception(e)`
90
+ 2. The span status is set to error with the exception message
91
+ 3. The exception is re-raised to the caller
92
+
93
+ ### Zero Overhead
94
+
95
+ When OpenTelemetry is not present, there is zero overhead - the tracing code checks once at startup and bypasses all instrumentation logic entirely when the SDK is not loaded.
96
+
17
97
  ![Persistence of Memory](https://upload.wikimedia.org/wikipedia/en/d/dd/The_Persistence_of_Memory.jpg)
18
98
 
19
99
 
@@ -33,7 +113,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
33
113
 
34
114
  ## Contributing
35
115
 
36
- If you have a fix you wish to provide, please fork the code, fix in your local project and then send a pull request on github. Please ensure that you include a test which verifies your fix and update the [changelog](CHANGELOG.md) with a one sentence description of your fix so you get credit as a contributor.
116
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute, including our policy on AI-authored contributions.
37
117
 
38
118
  ## Appreciation
39
119