Class: Redis::KeyHash

Inherits:
Object
  • Object
show all
Defined in:
lib/redis/key_hash.rb,
lib/redis/key_hash/version.rb

Overview

Namespace for key-hashing methods.

Constant Summary collapse

VERSION =

Version plan/history:

0.0.1 - Still in Prosperworks/ALI/vendor/gems/redis-key_hash.

0.0.2 - Broke out into Prosperworks/redis-key_hash, make public.

0.0.3 - Fix :rc to match redis.io/topics/cluster-spec,

added Rubocop checks.

0.0.4 - Verified existing behavior w/r/t Redis::Namespace.

Added more details in Redis::ImpendingCrossSlotError.

Rubocop polish and defiance.

Redis::KeyHash::ClassMethods inner-inner class removed.

Redis::KeyHash changed to a class, not a module.

Redis::ImpendingCrossSlotError changed from
ArgumentError to Redis::RuntimeError.

0.1.0 - (future) Big README.md and Rdoc update, solicit feedback

from select external beta users.

0.2.0 - (future) Incorporate feedback, announce.

'0.0.4'.freeze

Class Method Summary collapse

Class Method Details

.all_in_one_slot!(*keys, namespace: nil, styles: DEFAULT_STYLES) ⇒ Object

Like all_in_one_slot?, mismatch raises Redis::ImpendingCrossSlotError.

all keys as per the redis-namespace gem before testing.

the styles, false if there is any doubt.

under all styles by virtue of having a single hash_tag.

keys have a different hash_tag hence will not provably have the same hash_slot

Parameters:

  • keys

    String keys to be tested

  • namespace (defaults to: nil)

    String or nil if non-nil, applied as a prefix to

  • styles (defaults to: DEFAULT_STYLES)

    Array of Symbols and/or Regexps as per hash_tag().

Returns:

  • true if all of keys will hash to the same slot in all of

  • true if all of keys provably have the same hash_slot



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/redis/key_hash.rb', line 96

def self.all_in_one_slot!(*keys, namespace: nil, styles: DEFAULT_STYLES)
  namespaced_keys   = keys
  if namespace
    #
    # Although Redis::Namespace.add_namespace is private, I have
    # confirmed that when namespace is the empty string, "key"
    # maps to ":key".
    #
    # That is, namespace nil has no effect, but namespace ''
    # results in a ':' prepended to every key.
    #
    # Naturally, this can affect the key's hash tag.
    #
    namespaced_keys = keys.map { |key| "#{namespace}:#{key}" }
  end
  problems          = []
  styles.each do |style|
    tags            = namespaced_keys.map do |namespaced_key|
      hash_tag(namespaced_key,style: style)
    end.uniq
    next if tags.size <= 1
    problems << "style #{style} sees tags #{tags.join(',')}"
  end
  if 0 != problems.size
    raise Redis::ImpendingCrossSlotError.new(
            namespace,
            keys,
            namespaced_keys,
            problems
          )
  end
  true
end

.all_in_one_slot?(*keys, namespace: nil, styles: DEFAULT_STYLES) ⇒ Boolean

Tests whether all of keys will hash to the same slot in all specified sharding styles.

all keys as per the redis-namespace gem before testing.

all styles by virtue of having a single hash_tag, false otherwise.

Parameters:

  • keys

    String keys to be tested

  • namespace (defaults to: nil)

    String or nil if non-nil, applied as a prefix to

  • styles (defaults to: DEFAULT_STYLES)

    Array of Symbols and/or Regexps as per hash_tag().

Returns:

  • (Boolean)

    true if all of keys provably have the same hash_slot under



69
70
71
72
73
74
75
# File 'lib/redis/key_hash.rb', line 69

def self.all_in_one_slot?(*keys, namespace: nil, styles: DEFAULT_STYLES)
  all_in_one_slot!(*keys, namespace: namespace, styles: styles)
rescue Redis::ImpendingCrossSlotError
  return false
else
  return true
end

.crc16(key) ⇒ Object

Computes the Redis crc16 for a given key, as per the reference implementation provided in redis.io/topics/cluster-spec.

This implementation is taken largely from that reference document, changed only slightly to port to Ruby.

compute a hash_key.

Parameters:

  • String

    key

  • non-negative

    Integer the crc16 which Redis will use to



185
186
187
188
189
190
191
# File 'lib/redis/key_hash.rb', line 185

def self.crc16(key)
  crc = 0
  key.each_char do |char|
    crc = ((crc<<8) & 0xFFFF) ^ CRC16TAB[((crc>>8) ^ char.ord) & 0x00FF]
  end
  crc
end

.hash_slot(key, style: DEFAULT_STYLE) ⇒ Object

Computes the Redis hash_slot for a given key.

Uses :style as per hash_tag, but performs hashing as per RC only. We know through documentation and experimentation that RC uses crc16() and modulo 16384. We do not know what RLEC does, but until we have a better model we assume it is the same. This is probably a false assumption since the RLEC docs state that the number of shards can vary from cluster to cluster. But for many analyses, using the same hash as RC is still useful.

Parameters:

  • String

    key to be hashed

  • Symbol

    :rc or :rlec or Regexp which defines one capture group

  • non-negative

    Integer the hash which Redis will use to slot key



169
170
171
172
# File 'lib/redis/key_hash.rb', line 169

def self.hash_slot(key, style: DEFAULT_STYLE)
  tag = hash_tag(key, style: style)
  crc16(tag) % 16384
end

.hash_tag(key, style: DEFAULT_STYLE) ⇒ Object

Computes the hash tag for a given key under a given Redis clustering algorithm.

Parameters:

  • String

    key to be hashed

  • Symbol

    :rc or rlec or Regexp which defines one capture group

  • String

    the tag extracted from key as appropriate for :style.



139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/redis/key_hash.rb', line 139

def self.hash_tag(key, style: DEFAULT_STYLE)
  regexp = nil
  if KNOWN_STYLES.key?(style)
    regexp = KNOWN_STYLES[style] # some are predefined
  elsif style.is_a?(Regexp)
    regexp = style               # you can define your own
  end
  if !regexp
    raise ArgumentError, "bogus style #{style}"
  end
  match = regexp.match(key)
  match ? match[1] : key
end