mongo 1.6.4 → 1.7.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -13
- data/Rakefile +7 -10
- data/docs/{GridFS.md → GRID_FS.md} +0 -0
- data/docs/HISTORY.md +16 -0
- data/docs/READ_PREFERENCE.md +70 -10
- data/docs/TUTORIAL.md +2 -2
- data/lib/mongo.rb +2 -0
- data/lib/mongo/collection.rb +62 -11
- data/lib/mongo/connection.rb +31 -41
- data/lib/mongo/cursor.rb +42 -86
- data/lib/mongo/db.rb +10 -8
- data/lib/mongo/networking.rb +30 -65
- data/lib/mongo/repl_set_connection.rb +91 -170
- data/lib/mongo/sharded_connection.rb +221 -0
- data/lib/mongo/util/node.rb +29 -36
- data/lib/mongo/util/pool.rb +10 -3
- data/lib/mongo/util/pool_manager.rb +77 -90
- data/lib/mongo/util/sharding_pool_manager.rb +143 -0
- data/lib/mongo/util/support.rb +22 -2
- data/lib/mongo/util/tcp_socket.rb +10 -15
- data/lib/mongo/util/uri_parser.rb +17 -10
- data/lib/mongo/version.rb +1 -1
- data/test/collection_test.rb +133 -1
- data/test/connection_test.rb +50 -4
- data/test/db_api_test.rb +3 -3
- data/test/db_test.rb +6 -1
- data/test/replica_sets/basic_test.rb +3 -6
- data/test/replica_sets/complex_connect_test.rb +14 -2
- data/test/replica_sets/complex_read_preference_test.rb +237 -0
- data/test/replica_sets/connect_test.rb +47 -67
- data/test/replica_sets/count_test.rb +1 -1
- data/test/replica_sets/cursor_test.rb +70 -0
- data/test/replica_sets/read_preference_test.rb +171 -118
- data/test/replica_sets/refresh_test.rb +3 -3
- data/test/replica_sets/refresh_with_threads_test.rb +2 -2
- data/test/replica_sets/rs_test_helper.rb +2 -2
- data/test/sharded_cluster/basic_test.rb +112 -0
- data/test/sharded_cluster/mongo_config_test.rb +126 -0
- data/test/sharded_cluster/sc_test_helper.rb +39 -0
- data/test/test_helper.rb +3 -3
- data/test/threading/threading_with_large_pool_test.rb +1 -1
- data/test/tools/mongo_config.rb +307 -0
- data/test/tools/repl_set_manager.rb +12 -12
- data/test/unit/collection_test.rb +1 -1
- data/test/unit/cursor_test.rb +11 -6
- data/test/unit/db_test.rb +4 -0
- data/test/unit/grid_test.rb +2 -0
- data/test/unit/read_test.rb +39 -8
- data/test/uri_test.rb +4 -8
- metadata +144 -127
@@ -0,0 +1,221 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 10gen Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
# ++
|
18
|
+
|
19
|
+
module Mongo
|
20
|
+
|
21
|
+
# Instantiates and manages connections to a MongoDB sharded cluster for high availability.
|
22
|
+
class ShardedConnection < ReplSetConnection
|
23
|
+
|
24
|
+
SHARDED_CLUSTER_OPTS = [:refresh_mode, :refresh_interval]
|
25
|
+
|
26
|
+
attr_reader :seeds, :refresh_interval, :refresh_mode,
|
27
|
+
:refresh_version, :manager
|
28
|
+
|
29
|
+
# Create a connection to a MongoDB sharded cluster.
|
30
|
+
#
|
31
|
+
# If no args are provided, it will check <code>ENV["MONGODB_URI"]</code>.
|
32
|
+
#
|
33
|
+
# @param [Array] seeds "host:port" strings
|
34
|
+
#
|
35
|
+
# @option opts [String] :name (nil) The name of the sharded cluster to connect to. You
|
36
|
+
# can use this option to verify that you're connecting to the right sharded cluster.
|
37
|
+
# @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
|
38
|
+
# propagated to DB objects instantiated off of this Connection. This
|
39
|
+
# default can be overridden upon instantiation of any DB by explicitly setting a :safe value
|
40
|
+
# on initialization.
|
41
|
+
# @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
|
42
|
+
# @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
|
43
|
+
# connection pool. Note: this setting is relevant only for multi-threaded applications.
|
44
|
+
# @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
|
45
|
+
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
|
46
|
+
# Note: this setting is relevant only for multi-threaded applications.
|
47
|
+
# @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
|
48
|
+
# @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
|
49
|
+
# connection attempt.
|
50
|
+
# @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
|
51
|
+
# @option opts [Boolean] :refresh_mode (false) Set this to :sync to periodically update the
|
52
|
+
# state of the connection every :refresh_interval seconds. Sharded cluster connection failures
|
53
|
+
# will always trigger a complete refresh. This option is useful when you want to add new nodes
|
54
|
+
# or remove sharded cluster nodes not currently in use by the driver.
|
55
|
+
# @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
|
56
|
+
# between calls to check the sharded cluster's state.
|
57
|
+
# Note: that the number of seed nodes does not have to be equal to the number of sharded cluster members.
|
58
|
+
# The purpose of seed nodes is to permit the driver to find at least one sharded cluster member even if a member is down.
|
59
|
+
#
|
60
|
+
# @example Connect to a sharded cluster and provide two seed nodes.
|
61
|
+
# Mongo::ShardedConnection.new(['localhost:30000', 'localhost:30001'])
|
62
|
+
#
|
63
|
+
# @raise [MongoArgumentError] This is raised for usage errors.
|
64
|
+
#
|
65
|
+
# @raise [ConnectionFailure] This is raised for the various connection failures.
|
66
|
+
def initialize(*args)
|
67
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
68
|
+
|
69
|
+
nodes = args.flatten
|
70
|
+
|
71
|
+
if nodes.empty? and ENV.has_key?('MONGODB_URI')
|
72
|
+
parser = URIParser.new ENV['MONGODB_URI']
|
73
|
+
if parser.direct?
|
74
|
+
raise MongoArgumentError, "Mongo::ShardedConnection.new called with no arguments, but ENV['MONGODB_URI'] implies a direct connection."
|
75
|
+
end
|
76
|
+
opts = parser.connection_options.merge! opts
|
77
|
+
nodes = [parser.nodes]
|
78
|
+
end
|
79
|
+
|
80
|
+
unless nodes.length > 0
|
81
|
+
raise MongoArgumentError, "A ShardedConnection requires at least one seed node."
|
82
|
+
end
|
83
|
+
|
84
|
+
@seeds = nodes.map do |host_port|
|
85
|
+
host, port = host_port.split(":")
|
86
|
+
[ host, port.to_i ]
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: add a method for replacing this list of node.
|
90
|
+
@seeds.freeze
|
91
|
+
|
92
|
+
# Refresh
|
93
|
+
@last_refresh = Time.now
|
94
|
+
@refresh_version = 0
|
95
|
+
|
96
|
+
# No connection manager by default.
|
97
|
+
@manager = nil
|
98
|
+
@old_managers = []
|
99
|
+
|
100
|
+
# Lock for request ids.
|
101
|
+
@id_lock = Mutex.new
|
102
|
+
|
103
|
+
@pool_mutex = Mutex.new
|
104
|
+
@connected = false
|
105
|
+
|
106
|
+
@safe_mutex_lock = Mutex.new
|
107
|
+
@safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
|
108
|
+
|
109
|
+
@connect_mutex = Mutex.new
|
110
|
+
@refresh_mutex = Mutex.new
|
111
|
+
|
112
|
+
check_opts(opts)
|
113
|
+
setup(opts)
|
114
|
+
end
|
115
|
+
|
116
|
+
def valid_opts
|
117
|
+
GENERIC_OPTS + SHARDED_CLUSTER_OPTS
|
118
|
+
end
|
119
|
+
|
120
|
+
def inspect
|
121
|
+
"<Mongo::ShardedConnection:0x#{self.object_id.to_s(16)} @seeds=#{@seeds.inspect} " +
|
122
|
+
"@connected=#{@connected}>"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Initiate a connection to the sharded cluster.
|
126
|
+
def connect(force = !@connected)
|
127
|
+
return unless force
|
128
|
+
log(:info, "Connecting...")
|
129
|
+
@connect_mutex.synchronize do
|
130
|
+
discovered_seeds = @manager ? @manager.seeds : []
|
131
|
+
@old_managers << @manager if @manager
|
132
|
+
@manager = ShardingPoolManager.new(self, discovered_seeds | @seeds)
|
133
|
+
|
134
|
+
Thread.current[:managers] ||= Hash.new
|
135
|
+
Thread.current[:managers][self] = @manager
|
136
|
+
|
137
|
+
@manager.connect
|
138
|
+
@refresh_version += 1
|
139
|
+
@last_refresh = Time.now
|
140
|
+
@connected = true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Force a hard refresh of this connection's view
|
145
|
+
# of the sharded cluster.
|
146
|
+
#
|
147
|
+
# @return [Boolean] +true+ if hard refresh
|
148
|
+
# occurred. +false+ is returned when unable
|
149
|
+
# to get the refresh lock.
|
150
|
+
def hard_refresh!
|
151
|
+
log(:info, "Initiating hard refresh...")
|
152
|
+
connect(true)
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
def connected?
|
157
|
+
@connected && @manager.primary_pool
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns +true+ if it's okay to read from a secondary node.
|
161
|
+
# Since this is a sharded cluster, this must always be false.
|
162
|
+
#
|
163
|
+
# This method exist primarily so that Cursor objects will
|
164
|
+
# generate query messages with a slaveOkay value of +true+.
|
165
|
+
#
|
166
|
+
# @return [Boolean] +true+
|
167
|
+
def slave_ok?
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
171
|
+
def checkout(&block)
|
172
|
+
2.times do
|
173
|
+
if connected?
|
174
|
+
sync_refresh
|
175
|
+
else
|
176
|
+
connect
|
177
|
+
end
|
178
|
+
|
179
|
+
begin
|
180
|
+
socket = block.call
|
181
|
+
rescue => ex
|
182
|
+
checkin(socket) if socket
|
183
|
+
raise ex
|
184
|
+
end
|
185
|
+
|
186
|
+
if socket
|
187
|
+
return socket
|
188
|
+
else
|
189
|
+
@connected = false
|
190
|
+
#raise ConnectionFailure.new("Could not checkout a socket.")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
# Parse option hash
|
198
|
+
def setup(opts)
|
199
|
+
# Refresh
|
200
|
+
@refresh_mode = opts.fetch(:refresh_mode, false)
|
201
|
+
@refresh_interval = opts.fetch(:refresh_interval, 90)
|
202
|
+
|
203
|
+
if @refresh_mode && @refresh_interval < 60
|
204
|
+
@refresh_interval = 60 unless ENV['TEST_MODE'] = 'TRUE'
|
205
|
+
end
|
206
|
+
|
207
|
+
if @refresh_mode == :async
|
208
|
+
warn ":async refresh mode has been deprecated. Refresh
|
209
|
+
mode will be disabled."
|
210
|
+
elsif ![:sync, false].include?(@refresh_mode)
|
211
|
+
raise MongoArgumentError,
|
212
|
+
"Refresh mode must be either :sync or false."
|
213
|
+
end
|
214
|
+
|
215
|
+
opts[:connect_timeout] = opts[:connect_timeout] || 30
|
216
|
+
|
217
|
+
super opts
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
data/lib/mongo/util/node.rb
CHANGED
@@ -1,24 +1,18 @@
|
|
1
1
|
module Mongo
|
2
2
|
class Node
|
3
3
|
|
4
|
-
attr_accessor :host, :port, :address, :config, :connection, :socket,
|
5
|
-
:last_state
|
4
|
+
attr_accessor :host, :port, :address, :config, :connection, :socket, :last_state
|
6
5
|
|
7
|
-
def initialize(connection,
|
6
|
+
def initialize(connection, host_port)
|
8
7
|
@connection = connection
|
9
|
-
|
10
|
-
|
11
|
-
else
|
12
|
-
@host = data[0]
|
13
|
-
@port = data[1].nil? ? Connection::DEFAULT_PORT : data[1].to_i
|
14
|
-
end
|
15
|
-
@address = "#{host}:#{port}"
|
8
|
+
@host, @port = split_node(host_port)
|
9
|
+
@address = "#{@host}:#{@port}"
|
16
10
|
@config = nil
|
17
11
|
@socket = nil
|
18
12
|
end
|
19
13
|
|
20
14
|
def eql?(other)
|
21
|
-
other.is_a?(Node) &&
|
15
|
+
other.is_a?(Node) && @address == other.address
|
22
16
|
end
|
23
17
|
alias :== :eql?
|
24
18
|
|
@@ -35,16 +29,12 @@ module Mongo
|
|
35
29
|
# return nil.
|
36
30
|
def connect
|
37
31
|
begin
|
38
|
-
socket = nil
|
39
32
|
socket = @connection.socket_class.new(@host, @port,
|
40
33
|
@connection.op_timeout, @connection.connect_timeout
|
41
34
|
)
|
42
|
-
|
43
|
-
return nil if socket.nil?
|
44
35
|
rescue OperationTimeout, ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError => ex
|
45
36
|
@connection.log(:debug, "Failed connection to #{host_string} with #{ex.class}, #{ex.message}.")
|
46
37
|
socket.close if socket
|
47
|
-
return nil
|
48
38
|
end
|
49
39
|
|
50
40
|
@socket = socket
|
@@ -65,10 +55,10 @@ module Mongo
|
|
65
55
|
def active?
|
66
56
|
begin
|
67
57
|
result = @connection['admin'].command({:ping => 1}, :socket => @socket)
|
68
|
-
return result['ok'] == 1
|
69
58
|
rescue OperationFailure, SocketError, SystemCallError, IOError
|
70
59
|
return nil
|
71
60
|
end
|
61
|
+
result['ok'] == 1
|
72
62
|
end
|
73
63
|
|
74
64
|
# Get the configuration for the provided node as returned by the
|
@@ -78,7 +68,7 @@ module Mongo
|
|
78
68
|
begin
|
79
69
|
@config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
|
80
70
|
|
81
|
-
if @config['msg']
|
71
|
+
if @config['msg']
|
82
72
|
@connection.log(:warn, "#{config['msg']}")
|
83
73
|
end
|
84
74
|
|
@@ -89,11 +79,7 @@ module Mongo
|
|
89
79
|
"#{ex.class}: #{ex.message}")
|
90
80
|
|
91
81
|
# Socket may already be nil from issuing command
|
92
|
-
|
93
|
-
@socket.close
|
94
|
-
end
|
95
|
-
|
96
|
-
return nil
|
82
|
+
close
|
97
83
|
end
|
98
84
|
|
99
85
|
@config
|
@@ -119,18 +105,10 @@ module Mongo
|
|
119
105
|
return [] unless config['arbiters']
|
120
106
|
|
121
107
|
config['arbiters'].map do |arbiter|
|
122
|
-
|
108
|
+
split_node(arbiter)
|
123
109
|
end
|
124
110
|
end
|
125
111
|
|
126
|
-
def tags
|
127
|
-
connect unless connected?
|
128
|
-
set_config unless @config
|
129
|
-
return {} unless config['tags'] && !config['tags'].empty?
|
130
|
-
|
131
|
-
config['tags']
|
132
|
-
end
|
133
|
-
|
134
112
|
def primary?
|
135
113
|
@config['ismaster'] == true || @config['ismaster'] == 1
|
136
114
|
end
|
@@ -139,6 +117,10 @@ module Mongo
|
|
139
117
|
@config['secondary'] == true || @config['secondary'] == 1
|
140
118
|
end
|
141
119
|
|
120
|
+
def tags
|
121
|
+
@config['tags'] || {}
|
122
|
+
end
|
123
|
+
|
142
124
|
def host_port
|
143
125
|
[@host, @port]
|
144
126
|
end
|
@@ -147,19 +129,30 @@ module Mongo
|
|
147
129
|
address.hash
|
148
130
|
end
|
149
131
|
|
132
|
+
def healthy?
|
133
|
+
if @config.has_key?('secondary')
|
134
|
+
@config['ismaster'] || @config['secondary']
|
135
|
+
else
|
136
|
+
true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
150
140
|
private
|
151
141
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
|
142
|
+
def split_node(host_port)
|
143
|
+
if host_port.is_a?(String)
|
144
|
+
host_port = host_port.split(":")
|
145
|
+
end
|
146
|
+
|
147
|
+
host = host_port[0]
|
148
|
+
port = host_port[1].nil? ? Connection::DEFAULT_PORT : host_port[1].to_i
|
156
149
|
|
157
150
|
[host, port]
|
158
151
|
end
|
159
152
|
|
160
153
|
# Ensure that this node is a healty member of a replica set.
|
161
154
|
def check_set_membership(config)
|
162
|
-
if !config
|
155
|
+
if !config.has_key?('hosts')
|
163
156
|
message = "Will not connect to #{host_string} because it's not a member " +
|
164
157
|
"of a replica set."
|
165
158
|
raise ConnectionFailure, message
|
data/lib/mongo/util/pool.rb
CHANGED
@@ -72,7 +72,13 @@ module Mongo
|
|
72
72
|
close_sockets(@sockets)
|
73
73
|
@closed = true
|
74
74
|
end
|
75
|
+
@node.close if @node
|
75
76
|
end
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def tags
|
81
|
+
@node.tags
|
76
82
|
end
|
77
83
|
|
78
84
|
def closed?
|
@@ -81,7 +87,8 @@ module Mongo
|
|
81
87
|
|
82
88
|
def inspect
|
83
89
|
"#<Mongo::Pool:0x#{self.object_id.to_s(16)} @host=#{@host} @port=#{port} " +
|
84
|
-
"@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available
|
90
|
+
"@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available " +
|
91
|
+
"up=#{!closed?}>"
|
85
92
|
end
|
86
93
|
|
87
94
|
def host_string
|
@@ -132,8 +139,8 @@ module Mongo
|
|
132
139
|
|
133
140
|
def ping
|
134
141
|
begin
|
135
|
-
return self.connection['admin'].command({:ping => 1}, :socket => @node.socket)
|
136
|
-
rescue OperationFailure, SocketError, SystemCallError, IOError
|
142
|
+
return self.connection['admin'].command({:ping => 1}, :socket => @node.socket, :timeout => 1)
|
143
|
+
rescue ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError
|
137
144
|
return false
|
138
145
|
end
|
139
146
|
end
|
@@ -2,8 +2,10 @@ module Mongo
|
|
2
2
|
class PoolManager
|
3
3
|
|
4
4
|
attr_reader :connection, :arbiters, :primary, :secondaries, :primary_pool,
|
5
|
-
:
|
6
|
-
:max_bson_size
|
5
|
+
:secondary_pool, :secondary_pools, :hosts, :nodes, :members, :seeds,
|
6
|
+
:max_bson_size
|
7
|
+
|
8
|
+
attr_accessor :pinned_pools
|
7
9
|
|
8
10
|
# Create a new set of connection pools.
|
9
11
|
#
|
@@ -13,8 +15,8 @@ module Mongo
|
|
13
15
|
# time. The union of these lists will be used when attempting to connect,
|
14
16
|
# with the newly-discovered nodes being used first.
|
15
17
|
def initialize(connection, seeds=[])
|
18
|
+
@pinned_pools = {}
|
16
19
|
@connection = connection
|
17
|
-
@original_seeds = connection.seeds
|
18
20
|
@seeds = seeds
|
19
21
|
@previously_connected = false
|
20
22
|
end
|
@@ -30,8 +32,6 @@ module Mongo
|
|
30
32
|
members = connect_to_members
|
31
33
|
initialize_pools(members)
|
32
34
|
cache_discovered_seeds(members)
|
33
|
-
set_read_pool
|
34
|
-
set_tag_mappings
|
35
35
|
|
36
36
|
@members = members
|
37
37
|
@previously_connected = true
|
@@ -90,38 +90,54 @@ module Mongo
|
|
90
90
|
|
91
91
|
def close(opts={})
|
92
92
|
begin
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
pools.each { |pool| pool.close(opts) }
|
94
|
+
rescue ConnectionFailure
|
95
|
+
end
|
96
|
+
end
|
96
97
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
end
|
98
|
+
def read
|
99
|
+
read_pool.host_port
|
100
|
+
end
|
102
101
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
102
|
+
def read_pool(mode=@connection.read_preference, tags=@connection.tag_sets,
|
103
|
+
acceptable_latency=@connection.acceptable_latency)
|
104
|
+
|
105
|
+
if mode == :primary && !tags.empty?
|
106
|
+
raise MongoArgumentError, "Read preferecy :primary cannot be combined with tags"
|
107
|
+
end
|
108
|
+
|
109
|
+
pinned = @pinned_pools[Thread.current]
|
110
|
+
if mode && pinned && select_pool([pinned], tags, acceptable_latency) && !pinned.closed?
|
111
|
+
pool = pinned
|
112
|
+
else
|
113
|
+
pool = case mode
|
114
|
+
when :primary
|
115
|
+
@primary_pool
|
116
|
+
when :primary_preferred
|
117
|
+
@primary_pool || select_pool(@secondary_pools, tags, acceptable_latency)
|
118
|
+
when :secondary
|
119
|
+
select_pool(@secondary_pools, tags, acceptable_latency)
|
120
|
+
when :secondary_preferred
|
121
|
+
select_pool(@secondary_pools, tags, acceptable_latency) || @primary_pool
|
122
|
+
when :nearest
|
123
|
+
select_pool(pools, tags, acceptable_latency)
|
107
124
|
end
|
125
|
+
end
|
108
126
|
|
109
|
-
|
127
|
+
unless pool
|
128
|
+
raise ConnectionFailure, "No replica set member available for query " +
|
129
|
+
"with read preference matching mode #{mode} and tags matching #{tags}."
|
110
130
|
end
|
111
|
-
end
|
112
131
|
|
113
|
-
|
114
|
-
# successfully connected to.
|
115
|
-
def seeds
|
116
|
-
@seeds || []
|
132
|
+
pool
|
117
133
|
end
|
118
134
|
|
119
|
-
private
|
120
|
-
|
121
135
|
def pools
|
122
|
-
[@primary_pool, *@secondary_pools]
|
136
|
+
[@primary_pool, *@secondary_pools].compact
|
123
137
|
end
|
124
138
|
|
139
|
+
private
|
140
|
+
|
125
141
|
def validate_existing_member(member)
|
126
142
|
config = member.set_config
|
127
143
|
if !config
|
@@ -145,6 +161,7 @@ module Mongo
|
|
145
161
|
def initialize_data
|
146
162
|
@primary = nil
|
147
163
|
@primary_pool = nil
|
164
|
+
@read = nil
|
148
165
|
@read_pool = nil
|
149
166
|
@arbiters = []
|
150
167
|
@secondaries = []
|
@@ -152,9 +169,8 @@ module Mongo
|
|
152
169
|
@secondary_pools = []
|
153
170
|
@hosts = Set.new
|
154
171
|
@members = Set.new
|
155
|
-
@tags_to_pools = {}
|
156
|
-
@tag_map = {}
|
157
172
|
@refresh_required = false
|
173
|
+
@pinned_pools = {}
|
158
174
|
end
|
159
175
|
|
160
176
|
# Connect to each member of the replica set
|
@@ -167,7 +183,7 @@ module Mongo
|
|
167
183
|
|
168
184
|
seed.node_list.each do |host|
|
169
185
|
node = Mongo::Node.new(self.connection, host)
|
170
|
-
if node.connect && node.set_config
|
186
|
+
if node.connect && node.set_config && node.healthy?
|
171
187
|
members << node
|
172
188
|
end
|
173
189
|
end
|
@@ -180,13 +196,6 @@ module Mongo
|
|
180
196
|
members
|
181
197
|
end
|
182
198
|
|
183
|
-
def associate_tags_with_pool(tags, pool)
|
184
|
-
tags.each_key do |key|
|
185
|
-
@tags_to_pools[{key => tags[key]}] ||= []
|
186
|
-
@tags_to_pools[{key => tags[key]}] << pool
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
199
|
# Initialize the connection pools for the primary and secondary nodes.
|
191
200
|
def initialize_pools(members)
|
192
201
|
members.each do |member|
|
@@ -208,68 +217,48 @@ module Mongo
|
|
208
217
|
member.last_state = :primary
|
209
218
|
@primary = member.host_port
|
210
219
|
@primary_pool = Pool.new(self.connection, member.host, member.port,
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
220
|
+
:size => self.connection.pool_size,
|
221
|
+
:timeout => self.connection.pool_timeout,
|
222
|
+
:node => member
|
223
|
+
)
|
215
224
|
end
|
216
225
|
|
217
226
|
def assign_secondary(member)
|
218
227
|
member.last_state = :secondary
|
219
228
|
@secondaries << member.host_port
|
220
229
|
pool = Pool.new(self.connection, member.host, member.port,
|
221
|
-
|
222
|
-
|
223
|
-
|
230
|
+
:size => self.connection.pool_size,
|
231
|
+
:timeout => self.connection.pool_timeout,
|
232
|
+
:node => member
|
233
|
+
)
|
224
234
|
@secondary_pools << pool
|
225
|
-
associate_tags_with_pool(member.tags, pool)
|
226
235
|
end
|
227
236
|
|
228
|
-
|
229
|
-
|
230
|
-
def set_tag_mappings
|
231
|
-
@tags_to_pools.each do |key, pool_list|
|
232
|
-
if pool_list.length == 1
|
233
|
-
@tag_map[key] = pool_list.first
|
234
|
-
else
|
235
|
-
@tag_map[key] = nearby_pool_from_set(pool_list)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# Pick a node from the set of possible secondaries.
|
241
|
-
# If more than one node is available, use the ping
|
242
|
-
# time to figure out which nodes to choose from.
|
243
|
-
def set_read_pool
|
244
|
-
if @secondary_pools.empty?
|
245
|
-
@read_pool = @primary_pool
|
246
|
-
elsif @secondary_pools.size == 1
|
247
|
-
@read_pool = @secondary_pools[0]
|
248
|
-
@secondary_pool = @read_pool
|
249
|
-
else
|
250
|
-
@read_pool = nearby_pool_from_set(@secondary_pools)
|
251
|
-
@secondary_pool = @read_pool
|
252
|
-
end
|
253
|
-
end
|
237
|
+
def select_pool(candidates, tag_sets, acceptable_latency)
|
238
|
+
tag_sets = [tag_sets] unless tag_sets.is_a?(Array)
|
254
239
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
when 150..1000
|
262
|
-
ping_ranges[1] << pool
|
263
|
-
else
|
264
|
-
ping_ranges[2] << pool
|
240
|
+
if !tag_sets.empty?
|
241
|
+
matches = []
|
242
|
+
tag_sets.detect do |tag_set|
|
243
|
+
matches = candidates.select do |candidate|
|
244
|
+
tag_set.none? { |k,v| candidate.tags[k.to_s] != v } &&
|
245
|
+
candidate.ping_time
|
265
246
|
end
|
247
|
+
!matches.empty?
|
266
248
|
end
|
249
|
+
else
|
250
|
+
matches = candidates
|
251
|
+
end
|
267
252
|
|
268
|
-
|
269
|
-
|
270
|
-
end
|
253
|
+
matches.empty? ? nil : near_pool(matches, acceptable_latency)
|
254
|
+
end
|
271
255
|
|
272
|
-
|
256
|
+
def near_pool(pool_set, acceptable_latency)
|
257
|
+
nearest_pool = pool_set.min_by { |pool| pool.ping_time }
|
258
|
+
near_pools = pool_set.select do |pool|
|
259
|
+
(pool.ping_time - nearest_pool.ping_time) <= acceptable_latency
|
260
|
+
end
|
261
|
+
near_pools[ rand(near_pools.length) ]
|
273
262
|
end
|
274
263
|
|
275
264
|
# Iterate through the list of provided seed
|
@@ -278,11 +267,11 @@ module Mongo
|
|
278
267
|
#
|
279
268
|
# If we don't get a response, raise an exception.
|
280
269
|
def get_valid_seed_node
|
281
|
-
|
270
|
+
@seeds.each do |seed|
|
282
271
|
node = Mongo::Node.new(self.connection, seed)
|
283
272
|
if !node.connect
|
284
273
|
next
|
285
|
-
elsif node.set_config
|
274
|
+
elsif node.set_config && node.healthy?
|
286
275
|
return node
|
287
276
|
else
|
288
277
|
node.close
|
@@ -290,12 +279,10 @@ module Mongo
|
|
290
279
|
end
|
291
280
|
|
292
281
|
raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
|
293
|
-
"#{
|
282
|
+
"#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
|
294
283
|
end
|
295
284
|
|
296
|
-
|
297
|
-
@seeds | @original_seeds
|
298
|
-
end
|
285
|
+
private
|
299
286
|
|
300
287
|
def cache_discovered_seeds(members)
|
301
288
|
@seeds = members.map { |n| n.host_port }
|