[CDRIVER-3117] mongoc_database_command_with_opts does not default to DB read preference as documented Created: 07/May/19  Updated: 28/Oct/23  Resolved: 23/May/19

Status: Closed
Project: C Driver
Component/s: None
Affects Version/s: 1.13.0, 1.14.0
Fix Version/s: 1.15.0

Type: Bug Priority: Major - P3
Reporter: Kaitlin Mahar Assignee: Clyde Bazile III (Inactive)
Resolution: Fixed Votes: 0
Labels: docs
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

According to the documentation, if no read preference is passed in as a parameter and the command is not run in a transaction, mongoc_database_command_with_opts will use the database's read preference.

However, this does not seem to be the case. 

I've managed to repro this in both 1.13.0 and 1.14.0 from Swift as follows, running against MongoDB 4.0.5 (omitted some cleanup code for brevity): 

// create client
let uri = mongoc_uri_new_with_error("mongodb://localhost:27017", nil)
let client = mongoc_client_new_from_uri(uri)
 
// set up command monitoring
let callbacks = mongoc_apm_callbacks_new()
mongoc_apm_set_command_started_cb(callbacks, commandStarted) // this is a callback that just prints the command document out.
mongoc_client_set_apm_callbacks (client, callbacks, nil)
 
// create DB and set read preference
let db = mongoc_client_get_database(client, "foo")
let rp = mongoc_read_prefs_new(MONGOC_READ_SECONDARY)
mongoc_database_set_read_prefs(db, rp)
 
// verify read preference is secondary
let actualRP = mongoc_database_get_read_prefs(db)
let actualMode = mongoc_read_prefs_get_mode(actualRP)
print(actualMode) // prints mongoc_read_mode_t(rawValue: 2) (i.e. secondary)
 
// construct a command
let command = bson_new()
if !bson_append_utf8(command, "find", -1, "coll1", -1) {
    print("failed to append")
    return
}
 
let reply = bson_new()
let result = mongoc_database_command_with_opts(db,
                                               command, 
                                               nil, /* read pref */
                                               nil, /* opts */
                                               reply,
                                               nil /* error */)

The APM callback prints out the following command, which shows primaryPreferred used as the read preference:

{ "find" : "coll1", "$db" : "foo", "$readPreference" : { "mode" : "primaryPreferred" }, "lsid" : { "id" : { "$binary" : { "base64": "5p0G9OxnTWqsjdLf+3Lr1g==", "subType" : "04" } } } }

If I explicitly pass in the secondary read preference to mongoc_database_command_with_opts, the command uses secondary as well. 

Is this behavior intended?

We use this method for our MongoDatabase.runCommand implementation. Currently we can work around this by just always passing in the DB's read preference if the user doesn't specify one via RunCommandOptions



 Comments   
Comment by Githook User [ 23/May/19 ]

Author:

{'email': '34226620+bazile-clyde@users.noreply.github.com', 'name': 'bazile-clyde', 'username': 'bazile-clyde'}

Message: CDRIVER-3117 bad DB read prefs docs for mongoc_database_command_with_opts (#564)
Branch: master
https://github.com/mongodb/mongo-c-driver/commit/5c7f7d6d535626e7448c2882a7150756dc18b141

Comment by Githook User [ 23/May/19 ]

Author:

{'email': '34226620+bazile-clyde@users.noreply.github.com', 'name': 'bazile-clyde', 'username': 'bazile-clyde'}

Message: CDRIVER-3117 wrong documentation for mongoc_database_command_with_opt (#563)
Branch: master
https://github.com/mongodb/mongo-c-driver/commit/24969a77b28188db66b3506628bd740e79e34167

Comment by Kaitlin Mahar [ 07/May/19 ]

 patrick.freed pointed out to me that this is actually discussed in the SDAM spec:

The generic command method MUST act as a read operation for the purposes of server selection.

The generic command method has a default read preference of mode 'primary'. The generic command method MUST ignore any default read preference from client, database or collection configuration. The generic command method SHOULD allow an optional read preference argument.

If an explicit read preference argument is provided as part of the generic command method call, it MUST be used for server selection, regardless of the name of the command. It is up to the user to use an appropriate read preference, e.g. not calling renameCollection with a mode of 'secondary'.

N.B.: "used for server selection" does not supercede rules for server selection on "Standalone" topologies, which ignore any requested read preference.

Therefore, I believe libmongoc is doing the correct thing here, and it's just that the documentation is incorrect. 

Comment by Kaitlin Mahar [ 07/May/19 ]

Thanks jmikola. This method is only used for providing the runCommand method on MongoDatabase - we use the helpers everywhere else.

The issues around selecting the wrong server seem problematic. 

FWIW, C++ driver also appears to use this method for its equivalent database.run_command method.

Comment by Jeremy Mikola [ 07/May/19 ]

As a follow on question to this, I noticed that there are several database methods for running commands, including a separate one for commands that both read and write, like mapReduce. Could it be problematic that we only ever use mongoc_database_command_with_opts regardless of the particular command a user passes in to our runCommand method?

All of the command_with_opts methods utilize _mongoc_client_command_with_opts. At a quick glance, mongoc_database_command_with_opts appears to be consistent with mongoc_client_command_with_opts in that it specifies NULL as its default read preference.

I do find it odd that the read, read_write, and write variants all pass the DB-level read prefs, so perhaps this is an oversight of the generic command_with_opts methods.

I can tell you that using the generic command_with_opts method means that the is_read_command and is_write_command flags will never be set on the libmongoc command struct, which could lead to the wrong server being selected (e.g. selecting via read prefs instead of a writable server for a command that does write). Additionally, the generic method may prevent inheritance of client-level read or write concerns by the command document (may not be relevant if you're using higher-level helpers for things like findAndModify).

Comment by Kaitlin Mahar [ 07/May/19 ]

As a follow on question to this, I noticed that there are several database methods for running commands, including a separate one for commands that both read and write, like mapReduce. Could it be problematic that we only ever use mongoc_database_command_with_opts regardless of the particular command a user passes in to our runCommand method?

Generated at Wed Feb 07 21:17:10 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.