Class: Picky::Backends::Redis

Inherits:
Backend show all
Defined in:
lib/picky/backends/redis.rb,
lib/picky/backends/redis/list.rb,
lib/picky/backends/redis/basic.rb,
lib/picky/backends/redis/float.rb,
lib/picky/backends/redis/string.rb,
lib/picky/backends/redis/directly_manipulable.rb

Defined Under Namespace

Modules: DirectlyManipulable, NonScripting, Scripting Classes: Basic, Float, List, String

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Backend

#to_s

Constructor Details

#initialize(options = {}) ⇒ Redis

Returns a new instance of Redis.



12
13
14
15
16
17
18
19
# File 'lib/picky/backends/redis.rb', line 12

def initialize options = {}
  maybe_load_hiredis
  check_hiredis_gem
  check_redis_gem

  @client   = options[:client] || ::Redis.new(:db => (options[:db] || 15))
  @realtime = options[:realtime]
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



9
10
11
# File 'lib/picky/backends/redis.rb', line 9

def client
  @client
end

#realtimeObject (readonly)

Returns the value of attribute realtime.



9
10
11
# File 'lib/picky/backends/redis.rb', line 9

def realtime
  @realtime
end

Class Method Details

.extract_hostObject



199
200
201
# File 'lib/picky/backends/redis.rb', line 199

def self.extract_host
  @host ||= Socket.gethostname
end

Instance Method Details

#at_least_version(major_minor_patch, should_be) ⇒ Object

Compares two versions each in an array [major, minor, patch] format and returns true if the first version is higher or the same as the second one. False if not.

Note: Destructive.



80
81
82
83
# File 'lib/picky/backends/redis.rb', line 80

def at_least_version major_minor_patch, should_be
  3.times { return false if major_minor_patch.shift < should_be.shift }
  true
end

#check_hiredis_gemObject

It’s ok.



25
26
27
28
29
# File 'lib/picky/backends/redis.rb', line 25

def check_hiredis_gem
  require 'redis/connection/hiredis'
rescue LoadError
  # It's ok, the next check will fail if this one does.
end

#check_redis_gemObject

It’s ok, the next check will fail if this one does.



30
31
32
33
34
# File 'lib/picky/backends/redis.rb', line 30

def check_redis_gem
  require 'redis'
rescue LoadError => e
  warn_gem_missing 'redis', 'the Redis client'
end

#create_configuration(bundle, _ = nil) ⇒ Object

Returns an object that on #initial, #load returns an object that responds to:

[:key] # => value (a value for this config key)


57
58
59
# File 'lib/picky/backends/redis.rb', line 57

def create_configuration bundle, _ = nil
  String.new client, "#{PICKY_ENVIRONMENT}:#{bundle.identifier}:configuration", realtime: realtime
end

#create_inverted(bundle, _ = nil) ⇒ Object

Returns an object that on #initial, #load returns an object that responds to:

[:token] # => [id, id, id, id, id] (an array of ids)


39
40
41
# File 'lib/picky/backends/redis.rb', line 39

def create_inverted bundle, _ = nil
  List.new client, "#{PICKY_ENVIRONMENT}:#{bundle.identifier}:inverted", realtime: realtime
end

#create_realtime(bundle, _ = nil) ⇒ Object

Returns an object that on #initial, #load returns an object that responds to:

[id] # => [:sym1, :sym2]


63
64
65
# File 'lib/picky/backends/redis.rb', line 63

def create_realtime bundle, _ = nil
  List.new client, "#{PICKY_ENVIRONMENT}:#{bundle.identifier}:realtime", realtime: realtime
end

#create_similarity(bundle, _ = nil) ⇒ Object

Returns an object that on #initial, #load returns an object that responds to:

[:encoded] # => [:original, :original] (an array of original symbols this similarity encoded thing maps to)


51
52
53
# File 'lib/picky/backends/redis.rb', line 51

def create_similarity bundle, _ = nil
  List.new client, "#{PICKY_ENVIRONMENT}:#{bundle.identifier}:similarity", realtime: realtime
end

#create_weights(bundle, _ = nil) ⇒ Object

Returns an object that on #initial, #load returns an object that responds to:

[:token] # => 1.23 (a weight)


45
46
47
# File 'lib/picky/backends/redis.rb', line 45

def create_weights bundle, _ = nil
  Float.new client, "#{PICKY_ENVIRONMENT}:#{bundle.identifier}:weights", realtime: realtime
end

#generate_intermediate_result_idObject

Use the host and pid (generated lazily in child processes) for the result.



211
212
213
# File 'lib/picky/backends/redis.rb', line 211

