dalli 5.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02d0fa949b7a065f86fb3ac7b511a3feacc50b700e82dcb385e0e79c56583a9d
4
- data.tar.gz: d72b9e4b014ae1ae0b0d5a1ebe09ea48cdf9fbf0a2cda000efc19f17d5d34368
3
+ metadata.gz: ae3cbae2603955279bb78b75682ac1f56105d5ec2b2e0d464ab15255ed23369b
4
+ data.tar.gz: 2e5a3960e638293cfdf5663ef959d1ab9690620e09f0151a844cdeb9424cbf80
5
5
  SHA512:
6
- metadata.gz: bf26484aa345df2d43a78da570027b68a7fd0dd70d7a62275ebe95a5e29ed36c11a8b1385ff0c71818f55184245e085cd75962510f9f8559aad10b0d284f8d71
7
- data.tar.gz: 0b3913a33f61873d6e3da0d62ec643dbf49f679740d3469b7cb826083f02f3b32d37e2a20548634d09cecdc4389da7c5bdc43af530bb48956cb4084f91f10d9e
6
+ metadata.gz: 243382482bc345bf982f2cb96fae28b974f37b2eee653a71c7f3a85518ef5acdbfda6f2fddacfa31da6252774000e11a1c96d3707d29808c636951d85ace17be
7
+ data.tar.gz: 5938608c26cd307e397cad4fdf6e2fda4519fdbd8df2b7d75352b691996fec4dca59632cc0053b8bf410612c8e6cada73ccad7baf5c000a183bd04bb196d056e
data/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  Dalli Changelog
2
2
  =====================
3
3
 
4
+ 5.0.2
5
+ ==========
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
+
4
18
  5.0.1
5
19
  ==========
6
20
 
data/lib/dalli/client.rb CHANGED
@@ -311,7 +311,11 @@ module Dalli
311
311
  return if hash.empty?
312
312
 
313
313
  Instrumentation.trace('set_multi', multi_trace_attrs('set_multi', hash.size, hash.keys)) do
314
- pipelined_setter.process(hash, ttl_or_default(ttl), req_options)
314
+ if ring.servers.size == 1
315
+ single_server_set_multi(hash, ttl_or_default(ttl), req_options)
316
+ else
317
+ pipelined_setter.process(hash, ttl_or_default(ttl), req_options)
318
+ end
315
319
  end
316
320
  end
317
321
 
@@ -368,7 +372,11 @@ module Dalli
368
372
  return if keys.empty?
369
373
 
370
374
  Instrumentation.trace('delete_multi', multi_trace_attrs('delete_multi', keys.size, keys)) do
371
- pipelined_deleter.process(keys)
375
+ if ring.servers.size == 1
376
+ single_server_delete_multi(keys)
377
+ else
378
+ pipelined_deleter.process(keys)
379
+ end
372
380
  end
373
381
  end
374
382
 
@@ -522,13 +530,52 @@ module Dalli
522
530
 
523
531
  def get_multi_hash(keys)
524
532
  Instrumentation.trace_with_result('get_multi', get_multi_attributes(keys)) do |span|
525
- {}.tap do |hash|
526
- pipelined_getter.process(keys) { |k, data| hash[k] = data.first }
527
- record_hit_miss_metrics(span, keys.size, hash.size)
528
- end
533
+ hash = if ring.servers.size == 1
534
+ single_server_get_multi(keys)
535
+ else
536
+ {}.tap do |h|
537
+ pipelined_getter.process(keys) { |k, data| h[k] = data.first }
538
+ end
539
+ end
540
+ record_hit_miss_metrics(span, keys.size, hash.size)
541
+ hash
529
542
  end
530
543
  end
531
544
 
545
+ def single_server
546
+ server = ring.servers.first
547
+ server if server&.alive?
548
+ end
549
+
550
+ def single_server_get_multi(keys)
551
+ keys.map! { |k| @key_manager.validate_key(k.to_s) }
552
+ return {} unless (server = single_server)
553
+
554
+ result = server.request(:read_multi_req, keys)
555
+ result.transform_keys! { |k| @key_manager.key_without_namespace(k) }
556
+ result
557
+ rescue Dalli::NetworkError
558
+ {}
559
+ end
560
+
561
+ def single_server_set_multi(hash, ttl, req_options)
562
+ pairs = hash.transform_keys { |k| @key_manager.validate_key(k.to_s) }
563
+ return unless (server = single_server)
564
+
565
+ server.request(:write_multi_req, pairs, ttl, req_options)
566
+ rescue Dalli::NetworkError
567
+ nil
568
+ end
569
+
570
+ def single_server_delete_multi(keys)
571
+ validated_keys = keys.map { |k| @key_manager.validate_key(k.to_s) }
572
+ return unless (server = single_server)
573
+
574
+ server.request(:delete_multi_req, validated_keys)
575
+ rescue Dalli::NetworkError
576
+ nil
577
+ end
578
+
532
579
  def get_multi_attributes(keys)
