module Mongo::ReadPreference

Constants

MONGOS_MODES
READ_PREFERENCES
SECONDARY_OK_COMMANDS

Commands that may be sent to replica-set secondaries, depending on read preference and tags. All other commands are always run on the primary.

Public Class Methods

cmd_read_pref(read_pref, selector) click to toggle source

Given a command and read preference, possibly reroute to primary.

# File lib/mongo/functional/read_preference.rb, line 91
def self.cmd_read_pref(read_pref, selector)
  ReadPreference::validate(read_pref)
  if reroute_cmd_primary?(read_pref, selector)
    warn "Database command '#{selector.keys.first}' rerouted to primary node"
    read_pref = :primary
  end
  read_pref
end
mongos(mode, tag_sets) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 52
def self.mongos(mode, tag_sets)
  if mode != :secondary_preferred || !tag_sets.empty?
    mongos_read_preference = BSON::OrderedHash[:mode => MONGOS_MODES[mode]]
    mongos_read_preference[:tags] = tag_sets if !tag_sets.empty?
  end
  mongos_read_preference
end
reroute_cmd_primary?(read_pref, selector) click to toggle source

Returns true if the command should be rerouted to the primary.

# File lib/mongo/functional/read_preference.rb, line 85
def self.reroute_cmd_primary?(read_pref, selector)
  return false if read_pref == :primary
  !secondary_ok?(selector)
end
secondary_ok?(selector) click to toggle source

Returns true if it's ok to run the command on a secondary

# File lib/mongo/functional/read_preference.rb, line 70
def self.secondary_ok?(selector)
  command = selector.keys.first.to_s.downcase

  if command == 'mapreduce'
    out = selector.select { |k, v| k.to_s.downcase == 'out' }.first.last
    # the server only looks at the first key in the out object
    return out.respond_to?(:keys) && out.keys.first.to_s.downcase == 'inline'
  elsif command == 'aggregate'
    pipeline = selector['pipeline'] || selector[:pipeline]
    return pipeline.none? { |op| op.key?('$out') || op.key?(:$out) }
  end
  SECONDARY_OK_COMMANDS.member?(command)
end
validate(value) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 60
def self.validate(value)
  if READ_PREFERENCES.include?(value)
    return true
  else
    raise MongoArgumentError, "#{value} is not a valid read preference. " +
      "Please specify one of the following read preferences as a symbol: #{READ_PREFERENCES}"
  end
end

Public Instance Methods

read_pool(read_preference_override={}) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 108
def read_pool(read_preference_override={})
  return primary_pool if mongos?

  read_pref = read_preference.merge(read_preference_override)

  if pinned_pool && pinned_pool[:read_preference] == read_pref
    pool = pinned_pool[:pool]
  else
    unpin_pool
    pool = select_pool(read_pref)
  end

  unless pool
    raise ConnectionFailure, "No replica set member available for query " +
      "with read preference matching mode #{read_pref[:mode]} and tags " +
      "matching #{read_pref[:tags]}."
  end

  pool
end
read_preference() click to toggle source
# File lib/mongo/functional/read_preference.rb, line 100
def read_preference
  {
    :mode => @read,
    :tags => @tag_sets,
    :latency => @acceptable_latency
  }
end
select_near_pool(candidates, read_pref) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 153
def select_near_pool(candidates, read_pref)
  matching_pools = match_tag_sets(candidates, read_pref[:tags])
  select_near_pools(matching_pools, read_pref).first
end
select_pool(read_pref) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 129
def select_pool(read_pref)
  if read_pref[:mode] == :primary && !read_pref[:tags].empty?
    raise MongoArgumentError, "Read preference :primary cannot be combined with tags"
  end

  case read_pref[:mode]
    when :primary
      primary_pool
    when :primary_preferred
      primary_pool || select_secondary_pool(secondary_pools, read_pref)
    when :secondary
      select_secondary_pool(secondary_pools, read_pref)
    when :secondary_preferred
      select_secondary_pool(secondary_pools, read_pref) || primary_pool
    when :nearest
      select_near_pool(pools, read_pref)
  end
end
select_secondary_pool(candidates, read_pref) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 148
def select_secondary_pool(candidates, read_pref)
  matching_pools = match_tag_sets(secondary_pools, read_pref[:tags])
  select_near_pools(matching_pools, read_pref).first
end

Private Instance Methods

match_tag_sets(candidates, tag_sets = []) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 169
def match_tag_sets(candidates, tag_sets = [])
  return candidates if tag_sets.empty?
  matches = []
  tag_sets.find do |tag_set|
    matches = candidates.select do |candidate|
      tag_set.none? do |k,v|
        candidate.tags[k.to_s] != v
      end
    end
    !matches.empty?
  end
  matches
end
select_near_pools(candidates, read_pref) click to toggle source
# File lib/mongo/functional/read_preference.rb, line 160
def select_near_pools(candidates, read_pref)
  return candidates if candidates.empty?
  latency = read_pref[:latency]
  nearest_pool = candidates.min_by(&:ping_time)
  max_latency = nearest_pool.ping_time + latency
  near_pools = candidates.select { |candidate| candidate.ping_time <= max_latency }
  near_pools.shuffle!
end