Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lock] Fixed reading locks from replica set secondary nodes #37140

Merged
merged 1 commit into from Jun 10, 2020

Conversation

kralos
Copy link

@kralos kralos commented Jun 8, 2020

Q A
Branch? 5.1
Bug fix? yes
New feature? no
Deprecations? no
Tickets Fix #37139
License MIT
Doc PR symfony/symfony-docs#13775

Force lock existence query to use readPreference=primary in case the given mongo connection is using any of the following readPreferences:

  • primaryPreferred
  • secondary
  • secondaryPreferred
  • nearest

Any of the above would fail if a secondary node is queried during a lock release.

@kralos kralos requested a review from jderusse as a code owner June 8, 2020 05:34
@kralos kralos changed the base branch from master to 5.1 June 8, 2020 05:35
@kralos
Copy link
Author

kralos commented Jun 8, 2020

In order to add a CI test for this we would need a replicaSet in CI. Not sure this is worth doing?

@kralos kralos force-pushed the 37139-mongodb-lock-read-preference branch from 3b04eac to a0e4f1f Compare June 8, 2020 05:54
@kralos kralos changed the title #37139 Fixed reading locks from a replica set where connection reads … #37139 Fixed reading locks from a replica reading from secondary nodes Jun 8, 2020
@kralos kralos changed the title #37139 Fixed reading locks from a replica reading from secondary nodes #37139 Fixed reading locks from replica set secondary nodes Jun 8, 2020
@nicolas-grekas nicolas-grekas changed the title #37139 Fixed reading locks from replica set secondary nodes [Lock] Fixed reading locks from replica set secondary nodes Jun 9, 2020
@nicolas-grekas nicolas-grekas added this to the 5.1 milestone Jun 9, 2020
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to add a CI test for this we would need a replicaSet in CI. Not sure this is worth doing?

Unless there is a docker image that would allow doing that easily by using GitHub Actions, I agree.

@kralos
Copy link
Author

kralos commented Jun 9, 2020

I can't find anything that appears maintained. There's https://github.com/marketplace/actions/mongodb-in-github-actions#with-a-replica-set however their replica set is single instance (there is no secondary so we therefore can't test against it). I don't really want to create and maintain our own github action for this but might make sense in the future if we want to also test \Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler::doRead which i suspect has the same issue. I will be testing this in more depth over the coming months

Copy link
Contributor

@jmikola jmikola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just sharing a few comments after I was pinged in #37139. Feel free to take or leave my suggestions as you wish.

);
$buildInfo = $cursor->toArray()[0];
$this->databaseVersion = $buildInfo->version;
$this->readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: ReadPreference can be constructed with a string since driver version 1.3.0 (September 2017). I didn't find any reference to the mongodb extension or PHP library in composer.json so perhaps there's no minimum requirement and that's the reason for preferring the deprecated numeric constants (we hope to remove these in 2.0, per PHPC-1021).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

symfony doesn't require ext-mongodb, the closest it comes is "require-dev": { "mongodb/mongodb": "~1.1" }

since "mongodb/mongodb": "1.1.0" requires ext-mongodb: ^1.2.0 i think we should probably support pre ext-mongodb: 1.3.0 (needs to support ints).

I've added detection for string const vs int const to allow both forwards and backwards compatibility with the shift from int to string constructed ReadPreference

I'm assuming in https://jira.mongodb.org/browse/PHPC-1021 you're planning to just remove ReadPreference::RP_PRIMARY and ReadPreference::PRIMARY will remain in ext-mongodb: 2.0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

symfony doesn't require ext-mongodb, the closest it comes is "require-dev": { "mongodb/mongodb": "~1.1" }

Where is this? I didn't find any reference to mongodb in https://github.com/symfony/symfony/blob/5.1/composer.json

I'm assuming in https://jira.mongodb.org/browse/PHPC-1021 you're planning to just remove ReadPreference::RP_PRIMARY and ReadPreference::PRIMARY will remain in ext-mongodb: 2.0?

Correct. The new constants would stay. The old, integer constants happen to be tied to internal libmongoc constants, which is why we want to remove them (and why the values seemingly make no sense, since they're bitmasks IIRC).

In any event, I think the ternary you have in the current diff is future-proof. 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this? I didn't find any reference to mongodb in https://github.com/symfony/symfony/blob/5.1/composer.json

in the Component\Lock - src/Symfony/Component/Lock/composer.json

@@ -315,23 +317,15 @@ private function isDuplicateKeyException(WriteException $e): bool
return 11000 === $code;
}

private function getDatabaseVersion(): string
private function getReadPreference(): ReadPreference
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be called exactly once in exists(). ReadPreference is a very light value object, so I'd propose just removing this method (and cached class property) in favor of constructing it inline.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@jmikola
Copy link
Contributor

jmikola commented Jun 10, 2020

Side note: while looking at

yield ['mongodb://localhost/test?collection=lock', []];
, I noticed that the tests pass a custom collection option in the MongoDB connection string. Likewise, this adapter parses a default database name from the authentication database. I realize this likely needs to remain in 5.x for BC at this point, but I'd argue against having this behavior for two reasons:

  • The extension uses the connection string to cache internal libmongoc client objects (for socket persistence), so overloading the string to hold extra options not used by the driver will defeat any chance of being able to efficiently re-use existing sockets.
  • The database name in the connection string is documented as the defaultAuthDb and fallback for the authSource URI option. Suggestion folks use it to denote a default database could lead to it inadvertently being used for authentication (over the default "admin" database) if credentials were also provided.

IMO, both collection and database should be solicited as explicit options to the storage class and not pulled from the connection string, uriOptions, or driverOptions.

@kralos
Copy link
Author

kralos commented Jun 10, 2020

Side note: while looking at

yield ['mongodb://localhost/test?collection=lock', []];

, I noticed that the tests pass a custom collection option in the MongoDB connection string. Likewise, this adapter parses a default database name from the authentication database. I realize this likely needs to remain in 5.x for BC at this point, but I'd argue against having this behavior for two reasons:

* The extension uses the connection string to cache internal libmongoc client objects (for socket persistence), so overloading the string to hold extra options not used by the driver will defeat any chance of being able to efficiently re-use existing sockets.

* The database name in the connection string is documented as the [`defaultAuthDb`](https://www.php.net/manual/en/mongodb-driver-manager.construct.php) and fallback for the `authSource` URI option. Suggestion folks use it to denote a default database could lead to it inadvertently being used for authentication (over the default "admin" database) if credentials were also provided.

IMO, both collection and database should be solicited as explicit options to the storage class and not pulled from the connection string, uriOptions, or driverOptions.

regarding the use of collection in the connection URI's querystring

I absolutely agree we should not introduce non-standard querystring params. The reason this was added at the time was to allow Symfony\Component\Lock\Store\StoreFactory to construct a MongoDbStore from a uri alone. I think we should deprecate collection from the uri in MongoDbStore as well as uri construction via StoreFactory. Since StoreFactory doesn't support $options the only way to construct a MongoDbStore would be to pass it a \MongoDB\Collection.

regarding the use of database in the connection URI's path

I did a fair bit of testing with mongo cli client before implementing the current behavior. As far as i can tell I have mirrored what mongo cli does:

  • mongodb://server/db1 means defaultDb = db1
  • mongodb://user:pass@server/ means defaultDb = none / mongo --nodb and authDb = admin
  • mongodb://user:pass@server/db1 means defaultDb = db1 and authDb = db1
  • mongodb://user:pass@server/db1?authSource=db2 means defaultDb = db1 and authDb = db2

@jmikola
Copy link
Contributor

jmikola commented Jun 10, 2020

regarding the use of database in the connection URI's path
I did a fair bit of testing with mongo cli client before implementing the current behavior. As far as i can tell I have mirrored what mongo cli does:

Indeed, the MongoDB shell does use this as its default database since its REPL always has a db context. You can chalk this up to legacy behavior. FWIW, the connection strings provided by MongoDB Atlas also include the database component (I believe they use "admin"?).

From the driver's perspective, the root context is always the client object and applications explicitly select a database or collection. The connection string spec for drivers only defines this as a default auth database and makes no mention of it having any other purpose. The server manual for connection strings says the same. Given that authSource exists, I'd encourage everyone to consider the database component obsolete (unless you're working with the shell).

Some libraries built atop drivers also use the database component as their default database (e.g. default DB for storing models in the Mongoose ODM for Node.js); however, I have the same objections about that since it could also clash with authentication.

Having said all that, I imagine none of this can be changed in 5.x. If you'd like to preemptively open a ticket to address this in 6.0, I would still suggest addressing both database and collection and perhaps expanding the scope of that TODO ticket to cover any other MongoDB adapters that might exist in Symfony. It's been some time since I contributed, but I believe similar code may exist in the session adapter.

@kralos
Copy link
Author

kralos commented Jun 10, 2020

@jmikola Thanks heaps for your insight to the usage of collection / database in mongodb connection URIs. In the interest of allowing this bugfix to proceed, I've opened a separate ticket to deprecate the behavior you've raised concerns with #37180.

I'll make sure all of this also filters down to Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler

@fabpot fabpot force-pushed the 37139-mongodb-lock-read-preference branch from 46def68 to ebf7eaf Compare June 10, 2020 14:19
@fabpot
Copy link
Member

fabpot commented Jun 10, 2020

Thank you @kralos.

@fabpot fabpot merged commit 292be5f into symfony:5.1 Jun 10, 2020
@fabpot fabpot mentioned this pull request Jun 12, 2020
@kralos kralos deleted the 37139-mongodb-lock-read-preference branch August 17, 2020 23:40
javiereguiluz added a commit to javiereguiluz/symfony-docs that referenced this pull request Jan 27, 2021
…eadPreference (kralos)

This PR was merged into the 5.1 branch.

Discussion
----------

[Lock] #37139 Updated Lock MongoDbStore note on readPreference

Fixed documentation around `readPreference` for `MongoDbStore` in `Lock` component. See symfony/symfony#37140

Commits
-------

98574f5 #37139 Updated Lock MongoDbStore note on readPreference
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Lock] MongoDbStore reports a failure to release
5 participants