533
580
  multi_trace_attrs('get_multi', keys.size, keys)
534
581
  end
@@ -257,6 +257,82 @@ module Dalli
257
257
  @connection_manager.flush
258
258
  end
259
259
 
260
+ # Single-server fast path for get_multi. Inlines request formatting and
261
+ # response parsing to minimize per-key overhead. Avoids the PipelinedGetter
262
+ # machinery (IO.select, response buffering, server grouping).
263
+ def read_multi_req(keys)
264
+ is_raw = raw_mode?
265
+ # Inline request formatting — avoids RequestFormatter.meta_get overhead per key.
266
+ # In raw mode: "mg <key> v k q s\r\n" (no f flag, key at index 2)
267
+ # Normal mode: "mg <key> v f k q s\r\n" (key at index 3)
268
+ post_get = is_raw ? " v k q s\r\n" : " v f k q s\r\n"
269
+ keys.each do |key|
270
+ encoded_key, base64 = KeyRegularizer.encode(key)
271
+ write(base64 ? "mg #{encoded_key} b#{post_get}" : "mg #{encoded_key}#{post_get}")
272
+ end
273
+ write("mn\r\n")
274
+ @connection_manager.flush
275
+
276
+ read_multi_get_responses(is_raw)
277
+ end
278
+
279
+ def read_multi_get_responses(is_raw)
280
+ hash = {}
281
+ key_index = is_raw ? 2 : 3
282
+ while (line = @connection_manager.read_line)
283
+ break if line.start_with?('MN')
284
+ next unless line.start_with?('VA ')
285
+
286
+ key, value = parse_multi_get_value(line, key_index, is_raw)
287
+ hash[key] = value if key
288
+ end
289
+ hash
290
+ end
291
+
292
+ def parse_multi_get_value(line, key_index, is_raw)
293
+ tokens = line.chomp!(TERMINATOR).split
294
+ value = @connection_manager.read(tokens[1].to_i + TERMINATOR.bytesize)&.chomp!(TERMINATOR)
295
+ raw_key = tokens[key_index]
296
+ return unless raw_key
297
+
298
+ key = KeyRegularizer.decode(raw_key[1..], tokens.include?('b'))
299
+ bitflags = is_raw ? 0 : response_processor.bitflags_from_tokens(tokens)
300
+ [key, @value_marshaller.retrieve(value, bitflags)]
301
+ end
302
+
303
+ # Single-server fast path for set_multi. Inlines request formatting to
304
+ # minimize per-key overhead. Avoids PipelinedSetter server grouping.
305
+ def write_multi_req(pairs, ttl, req_options)
306
+ ttl = TtlSanitizer.sanitize(ttl) if ttl
307
+ pairs.each do |key, raw_value|
308
+ (value, bitflags) = @value_marshaller.store(key, raw_value, req_options)
309
+ encoded_key, base64 = KeyRegularizer.encode(key)
310
+ # Inline format: "ms <key> <size> c [b] F<flags> T<ttl> MS q\r\n"
311
+ cmd = "ms #{encoded_key} #{value.bytesize} c"
312
+ cmd << ' b' if base64
313
+ cmd << " F#{bitflags}" if bitflags
314
+ cmd << " T#{ttl}" if ttl
315
+ cmd << " MS q\r\n"
316
+ write(cmd)
317
+ write(value)
318
+ write(TERMINATOR)
319
+ end
320
+ write_noop
321
+ response_processor.consume_all_responses_until_mn
322
+ end
323
+
324
+ # Single-server fast path for delete_multi. Writes all quiet delete requests
325
+ # terminated by a noop, then consumes all responses.
326
+ def delete_multi_req(keys)
327
+ keys.each do |key|
328
+ encoded_key, base64 = KeyRegularizer.encode(key)
329
+ # Inline format: "md <key> [b] q\r\n"
330
+ write(base64 ? "md #{encoded_key} b q\r\n" : "md #{encoded_key} q\r\n")
331
+ end
332
+ write_noop
333
+ response_processor.consume_all_responses_until_mn
334
+ end
335
+
260
336
  require_relative 'key_regularizer'
261
337
  require_relative 'request_formatter'
262
338
  require_relative 'response_processor'
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '5.0.1'
4
+ VERSION = '5.0.2'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.6'
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein