dalli 4.3.0 → 5.0.0
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/CHANGELOG.md +77 -0
- data/Gemfile +4 -0
- data/README.md +3 -15
- data/lib/dalli/client.rb +57 -59
- data/lib/dalli/instrumentation.rb +26 -24
- data/lib/dalli/key_manager.rb +1 -1
- data/lib/dalli/options.rb +1 -1
- data/lib/dalli/pipelined_getter.rb +2 -4
- data/lib/dalli/pipelined_setter.rb +1 -1
- data/lib/dalli/protocol/base.rb +12 -14
- data/lib/dalli/protocol/connection_manager.rb +8 -2
- data/lib/dalli/protocol/meta.rb +3 -7
- data/lib/dalli/protocol/server_config_parser.rb +1 -1
- data/lib/dalli/ring.rb +2 -2
- data/lib/dalli/servers_arg_normalizer.rb +1 -1
- data/lib/dalli/socket.rb +17 -8
- data/lib/dalli/version.rb +2 -2
- data/lib/dalli.rb +3 -2
- metadata +6 -12
- data/lib/dalli/protocol/binary/request_formatter.rb +0 -117
- data/lib/dalli/protocol/binary/response_header.rb +0 -36
- data/lib/dalli/protocol/binary/response_processor.rb +0 -239
- data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
- data/lib/dalli/protocol/binary.rb +0 -200
- data/lib/dalli/protocol_deprecations.rb +0 -45
- /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
- /data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +0 -0
- /data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16d7d5950137f4a51ae41392eaa576732595ac4020ae7be972d6fb4b33b9861d
|
|
4
|
+
data.tar.gz: ebb799522259a9a32a86e1dbfd13e810427f787f8110415509288655a5a5460b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74e32eace9001bd50be0617f10234f1440f86e506facc7247cf6041cf81fa85e966a5721e2a7ff364eb89da19bd46cc92b2631d99d4cbb21aee8240a475a76b5
|
|
7
|
+
data.tar.gz: 8e2bc294a1fa08a04f0bdfa300394751e63a7c1a21cac6aa1080a37e9491fe33091150047f664607ae42596d0bb2ac60f05c06ad97aee83165de754b9cceeed2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
Dalli Changelog
|
|
2
2
|
=====================
|
|
3
3
|
|
|
4
|
+
5.0.0
|
|
5
|
+
==========
|
|
6
|
+
|
|
7
|
+
**Breaking Changes:**
|
|
8
|
+
|
|
9
|
+
- **Removed binary protocol** - The meta protocol is now the only supported protocol
|
|
10
|
+
- The `:protocol` option is no longer used
|
|
11
|
+
- Requires memcached 1.6+ (for meta protocol support)
|
|
12
|
+
- Users on older memcached versions must upgrade or stay on Dalli 4.x
|
|
13
|
+
|
|
14
|
+
- **Removed SASL authentication** - The meta protocol does not support authentication
|
|
15
|
+
- Use network-level security (firewall rules, VPN) or memcached's TLS support instead
|
|
16
|
+
- Users requiring SASL authentication must stay on Dalli 4.x with binary protocol
|
|
17
|
+
|
|
18
|
+
- **Ruby 3.3+ required** - Dropped support for Ruby 3.1 and 3.2
|
|
19
|
+
- Ruby 3.2 reached end-of-life in March 2026
|
|
20
|
+
- JRuby remains supported
|
|
21
|
+
|
|
22
|
+
Performance:
|
|
23
|
+
|
|
24
|
+
- **~7% read performance improvement** (CRuby only)
|
|
25
|
+
- Use native `IO#read` instead of custom `readfull` implementation
|
|
26
|
+
- Enabled by Ruby 3.3's `IO#timeout=` support
|
|
27
|
+
- JRuby continues to use `readfull` for compatibility
|
|
28
|
+
|
|
29
|
+
OpenTelemetry:
|
|
30
|
+
|
|
31
|
+
- Migrate to stable OTel semantic conventions (#1070)
|
|
32
|
+
- `db.system` renamed to `db.system.name`
|
|
33
|
+
- `db.operation` renamed to `db.operation.name`
|
|
34
|
+
- `server.address` now contains hostname only; `server.port` is a separate integer attribute
|
|
35
|
+
- `get_with_metadata` and `fetch_with_lock` now include `server.address`/`server.port`
|
|
36
|
+
- Add `db.query.text` span attribute with configurable modes
|
|
37
|
+
- `:otel_db_statement` option: `:include`, `:obfuscate`, or `nil` (default: omitted)
|
|
38
|
+
- Add `peer.service` span attribute
|
|
39
|
+
- `:otel_peer_service` option for logical service naming
|
|
40
|
+
|
|
41
|
+
Internal:
|
|
42
|
+
|
|
43
|
+
- Simplified protocol directory structure: moved `lib/dalli/protocol/meta/*` to `lib/dalli/protocol/`
|
|
44
|
+
- Removed deprecated binary protocol files and SASL authentication code
|
|
45
|
+
- Removed `require 'set'` (autoloaded in Ruby 3.3+)
|
|
46
|
+
|
|
47
|
+
4.3.1
|
|
48
|
+
==========
|
|
49
|
+
|
|
50
|
+
Bug Fixes:
|
|
51
|
+
|
|
52
|
+
- Fix socket compatibility with gems that monkey-patch TCPSocket (#996, #1012)
|
|
53
|
+
- Gems like `socksify` and `resolv-replace` modify `TCPSocket#initialize`, breaking Ruby 3.0+'s `connect_timeout:` keyword argument
|
|
54
|
+
- Detection now uses parameter signature checking instead of gem-specific method detection
|
|
55
|
+
- Falls back to `Timeout.timeout` when monkey-patching is detected
|
|
56
|
+
- Detection result is cached for performance
|
|
57
|
+
|
|
58
|
+
- Fix network retry bug with `socket_max_failures: 0` (#1065)
|
|
59
|
+
- Previously, setting `socket_max_failures: 0` could still cause retries due to error handling
|
|
60
|
+
- Introduced `RetryableNetworkError` subclass to distinguish retryable vs non-retryable errors
|
|
61
|
+
- `down!` now raises non-retryable `NetworkError`, `reconnect!` raises `RetryableNetworkError`
|
|
62
|
+
- Thanks to Graham Cooper (Shopify) for this fix
|
|
63
|
+
|
|
64
|
+
- Fix "character class has duplicated range" Ruby warning (#1067)
|
|
65
|
+
- Fixed regex in `KeyManager::VALID_NAMESPACE_SEPARATORS` that caused warnings on newer Ruby versions
|
|
66
|
+
- Thanks to Hartley McGuire for this fix
|
|
67
|
+
|
|
68
|
+
Improvements:
|
|
69
|
+
|
|
70
|
+
- Add StrictWarnings test helper to catch Ruby warnings early (#1067)
|
|
71
|
+
|
|
72
|
+
- Use bulk attribute setter for OpenTelemetry spans (#1068)
|
|
73
|
+
- Reduces lock acquisitions when setting span attributes
|
|
74
|
+
- Thanks to Robert Laurin (Shopify) for this optimization
|
|
75
|
+
|
|
76
|
+
- Fix double recording of exceptions on OpenTelemetry spans (#1069)
|
|
77
|
+
- OpenTelemetry's `in_span` method already records exceptions and sets error status automatically
|
|
78
|
+
- Removed redundant explicit exception recording that caused exceptions to appear twice in traces
|
|
79
|
+
- Thanks to Robert Laurin (Shopify) for this fix
|
|
80
|
+
|
|
4
81
|
4.3.0
|
|
5
82
|
==========
|
|
6
83
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -10,26 +10,14 @@ 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
|
|
14
13
|
* OpenTelemetry distributed tracing (automatic when SDK is present)
|
|
15
14
|
|
|
16
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).
|
|
17
16
|
|
|
18
17
|
## Requirements
|
|
19
18
|
|
|
20
|
-
* Ruby 3.
|
|
21
|
-
* memcached 1.
|
|
22
|
-
|
|
23
|
-
## Protocol Options
|
|
24
|
-
|
|
25
|
-
Dalli supports two protocols for communicating with memcached:
|
|
26
|
-
|
|
27
|
-
* `:binary` (default) - Works with all memcached versions, supports SASL authentication
|
|
28
|
-
* `:meta` - Requires memcached 1.6+, better performance for some operations, no authentication support
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
Dalli::Client.new('localhost:11211', protocol: :meta)
|
|
32
|
-
```
|
|
19
|
+
* Ruby 3.3 or later (JRuby also supported)
|
|
20
|
+
* memcached 1.6 or later
|
|
33
21
|
|
|
34
22
|
## Configuration Options
|
|
35
23
|
|
|
@@ -64,7 +52,7 @@ By default, Dalli uses Ruby's Marshal for serialization. Deserializing untrusted
|
|
|
64
52
|
Dalli::Client.new('localhost:11211', serializer: JSON)
|
|
65
53
|
```
|
|
66
54
|
|
|
67
|
-
See the [
|
|
55
|
+
See the [5.0-Upgrade.md](5.0-Upgrade.md) guide for upgrade information.
|
|
68
56
|
|
|
69
57
|
## OpenTelemetry Tracing
|
|
70
58
|
|
data/lib/dalli/client.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'digest/md5'
|
|
4
|
-
require 'set'
|
|
5
4
|
|
|
6
5
|
# encoding: ascii
|
|
7
6
|
module Dalli
|
|
@@ -48,15 +47,17 @@ module Dalli
|
|
|
48
47
|
# serialization pipeline.
|
|
49
48
|
# - :digest_class - defaults to Digest::MD5, allows you to pass in an object that responds to the hexdigest method,
|
|
50
49
|
# useful for injecting a FIPS compliant hash object.
|
|
51
|
-
# - :
|
|
52
|
-
#
|
|
50
|
+
# - :otel_db_statement - controls the +db.query.text+ span attribute when OpenTelemetry is loaded.
|
|
51
|
+
# +:include+ logs the full operation and key(s), +:obfuscate+ replaces keys with "?",
|
|
52
|
+
# +nil+ (default) omits the attribute entirely.
|
|
53
|
+
# - :otel_peer_service - when set, adds a +peer.service+ span attribute with this value for logical service naming.
|
|
53
54
|
#
|
|
54
55
|
def initialize(servers = nil, options = {})
|
|
55
56
|
@normalized_servers = ::Dalli::ServersArgNormalizer.normalize_servers(servers)
|
|
56
57
|
@options = normalize_options(options)
|
|
58
|
+
warn_removed_options(@options)
|
|
57
59
|
@key_manager = ::Dalli::KeyManager.new(@options)
|
|
58
60
|
@ring = nil
|
|
59
|
-
emit_deprecation_warnings
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
#
|
|
@@ -98,10 +99,7 @@ module Dalli
|
|
|
98
99
|
end
|
|
99
100
|
|
|
100
101
|
##
|
|
101
|
-
# Get value with extended metadata
|
|
102
|
-
#
|
|
103
|
-
# IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
|
|
104
|
-
# It will raise an error if used with the binary protocol.
|
|
102
|
+
# Get value with extended metadata.
|
|
105
103
|
#
|
|
106
104
|
# @param key [String] the cache key
|
|
107
105
|
# @param options [Hash] options controlling what metadata to return
|
|
@@ -129,13 +127,11 @@ module Dalli
|
|
|
129
127
|
# # => { value: "data", cas: 123, hit_before: true, last_access: 42 }
|
|
130
128
|
#
|
|
131
129
|
def get_with_metadata(key, options = {})
|
|
132
|
-
raise_unless_meta_protocol!
|
|
133
|
-
|
|
134
130
|
key = key.to_s
|
|
135
131
|
key = @key_manager.validate_key(key)
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
server = ring.server_for_key(key)
|
|
134
|
+
Instrumentation.trace('get_with_metadata', trace_attrs('get_with_metadata', key, server)) do
|
|
139
135
|
server.request(:meta_get, key, options)
|
|
140
136
|
end
|
|
141
137
|
rescue NetworkError => e
|
|
@@ -204,9 +200,6 @@ module Dalli
|
|
|
204
200
|
# cache entry (the "thundering herd" problem). Only one client wins the right to
|
|
205
201
|
# regenerate; other clients receive the stale value (if available) or wait.
|
|
206
202
|
#
|
|
207
|
-
# IMPORTANT: This method requires memcached 1.6+ and the meta protocol (protocol: :meta).
|
|
208
|
-
# It will raise an error if used with the binary protocol.
|
|
209
|
-
#
|
|
210
203
|
# @param key [String] the cache key
|
|
211
204
|
# @param ttl [Integer] time-to-live for the cached value in seconds
|
|
212
205
|
# @param lock_ttl [Integer] how long the lock/stub lives (default: 30 seconds)
|
|
@@ -232,12 +225,11 @@ module Dalli
|
|
|
232
225
|
def fetch_with_lock(key, ttl: nil, lock_ttl: 30, recache_threshold: nil, req_options: nil, &block)
|
|
233
226
|
raise ArgumentError, 'Block is required for fetch_with_lock' unless block_given?
|
|
234
227
|
|
|
235
|
-
raise_unless_meta_protocol!
|
|
236
|
-
|
|
237
228
|
key = key.to_s
|
|
238
229
|
key = @key_manager.validate_key(key)
|
|
239
230
|
|
|
240
|
-
|
|
231
|
+
server = ring.server_for_key(key)
|
|
232
|
+
Instrumentation.trace('fetch_with_lock', trace_attrs('fetch_with_lock', key, server)) do
|
|
241
233
|
fetch_with_lock_request(key, ttl, lock_ttl, recache_threshold, req_options, &block)
|
|
242
234
|
end
|
|
243
235
|
rescue NetworkError => e
|
|
@@ -318,10 +310,7 @@ module Dalli
|
|
|
318
310
|
def set_multi(hash, ttl = nil, req_options = nil)
|
|
319
311
|
return if hash.empty?
|
|
320
312
|
|
|
321
|
-
Instrumentation.trace('set_multi',
|
|
322
|
-
'db.operation' => 'set_multi',
|
|
323
|
-
'db.memcached.key_count' => hash.size
|
|
324
|
-
}) do
|
|
313
|
+
Instrumentation.trace('set_multi', multi_trace_attrs('set_multi', hash.size, hash.keys)) do
|
|
325
314
|
pipelined_setter.process(hash, ttl_or_default(ttl), req_options)
|
|
326
315
|
end
|
|
327
316
|
end
|
|
@@ -378,10 +367,7 @@ module Dalli
|
|
|
378
367
|
def delete_multi(keys)
|
|
379
368
|
return if keys.empty?
|
|
380
369
|
|
|
381
|
-
Instrumentation.trace('delete_multi',
|
|
382
|
-
'db.operation' => 'delete_multi',
|
|
383
|
-
'db.memcached.key_count' => keys.size
|
|
384
|
-
}) do
|
|
370
|
+
Instrumentation.trace('delete_multi', multi_trace_attrs('delete_multi', keys.size, keys)) do
|
|
385
371
|
pipelined_deleter.process(keys)
|
|
386
372
|
end
|
|
387
373
|
end
|
|
@@ -515,15 +501,11 @@ module Dalli
|
|
|
515
501
|
|
|
516
502
|
private
|
|
517
503
|
|
|
518
|
-
# Records hit/miss metrics on a span for cache observability.
|
|
519
|
-
# @param span [OpenTelemetry::Trace::Span, nil] the span to record on
|
|
520
|
-
# @param key_count [Integer] total keys requested
|
|
521
|
-
# @param hit_count [Integer] keys found in cache
|
|
522
504
|
def record_hit_miss_metrics(span, key_count, hit_count)
|
|
523
505
|
return unless span
|
|
524
506
|
|
|
525
|
-
span.
|
|
526
|
-
|
|
507
|
+
span.add_attributes('db.memcached.hit_count' => hit_count,
|
|
508
|
+
'db.memcached.miss_count' => key_count - hit_count)
|
|
527
509
|
end
|
|
528
510
|
|
|
529
511
|
def get_multi_yielding(keys)
|
|
@@ -548,7 +530,30 @@ module Dalli
|
|
|
548
530
|
end
|
|
549
531
|
|
|
550
532
|
def get_multi_attributes(keys)
|
|
551
|
-
|
|
533
|
+
multi_trace_attrs('get_multi', keys.size, keys)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def trace_attrs(operation, key, server)
|
|
537
|
+
attrs = { 'db.operation.name' => operation, 'server.address' => server.hostname }
|
|
538
|
+
attrs['server.port'] = server.port if server.socket_type == :tcp
|
|
539
|
+
attrs['peer.service'] = @options[:otel_peer_service] if @options[:otel_peer_service]
|
|
540
|
+
add_query_text(attrs, operation, key)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def multi_trace_attrs(operation, key_count, keys)
|
|
544
|
+
attrs = { 'db.operation.name' => operation, 'db.memcached.key_count' => key_count }
|
|
545
|
+
attrs['peer.service'] = @options[:otel_peer_service] if @options[:otel_peer_service]
|
|
546
|
+
add_query_text(attrs, operation, keys)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def add_query_text(attrs, operation, key_or_keys)
|
|
550
|
+
case @options[:otel_db_statement]
|
|
551
|
+
when :include
|
|
552
|
+
attrs['db.query.text'] = "#{operation} #{Array(key_or_keys).join(' ')}"
|
|
553
|
+
when :obfuscate
|
|
554
|
+
attrs['db.query.text'] = "#{operation} ?"
|
|
555
|
+
end
|
|
556
|
+
attrs
|
|
552
557
|
end
|
|
553
558
|
|
|
554
559
|
def check_positive!(amt)
|
|
@@ -585,16 +590,7 @@ module Dalli
|
|
|
585
590
|
end
|
|
586
591
|
|
|
587
592
|
def ring
|
|
588
|
-
@ring ||= Dalli::Ring.new(@normalized_servers,
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
def protocol_implementation
|
|
592
|
-
@protocol_implementation ||= case @options[:protocol]&.to_s
|
|
593
|
-
when 'meta'
|
|
594
|
-
Dalli::Protocol::Meta
|
|
595
|
-
else
|
|
596
|
-
Dalli::Protocol::Binary
|
|
597
|
-
end
|
|
593
|
+
@ring ||= Dalli::Ring.new(@normalized_servers, @options)
|
|
598
594
|
end
|
|
599
595
|
|
|
600
596
|
##
|
|
@@ -605,7 +601,7 @@ module Dalli
|
|
|
605
601
|
#
|
|
606
602
|
# This method also forces retries on network errors - when
|
|
607
603
|
# a particular memcached instance becomes unreachable, or the
|
|
608
|
-
#
|
|
604
|
+
# operation times out.
|
|
609
605
|
##
|
|
610
606
|
def perform(*all_args)
|
|
611
607
|
return yield if block_given?
|
|
@@ -616,13 +612,10 @@ module Dalli
|
|
|
616
612
|
key = @key_manager.validate_key(key)
|
|
617
613
|
|
|
618
614
|
server = ring.server_for_key(key)
|
|
619
|
-
Instrumentation.trace(op.to_s,
|
|
620
|
-
'db.operation' => op.to_s,
|
|
621
|
-
'server.address' => server.name
|
|
622
|
-
}) do
|
|
615
|
+
Instrumentation.trace(op.to_s, trace_attrs(op.to_s, key, server)) do
|
|
623
616
|
server.request(op, key, *args)
|
|
624
617
|
end
|
|
625
|
-
rescue
|
|
618
|
+
rescue RetryableNetworkError => e
|
|
626
619
|
Dalli.logger.debug { e.inspect }
|
|
627
620
|
Dalli.logger.debug { 'retrying request with new server' }
|
|
628
621
|
retry
|
|
@@ -635,6 +628,21 @@ module Dalli
|
|
|
635
628
|
raise ArgumentError, "cannot convert :expires_in => #{opts[:expires_in].inspect} to an integer"
|
|
636
629
|
end
|
|
637
630
|
|
|
631
|
+
REMOVED_OPTIONS = {
|
|
632
|
+
protocol: 'Dalli 5.0 only supports the meta protocol. The :protocol option has been removed.',
|
|
633
|
+
username: 'Dalli 5.0 removed SASL authentication support. The :username option is ignored.',
|
|
634
|
+
password: 'Dalli 5.0 removed SASL authentication support. The :password option is ignored.'
|
|
635
|
+
}.freeze
|
|
636
|
+
private_constant :REMOVED_OPTIONS
|
|
637
|
+
|
|
638
|
+
def warn_removed_options(opts)
|
|
639
|
+
REMOVED_OPTIONS.each do |key, message|
|
|
640
|
+
next unless opts.key?(key)
|
|
641
|
+
|
|
642
|
+
Dalli.logger.warn(message)
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
638
646
|
def pipelined_getter
|
|
639
647
|
PipelinedGetter.new(ring, @key_manager)
|
|
640
648
|
end
|
|
@@ -646,15 +654,5 @@ module Dalli
|
|
|
646
654
|
def pipelined_deleter
|
|
647
655
|
PipelinedDeleter.new(ring, @key_manager)
|
|
648
656
|
end
|
|
649
|
-
|
|
650
|
-
def raise_unless_meta_protocol!
|
|
651
|
-
return if protocol_implementation == Dalli::Protocol::Meta
|
|
652
|
-
|
|
653
|
-
raise Dalli::DalliError,
|
|
654
|
-
'This operation requires the meta protocol (memcached 1.6+). ' \
|
|
655
|
-
'Use protocol: :meta when creating the client.'
|
|
656
|
-
end
|
|
657
|
-
|
|
658
|
-
include ProtocolDeprecations
|
|
659
657
|
end
|
|
660
658
|
end
|
|
@@ -8,25 +8,36 @@ module Dalli
|
|
|
8
8
|
# When OpenTelemetry is loaded, Dalli automatically creates spans for cache operations.
|
|
9
9
|
# When OpenTelemetry is not available, all tracing methods are no-ops with zero overhead.
|
|
10
10
|
#
|
|
11
|
+
# Dalli 5.0 uses the stable OTel semantic conventions for database spans.
|
|
12
|
+
#
|
|
11
13
|
# == Span Attributes
|
|
12
14
|
#
|
|
13
15
|
# All spans include the following default attributes:
|
|
14
|
-
# - +db.system+ - Always "memcached"
|
|
16
|
+
# - +db.system.name+ - Always "memcached"
|
|
15
17
|
#
|
|
16
18
|
# Single-key operations (+get+, +set+, +delete+, +incr+, +decr+, etc.) add:
|
|
17
|
-
# - +db.operation+ - The operation name (e.g., "get", "set")
|
|
18
|
-
# - +server.address+ - The
|
|
19
|
+
# - +db.operation.name+ - The operation name (e.g., "get", "set")
|
|
20
|
+
# - +server.address+ - The server hostname (e.g., "localhost")
|
|
21
|
+
# - +server.port+ - The server port as an integer (e.g., 11211); omitted for Unix sockets
|
|
19
22
|
#
|
|
20
23
|
# Multi-key operations (+get_multi+) add:
|
|
21
|
-
# - +db.operation+ - "get_multi"
|
|
24
|
+
# - +db.operation.name+ - "get_multi"
|
|
22
25
|
# - +db.memcached.key_count+ - Number of keys requested
|
|
23
26
|
# - +db.memcached.hit_count+ - Number of keys found in cache
|
|
24
27
|
# - +db.memcached.miss_count+ - Number of keys not found
|
|
25
28
|
#
|
|
26
29
|
# Bulk write operations (+set_multi+, +delete_multi+) add:
|
|
27
|
-
# - +db.operation+ - The operation name
|
|
30
|
+
# - +db.operation.name+ - The operation name
|
|
28
31
|
# - +db.memcached.key_count+ - Number of keys in the operation
|
|
29
32
|
#
|
|
33
|
+
# == Optional Attributes
|
|
34
|
+
#
|
|
35
|
+
# - +db.query.text+ - The operation and key(s), controlled by the +:otel_db_statement+ client option:
|
|
36
|
+
# - +:include+ - Full text (e.g., "get mykey")
|
|
37
|
+
# - +:obfuscate+ - Obfuscated (e.g., "get ?")
|
|
38
|
+
# - +nil+ (default) - Attribute omitted
|
|
39
|
+
# - +peer.service+ - Logical service name, set via the +:otel_peer_service+ client option
|
|
40
|
+
#
|
|
30
41
|
# == Error Handling
|
|
31
42
|
#
|
|
32
43
|
# When an exception occurs during a traced operation:
|
|
@@ -40,8 +51,8 @@ module Dalli
|
|
|
40
51
|
##
|
|
41
52
|
module Instrumentation
|
|
42
53
|
# Default attributes included on all memcached spans.
|
|
43
|
-
# @return [Hash] frozen hash with 'db.system' => 'memcached'
|
|
44
|
-
DEFAULT_ATTRIBUTES = { 'db.system' => 'memcached' }.freeze
|
|
54
|
+
# @return [Hash] frozen hash with 'db.system.name' => 'memcached'
|
|
55
|
+
DEFAULT_ATTRIBUTES = { 'db.system.name' => 'memcached' }.freeze
|
|
45
56
|
|
|
46
57
|
class << self
|
|
47
58
|
# Returns the OpenTelemetry tracer if available, nil otherwise.
|
|
@@ -75,27 +86,24 @@ module Dalli
|
|
|
75
86
|
# @param name [String] the span name (e.g., 'get', 'set', 'delete')
|
|
76
87
|
# @param attributes [Hash] span attributes to merge with defaults.
|
|
77
88
|
# Common attributes include:
|
|
78
|
-
# - 'db.operation' - the operation name
|
|
79
|
-
# - 'server.address' - the
|
|
89
|
+
# - 'db.operation.name' - the operation name
|
|
90
|
+
# - 'server.address' - the server hostname
|
|
91
|
+
# - 'server.port' - the server port (integer)
|
|
80
92
|
# - 'db.memcached.key_count' - number of keys (for multi operations)
|
|
81
93
|
# @yield the cache operation to trace
|
|
82
94
|
# @return [Object] the result of the block
|
|
83
95
|
# @raise [StandardError] re-raises any exception from the block
|
|
84
96
|
#
|
|
85
97
|
# @example Tracing a set operation
|
|
86
|
-
# trace('set', { 'db.operation' => 'set', 'server.address' => 'localhost
|
|
98
|
+
# trace('set', { 'db.operation.name' => 'set', 'server.address' => 'localhost', 'server.port' => 11211 }) do
|
|
87
99
|
# server.set(key, value, ttl)
|
|
88
100
|
# end
|
|
89
101
|
#
|
|
90
102
|
def trace(name, attributes = {})
|
|
91
103
|
return yield unless enabled?
|
|
92
104
|
|
|
93
|
-
tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client) do |
|
|
105
|
+
tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client) do |_span|
|
|
94
106
|
yield
|
|
95
|
-
rescue StandardError => e
|
|
96
|
-
span.record_exception(e)
|
|
97
|
-
span.status = OpenTelemetry::Trace::Status.error(e.message)
|
|
98
|
-
raise
|
|
99
107
|
end
|
|
100
108
|
end
|
|
101
109
|
|
|
@@ -114,7 +122,7 @@ module Dalli
|
|
|
114
122
|
# @raise [StandardError] re-raises any exception from the block
|
|
115
123
|
#
|
|
116
124
|
# @example Recording hit/miss metrics after get_multi
|
|
117
|
-
# trace_with_result('get_multi', { 'db.operation' => 'get_multi' }) do |span|
|
|
125
|
+
# trace_with_result('get_multi', { 'db.operation.name' => 'get_multi' }) do |span|
|
|
118
126
|
# results = fetch_from_cache(keys)
|
|
119
127
|
# if span
|
|
120
128
|
# span.set_attribute('db.memcached.hit_count', results.size)
|
|
@@ -123,16 +131,10 @@ module Dalli
|
|
|
123
131
|
# results
|
|
124
132
|
# end
|
|
125
133
|
#
|
|
126
|
-
def trace_with_result(name, attributes = {})
|
|
134
|
+
def trace_with_result(name, attributes = {}, &)
|
|
127
135
|
return yield(nil) unless enabled?
|
|
128
136
|
|
|
129
|
-
tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client)
|
|
130
|
-
yield(span)
|
|
131
|
-
rescue StandardError => e
|
|
132
|
-
span.record_exception(e)
|
|
133
|
-
span.status = OpenTelemetry::Trace::Status.error(e.message)
|
|
134
|
-
raise
|
|
135
|
-
end
|
|
137
|
+
tracer.in_span(name, attributes: DEFAULT_ATTRIBUTES.merge(attributes), kind: :client, &)
|
|
136
138
|
end
|
|
137
139
|
end
|
|
138
140
|
end
|
data/lib/dalli/key_manager.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Dalli
|
|
|
31
31
|
|
|
32
32
|
# Valid separators: non-alphanumeric, single printable ASCII characters
|
|
33
33
|
# Excludes: alphanumerics, whitespace, control characters
|
|
34
|
-
VALID_NAMESPACE_SEPARATORS = /\A[^a-zA-Z0-9\
|
|
34
|
+
VALID_NAMESPACE_SEPARATORS = /\A[^a-zA-Z0-9 \x00-\x1F\x7F]\z/
|
|
35
35
|
|
|
36
36
|
def initialize(client_options)
|
|
37
37
|
@key_options =
|
data/lib/dalli/options.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'set'
|
|
4
|
-
|
|
5
3
|
module Dalli
|
|
6
4
|
##
|
|
7
5
|
# Contains logic for the pipelined gets implemented by the client.
|
|
@@ -36,7 +34,7 @@ module Dalli
|
|
|
36
34
|
|
|
37
35
|
servers = fetch_responses(servers, start_time, @ring.socket_timeout, &block) until servers.empty?
|
|
38
36
|
end
|
|
39
|
-
rescue
|
|
37
|
+
rescue Dalli::RetryableNetworkError => e
|
|
40
38
|
Dalli.logger.debug { e.inspect }
|
|
41
39
|
Dalli.logger.debug { 'retrying pipelined gets because of timeout' }
|
|
42
40
|
retry
|
|
@@ -143,7 +141,7 @@ module Dalli
|
|
|
143
141
|
servers
|
|
144
142
|
rescue NetworkError
|
|
145
143
|
# Abort and raise if we encountered a network error. This triggers
|
|
146
|
-
# a retry at the top level.
|
|
144
|
+
# a retry at the top level on RetryableNetworkError.
|
|
147
145
|
abort_without_timeout(servers)
|
|
148
146
|
raise
|
|
149
147
|
end
|
|
@@ -28,7 +28,7 @@ module Dalli
|
|
|
28
28
|
servers = setup_requests(hash, ttl, req_options)
|
|
29
29
|
finish_requests(servers)
|
|
30
30
|
end
|
|
31
|
-
rescue
|
|
31
|
+
rescue Dalli::RetryableNetworkError => e
|
|
32
32
|
Dalli.logger.debug { e.inspect }
|
|
33
33
|
Dalli.logger.debug { 'retrying pipelined sets because of network error' }
|
|
34
34
|
retry
|
data/lib/dalli/protocol/base.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Dalli
|
|
|
22
22
|
|
|
23
23
|
def initialize(attribs, client_options = {})
|
|
24
24
|
hostname, port, socket_type, @weight, user_creds = ServerConfigParser.parse(attribs)
|
|
25
|
+
warn_uri_credentials(user_creds)
|
|
25
26
|
@options = client_options.merge(user_creds)
|
|
26
27
|
@raw_mode = client_options[:raw]
|
|
27
28
|
@value_marshaller = @raw_mode ? StringMarshaller.new(@options) : ValueMarshaller.new(@options)
|
|
@@ -141,18 +142,6 @@ module Dalli
|
|
|
141
142
|
!response_buffer.in_progress?
|
|
142
143
|
end
|
|
143
144
|
|
|
144
|
-
def username
|
|
145
|
-
@options[:username] || ENV.fetch('MEMCACHE_USERNAME', nil)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def password
|
|
149
|
-
@options[:password] || ENV.fetch('MEMCACHE_PASSWORD', nil)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def require_auth?
|
|
153
|
-
!username.nil?
|
|
154
|
-
end
|
|
155
|
-
|
|
156
145
|
def quiet?
|
|
157
146
|
Thread.current[::Dalli::QUIET]
|
|
158
147
|
end
|
|
@@ -162,6 +151,16 @@ module Dalli
|
|
|
162
151
|
|
|
163
152
|
private
|
|
164
153
|
|
|
154
|
+
URI_CREDENTIAL_WARNING = 'Dalli 5.0 removed SASL authentication. ' \
|
|
155
|
+
'Credentials in memcached:// URIs are ignored.'
|
|
156
|
+
private_constant :URI_CREDENTIAL_WARNING
|
|
157
|
+
|
|
158
|
+
def warn_uri_credentials(user_creds)
|
|
159
|
+
return if user_creds[:username].nil? && user_creds[:password].nil?
|
|
160
|
+
|
|
161
|
+
Dalli.logger.warn(URI_CREDENTIAL_WARNING)
|
|
162
|
+
end
|
|
163
|
+
|
|
165
164
|
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze
|
|
166
165
|
private_constant :ALLOWED_QUIET_OPS
|
|
167
166
|
|
|
@@ -216,8 +215,7 @@ module Dalli
|
|
|
216
215
|
|
|
217
216
|
def connect
|
|
218
217
|
@connection_manager.establish_connection
|
|
219
|
-
|
|
220
|
-
@version = version # Connect socket if not authed
|
|
218
|
+
@version = version
|
|
221
219
|
up!
|
|
222
220
|
end
|
|
223
221
|
|
|
@@ -156,7 +156,13 @@ module Dalli
|
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def read(count)
|
|
159
|
-
|
|
159
|
+
# JRuby doesn't support IO#timeout=, so use custom readfull implementation
|
|
160
|
+
# CRuby 3.3+ has IO#timeout= which makes IO#read work with timeouts
|
|
161
|
+
if RUBY_ENGINE == 'jruby'
|
|
162
|
+
@sock.readfull(count)
|
|
163
|
+
else
|
|
164
|
+
@sock.read(count)
|
|
165
|
+
end
|
|
160
166
|
rescue SystemCallError, *TIMEOUT_ERRORS, *SSL_ERRORS, EOFError => e
|
|
161
167
|
error_on_request!(e)
|
|
162
168
|
end
|
|
@@ -199,7 +205,7 @@ module Dalli
|
|
|
199
205
|
def reconnect!(message)
|
|
200
206
|
close
|
|
201
207
|
sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
|
|
202
|
-
raise Dalli::
|
|
208
|
+
raise Dalli::RetryableNetworkError, message
|
|
203
209
|
end
|
|
204
210
|
|
|
205
211
|
def reset_down_info
|
data/lib/dalli/protocol/meta.rb
CHANGED
|
@@ -257,13 +257,9 @@ module Dalli
|
|
|
257
257
|
@connection_manager.flush
|
|
258
258
|
end
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
require_relative 'meta/key_regularizer'
|
|
265
|
-
require_relative 'meta/request_formatter'
|
|
266
|
-
require_relative 'meta/response_processor'
|
|
260
|
+
require_relative 'key_regularizer'
|
|
261
|
+
require_relative 'request_formatter'
|
|
262
|
+
require_relative 'response_processor'
|
|
267
263
|
end
|
|
268
264
|
end
|
|
269
265
|
end
|
|
@@ -6,7 +6,7 @@ module Dalli
|
|
|
6
6
|
module Protocol
|
|
7
7
|
##
|
|
8
8
|
# Dalli::Protocol::ServerConfigParser parses a server string passed to
|
|
9
|
-
# a Dalli::Protocol::
|
|
9
|
+
# a Dalli::Protocol::Meta instance into the hostname, port, weight, and
|
|
10
10
|
# socket_type.
|
|
11
11
|
##
|
|
12
12
|
class ServerConfigParser
|
data/lib/dalli/ring.rb
CHANGED
|
@@ -23,9 +23,9 @@ module Dalli
|
|
|
23
23
|
|
|
24
24
|
attr_accessor :servers, :continuum
|
|
25
25
|
|
|
26
|
-
def initialize(servers_arg,
|
|
26
|
+
def initialize(servers_arg, options)
|
|
27
27
|
@servers = servers_arg.map do |s|
|
|
28
|
-
|
|
28
|
+
Dalli::Protocol::Meta.new(s, options)
|
|
29
29
|
end
|
|
30
30
|
@continuum = nil
|
|
31
31
|
@continuum = build_continuum(servers) if servers.size > 1
|