mongo 1.6.4 → 1.7.0.rc0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 }
|