def generate_intermediate_result_id
  @intermediate_result_id ||= "#{host}:#{pid}:picky:result"
end

#hostObject



202
203
204
# File 'lib/picky/backends/redis.rb', line 202

def host
  self.class.extract_host
end

#identifiers_for(combinations) ⇒ Object



215
216
217
218
219
# File 'lib/picky/backends/redis.rb', line 215

def identifiers_for combinations
  combinations.inject([]) do |identifiers, combination|
    identifiers << "#{PICKY_ENVIRONMENT}:#{combination.identifier}"
  end
end

#ids(combinations, amount, offset) ⇒ Object

Returns the result ids for the allocation.

Developers wanting to program fast intersection routines, can do so analogue to this in their own backend implementations.

Note: We use the amount and offset hints to speed Redis up.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/picky/backends/redis.rb', line 157

def ids combinations, amount, offset
  # TODO This is actually not correct:
  #      A dumped/loaded Redis backend should use
  #      the Redis backend calculation method.
  #      So loaded? would be more appropriate.
  #
  if realtime
    # Just checked once on the first call.
    #
    if redis_with_scripting?
      @ids_script = "local intersected = redis.call('zinterstore', ARGV[1], #(KEYS), unpack(KEYS)); if intersected == 0 then redis.call('del', ARGV[1]); return {}; end local results = redis.call('zrange', ARGV[1], tonumber(ARGV[2]), tonumber(ARGV[3])); redis.call('del', ARGV[1]); return results;"

      require 'digest/sha1'
      @ids_script_hash = nil

      # Overrides _this_ method.
      #
      extend Scripting
    else
      # Overrides _this_ method.
      #
      extend NonScripting
    end
  else
    # Remove _this_ method and use the super
    # class method from now on.
    #
    # Note: This fails if there are multiple
    # Redis backends with different versions.
    #
    self.class.send :remove_method, __method__
  end
  # Call the newly installed / super class version.
  #
  ids combinations, amount, offset
end

#maybe_load_hiredisObject



20
21
22
23
24
# File 'lib/picky/backends/redis.rb', line 20

def maybe_load_hiredis
  require 'hiredis'
rescue LoadError
  # It's ok.
end

#pidObject



206
207
208
# File 'lib/picky/backends/redis.rb', line 206

def pid
  @pid ||= Process.pid
end

#redis_versionObject

Returns an array describing the current Redis version.

Note: This method assumes that clients answer

to #info with a hash (string/symbol keys)
detailing the infos.

Example:

backend.redis_version # => [2, 4, 1]


95
96
97
98
99
# File 'lib/picky/backends/redis.rb', line 95

def redis_version
  infos          = client.info
  version_string = infos['redis_version'] || infos[:redis_version]
  version_string.split('.').map &:to_i
end

#redis_with_scripting?Boolean

Does the Redis version already include scripting support?

Returns:



70
71
72
# File 'lib/picky/backends/redis.rb', line 70

def redis_with_scripting?
  at_least_version redis_version, [2, 6, 0]
end

#weight(combinations) ⇒ Object

Returns the total weight for the combinations.



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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/picky/backends/redis.rb', line 103

def weight combinations
  # Note: A nice experiment that generated far too many strings.
  #
  # if redis_with_scripting?
  #   @@weight_script = "local sum = 0; for i=1,#(KEYS),2 do local value = redis.call('hget', KEYS[i], KEYS[i+1]); if value then sum = sum + value end end return sum;"
  #
  #   require 'digest/sha1'
  #   @@weight_sent_once = nil
  #
  #   # Scripting version of #ids.
  #   #
  #   class << self
  #     def weight combinations
  #       namespaces_keys = combinations.inject([]) do |namespaces_keys, combination|
  #         namespaces_keys << "#{combination.bundle.identifier}:weights"
  #         namespaces_keys << combination.token.text
  #       end
  #
  #       # Assume it's using EVALSHA.
  #       #
  #       begin
  #         client.evalsha @@weight_sent_once,
  #                        namespaces_keys.size,
  #                        *namespaces_keys
  #       rescue RuntimeError => e
  #         # Make the server have a SHA-1 for the script.
  #         #
  #         @@weight_sent_once = Digest::SHA1.hexdigest @@weight_script
  #         client.eval @@weight_script,
  #                     namespaces_keys.size,
  #                     *namespaces_keys
  #       end
  #     end
  #   end
  # else
  #   class << self
  #     def weight combinations
      combinations.score
  #     end
  #   end
  # end
  # # Call the newly installed version.
  # #
  # weight combinations
end