Skip to content

Commit f9db623

Browse files
authored
Load balancer: SDAM and Server Selection (#816)
CDRIVER-4053 Load balancer: SDAM changes - Add a LoadBalanced topology type and LoadBalancer server type. - Manually set the topology description to contain one server of type LoadBalancer when opening. - Prevent updates to the topology description after opening. - Disable SDAM monitoring for a LoadBalanced topology. - Emit correct SDAM monitoring events for a LoadBalanced topology. - Bypass the cooldown period for the single-threaded topology scanner. CDRIVER-4055 Load balancer: Server Selection - Always return the one load balancer in server selection.
1 parent 01bbeb8 commit f9db623

22 files changed

+1082
-15
lines changed

src/libmongoc/src/mongoc/mongoc-cmd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ _mongoc_cmd_parts_assemble_mongod (mongoc_cmd_parts_t *parts,
629629
case MONGOC_TOPOLOGY_LOAD_BALANCED:
630630
case MONGOC_TOPOLOGY_DESCRIPTION_TYPES:
631631
default:
632-
/* must not call this function w/ sharded or unknown topology type */
632+
/* must not call this function w/ sharded, load balanced, or unknown topology type */
633633
BSON_ASSERT (false);
634634
}
635635
} /* if (!parts->is_write_command) */

src/libmongoc/src/mongoc/mongoc-server-description.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,9 @@ mongoc_server_description_new_copy (
807807
&description->error);
808808
} else {
809809
mongoc_server_description_reset (copy);
810+
/* preserve the original server description type, which is manually set
811+
* for a LoadBalancer server */
812+
copy->type = description->type;
810813
}
811814

812815
/* Preserve the error */

src/libmongoc/src/mongoc/mongoc-topology-background-monitoring.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ _mongoc_topology_background_monitoring_start (mongoc_topology_t *topology)
146146

147147
_mongoc_handshake_freeze ();
148148
_mongoc_topology_description_monitor_opening (&topology->description);
149+
if (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
150+
/* Do not proceed to start monitoring threads. */
151+
TRACE ("%s", "disabling monitoring for load balanced topology");
152+
return;
153+
}
149154

150155
/* Reconcile to create the first server monitors. */
151156
_mongoc_topology_background_monitoring_reconcile (topology);

src/libmongoc/src/mongoc/mongoc-topology-description-apm.c

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
/* ServerOpeningEvent */
2828
void
2929
_mongoc_topology_description_monitor_server_opening (
30-
const mongoc_topology_description_t *td,
31-
mongoc_server_description_t *sd)
30+
const mongoc_topology_description_t *td, mongoc_server_description_t *sd)
3231
{
3332
if (td->apm_callbacks.server_opening && !sd->opened) {
3433
mongoc_apm_server_opening_t event;
@@ -115,10 +114,35 @@ _mongoc_topology_description_monitor_opening (mongoc_topology_description_t *td)
115114
for (i = 0; i < td->servers->items_len; i++) {
116115
sd = (mongoc_server_description_t *) mongoc_set_get_item (td->servers,
117116
(int) i);
118-
119117
_mongoc_topology_description_monitor_server_opening (td, sd);
120118
}
121119

120+
/* If this is a load balanced topology:
121+
* - update the one server description to be LoadBalancer
122+
* - emit a server changed event Unknown => LoadBalancer
123+
* - emit a topology changed event
124+
*/
125+
if (td->type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
126+
mongoc_server_description_t *prev_sd;
127+
128+
/* LoadBalanced deployments must have exactly one host listed. Otherwise,
129+
* an error would have occurred when constructing the topology. */
130+
BSON_ASSERT (td->servers->items_len == 1);
131+
sd = (mongoc_server_description_t *) mongoc_set_get_item (td->servers, 0);
132+
prev_sd = mongoc_server_description_new_copy (sd);
133+
BSON_ASSERT (prev_sd);
134+
if (td->apm_callbacks.topology_changed) {
135+
mongoc_topology_description_destroy (prev_td);
136+
_mongoc_topology_description_copy_to (td, prev_td);
137+
}
138+
sd->type = MONGOC_SERVER_LOAD_BALANCER;
139+
_mongoc_topology_description_monitor_server_changed (td, prev_sd, sd);
140+
mongoc_server_description_destroy (prev_sd);
141+
if (td->apm_callbacks.topology_changed) {
142+
_mongoc_topology_description_monitor_changed (prev_td, td);
143+
}
144+
}
145+
122146
if (prev_td) {
123147
mongoc_topology_description_destroy (prev_td);
124148
bson_free (prev_td);
@@ -152,6 +176,14 @@ _mongoc_topology_description_monitor_closed (
152176
if (td->apm_callbacks.topology_closed) {
153177
mongoc_apm_topology_closed_t event;
154178

179+
if (td->type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
180+
mongoc_server_description_t *sd;
181+
182+
/* LoadBalanced deployments must have exactly one host listed. */
183+
BSON_ASSERT (td->servers->items_len == 1);
184+
sd = (mongoc_server_description_t *) mongoc_set_get_item (td->servers, 0);
185+
_mongoc_topology_description_monitor_server_closed (td, sd);
186+
}
155187
bson_oid_copy (&td->topology_id, &event.topology_id);
156188
event.context = td->apm_context;
157189
td->apm_callbacks.topology_closed (&event);

src/libmongoc/src/mongoc/mongoc-topology-description.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,14 @@ _mongoc_topology_description_server_is_candidate (
352352
default:
353353
return false;
354354
}
355+
356+
/* Note, there is no call path that leads to the
357+
* MONGOC_TOPOLOGY_LOAD_BALANCED case. Server selection for load balanced
358+
* topologies bypasses this logic. This silences compiler warnings on
359+
* unhandled enum values. */
360+
case MONGOC_TOPOLOGY_LOAD_BALANCED:
361+
return desc_type == MONGOC_SERVER_LOAD_BALANCER;
362+
355363
default:
356364
return false;
357365
}
@@ -712,6 +720,16 @@ mongoc_topology_description_suitable_servers (
712720
topology->servers, _mongoc_find_suitable_mongos_cb, &data);
713721
}
714722

723+
/* Load balanced clusters --
724+
* Always select the only server. */
725+
if (topology->type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
726+
BSON_ASSERT (topology->servers->items_len == 1);
727+
server = (mongoc_server_description_t *) mongoc_set_get_item (
728+
topology->servers, 0);
729+
_mongoc_array_append_val (set, server);
730+
goto DONE;
731+
}
732+
715733
/* Ways to get here:
716734
* - secondary read
717735
* - secondary preferred read
@@ -1132,6 +1150,11 @@ mongoc_topology_description_invalidate_server (
11321150
{
11331151
BSON_ASSERT (error);
11341152

1153+
if (topology->type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
1154+
/* Load balancers must never be marked unknown. */
1155+
return;
1156+
}
1157+
11351158
/* send NULL hello reply */
11361159
mongoc_topology_description_handle_hello (
11371160
topology, id, NULL, MONGOC_RTT_UNSET, error);
@@ -1793,6 +1816,8 @@ _mongoc_topology_description_type (mongoc_topology_description_t *topology)
17931816
return "RSWithPrimary";
17941817
case MONGOC_TOPOLOGY_SINGLE:
17951818
return "Single";
1819+
case MONGOC_TOPOLOGY_LOAD_BALANCED:
1820+
return "LoadBalanced";
17961821
case MONGOC_TOPOLOGY_DESCRIPTION_TYPES:
17971822
default:
17981823
MONGOC_ERROR ("Invalid mongoc_topology_description_type_t type");

src/libmongoc/src/mongoc/mongoc-topology.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ _mongoc_topology_scanner_setup_err_cb (uint32_t id,
131131

132132
topology = (mongoc_topology_t *) data;
133133

134+
if (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
135+
/* In load balanced mode, scanning is only for connection establishment.
136+
* It must not modify the topology description. */
137+
return;
138+
}
139+
134140
mongoc_topology_description_handle_hello (&topology->description,
135141
id,
136142
NULL /* hello reply */,
@@ -167,6 +173,12 @@ _mongoc_topology_scanner_cb (uint32_t id,
167173

168174
topology = (mongoc_topology_t *) data;
169175

176+
if (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
177+
/* In load balanced mode, scanning is only for connection establishment.
178+
* It must not modify the topology description. */
179+
return;
180+
}
181+
170182
bson_mutex_lock (&topology->mutex);
171183
sd = mongoc_topology_description_server_by_id (
172184
&topology->description, id, NULL);
@@ -418,6 +430,15 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded)
418430
if (mongoc_uri_get_option_as_bool (
419431
topology->uri, MONGOC_URI_LOADBALANCED, false)) {
420432
init_type = MONGOC_TOPOLOGY_LOAD_BALANCED;
433+
if (topology->single_threaded) {
434+
/* Cooldown only applies to server monitoring for single-threaded
435+
* clients. In load balanced mode, the topology scanner is used to
436+
* create connections. The cooldown period does not apply. A network
437+
* error to a load balanced connection does not imply subsequent
438+
* connection attempts will be to the same server and that a delay
439+
* should occur. */
440+
_mongoc_topology_bypass_cooldown (topology);
441+
}
421442
} else if (service && !has_directconnection) {
422443
init_type = MONGOC_TOPOLOGY_UNKNOWN;
423444
} else if (has_directconnection) {
@@ -945,6 +966,88 @@ mongoc_topology_select (mongoc_topology_t *topology,
945966
}
946967
}
947968

969+
/* Bypasses normal server selection behavior for a load balanced topology.
970+
* Returns the id of the one load balancer server. Returns 0 on failure.
971+
* Successful post-condition: On a single threaded client, a connection will
972+
* have been established. */
973+
static uint32_t
974+
_mongoc_topology_select_server_id_loadbalanced (mongoc_topology_t *topology,
975+
bson_error_t *error)
976+
{
977+
mongoc_server_description_t *selected_server;
978+
int32_t selected_server_id;
979+
mongoc_topology_scanner_node_t *node;
980+
bson_error_t scanner_error = {0};
981+
982+
bson_mutex_lock (&topology->mutex);
983+
984+
BSON_ASSERT (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED);
985+
986+
/* Emit the opening SDAM events if they have not emitted already. */
987+
_mongoc_topology_description_monitor_opening (&topology->description);
988+
selected_server =
989+
mongoc_topology_description_select (&topology->description,
990+
MONGOC_SS_WRITE,
991+
NULL /* read prefs */,
992+
0 /* local threshold */);
993+
994+
if (!selected_server) {
995+
_mongoc_server_selection_error (
996+
"No suitable server found in load balanced deployment", NULL, error);
997+
bson_mutex_unlock (&topology->mutex);
998+
return 0;
999+
}
1000+
1001+
selected_server_id = selected_server->id;
1002+
bson_mutex_unlock (&topology->mutex);
1003+
1004+
if (!topology->single_threaded) {
1005+
return selected_server_id;
1006+
}
1007+
1008+
/* If this is a single threaded topology, we must ensure that a connection is
1009+
* available to this server. Wrapping drivers make the assumption that
1010+
* successful server selection implies a connection is available. */
1011+
node =
1012+
mongoc_topology_scanner_get_node (topology->scanner, selected_server_id);
1013+
if (!node) {
1014+
_mongoc_server_selection_error (
1015+
"Topology scanner in invalid state; cannot find load balancer",
1016+
NULL,
1017+
error);
1018+
return 0;
1019+
}
1020+
1021+
if (!node->stream) {
1022+
TRACE ("%s",
1023+
"Server selection performing scan since no connection has "
1024+
"been established");
1025+
_mongoc_topology_do_blocking_scan (topology, &scanner_error);
1026+
}
1027+
1028+
if (!node->stream) {
1029+
/* Use the same error domain / code that is returned in mongoc-cluster.c
1030+
* when fetching a stream fails. */
1031+
if (scanner_error.code) {
1032+
bson_set_error (error,
1033+
MONGOC_ERROR_STREAM,
1034+
MONGOC_ERROR_STREAM_NOT_ESTABLISHED,
1035+
"Could not establish stream for node %s: %s",
1036+
node->host.host_and_port,
1037+
scanner_error.message);
1038+
} else {
1039+
bson_set_error (error,
1040+
MONGOC_ERROR_STREAM,
1041+
MONGOC_ERROR_STREAM_NOT_ESTABLISHED,
1042+
"Could not establish stream for node %s",
1043+
node->host.host_and_port);
1044+
}
1045+
return 0;
1046+
}
1047+
1048+
return selected_server_id;
1049+
}
1050+
9481051
/*
9491052
*-------------------------------------------------------------------------
9501053
*
@@ -999,6 +1102,12 @@ mongoc_topology_select_server_id (mongoc_topology_t *topology,
9991102
bson_mutex_unlock (&topology->mutex);
10001103
return 0;
10011104
}
1105+
1106+
if (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
1107+
bson_mutex_unlock (&topology->mutex);
1108+
return _mongoc_topology_select_server_id_loadbalanced (topology, error);
1109+
}
1110+
10021111
bson_mutex_unlock (&topology->mutex);
10031112

10041113
heartbeat_msec = topology->description.heartbeat_msec;
@@ -1305,6 +1414,13 @@ _mongoc_topology_update_from_handshake (mongoc_topology_t *topology,
13051414

13061415
bson_mutex_lock (&topology->mutex);
13071416

1417+
if (topology->description.type == MONGOC_TOPOLOGY_LOAD_BALANCED) {
1418+
/* In load balanced mode, scanning is only for connection establishment.
1419+
* It must not modify the topology description. */
1420+
bson_mutex_unlock (&topology->mutex);
1421+
return true;
1422+
}
1423+
13081424
/* return false if server was removed from topology */
13091425
has_server = _mongoc_topology_update_no_lock (sd->id,
13101426
&sd->last_hello_response,

src/libmongoc/tests/json-test.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ topology_type_from_test (const char *type)
5252
return MONGOC_TOPOLOGY_SINGLE;
5353
} else if (strcmp (type, "Sharded") == 0) {
5454
return MONGOC_TOPOLOGY_SHARDED;
55+
} else if (strcmp (type, "LoadBalanced") == 0) {
56+
return MONGOC_TOPOLOGY_LOAD_BALANCED;
5557
}
5658

5759
fprintf (stderr, "can't parse this: %s", type);
@@ -80,6 +82,8 @@ server_type_from_test (const char *type)
8082
return MONGOC_SERVER_RS_GHOST;
8183
} else if (strcmp (type, "Unknown") == 0) {
8284
return MONGOC_SERVER_UNKNOWN;
85+
} else if (strcmp (type, "LoadBalancer") == 0) {
86+
return MONGOC_SERVER_LOAD_BALANCER;
8387
}
8488
fprintf (stderr, "ERROR: Unknown server type %s\n", type);
8589
BSON_ASSERT (0);
@@ -175,8 +179,7 @@ server_description_by_hostname (mongoc_topology_description_t *topology,
175179
*-----------------------------------------------------------------------
176180
*/
177181
void
178-
process_sdam_test_hello_responses (bson_t *phase,
179-
mongoc_topology_t *topology)
182+
process_sdam_test_hello_responses (bson_t *phase, mongoc_topology_t *topology)
180183
{
181184
mongoc_topology_description_t *td;
182185
mongoc_server_description_t *sd;
@@ -190,7 +193,7 @@ process_sdam_test_hello_responses (bson_t *phase,
190193
description = bson_iter_utf8 (&phase_field_iter, NULL);
191194
MONGOC_DEBUG ("phase: %s", description);
192195
}
193-
/* grab hello responses out and feed them to topology */
196+
/* grab hello responses (if present) and feed them to topology */
194197
if (bson_iter_init_find (&phase_field_iter, phase, "responses")) {
195198
bson_t hellos;
196199
bson_t hello;
@@ -317,9 +320,6 @@ process_sdam_test_hello_responses (bson_t *phase,
317320
generation);
318321
bson_mutex_unlock (&topology->mutex);
319322
}
320-
} else {
321-
test_error (
322-
"test does not have 'responses' or 'applicationErrors' array");
323323
}
324324
}
325325

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"description": "Load balancer can be discovered and only has the address property set",
3+
"uri": "mongodb://a/?loadBalanced=true",
4+
"phases": [
5+
{
6+
"outcome": {
7+
"servers": {
8+
"a:27017": {
9+
"type": "LoadBalancer",
10+
"setName": null,
11+
"setVersion": null,
12+
"electionId": null,
13+
"logicalSessionTimeoutMinutes": null,
14+
"minWireVersion": null,
15+
"maxWireVersion": null,
16+
"topologyVersion": null
17+
}
18+
},
19+
"topologyType": "LoadBalanced",
20+
"setName": null,
21+
"logicalSessionTimeoutMinutes": null,
22+
"maxSetVersion": null,
23+
"maxElectionId": null,
24+
"compatible": true
25+
}
26+
}
27+
]
28+
}

0 commit comments

Comments
 (0)