Skip to content

Commit 7f263eb

Browse files
RUBY-3741 Support deprioritizing for all topologies (#2984)
1 parent 1708c3d commit 7f263eb

File tree

11 files changed

+352
-30
lines changed

11 files changed

+352
-30
lines changed

lib/mongo/server_selector/base.rb

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,8 @@ def ==(other)
165165
# @param [ true | false ] write_aggregation Whether we need a server that
166166
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
167167
# @param [ Array<Server> ] deprioritized A list of servers that should
168-
# be selected from only if no other servers are available. This is
169-
# used to avoid selecting the same server twice in a row when
170-
# retrying a command.
168+
# be selected from only if no other servers are available.
169+
#
171170
# @param [ Float | nil ] :timeout Timeout in seconds for the operation,
172171
# if any.
173172
#
@@ -287,6 +286,9 @@ def select_server(
287286
end
288287

289288
server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized)
289+
if server.nil? && deprioritized.any?
290+
server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: [])
291+
end
290292

291293
if server
292294
unless cluster.topology.compatible?
@@ -358,20 +360,20 @@ def try_select_server(cluster, write_aggregation: false, deprioritized: [])
358360

359361
if is_write_supported
360362
# 2. If all servers support secondary writes, we respect read preference.
361-
suitable_servers(cluster)
363+
suitable_servers(cluster, deprioritized)
362364
else
363365
# 3. Otherwise we fallback to primary for replica set.
364366
[cluster.servers.detect(&:primary?)]
365367
end
366368
else
367-
suitable_servers(cluster)
369+
suitable_servers(cluster, deprioritized)
368370
end
369371

370372
# This list of servers may be ordered in a specific way
371373
# by the selector (e.g. for secondary preferred, the first
372374
# server may be a secondary and the second server may be primary)
373375
# and we should take the first server here respecting the order
374-
server = suitable_server(servers, deprioritized)
376+
server = suitable_server(servers)
375377

376378
if server
377379
if Lint.enabled?
@@ -396,12 +398,14 @@ def try_select_server(cluster, write_aggregation: false, deprioritized: [])
396398
# latency filtering.
397399
#
398400
# @param [ Cluster ] cluster The cluster.
401+
# @param [ Array<Server> ] deprioritized A list of servers that should
402+
# be selected from only if no other servers are available.
399403
#
400404
# @return [ Array<Server> ] The candidate servers.
401405
#
402406
# @api private
403-
def candidates(cluster)
404-
servers = cluster.servers
407+
def candidates(cluster, deprioritized = [])
408+
servers = cluster.servers.reject { |s| deprioritized.include?(s) }
405409
servers.each do |server|
406410
validate_max_staleness_support!(server)
407411
end
@@ -420,44 +424,57 @@ def candidates(cluster)
420424
# Returns servers satisfying the server selector from the cluster.
421425
#
422426
# @param [ Cluster ] cluster The cluster.
427+
# @param [ Array<Server> ] deprioritized A list of servers that should
428+
# be selected from only if no other servers are available.
429+
#
430+
# @return [ Array<Server> ] The suitable servers.
431+
#
432+
# @api private
433+
def suitable_servers(cluster, deprioritized = [])
434+
result = suitable_servers_impl(cluster, deprioritized)
435+
if result.empty? && deprioritized.any?
436+
result = suitable_servers_impl(cluster, [])
437+
end
438+
result
439+
end
440+
441+
private
442+
443+
# Internal implementation of suitable_servers that applies deprioritization
444+
# filtering to the candidate servers.
445+
#
446+
# @param [ Cluster ] cluster The cluster.
447+
# @param [ Array<Server> ] deprioritized A list of servers that should
448+
# be excluded from the candidate pool.
423449
#
424450
# @return [ Array<Server> ] The suitable servers.
425451
#
426452
# @api private
427-
def suitable_servers(cluster)
453+
def suitable_servers_impl(cluster, deprioritized)
428454
if cluster.single?
429-
candidates(cluster)
455+
candidates(cluster, deprioritized)
430456
elsif cluster.sharded?
431457
local_threshold = local_threshold_with_cluster(cluster)
432-
servers = candidates(cluster)
458+
servers = candidates(cluster, deprioritized)
433459
near_servers(servers, local_threshold)
434460
elsif cluster.replica_set?
435461
validate_max_staleness_value!(cluster)
436-
candidates(cluster)
462+
candidates(cluster, deprioritized)
437463
else
438464
# Unknown cluster - no servers
439465
[]
440466
end
441467
end
442468

