Module: Redis::KeyHash::ClassMethods

Defined in:
lib/redis/key_hash.rb

Instance Method Summary collapse

Instance 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



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/redis/key_hash.rb', line 78

def all_in_one_slot!(*keys, namespace: nil, styles: DEFAULT_STYLES)
  nkeys       = namespace ? keys.map { |key| "#{namespace}:#{key}" } : keys
  style2slot  = Hash.new
  problems    = []
  styles.each do |style|
    tags      = nkeys.map { |nkey| hash_tag(nkey,style: style) }.uniq
    next if tags.size <= 1
    problems << "style #{style} sees tags #{tags.join(',')}"
  end
  if 0 != problems.size
    err  = "CROSSSLOT"
    err += " namespace=#{namespace}"
    err += " keys=#{keys}"
    err += " problems=#{problems}"
    raise Redis::ImpendingCrossSlotError, err
  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



49
50
51
52
53
54
55
56
57
# File 'lib/redis/key_hash.rb', line 49

def all_in_one_slot?(*keys, namespace: nil, styles: DEFAULT_STYLES)
  begin
    all_in_one_slot!(*keys, namespace: namespace, styles: styles)
  rescue Redis::ImpendingCrossSlotError
    return false
  else
    return true
  end
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



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

def 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



153
154
155
156
# File 'lib/redis/key_hash.rb', line 153

def 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.

The :style => :rc implementation matches Redis Cluster exactly and is taken largely from the reference example provided in redis.io/topics/cluster-spec.

The :style => :rlec implementation is partly speculative. It is mostly interpreted from the default RedisLabs Enterprise Cluster document redislabs.com/redis-enterprise-documentation/concepts-architecture/architecture/database-clustering/ plus our experience at ProsperWorks that, out of the box, RLEC uses the last {}-expr, not the first as in RC, from :rc (which we would not care about), they are also structually different (which can cause bugs unless we take special care). :rc uses the first {}-expr in the, :rlec uses the last {}-expr.

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.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/redis/key_hash.rb', line 120

def hash_tag(key, style: DEFAULT_STYLE)
  regexp = nil
  case style
  when :rc
    regexp = /{([^}]*)}/     # RC uses the first {}-expr
  when :rlec
    regexp = /.*\{(.*)\}.*/  # RLEC default per docs, uses last {}-expr
  when Regexp
    regexp = style           # you can define your own
  end
  if !regexp
    raise ArgumentError, "bogus style #{style}"
  end
  match = regexp.match(key)
  return match ? match[1] : key
end