redis 4.8.0 → 5.0.8

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -1
  3. data/README.md +101 -161
  4. data/lib/redis/client.rb +82 -625
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +5 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +28 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +31 -40
  16. data/lib/redis/commands/sorted_sets.rb +84 -12
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +4 -7
  21. data/lib/redis/distributed.rb +114 -64
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +76 -184
  28. metadata +10 -54
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -20,7 +20,7 @@ class Redis
20
20
  def initialize(node_configs, options = {})
21
21
  @tag = options[:tag] || /^\{(.+?)\}/
22
22
  @ring = options[:ring] || HashRing.new
23
- @node_configs = node_configs.dup
23
+ @node_configs = node_configs.map(&:dup)
24
24
  @default_options = options.dup
25
25
  node_configs.each { |node_config| add_node(node_config) }
26
26
  @subscribed_node = nil
@@ -41,6 +41,8 @@ class Redis
41
41
  def add_node(options)
42
42
  options = { url: options } if options.is_a?(String)
43
43
  options = @default_options.merge(options)
44
+ options.delete(:tag)
45
+ options.delete(:ring)
44
46
  @ring.add_node Redis.new(options)
45
47
  end
46
48
 
@@ -64,6 +66,10 @@ class Redis
64
66
  on_each_node :quit
65
67
  end
66
68
 
69
+ def close
70
+ on_each_node :close
71
+ end
72
+
67
73
  # Asynchronously save the dataset to disk.
68
74
  def bgsave
69
75
  on_each_node :bgsave
@@ -161,6 +167,7 @@ class Redis
161
167
 
162
168
  # Delete a key.
163
169
  def del(*args)
170
+ args.flatten!(1)
164
171
  keys_per_node = args.group_by { |key| node_for(key) }
165
172
  keys_per_node.inject(0) do |sum, (node, keys)|
166
173
  sum + node.del(*keys)
@@ -169,6 +176,7 @@ class Redis
169
176
 
170
177
  # Unlink keys.
171
178
  def unlink(*args)
179
+ args.flatten!(1)
172
180
  keys_per_node = args.group_by { |key| node_for(key) }
173
181
  keys_per_node.inject(0) do |sum, (node, keys)|
174
182
  sum + node.unlink(*keys)
@@ -177,23 +185,16 @@ class Redis
177
185
 
178
186
  # Determine if a key exists.
179
187
  def exists(*args)
180
- if !Redis.exists_returns_integer && args.size == 1
181
- ::Redis.deprecate!(
182
- "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
183
- "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
184
- "(#{::Kernel.caller(1, 1).first})\n"
185
- )
186
- exists?(*args)
187
- else
188
- keys_per_node = args.group_by { |key| node_for(key) }
189
- keys_per_node.inject(0) do |sum, (node, keys)|
190
- sum + node._exists(*keys)
191
- end
188
+ args.flatten!(1)
189
+ keys_per_node = args.group_by { |key| node_for(key) }
190
+ keys_per_node.inject(0) do |sum, (node, keys)|
191
+ sum + node.exists(*keys)
192
192
  end
193
193
  end
194
194
 
195
195
  # Determine if any of the keys exists.
196
196
  def exists?(*args)
197
+ args.flatten!(1)
197
198
  keys_per_node = args.group_by { |key| node_for(key) }
198
199
  keys_per_node.each do |node, keys|
199
200
  return true if node.exists?(*keys)
@@ -297,7 +298,7 @@ class Redis
297
298
  end
298
299
 
299
300
  # Set multiple keys to multiple values.
300
- def mset(*_args)
301
+ def mset(*)
301
302
  raise CannotDistribute, :mset
302
303
  end
303
304
 
@@ -306,7 +307,7 @@ class Redis
306
307
  end
307
308
 
308
309
  # Set multiple keys to multiple values, only if none of the keys exist.
309
- def msetnx(*_args)
310
+ def msetnx(*)
310
311
  raise CannotDistribute, :msetnx
311
312
  end
312
313
 
@@ -331,11 +332,13 @@ class Redis
331
332
 
332
333
  # Get the values of all the given keys as an Array.
333
334
  def mget(*keys)
335
+ keys.flatten!(1)
334
336
  mapped_mget(*keys).values_at(*keys)
335
337
  end
336
338
 
337
339
  # Get the values of all the given keys as a Hash.
338
340
  def mapped_mget(*keys)
341
+ keys.flatten!(1)
339
342
  keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
340
343
  results.merge! node.mapped_mget(*subkeys)
341
344
  end
@@ -373,8 +376,9 @@ class Redis
373
376
 
374
377
  # Perform a bitwise operation between strings and store the resulting string in a key.
375
378
  def bitop(operation, destkey, *keys)
379
+ keys.flatten!(1)
376
380
  ensure_same_node(:bitop, [destkey] + keys) do |node|
377
- node.bitop(operation, destkey, *keys)
381
+ node.bitop(operation, destkey, keys)
378
382
  end
379
383
  end
380
384
 
@@ -463,23 +467,15 @@ class Redis
463
467
  timeout = if args.last.is_a?(Hash)
464
468
  options = args.pop
465
469
  options[:timeout]
466
- elsif args.last.respond_to?(:to_int)
467
- last_arg = args.pop
468
- ::Redis.deprecate!(
469
- "Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
470
- " redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
471
- "(called from: #{caller(2, 1).first})"
472
- )
473
- last_arg.to_int
474
470
  end
475
471
 
476
- keys = args.flatten
472
+ args.flatten!(1)
477
473
 
478
- ensure_same_node(cmd, keys) do |node|
474
+ ensure_same_node(cmd, args) do |node|
479
475
  if timeout
480
- node.__send__(cmd, keys, timeout: timeout)
476
+ node.__send__(cmd, args, timeout: timeout)
481
477
  else
482
- node.__send__(cmd, keys)
478
+ node.__send__(cmd, args)
483
479
  end
484
480
  end
485
481
  end
@@ -490,6 +486,18 @@ class Redis
490
486
  _bpop(:blpop, args)
491
487
  end
492
488
 
489
+ def bzpopmax(*args)
490
+ _bpop(:bzpopmax, args) do |reply|
491
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
492
+ end
493
+ end
494
+
495
+ def bzpopmin(*args)
496
+ _bpop(:bzpopmin, args) do |reply|
497
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
498
+ end
499
+ end
500
+
493
501
  # Remove and get the last element in a list, or block until one is
494
502
  # available.
495
503
  def brpop(*args)
@@ -498,9 +506,9 @@ class Redis
498
506
 
499
507
  # Pop a value from a list, push it to another list and return it; or block
500
508
  # until one is available.
501
- def brpoplpush(source, destination, deprecated_timeout = 0, **options)
509
+ def brpoplpush(source, destination, **options)
502
510
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
503
- node.brpoplpush(source, destination, deprecated_timeout, **options)
511
+ node.brpoplpush(source, destination, **options)
504
512
  end
505
513
  end
506
514
 
@@ -534,29 +542,43 @@ class Redis
534
542
  node_for(key).ltrim(key, start, stop)
535
543
  end
536
544
 
545
+ # Iterate over keys, blocking and removing elements from the first non empty liist found.
546
+ def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
547
+ ensure_same_node(:blmpop, keys) do |node|
548
+ node.blmpop(timeout, *keys, modifier: modifier, count: count)
549
+ end
550
+ end
551
+
552
+ # Iterate over keys, removing elements from the first non list found.
553
+ def lmpop(*keys, modifier: "LEFT", count: nil)
554
+ ensure_same_node(:lmpop, keys) do |node|
555
+ node.lmpop(*keys, modifier: modifier, count: count)
556
+ end
557
+ end
558
+
537
559
  # Get the number of members in a set.
538
560
  def scard(key)
539
561
  node_for(key).scard(key)
540
562
  end
541
563
 
542
564
  # Add one or more members to a set.
543
- def sadd(key, member)
544
- node_for(key).sadd(key, member)
565
+ def sadd(key, *members)
566
+ node_for(key).sadd(key, *members)
545
567
  end
546
568
 
547
569
  # Add one or more members to a set.
548
- def sadd?(key, member)
549
- node_for(key).sadd?(key, member)
570
+ def sadd?(key, *members)
571
+ node_for(key).sadd?(key, *members)
550
572
  end
551
573
 
552
574
  # Remove one or more members from a set.
553
- def srem(key, member)
554
- node_for(key).srem(key, member)
575
+ def srem(key, *members)
576
+ node_for(key).srem(key, *members)
555
577
  end
556
578
 
557
579
  # Remove one or more members from a set.
558
- def srem?(key, member)
559
- node_for(key).srem?(key, member)
580
+ def srem?(key, *members)
581
+ node_for(key).srem?(key, *members)
560
582
  end
561
583
 
562
584
  # Remove and return a random member from a set.
@@ -603,43 +625,49 @@ class Redis
603
625
 