443-
private
444-
445469
# Returns a server from the list of servers that is suitable for
446470
# executing the operation.
447471
#
448472
# @param [ Array<Server> ] servers The candidate servers.
449-
# @param [ Array<Server> ] deprioritized A list of servers that should
450-
# be selected from only if no other servers are available.
451473
#
452474
# @return [ Server | nil ] The suitable server or nil if no suitable
453475
# server is available.
454-
def suitable_server(servers, deprioritized)
455-
preferred = servers - deprioritized
456-
if preferred.empty?
457-
servers.first
458-
else
459-
preferred.first
460-
end
476+
def suitable_server(servers)
477+
servers.first
461478
end
462479

463480
# Convert this server preference definition into a format appropriate

spec/runners/server_selection.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class Spec
5353
# @since 2.0.0
5454
attr_reader :suitable_servers
5555

56+
# @return [ Array<Hash> ] deprioritized_servers The set of servers that are deprioritized
57+
# and must only be selected if no other suitable server exists.
58+
attr_reader :deprioritized_servers
59+
5660
# @return [ Mongo::Cluster::Topology ] type The topology type.
5761
#
5862
# @since 2.0.0
@@ -72,6 +76,7 @@ def initialize(test_path)
7276
@max_staleness = @read_preference['maxStalenessSeconds']
7377
@candidate_servers = @test['topology_description']['servers']
7478
@suitable_servers = @test['suitable_servers'] || []
79+
@deprioritized_servers = @test['deprioritized_servers'] || []
7580
@in_latency_window = @test['in_latency_window'] || []
7681
@type = Mongo::Cluster::Topology.const_get(@test['topology_description']['type'])
7782
end
@@ -123,7 +128,7 @@ def candidate_servers
123128
end
124129
end
125130

126-
def define_server_selection_spec_tests(test_paths)
131+
def define_server_selection_spec_tests(test_paths, skipped_tests = {})
127132
# Linter insists that a server selection semaphore is present when
128133
# performing server selection.
129134
require_no_linting
@@ -132,6 +137,13 @@ def define_server_selection_spec_tests(test_paths)
132137

133138
spec = Mongo::ServerSelection::Read::Spec.new(file)
134139

140+
if skipped_tests.keys.include?(File.basename(file))
141+
it spec.description do
142+
skip("Skipped due to #{skipped_tests[File.basename(file)]}")
143+
end
144+
next
145+
end
146+
135147
context(spec.description) do
136148
# Cluster needs a topology and topology needs a cluster...
137149
# This temporary cluster is used for topology construction.
@@ -234,6 +246,13 @@ def define_server_selection_spec_tests(test_paths)
234246
end
235247
end
236248

249+
let(:deprioritized_servers) do
250+
spec.deprioritized_servers.collect do |server|
251+
Mongo::Server.new(Mongo::Address.new(server['address']), cluster, monitoring, listeners,
252+
options.merge(monitoring_io: false))
253+
end
254+
end
255+
237256
let(:in_latency_window) do
238257
spec.in_latency_window.collect do |server|
239258
Mongo::Server.new(Mongo::Address.new(server['address']), cluster, monitoring, listeners,
@@ -286,21 +305,28 @@ def define_server_selection_spec_tests(test_paths)
286305
if spec.in_latency_window.length == 1
287306

288307
it 'selects the expected server' do
289-
[server_selector.select_server(cluster)].should == in_latency_window
308+
[
309+
server_selector.select_server(cluster, deprioritized: deprioritized_servers)
310+
].should == in_latency_window
290311
end
291312

292313
else
293314

294315
it 'selects a server in the suitable list' do
295-
in_latency_window.should include(server_selector.select_server(cluster))
316+
expect(in_latency_window)
317+
.to include(
318+
server_selector.select_server(
319+
cluster,
320+
deprioritized: deprioritized_servers)
321+
)
296322
end
297323

298324
let(:expected_addresses) do
299325
in_latency_window.map(&:address).map(&:seed).sort
300326
end
301327

302328
let(:actual_addresses) do
303-
server_selector.suitable_servers(cluster).map(&:address).map(&:seed).sort
329+
server_selector.suitable_servers(cluster, deprioritized_servers).map(&:address).map(&:seed).sort
304330
end
305331

306332
it 'identifies expected suitable servers' do
@@ -317,7 +343,7 @@ def define_server_selection_spec_tests(test_paths)
317343
end
318344

319345
let(:actual_addresses) do
320-
servers = server_selector.send(:suitable_servers, cluster)
346+
servers = server_selector.send(:suitable_servers, cluster, deprioritized_servers)
321347

322348
# The tests expect that only secondaries are "suitable" for
323349
# server selection with secondary preferred read preference.
@@ -354,7 +380,7 @@ def define_server_selection_spec_tests(test_paths)
354380

355381
it 'Raises a NoServerAvailable Exception' do
356382
expect do
357-
server_selector.select_server(cluster)
383+
server_selector.select_server(cluster, deprioritized: deprioritized_servers)
358384
end.to raise_exception(Mongo::Error::NoServerAvailable)
359385
end
360386

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
topology_description:
2+
type: ReplicaSetWithPrimary
3+
servers:
4+
- &1
5+
address: b:27017
6+
avg_rtt_ms: 5
7+
type: RSSecondary
8+
tags:
9+
data_center: nyc
10+
- &2
11+
address: c:27017
12+
avg_rtt_ms: 5
13+
type: RSSecondary
14+
tags:
15+
data_center: nyc
16+
- &3
17+
address: a:27017
18+
avg_rtt_ms: 5
19+
type: RSPrimary
20+
tags:
21+
data_center: nyc
22+
operation: read
23+
read_preference:
24+
mode: PrimaryPreferred
25+
tag_sets:
26+
- {}
27+
deprioritized_servers:
28+
- *1
29+
- *2
30+
- *3
31+
suitable_servers:
32+
- *3
33+
in_latency_window:
34+
- *3
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
topology_description:
2+
type: ReplicaSetWithPrimary
3+
servers:
4+
- &1
5+
address: b:27017
6+
avg_rtt_ms: 5
7+
type: RSSecondary
8+
tags:
9+
data_center: nyc
10+
- &2
11+
address: c:27017
12+
avg_rtt_ms: 5
13+
type: RSSecondary
14+
tags:
15+
data_center: nyc
16+
- &3
17+
address: a:27017
18+
avg_rtt_ms: 5
19+
type: RSPrimary
20+
tags:
21+
data_center: nyc
22+
operation: read
23+
read_preference:
24+
mode: SecondaryPreferred
25+
tag_sets:
26+
- {}
27+
deprioritized_servers:
28+
- *1
29+
- *2
30+
- *3
31+
suitable_servers:
32+
- *1
33+
- *2
34+
in_latency_window:
35+
- *1
36+
- *2
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
topology_description:
2+
type: ReplicaSetWithPrimary
3+
servers:
4+
- &1
5+
address: b:27017
6+
avg_rtt_ms: 5
7+
type: RSSecondary
8+
tags:
9+
data_center: nyc
10+
- &2
11+
address: c:27017
12+
avg_rtt_ms: 100
13+
type: RSSecondary
14+
tags:
15+
data_center: nyc
16+
- &3
17+
address: a:27017
18+
avg_rtt_ms: 26
19+
type: RSPrimary
20+
tags:
21+
data_center: nyc
22+
operation: read
23+
read_preference:
24+
mode: Nearest
25+
tag_sets:
26+
- data_center: nyc
27+
deprioritized_servers:
28+
- *1
29+
suitable_servers:
30+
- *3
31+
- *2
32+
in_latency_window:
33+
- *3
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
topology_description:
2+
type: ReplicaSetWithPrimary
3+
servers:
4+
- address: b:27017
5+
avg_rtt_ms: 5
6+
type: RSSecondary
7+
tags:
8+
data_center: nyc
9+
- &2
10+
address: c:27017
11+
avg_rtt_ms: 100
12+
type: RSSecondary
13+
tags:
14+
data_center: nyc
15+
- &3
16+
address: a:27017
17+
avg_rtt_ms: 25
18+
type: RSPrimary
19+
tags:
20+
data_center: nyc
21+
22+
operation: read
23+
24+
read_preference:
25+
mode: Nearest
26+
tag_sets:
27+
- data_center: nyc
28+
29+
deprioritized_servers:
30+
- address: "b:27017"
31+
avg_rtt_ms: 50
32+
type: RSPrimary
33+
tags:
34+
data_center: nyc
35+
36+
suitable_servers:
37+
- *2
38+
- *3
39+
40+
in_latency_window:
41+
- *3

0 commit comments

Comments
 (0)