dalli 3.2.8 → 5.0.1

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 +235 -1
  3. data/Gemfile +16 -2
  4. data/README.md +82 -2
  5. data/lib/dalli/client.rb +239 -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 +99 -12
  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: 02d0fa949b7a065f86fb3ac7b511a3feacc50b700e82dcb385e0e79c56583a9d
4
+ data.tar.gz: d72b9e4b014ae1ae0b0d5a1ebe09ea48cdf9fbf0a2cda000efc19f17d5d34368
5
5
  SHA512:
6
- metadata.gz: 3e316a4d60c3327cecec46a0a34c52536130199124035c375bf1933676d85fbf09e99a985ae44faf4e27b0fb1f8b8faef1656a812e6bdfc387406c5e18874461
7
- data.tar.gz: bce3e0e41c280e99889bea6835c1b2f701cbcb5e4f398203ae2b4d6ebc05cdc505ee5955365715f8e00441d69701ceac84de37a98eac8581d003d1ab411a33e9
6
+ metadata.gz: bf26484aa345df2d43a78da570027b68a7fd0dd70d7a62275ebe95a5e29ed36c11a8b1385ff0c71818f55184245e085cd75962510f9f8559aad10b0d284f8d71
7
+ data.tar.gz: 0b3913a33f61873d6e3da0d62ec643dbf49f679740d3469b7cb826083f02f3b32d37e2a20548634d09cecdc4389da7c5bdc43af530bb48956cb4084f91f10d9e
data/CHANGELOG.md CHANGED
@@ -1,9 +1,243 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
- Unreleased
4
+ 5.0.1
5
5
  ==========
6
6
 
7
+ Performance:
8
+
9
+ - Reduce object allocations in pipelined get response processing (#1072, #1078)
10
+ - 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
11
+ - Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing
12
+ - Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
13
+ - `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`
14
+ - Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
15
+
16
+ Bug Fixes:
17
+
18
+ - Rescue `IOError` in connection manager `write`/`flush` methods (#1075)
19
+ - Prevents unhandled exceptions when a connection is closed mid-operation
20
+ - Thanks to Graham Cooper (Shopify) for this fix
21
+
22
+ Development:
23
+
24
+ - Add `rubocop-thread_safety` for detecting thread-safety issues (#1076)
25
+ - Add CONTRIBUTING.md with AI contribution policy (#1074)
26
+
27
+ 5.0.0
28
+ ==========
29
+
30
+ **Breaking Changes:**
31
+
32
+ - **Removed binary protocol** - The meta protocol is now the only supported protocol
33
+ - The `:protocol` option is no longer used
34
+ - Requires memcached 1.6+ (for meta protocol support)
35
+ - Users on older memcached versions must upgrade or stay on Dalli 4.x
36
+
37
+ - **Removed SASL authentication** - The meta protocol does not support authentication
38
+ - Use network-level security (firewall rules, VPN) or memcached's TLS support instead
39
+ - Users requiring SASL authentication must stay on Dalli 4.x with binary protocol
40
+
41
+ - **Ruby 3.3+ required** - Dropped support for Ruby 3.1 and 3.2
42
+ - Ruby 3.2 reached end-of-life in March 2026
43
+ - JRuby remains supported
44
+
45
+ Performance:
46
+
47
+ - **~7% read performance improvement** (CRuby only)
48
+ - Use native `IO#read` instead of custom `readfull` implementation
49
+ - Enabled by Ruby 3.3's `IO#timeout=` support
50
+ - JRuby continues to use `readfull` for compatibility
51
+
52
+ OpenTelemetry:
53
+
54
+ - Migrate to stable OTel semantic conventions (#1070)
55
+ - `db.system` renamed to `db.system.name`
56
+ - `db.operation` renamed to `db.operation.name`
57
+ - `server.address` now contains hostname only; `server.port` is a separate integer attribute
58
+ - `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
59
+ - Add `db.query.text` span attribute with configurable modes
60
+ - `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
61
+ - Add `peer.service` span attribute
62
+ - `:otel_peer_service` option for logical service naming
63
+
64
+ Internal:
65
+
66
+ - Simplified protocol directory structure: moved `lib/dalli/protocol/meta/*` to `lib/dalli/protocol/`
67
+ - Removed deprecated binary protocol files and SASL authentication code
68
+ - Removed `require 'set'` (autoloaded in Ruby 3.3+)
69
+
70
+ 4.3.3
71
+ ==========
72
+
73
+ Performance:
74
+
75
+ - Reduce object allocations in pipelined get response processing (#1072)
76
+ - 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
77
+ - Inline response processor parsing: avoid intermediate array allocations from `split`-based header parsing in both binary and meta protocols
78
+ - Block-based `pipeline_next_responses`: yield `(key, value, cas)` directly when a block is given, avoiding per-call Hash allocation
79
+ - `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`
80
+ - Add cross-version benchmark script (`bin/compare_versions`) for reproducible performance comparisons across Dalli versions
81
+
82
+ Bug Fixes:
83
+
84
+ - Skip OTel integration tests when meta protocol is unavailable (#1072)
85
+
86
+ 4.3.2
87
+ ==========
88
+
89
+ OpenTelemetry:
90
+
91
+ - Migrate to stable OTel semantic conventions
92
+ - `db.system` renamed to `db.system.name`
93
+ - `db.operation` renamed to `db.operation.name`
94
+ - `server.address` now contains hostname only; `server.port` is a separate integer attribute
95
+ - `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
96
+ - Add `db.query.text` span attribute with configurable modes
97
+ - `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
98
+ - Add `peer.service` span attribute
99
+ - `:otel_peer_service` option for logical service naming
100
+
101
+ 4.3.1
102
+ ==========
103
+
104
+ Bug Fixes:
105
+
106
+ - Fix socket compatibility with gems that monkey-patch TCPSocket (#996, #1012)
107
+ - Gems like `socksify` and `resolv-replace` modify `TCPSocket#initialize`, breaking Ruby 3.0+'s `connect_timeout:` keyword argument
108
+ - Detection now uses parameter signature checking instead of gem-specific method detection
109
+ - Falls back to `Timeout.timeout` when monkey-patching is detected
110
+ - Detection result is cached for performance
111
+
112
+ - Fix network retry bug with `socket_max_failures: 0` (#1065)
113
+ - Previously, setting `socket_max_failures: 0` could still cause retries due to error handling
114
+ - Introduced `RetryableNetworkError` subclass to distinguish retryable vs non-retryable errors
115
+ - `down!` now raises non-retryable `NetworkError`, `reconnect!` raises `RetryableNetworkError`
116
+ - Thanks to Graham Cooper (Shopify) for this fix
117
+
118
+ - Fix "character class has duplicated range" Ruby warning (#1067)
119
+ - Fixed regex in `KeyManager::VALID_NAMESPACE_SEPARATORS` that caused warnings on newer Ruby versions
120
+ - Thanks to Hartley McGuire for this fix
121
+
122
+ Improvements:
123
+
124
+ - Add StrictWarnings test helper to catch Ruby warnings early (#1067)
125
+
126
+ - Use bulk attribute setter for OpenTelemetry spans (#1068)
127
+ - Reduces lock acquisitions when setting span attributes
128
+ - Thanks to Robert Laurin (Shopify) for this optimization
129
+
130
+ - Fix double recording of exceptions on OpenTelemetry spans (#1069)
131
+ - OpenTelemetry's `in_span` method already records exceptions and sets error status automatically
132
+ - Removed redundant explicit exception recording that caused exceptions to appear twice in traces
133
+ - Thanks to Robert Laurin (Shopify) for this fix
134
+
135
+ 4.3.0
136
+ ==========
137
+
138
+ New Features:
139
+
140
+ - Add `namespace_separator` option to customize the separator between namespace and key (#1019)
141
+ - Default is `:` for backward compatibility
142
+ - Must be a single non-alphanumeric character (e.g., `:`, `/`, `|`, `.`)
143
+ - Example: `Dalli::Client.new(servers, namespace: 'myapp', namespace_separator: '/')`
144
+
145
+ Bug Fixes:
146
+
147
+ - Fix architecture-dependent struct timeval packing for socket timeouts (#1034)
148
+ - Detects correct pack format for time_t and suseconds_t on each platform
149
+ - Fixes timeout issues on architectures with 64-bit time_t
150
+
151
+ - Fix get_multi hanging with large key counts (#776, #941)
152
+ - Add interleaved read/write for pipelined gets to prevent socket buffer deadlock
153
+ - For batches over 10,000 keys per server, requests are now sent in chunks
154
+
155
+ - **Breaking:** Enforce string-only values in raw mode (#1022)
156
+ - `set(key, nil, raw: true)` now raises `MarshalError` instead of storing `""`
157
+ - `set(key, 123, raw: true)` now raises `MarshalError` instead of storing `"123"`
158
+ - This matches the behavior of client-level `raw: true` mode
159
+ - To store counters, use string values: `set('counter', '0', raw: true)`
160
+
161
+ CI:
162
+
163
+ - Add TruffleRuby to CI test matrix (#988)
164
+
165
+ 4.2.0
166
+ ==========
167
+
168
+ Performance:
169
+
170
+ - Buffered I/O: Use `socket.sync = false` with explicit flush to reduce syscalls for pipelined operations
171
+ - get_multi optimizations: Use Set for O(1) server tracking lookups
172
+ - Raw mode optimization: Skip bitflags request in meta protocol when in raw mode (saves 2 bytes per request)
173
+
174
+ New Features:
175
+
176
+ - OpenTelemetry tracing support: Automatically instruments operations when OpenTelemetry SDK is present
177
+ - Zero overhead when OpenTelemetry is not loaded
178
+ - Traces `get`, `set`, `delete`, `get_multi`, `set_multi`, `delete_multi`, `get_with_metadata`, and `fetch_with_lock`
179
+ - Spans include `db.system: memcached` and `db.operation` attributes
180
+ - Single-key operations include `server.address` attribute
181
+ - Multi-key operations include `db.memcached.key_count` attribute
182
+ - `get_multi` spans include `db.memcached.hit_count` and `db.memcached.miss_count` for cache efficiency metrics
183
+ - Exceptions are automatically recorded on spans with error status
184
+
185
+ 4.1.0
186
+ ==========
187
+
188
+ New Features:
189
+
190
+ - Add `set_multi` for efficient bulk set operations using pipelined requests
191
+ - Add `delete_multi` for efficient bulk delete operations using pipelined requests
192
+ - Add `fetch_with_lock` for thundering herd protection using meta protocol's vivify/recache flags (requires memcached 1.6+)
193
+ - Add thundering herd protection support to meta protocol (requires memcached 1.6+):
194
+ - `N` (vivify) flag for creating stubs on cache miss
195
+ - `R` (recache) flag for winning recache race when TTL is below threshold
196
+ - Response flags `W` (won recache), `X` (stale), `Z` (lost race)
197
+ - `delete_stale` method for marking items as stale instead of deleting
198
+ - Add `get_with_metadata` for advanced cache operations with metadata retrieval (requires memcached 1.6+):
199
+ - Returns hash with `:value`, `:cas`, `:won_recache`, `:stale`, `:lost_recache`
200
+ - Optional `:return_hit_status` returns `:hit_before` (true/false for previous access)
201
+ - Optional `:return_last_access` returns `:last_access` (seconds since last access)
202
+ - Optional `:skip_lru_bump` prevents LRU update on access
203
+ - Optional `:vivify_ttl` and `:recache_ttl` for thundering herd protection
204
+
205
+ Deprecations:
206
+
207
+ - Binary protocol is deprecated and will be removed in Dalli 5.0. Use `protocol: :meta` instead (requires memcached 1.6+)
208
+ - SASL authentication is deprecated and will be removed in Dalli 5.0. Consider using network-level security or memcached's TLS support
209
+
210
+ 4.0.1
211
+ ==========
212
+
213
+ - Add `:raw` client option to skip serialization entirely, returning raw byte strings
214
+ - Handle `OpenSSL::SSL::SSLError` in connection manager
215
+
216
+ 4.0.0
217
+ ==========
218
+
219
+ BREAKING CHANGES:
220
+
221
+ - Require Ruby 3.1+ (dropped support for Ruby 2.6, 2.7, and 3.0)
222
+ - Removed `Dalli::Server` deprecated alias - use `Dalli::Protocol::Binary` instead
223
+ - Removed `:compression` option - use `:compress` instead
224
+ - Removed `close_on_fork` method - use `reconnect_on_fork` instead
225
+
226
+ Other changes:
227
+
228
+ - Add security warning when using default Marshal serializer (silence with `silence_marshal_warning: true`)
229
+ - Add defense-in-depth input validation for stats command arguments
230
+ - Add `string_fastpath` option to skip serialization for simple strings (byroot)
231
+ - Meta protocol set performance improvement (danmayer)
232
+ - Fix connection_pool 3.0 compatibility for Rack session store
233
+ - Fix session recovery after deletion (stengineering0)
234
+ - Fix cannot read response data included terminator `\r\n` when use meta protocol (matsubara0507)
235
+ - Support SERVER_ERROR response from Memcached as per the [memcached spec](https://github.com/memcached/memcached/blob/e43364402195c8e822bb8f88755a60ab8bbed62a/doc/protocol.txt#L172) (grcooper)
236
+ - Update Socket timeout handling to use Socket#timeout= when available (nickamorim)
237
+ - Serializer: reraise all .load errors as UnmarshalError (olleolleolle)
238
+ - Reconnect gracefully when a fork is detected instead of crashing (PatrickTulskie)
239
+ - Update CI to test against memcached 1.6.40
240
+
7
241
  3.2.8
8
242
  ==========
9
243
 
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