604
626
  # Subtract multiple sets.
605
627
  def sdiff(*keys)
628
+ keys.flatten!(1)
606
629
  ensure_same_node(:sdiff, keys) do |node|
607
- node.sdiff(*keys)
630
+ node.sdiff(keys)
608
631
  end
609
632
  end
610
633
 
611
634
  # Subtract multiple sets and store the resulting set in a key.
612
635
  def sdiffstore(destination, *keys)
613
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
614
- node.sdiffstore(destination, *keys)
636
+ keys.flatten!(1)
637
+ ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
638
+ node.sdiffstore(destination, keys)
615
639
  end
616
640
  end
617
641
 
618
642
  # Intersect multiple sets.
619
643
  def sinter(*keys)
644
+ keys.flatten!(1)
620
645
  ensure_same_node(:sinter, keys) do |node|
621
- node.sinter(*keys)
646
+ node.sinter(keys)
622
647
  end
623
648
  end
624
649
 
625
650
  # Intersect multiple sets and store the resulting set in a key.
626
651
  def sinterstore(destination, *keys)
627
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
628
- node.sinterstore(destination, *keys)
652
+ keys.flatten!(1)
653
+ ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
654
+ node.sinterstore(destination, keys)
629
655
  end
630
656
  end
631
657
 
632
658
  # Add multiple sets.
633
659
  def sunion(*keys)
660
+ keys.flatten!(1)
634
661
  ensure_same_node(:sunion, keys) do |node|
635
- node.sunion(*keys)
662
+ node.sunion(keys)
636
663
  end
637
664
  end
638
665
 
639
666
  # Add multiple sets and store the resulting set in a key.
640
667
  def sunionstore(destination, *keys)
641
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
642
- node.sunionstore(destination, *keys)
668
+ keys.flatten!(1)
669
+ ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
670
+ node.sunionstore(destination, keys)
643
671
  end
644
672
  end
645
673
 
@@ -680,6 +708,20 @@ class Redis
680
708
  node_for(key).zmscore(key, *members)
681
709
  end
682
710
 
711
+ # Iterate over keys, blocking and removing members from the first non empty sorted set found.
712
+ def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
713
+ ensure_same_node(:bzmpop, keys) do |node|
714
+ node.bzmpop(timeout, *keys, modifier: modifier, count: count)
715
+ end
716
+ end
717
+
718
+ # Iterate over keys, removing members from the first non empty sorted set found.
719
+ def zmpop(*keys, modifier: "MIN", count: nil)
720
+ ensure_same_node(:zmpop, keys) do |node|
721
+ node.zmpop(*keys, modifier: modifier, count: count)
722
+ end
723
+ end
724
+
683
725
  # Return a range of members in a sorted set, by index, score or lexicographical ordering.
684
726
  def zrange(key, start, stop, **options)
685
727
  node_for(key).zrange(key, start, stop, **options)
@@ -738,43 +780,49 @@ class Redis
738
780
 
739
781
  # Get the intersection of multiple sorted sets
740
782
  def zinter(*keys, **options)
783
+ keys.flatten!(1)
741
784
  ensure_same_node(:zinter, keys) do |node|
742
- node.zinter(*keys, **options)
785
+ node.zinter(keys, **options)
743
786
  end
744
787
  end
745
788
 
746
789
  # Intersect multiple sorted sets and store the resulting sorted set in a new
747
790
  # key.
748
- def zinterstore(destination, keys, **options)
749
- ensure_same_node(:zinterstore, [destination] + keys) do |node|
791
+ def zinterstore(destination, *keys, **options)
792
+ keys.flatten!(1)
793
+ ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
750
794
  node.zinterstore(destination, keys, **options)
751
795
  end
752
796
  end
753
797
 
754
798
  # Return the union of multiple sorted sets.
755
799
  def zunion(*keys, **options)
800
+ keys.flatten!(1)
756
801
  ensure_same_node(:zunion, keys) do |node|
757
- node.zunion(*keys, **options)
802
+ node.zunion(keys, **options)
758
803
  end
759
804
  end
760
805
 
761
806
  # Add multiple sorted sets and store the resulting sorted set in a new key.
762
- def zunionstore(destination, keys, **options)
763
- ensure_same_node(:zunionstore, [destination] + keys) do |node|
807
+ def zunionstore(destination, *keys, **options)
808
+ keys.flatten!(1)
809
+ ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
764
810
  node.zunionstore(destination, keys, **options)
765
811
  end
766
812
  end
767
813
 
768
814
  # Return the difference between the first and all successive input sorted sets.
769
815
  def zdiff(*keys, **options)
816
+ keys.flatten!(1)
770
817
  ensure_same_node(:zdiff, keys) do |node|
771
- node.zdiff(*keys, **options)
818
+ node.zdiff(keys, **options)
772
819
  end
773
820
  end
774
821
 
775
822
  # Compute the difference between the first and all successive input sorted sets
776
823
  # and store the resulting sorted set in a new key.
777
- def zdiffstore(destination, keys, **options)
824
+ def zdiffstore(destination, *keys, **options)
825
+ keys.flatten!(1)
778
826
  ensure_same_node(:zdiffstore, [destination] + keys) do |node|
779
827
  node.zdiffstore(destination, keys, **options)
780
828
  end
@@ -801,7 +849,7 @@ class Redis
801
849
  end
802
850
 
803
851
  def mapped_hmset(key, hash)
804
- node_for(key).hmset(key, *hash.to_a.flatten)
852
+ node_for(key).hmset(key, hash)
805
853
  end
806
854
 
807
855
  # Get the value of a hash field.
@@ -811,11 +859,13 @@ class Redis
811
859
 
812
860
  # Get the values of all the given hash fields.
813
861
  def hmget(key, *fields)
814
- node_for(key).hmget(key, *fields)
862
+ fields.flatten!(1)
863
+ node_for(key).hmget(key, fields)
815
864
  end
816
865
 
817
866
  def mapped_hmget(key, *fields)
818
- Hash[*fields.zip(hmget(key, *fields)).flatten]
867
+ fields.flatten!(1)
868
+ node_for(key).mapped_hmget(key, fields)
819
869
  end
820
870
 
821
871
  def hrandfield(key, count = nil, **options)
@@ -824,7 +874,8 @@ class Redis
824
874
 
825
875
  # Delete one or more hash fields.
826
876
  def hdel(key, *fields)
827
- node_for(key).hdel(key, *fields)
877
+ fields.flatten!(1)
878
+ node_for(key).hdel(key, fields)
828
879
  end
829
880
 
830
881
  # Determine if a hash field exists.
@@ -881,7 +932,7 @@ class Redis
881
932
 
882
933
  # Stop listening for messages posted to the given channels.
883
934
  def unsubscribe(*channels)
884
- raise "Can't unsubscribe if not subscribed." unless subscribed?
935
+ raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
885
936
 
886
937
  @subscribed_node.unsubscribe(*channels)
887
938
  end
@@ -928,9 +979,7 @@ class Redis
928
979
  def multi(&block)
929
980
  raise CannotDistribute, :multi unless @watch_key
930
981
 
931
- result = node_for(@watch_key).multi(&block)
932
- @watch_key = nil if block_given?
933
- result
982
+ node_for(@watch_key).multi(&block)
934
983
  end
935
984
 
936
985
  # Execute all commands issued after MULTI.
@@ -1020,7 +1069,8 @@ class Redis
1020
1069
  end
1021
1070
 
1022
1071
  def key_tag(key)
1023
- key.to_s[@tag, 1] if @tag
1072
+ key = key.to_s
1073
+ key[@tag, 1] if key.match?(@tag)
1024
1074
  end
1025
1075
 
1026
1076
  def ensure_same_node(command, keys)
data/lib/redis/errors.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Redis
4
4
  # Base error for all redis-rb errors.
5
- class BaseError < RuntimeError
5
+ class BaseError < StandardError
6
6
  end
7
7
 
8
8
  # Raised by the connection when a protocol error occurs.
@@ -20,6 +20,15 @@ class Redis
20
20
  class CommandError < BaseError
21
21
  end
22
22
 
23
+ class PermissionError < CommandError
24
+ end
25
+
26
+ class WrongTypeError < CommandError
27
+ end
28
+
29
+ class OutOfMemoryError < CommandError
30
+ end
31
+
23
32
  # Base error for connection related errors.
24
33
  class BaseConnectionError < BaseError
25
34
  end
@@ -40,58 +49,14 @@ class Redis
40
49
  class InheritedError < BaseConnectionError
41
50
  end
42
51
 
52
+ # Generally raised during Redis failover scenarios
53
+ class ReadOnlyError < BaseConnectionError
54
+ end
55
+
43
56
  # Raised when client options are invalid.
44
57
  class InvalidClientOptionError < BaseError
45
58
  end
46
59
 
47
- class Cluster
48
- # Raised when client connected to redis as cluster mode
49
- # and failed to fetch cluster state information by commands.
50
- class InitialSetupError < BaseError
51
- # @param errors [Array<Redis::BaseError>]
52
- def initialize(errors)
53
- super("Redis client could not fetch cluster information: #{errors.map(&:message).uniq.join(',')}")
54
- end
55
- end
56
-
57
- # Raised when client connected to redis as cluster mode
58
- # and some cluster subcommands were called.
59
- class OrchestrationCommandNotSupported < BaseError
60
- def initialize(command, subcommand = '')
61
- str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
62
- msg = "#{str} command should be used with care "\
63
- 'only by applications orchestrating Redis Cluster, like redis-trib, '\
64
- 'and the command if used out of the right context can leave the cluster '\
65
- 'in a wrong state or cause data loss.'
66
- super(msg)
67
- end
68
- end
69
-
70
- # Raised when error occurs on any node of cluster.
71
- class CommandErrorCollection < BaseError
72
- attr_reader :errors
73
-
74
- # @param errors [Hash{String => Redis::CommandError}]
75
- # @param error_message [String]
76
- def initialize(errors, error_message = 'Command errors were replied on any node')
77
- @errors = errors
78
- super(error_message)
79
- end
80
- end
81
-
82
- # Raised when cluster client can't select node.
83
- class AmbiguousNodeError < BaseError
84
- def initialize(command)
85
- super("Cluster client doesn't know which node the #{command} command should be sent to.")
86
- end
87
- end
88
-
89
- # Raised when commands in pipelining include cross slot keys.
90
- class CrossSlotPipeliningError < BaseError
91
- def initialize(keys)
92
- super("Cluster client couldn't send pipelining to single node. "\
93
- "The commands include cross slot keys. #{keys}")
94
- end
95
- end
60
+ class SubscriptionError < BaseError
96
61
  end
97
62
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'zlib'
4
+ require 'digest/md5'
4
5
 
5
6
  class Redis
6
7
  class HashRing
@@ -25,7 +26,7 @@ class Redis
25
26
  def add_node(node)
26
27
  @nodes << node
27
28
  @replicas.times do |i|
28
- key = Zlib.crc32("#{node.id}:#{i}")
29
+ key = server_hash_for("#{node.id}:#{i}")
29
30
  @ring[key] = node
30
31
  @sorted_keys << key
31
32
  end
@@ -35,7 +36,7 @@ class Redis
35
36
  def remove_node(node)
36
37
  @nodes.reject! { |n| n.id == node.id }
37
38
  @replicas.times do |i|
38
- key = Zlib.crc32("#{node.id}:#{i}")
39
+ key = server_hash_for("#{node.id}:#{i}")
39
40
  @ring.delete(key)
40
41
  @sorted_keys.reject! { |k| k == key }
41
42
  end
@@ -43,47 +44,46 @@ class Redis
43
44
 
44
45
  # get the node in the hash ring for this key
45
46
  def get_node(key)
46
- get_node_pos(key)[0]
47
- end
48
-
49
- def get_node_pos(key)
50
- return [nil, nil] if @ring.empty?
51
-
52
- crc = Zlib.crc32(key)
53
- idx = HashRing.binary_search(@sorted_keys, crc)
54
- [@ring[@sorted_keys[idx]], idx]
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
55
50
  end
56
51
 
57
52
  def iter_nodes(key)
58
53
  return [nil, nil] if @ring.empty?
59
54
 
60
- _, pos = get_node_pos(key)
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
61
57
  @ring.size.times do |n|
62
58
  yield @ring[@sorted_keys[(pos + n) % @ring.size]]
63
59
  end
64
60
  end
65
61
 
62
+ private
63
+
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
67
+
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
71
+
66
72
  # Find the closest index in HashRing with value <= the given value
67
- def self.binary_search(ary, value)
68
- upper = ary.size - 1
73
+ def binary_search(ary, value)
74
+ upper = ary.size
69
75
  lower = 0
70
- idx = 0
71
-
72
- while lower <= upper
73
- idx = (lower + upper) / 2
74
- comp = ary[idx] <=> value
75
76
 
76
- if comp == 0
77
- return idx
78
- elsif comp > 0
79
- upper = idx - 1
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
80
81
  else
81
- lower = idx + 1
82
+ lower = mid + 1
82
83
  end
83
84
  end
84
85
 
85
- upper = ary.size - 1 if upper < 0
86
- upper
86
+ upper - 1
87
87
  end
88
88
  end
89
89
  end