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

Deprecate master/slave terminology and underlying feature #4055

Closed
wants to merge 1 commit into from

Conversation

greg0ire
Copy link
Member

@greg0ire greg0ire commented Jun 7, 2020

Q A
Type improvement
BC Break no
Fixed issues #4052

Summary

MasterSlaveConnection is left untouched, deprecated, copy/pasted/improved
Alternative to #4054

Targeting 2.11.x because of the deprecation.

/**
* Connects to a specific connection.
*/
private function connectTo(string $connectionName): DriverConnection
Copy link
Member Author

Choose a reason for hiding this comment

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

If you think somebody relies on connectTo or chooseConnectionConfiguration, we could make them public. Doing so after this is merged and release would not be a BC break though.

Copy link
Member

Choose a reason for hiding this comment

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

If somebody needs to choose the connection explicitly, they should operate those connections manually w/o this wrapper.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why? And who would that somebody be? This method is private, so it can't be used by somebody outside this class if that's who you were thinking.

Copy link
Member

Choose a reason for hiding this comment

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

If you think somebody relies on connectTo or chooseConnectionConfiguration, we could make them public.

It was the answer to your comment. I don't think that this should be made part of the public API.

albe
albe previously approved these changes Jun 7, 2020
@@ -142,17 +142,29 @@ public static function getConnection(

$params = self::parseDatabaseUrl($params);

// URL support for MasterSlaveConnection
// @todo: deprecated, notice thrown by connection constructor
Copy link
Member

Choose a reason for hiding this comment

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

Will the Connection constructor get a deprecation message in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah right that's a part I forgot to backport from #4054

Copy link
Member Author

Choose a reason for hiding this comment

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

Unsure about the @todo part, I copied it from #4054, is this a way we have to remember to remove things on 3.0.x?

Copy link
Member

Choose a reason for hiding this comment

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

I don't know. I thought @deprecated and/or trigger_error() is usually in use.

@codecov-commenter
Copy link

codecov-commenter commented Jun 7, 2020

Codecov Report

Merging #4055 into 2.11.x will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##             2.11.x    #4055   +/-   ##
=========================================
  Coverage     71.02%   71.02%           
  Complexity     5261     5261           
=========================================
  Files           217      217           
  Lines         13355    13358    +3     
=========================================
+ Hits           9485     9488    +3     
  Misses         3870     3870           
Impacted Files Coverage Δ Complexity Δ
...octrine/DBAL/Connections/MasterSlaveConnection.php 67.85% <100.00%> (+0.88%) 42.00 <0.00> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3cfb57d...5149e52. Read the comment docs.

Copy link
Member

@morozov morozov left a comment

Choose a reason for hiding this comment

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

API-wise looks good. We should be able to address the implementation details issues later.

*
* @var DriverConnection[]|null[]
*/
private $connections = ['primary' => null, 'replica' => null];
Copy link
Member

Choose a reason for hiding this comment

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

As far as I understand, it's one primary and many replicas. Why do we need to keep them under one array with magic keys? It's fine if we don't resolve it in this ticket.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can resolve it in this ticket, I think that will be the easiest part TBH :P

Copy link
Member Author

Choose a reason for hiding this comment

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

In fact it's just the one replica (picked at random), as stated in the comment above. Your comment still applies though.

/**
* Connects to a specific connection.
*/
private function connectTo(string $connectionName): DriverConnection
Copy link
Member

Choose a reason for hiding this comment

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

If somebody needs to choose the connection explicitly, they should operate those connections manually w/o this wrapper.

lib/Doctrine/DBAL/Connections/PrimaryReplicaConnection.php Outdated Show resolved Hide resolved
@greg0ire greg0ire force-pushed the deprecate-master-slave branch from 02971b3 to 0f368ee Compare June 7, 2020 19:19
}


parent::__construct($params, $driver, $config, $eventManager);
Copy link
Member

Choose a reason for hiding this comment

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

This is another place where my eyes are bleeding. The parent class doesn't understand the "replica" key but for some reason, we're passing it there. Shouldn't the concept of multiple connections be encapsulated to this very class?

Copy link
Member Author

@greg0ire greg0ire Jun 7, 2020

Choose a reason for hiding this comment

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

🤔 uh yeah that's nasty. Are you suggesting unset-ing replica and primary, storing them in some other property, or not calling the parent constructor altogether (and then maybe stop extending Connection??)

Copy link
Member

@morozov morozov Jun 7, 2020

Choose a reason for hiding this comment

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

We cannot avoid extending the connection because there's no DBAL-level connection interface to implement. Unsetting and managing the replica parameters within the class would help migrate to composition later.


public function query(): Statement
{
$this->switchToPrimary();
Copy link
Member

Choose a reason for hiding this comment

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

And it's even worse here. Why are we switching to the primary endpoint to execute a query?

Copy link
Member Author

@greg0ire greg0ire Jun 7, 2020

Choose a reason for hiding this comment

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

I won't lie, I have no idea…

Copy link
Member

Choose a reason for hiding this comment

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

the query function can be used for any kind of SQL, including write operations. This cannot be detected without parsing the SQL statement, which is a rabbit hole. This is why for safety reason the connectoin assumes that when using $connection->query(), you might be performing a write operation.

Copy link
Member

Choose a reason for hiding this comment

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

This is why the developer should be in control of which connection to use for a given statement. Not the abstraction layer.

@greg0ire
Copy link
Member Author

greg0ire commented Jun 7, 2020

@morozov this looks nasty too:

* ATTENTION: You can write to the slave with this connection if you execute a write query without
* opening up a transaction. For example:
*
* $conn = DriverManager::getConnection(...);
* $conn->executeQuery("DELETE FROM table");
*
* Be aware that Connection#executeQuery is a method specifically for READ
* operations only.


$driverOptions = $params['driverOptions'] ?? [];

$connectionParams = $this->chooseConnectionConfiguration($connectionName, $params);
Copy link
Member

@morozov morozov Jun 7, 2020

Choose a reason for hiding this comment

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

So does this mean that every time we "switch" between the primary and replica endpoints, the replica will be randomly chosen and reconnected to while the old replica connection will be closed? Even if there's only one replica configured. What's the rationale behind this behavior?

Copy link
Member Author

@greg0ire greg0ire Jun 7, 2020

Choose a reason for hiding this comment

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

🤔 we are not supposed to switch from the primary to the replica, are we?

* 3. If master was picked once during the lifetime of the connection it will always get picked afterwards.

Copy link
Member

Choose a reason for hiding this comment

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

This is really questionable behavior. If I insert a "user logged in" event in one table and then query a heavy report from the other, why should the report use the master connection?

Copy link
Member

Choose a reason for hiding this comment

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

The step from primary to replica is not really a common use-case, but what you describe could be an example. If the application does a single write early in the request and you can isolate that and don't need to care about replication lag with this write, then you could switch back to a replica. The idea would be:

$connection->connect('primary');
$connection->executeUpdate("....");
$connection->connect("replica");

Copy link
Member

@morozov morozov Jun 7, 2020

Choose a reason for hiding this comment

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

Why not just do $primaryConnection->executeUpdate(); $replicaConnection->doWhatever();? Why keep one connection as a shared state and switch between the inner connections implicitly?

Copy link

Choose a reason for hiding this comment

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

The answer is probably "convenience" (at the cost of some usefulness). Like always with such things ;)

@morozov
Copy link
Member

morozov commented Jun 7, 2020

Given the number of WTFs that this code produces, I'd personally prefer to just deprecate it for now. If we don't manage to implement a proper replacement (and given all the possible variations of the behavior, I don't think we should), the old code could be just copy/pasted in the project that needs this functionality. But looking at the behaviors that it implements, I hope nobody in their right mind uses it in production.

@greg0ire
Copy link
Member Author

greg0ire commented Jun 7, 2020

@morozov I agree. In my company, we have a RW endpoint and an RO endpoint, we just created 2 connections, and don't use this class, and it works just fine. These features could be brought back at some point with a brand new design, that would have to avoid this unholy mix of composition and inheritance.

@morozov
Copy link
Member

morozov commented Jun 7, 2020

In my company, we have a RW endpoint and an RO endpoint, we just created 2 connections, and don't use this class, and it works just fine.

This is how I see it should be implemented. The existing implementation has too many questionable assumptions to be the one and the only that is shipped with DBAL.

@greg0ire greg0ire force-pushed the deprecate-master-slave branch from 0f368ee to e6a7703 Compare June 7, 2020 20:13
@greg0ire
Copy link
Member Author

greg0ire commented Jun 7, 2020

Ah I need to add an upgrade note for this.

@greg0ire greg0ire force-pushed the deprecate-master-slave branch from e6a7703 to 552eb88 Compare June 7, 2020 20:15
@greg0ire greg0ire changed the title Deprecate master/slave terminology Deprecate master/slave terminology and underlying feature Jun 7, 2020
UPGRADE.md Outdated
## Deprecated `MasterSlaveConnection`

The naming is offensive, the implementation very questionable. Use regular
connections and switch between them explicitely.
Copy link
Member

@morozov morozov Jun 7, 2020

Choose a reason for hiding this comment

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

Should it be “explicitly”? I'd also avoid “switch” — it implies shared state. Let's use “Use each endpoint connection explicitly”.

@greg0ire greg0ire force-pushed the deprecate-master-slave branch from 552eb88 to 0d621ba Compare June 7, 2020 20:45
morozov
morozov previously approved these changes Jun 7, 2020
Not only is the terminology needlessly hurtful, the design is a very
leaky abstraction, and users are better off using regular connections
and managing them themselves explicitly.
@beberlei
Copy link
Member

beberlei commented Jun 9, 2020

Closing in favor of #4054

@beberlei beberlei closed this Jun 9, 2020
@greg0ire greg0ire deleted the deprecate-master-slave branch June 9, 2020 11:36
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants