From 6e014bb7537320d69f58d3e7fbf6320488c4087e Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Tue, 13 Sep 2016 18:18:37 -0700 Subject: [PATCH] Add ILoadBalancer interface Remove redundant LoadBalancer docs (those without @since). Change-Id: I981177b6854cfdbea4a51b6db7e365dac0da258a --- autoload.php | 1 + includes/db/loadbalancer/ILoadBalancer.php | 473 +++++++++++++++++++++ includes/db/loadbalancer/LoadBalancer.php | 217 +--------- 3 files changed, 487 insertions(+), 204 deletions(-) create mode 100644 includes/db/loadbalancer/ILoadBalancer.php diff --git a/autoload.php b/autoload.php index 309a7d0b55..6654f5bf74 100644 --- a/autoload.php +++ b/autoload.php @@ -580,6 +580,7 @@ $wgAutoloadLocalClasses = [ 'IEUrlExtension' => __DIR__ . '/includes/libs/IEUrlExtension.php', 'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php', 'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php', + 'ILoadBalancer' => __DIR__ . '/includes/db/loadbalancer/ILoadBalancer.php', 'IP' => __DIR__ . '/includes/utils/IP.php', 'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php', 'IPTC' => __DIR__ . '/includes/media/IPTC.php', diff --git a/includes/db/loadbalancer/ILoadBalancer.php b/includes/db/loadbalancer/ILoadBalancer.php new file mode 100644 index 0000000000..9313ccd37c --- /dev/null +++ b/includes/db/loadbalancer/ILoadBalancer.php @@ -0,0 +1,473 @@ +mErrorConnection. + * + * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError. + * + * @param int $i Server index + * @param string|bool $wiki Wiki ID, or false for the current wiki + * @return IDatabase|bool Returns false on errors + */ + public function openConnection( $i, $wiki = false ); + + /** + * @return int + */ + public function getWriterIndex(); + + /** + * Returns true if the specified index is a valid server index + * + * @param string $i + * @return bool + */ + public function haveIndex( $i ); + + /** + * Returns true if the specified index is valid and has non-zero load + * + * @param string $i + * @return bool + */ + public function isNonZeroLoad( $i ); + + /** + * Get the number of defined servers (not the number of open connections) + * + * @return int + */ + public function getServerCount(); + + /** + * Get the host name or IP address of the server with the specified index + * Prefer a readable name if available. + * @param string $i + * @return string + */ + public function getServerName( $i ); + + /** + * Return the server info structure for a given index, or false if the index is invalid. + * @param int $i + * @return array|bool + */ + public function getServerInfo( $i ); + + /** + * Sets the server info structure for the given index. Entry at index $i + * is created if it doesn't exist + * @param int $i + * @param array $serverInfo + */ + public function setServerInfo( $i, array $serverInfo ); + + /** + * Get the current master position for chronology control purposes + * @return DBMasterPos|bool Returns false if not applicable + */ + public function getMasterPos(); + + /** + * Disable this load balancer. All connections are closed, and any attempt to + * open a new connection will result in a DBAccessError. + */ + public function disable(); + + /** + * Close all open connections + */ + public function closeAll(); + + /** + * Close a connection + * + * Using this function makes sure the LoadBalancer knows the connection is closed. + * If you use $conn->close() directly, the load balancer won't update its state. + * + * @param IDatabase $conn + */ + public function closeConnection( IDatabase $conn ); + + /** + * Commit transactions on all open connections + * @param string $fname Caller name + * @throws DBExpectedError + */ + public function commitAll( $fname = __METHOD__ ); + + /** + * Perform all pre-commit callbacks that remain part of the atomic transactions + * and disable any post-commit callbacks until runMasterPostTrxCallbacks() + * + * Use this only for mutli-database commits + */ + public function finalizeMasterChanges(); + + /** + * Perform all pre-commit checks for things like replication safety + * + * Use this only for mutli-database commits + * + * @param array $options Includes: + * - maxWriteDuration : max write query duration time in seconds + * @throws DBTransactionError + */ + public function approveMasterChanges( array $options ); + + /** + * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set) + * + * The DBO_TRX setting will be reverted to the default in each of these methods: + * - commitMasterChanges() + * - rollbackMasterChanges() + * - commitAll() + * This allows for custom transaction rounds from any outer transaction scope. + * + * @param string $fname + * @throws DBExpectedError + */ + public function beginMasterChanges( $fname = __METHOD__ ); + + /** + * Issue COMMIT on all master connections where writes where done + * @param string $fname Caller name + * @throws DBExpectedError + */ + public function commitMasterChanges( $fname = __METHOD__ ); + + /** + * Issue all pending post-COMMIT/ROLLBACK callbacks + * + * Use this only for mutli-database commits + * + * @param integer $type IDatabase::TRIGGER_* constant + * @return Exception|null The first exception or null if there were none + */ + public function runMasterPostTrxCallbacks( $type ); + + /** + * Issue ROLLBACK only on master, only if queries were done on connection + * @param string $fname Caller name + * @throws DBExpectedError + */ + public function rollbackMasterChanges( $fname = __METHOD__ ); + + /** + * Suppress all pending post-COMMIT/ROLLBACK callbacks + * + * Use this only for mutli-database commits + * + * @return Exception|null The first exception or null if there were none + */ + public function suppressTransactionEndCallbacks(); + + /** + * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot + * + * @param string $fname Caller name + */ + public function flushReplicaSnapshots( $fname = __METHOD__ ); + + /** + * @return bool Whether a master connection is already open + */ + public function hasMasterConnection(); + + /** + * Determine if there are pending changes in a transaction by this thread + * @return bool + */ + public function hasMasterChanges(); + + /** + * Get the timestamp of the latest write query done by this thread + * @return float|bool UNIX timestamp or false + */ + public function lastMasterChangeTimestamp(); + + /** + * Check if this load balancer object had any recent or still + * pending writes issued against it by this PHP thread + * + * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout] + * @return bool + */ + public function hasOrMadeRecentMasterChanges( $age = null ); + + /** + * Get the list of callers that have pending master changes + * + * @return string[] List of method names + */ + public function pendingMasterChangeCallers(); + + /** + * @note This method will trigger a DB connection if not yet done + * @param string|bool $wiki Wiki ID, or false for the current wiki + * @return bool Whether the generic connection for reads is highly "lagged" + */ + public function getLaggedReplicaMode( $wiki = false ); + + /** + * @note This method will never cause a new DB connection + * @return bool Whether any generic connection used for reads was highly "lagged" + */ + public function laggedReplicaUsed(); + + /** + * @note This method may trigger a DB connection if not yet done + * @param string|bool $wiki Wiki ID, or false for the current wiki + * @param IDatabase|null DB master connection; used to avoid loops [optional] + * @return string|bool Reason the master is read-only or false if it is not + */ + public function getReadOnlyReason( $wiki = false, IDatabase $conn = null ); + + /** + * Disables/enables lag checks + * @param null|bool $mode + * @return bool + */ + public function allowLagged( $mode = null ); + + /** + * @return bool + */ + public function pingAll(); + + /** + * Call a function with each open connection object + * @param callable $callback + * @param array $params + */ + public function forEachOpenConnection( $callback, array $params = [] ); + + /** + * Call a function with each open connection object to a master + * @param callable $callback + * @param array $params + */ + public function forEachOpenMasterConnection( $callback, array $params = [] ); + + /** + * Call a function with each open replica DB connection object + * @param callable $callback + * @param array $params + */ + public function forEachOpenReplicaConnection( $callback, array $params = [] ); + + /** + * Get the hostname and lag time of the most-lagged replica DB + * + * This is useful for maintenance scripts that need to throttle their updates. + * May attempt to open connections to replica DBs on the default DB. If there is + * no lag, the maximum lag will be reported as -1. + * + * @param bool|string $wiki Wiki ID, or false for the default database + * @return array ( host, max lag, index of max lagged host ) + */ + public function getMaxLag( $wiki = false ); + + /** + * Get an estimate of replication lag (in seconds) for each server + * + * Results are cached for a short time in memcached/process cache + * + * Values may be "false" if replication is too broken to estimate + * + * @param string|bool $wiki + * @return int[] Map of (server index => float|int|bool) + */ + public function getLagTimes( $wiki = false ); + + /** + * Get the lag in seconds for a given connection, or zero if this load + * balancer does not have replication enabled. + * + * This should be used in preference to Database::getLag() in cases where + * replication may not be in use, since there is no way to determine if + * replication is in use at the connection level without running + * potentially restricted queries such as SHOW SLAVE STATUS. Using this + * function instead of Database::getLag() avoids a fatal error in this + * case on many installations. + * + * @param IDatabase $conn + * @return int|bool Returns false on error + */ + public function safeGetLag( IDatabase $conn ); + + /** + * Wait for a replica DB to reach a specified master position + * + * This will connect to the master to get an accurate position if $pos is not given + * + * @param IDatabase $conn Replica DB + * @param DBMasterPos|bool $pos Master position; default: current position + * @param integer|null $timeout Timeout in seconds [optional] + * @return bool Success + */ + public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = null ); + + /** + * Clear the cache for slag lag delay times + * + * This is only used for testing + */ + public function clearLagTimeCache(); + + /** + * Set a callback via IDatabase::setTransactionListener() on + * all current and future master connections of this load balancer + * + * @param string $name Callback name + * @param callable|null $callback + */ + public function setTransactionListener( $name, callable $callback = null ); +} diff --git a/includes/db/loadbalancer/LoadBalancer.php b/includes/db/loadbalancer/LoadBalancer.php index b4898d571c..5e7743a1a8 100644 --- a/includes/db/loadbalancer/LoadBalancer.php +++ b/includes/db/loadbalancer/LoadBalancer.php @@ -27,7 +27,7 @@ use Psr\Log\LoggerInterface; * * @ingroup Database */ -class LoadBalancer { +class LoadBalancer implements ILoadBalancer { /** @var array[] Map of (server index => server config array) */ private $mServers; /** @var array[] Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */ @@ -97,22 +97,6 @@ class LoadBalancer { /** @var integer Seconds to cache master server read-only status */ const TTL_CACHE_READONLY = 5; - /** - * @param array $params Array with keys: - * - servers : Required. Array of server info structures. - * - loadMonitor : Name of a class used to fetch server lag and load. - * - readOnlyReason : Reason the master DB is read-only if so [optional] - * - waitTimeout : Maximum time to wait for replicas for consistency [optional] - * - srvCache : BagOStuff object [optional] - * - wanCache : WANObjectCache object [optional] - * - localDomain: The wiki ID of the "local"/"current" wiki [optional] - * - errorLogger: Callback that takes an Exception and logs it [optional] - * - connLogger : LoggerInterface object [optional] - * - queryLogger : LoggerInterface object [optional] - * - replLogger : LoggerInterface object [optional] - * - perfLogger : LoggerInterface object [optional] - * @throws InvalidArgumentException - */ public function __construct( array $params ) { if ( !isset( $params['servers'] ) ) { throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' ); @@ -253,17 +237,6 @@ class LoadBalancer { return ArrayUtils::pickRandom( $loads ); } - /** - * Get the index of the reader connection, which may be a replica DB - * This takes into account load ratios and lag times. It should - * always return a consistent index during a given invocation - * - * Side effect: opens connections to databases - * @param string|bool $group Query group, or false for the generic reader - * @param string|bool $wiki Wiki ID, or false for the current wiki - * @throws MWException - * @return bool|int|string - */ public function getReaderIndex( $group = false, $wiki = false ) { if ( count( $this->mServers ) == 1 ) { # Skip the load balancing if there's only one server @@ -383,12 +356,6 @@ class LoadBalancer { return $i; } - /** - * Set the master wait position - * If a DB_REPLICA connection has been opened already, waits - * Otherwise sets a variable telling it to wait if such a connection is opened - * @param DBMasterPos $pos - */ public function waitFor( $pos ) { $this->mWaitForPos = $pos; $i = $this->mReadIndex; @@ -431,12 +398,6 @@ class LoadBalancer { return $ok; } - /** - * Set the master wait position and wait for ALL replica DBs to catch up to it - * @param DBMasterPos $pos - * @param int $timeout Max seconds to wait; default is mWaitTimeout - * @return bool Success (able to connect and no timeouts reached) - */ public function waitForAll( $pos, $timeout = null ) { $this->mWaitForPos = $pos; $serverCount = count( $this->mServers ); @@ -451,13 +412,6 @@ class LoadBalancer { return $ok; } - /** - * Get any open connection to a given server index, local or foreign - * Returns false if there is no connection open - * - * @param int $i Server index - * @return DatabaseBase|bool False on failure - */ public function getAnyOpenConnection( $i ) { foreach ( $this->mConns as $connsByServer ) { if ( !empty( $connsByServer[$i] ) ) { @@ -533,17 +487,6 @@ class LoadBalancer { return $ok; } - /** - * Get a connection by index - * This is the main entry point for this class. - * - * @param int $i Server index - * @param array|string|bool $groups Query group(s), or false for the generic reader - * @param string|bool $wiki Wiki ID, or false for the current wiki - * - * @throws MWException - * @return DatabaseBase - */ public function getConnection( $i, $groups = [], $wiki = false ) { if ( $i === null || $i === false ) { throw new MWException( 'Attempt to call ' . __METHOD__ . @@ -610,14 +553,6 @@ class LoadBalancer { return $conn; } - /** - * Mark a foreign connection as being available for reuse under a different - * DB name or prefix. This mechanism is reference-counted, and must be called - * the same number of times as getConnection() to work. - * - * @param DatabaseBase $conn - * @throws MWException - */ public function reuseConnection( $conn ) { $serverIndex = $conn->getLBInfo( 'serverIndex' ); $refCount = $conn->getLBInfo( 'foreignPoolRefCount' ); @@ -667,6 +602,7 @@ class LoadBalancer { * @param array|string|bool $groups Query group(s), or false for the generic reader * @param string|bool $wiki Wiki ID, or false for the current wiki * @return DBConnRef + * @since 1.22 */ public function getConnectionRef( $db, $groups = [], $wiki = false ) { return new DBConnRef( $this, $this->getConnection( $db, $groups, $wiki ) ); @@ -683,6 +619,7 @@ class LoadBalancer { * @param array|string|bool $groups Query group(s), or false for the generic reader * @param string|bool $wiki Wiki ID, or false for the current wiki * @return DBConnRef + * @since 1.22 */ public function getLazyConnectionRef( $db, $groups = [], $wiki = false ) { $wiki = ( $wiki !== false ) ? $wiki : $this->localDomain; @@ -690,20 +627,6 @@ class LoadBalancer { return new DBConnRef( $this, [ $db, $groups, $wiki ] ); } - /** - * Open a connection to the server given by the specified index - * Index must be an actual index into the array. - * If the server is already open, returns it. - * - * On error, returns false, and the connection which caused the - * error will be available via $this->mErrorConnection. - * - * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError. - * - * @param int $i Server index - * @param string|bool $wiki Wiki ID, or false for the current wiki - * @return DatabaseBase|bool Returns false on errors - */ public function openConnection( $i, $wiki = false ) { if ( $wiki !== false ) { $conn = $this->openForeignConnection( $i, $wiki ); @@ -929,49 +852,22 @@ class LoadBalancer { return false; /* not reached */ } - /** - * @return int - * @since 1.26 - */ public function getWriterIndex() { return 0; } - /** - * Returns true if the specified index is a valid server index - * - * @param string $i - * @return bool - */ public function haveIndex( $i ) { return array_key_exists( $i, $this->mServers ); } - /** - * Returns true if the specified index is valid and has non-zero load - * - * @param string $i - * @return bool - */ public function isNonZeroLoad( $i ) { return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0; } - /** - * Get the number of defined servers (not the number of open connections) - * - * @return int - */ public function getServerCount() { return count( $this->mServers ); } - /** - * Get the host name or IP address of the server with the specified index - * Prefer a readable name if available. - * @param string $i - * @return string - */ public function getServerName( $i ) { if ( isset( $this->mServers[$i]['hostName'] ) ) { $name = $this->mServers[$i]['hostName']; @@ -984,11 +880,6 @@ class LoadBalancer { return ( $name != '' ) ? $name : 'localhost'; } - /** - * Return the server info structure for a given index, or false if the index is invalid. - * @param int $i - * @return array|bool - */ public function getServerInfo( $i ) { if ( isset( $this->mServers[$i] ) ) { return $this->mServers[$i]; @@ -997,20 +888,10 @@ class LoadBalancer { } } - /** - * Sets the server info structure for the given index. Entry at index $i - * is created if it doesn't exist - * @param int $i - * @param array $serverInfo - */ public function setServerInfo( $i, array $serverInfo ) { $this->mServers[$i] = $serverInfo; } - /** - * Get the current master position for chronology control purposes - * @return DBMasterPos|bool Returns false if not applicable - */ public function getMasterPos() { # If this entire request was served from a replica DB without opening a connection to the # master (however unlikely that may be), then we can fetch the position from the replica DB. @@ -1041,9 +922,6 @@ class LoadBalancer { $this->disabled = true; } - /** - * Close all open connections - */ public function closeAll() { $this->forEachOpenConnection( function ( DatabaseBase $conn ) { $conn->close(); @@ -1057,15 +935,7 @@ class LoadBalancer { $this->connsOpened = 0; } - /** - * Close a connection - * - * Using this function makes sure the LoadBalancer knows the connection is closed. - * If you use $conn->close() directly, the load balancer won't update its state. - * - * @param DatabaseBase $conn - */ - public function closeConnection( DatabaseBase $conn ) { + public function closeConnection( IDatabase $conn ) { $serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns foreach ( $this->mConns as $type => $connsByServer ) { if ( !isset( $connsByServer[$serverIndex] ) ) { @@ -1084,11 +954,6 @@ class LoadBalancer { $conn->close(); } - /** - * Commit transactions on all open connections - * @param string $fname Caller name - * @throws DBExpectedError - */ public function commitAll( $fname = __METHOD__ ) { $failures = []; @@ -1215,11 +1080,6 @@ class LoadBalancer { } } - /** - * Issue COMMIT on all master connections where writes where done - * @param string $fname Caller name - * @throws DBExpectedError - */ public function commitMasterChanges( $fname = __METHOD__ ) { $failures = []; @@ -1323,9 +1183,9 @@ class LoadBalancer { } /** - * @param DatabaseBase $conn + * @param IDatabase $conn */ - private function applyTransactionRoundFlags( DatabaseBase $conn ) { + private function applyTransactionRoundFlags( IDatabase $conn ) { if ( $conn->getFlag( DBO_DEFAULT ) ) { // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT. // Force DBO_TRX even in CLI mode since a commit round is expected soon. @@ -1337,9 +1197,9 @@ class LoadBalancer { } /** - * @param DatabaseBase $conn + * @param IDatabase $conn */ - private function undoTransactionRoundFlags( DatabaseBase $conn ) { + private function undoTransactionRoundFlags( IDatabase $conn ) { if ( $conn->getFlag( DBO_DEFAULT ) ) { $conn->restoreFlags( $conn::RESTORE_PRIOR ); } @@ -1423,11 +1283,6 @@ class LoadBalancer { return $fnames; } - /** - * @note This method will trigger a DB connection if not yet done - * @param string|bool $wiki Wiki ID, or false for the current wiki - * @return bool Whether the generic connection for reads is highly "lagged" - */ public function getLaggedReplicaMode( $wiki = false ) { // No-op if there is only one DB (also avoids recursion) if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) { @@ -1475,11 +1330,11 @@ class LoadBalancer { /** * @note This method may trigger a DB connection if not yet done * @param string|bool $wiki Wiki ID, or false for the current wiki - * @param DatabaseBase|null DB master connection; used to avoid loops [optional] + * @param IDatabase|null DB master connection; used to avoid loops [optional] * @return string|bool Reason the master is read-only or false if it is not * @since 1.27 */ - public function getReadOnlyReason( $wiki = false, DatabaseBase $conn = null ) { + public function getReadOnlyReason( $wiki = false, IDatabase $conn = null ) { if ( $this->readOnlyReason !== false ) { return $this->readOnlyReason; } elseif ( $this->getLaggedReplicaMode( $wiki ) ) { @@ -1499,10 +1354,10 @@ class LoadBalancer { /** * @param string $wiki Wiki ID, or false for the current wiki - * @param DatabaseBase|null DB master connectionl used to avoid loops [optional] + * @param IDatabase|null DB master connectionl used to avoid loops [optional] * @return bool */ - private function masterRunningReadOnly( $wiki, DatabaseBase $conn = null ) { + private function masterRunningReadOnly( $wiki, IDatabase $conn = null ) { $cache = $this->wanCache; $masterServer = $this->getServerName( $this->getWriterIndex() ); @@ -1524,11 +1379,6 @@ class LoadBalancer { ); } - /** - * Disables/enables lag checks - * @param null|bool $mode - * @return bool - */ public function allowLagged( $mode = null ) { if ( $mode === null ) { return $this->mAllowLagged; @@ -1538,9 +1388,6 @@ class LoadBalancer { return $this->mAllowLagged; } - /** - * @return bool - */ public function pingAll() { $success = true; $this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( &$success ) { @@ -1552,11 +1399,6 @@ class LoadBalancer { return $success; } - /** - * Call a function with each open connection object - * @param callable $callback - * @param array $params - */ public function forEachOpenConnection( $callback, array $params = [] ) { foreach ( $this->mConns as $connsByServer ) { foreach ( $connsByServer as $serverConns ) { @@ -1607,16 +1449,6 @@ class LoadBalancer { } } - /** - * Get the hostname and lag time of the most-lagged replica DB - * - * This is useful for maintenance scripts that need to throttle their updates. - * May attempt to open connections to replica DBs on the default DB. If there is - * no lag, the maximum lag will be reported as -1. - * - * @param bool|string $wiki Wiki ID, or false for the default database - * @return array ( host, max lag, index of max lagged host ) - */ public function getMaxLag( $wiki = false ) { $maxLag = -1; $host = ''; @@ -1638,16 +1470,6 @@ class LoadBalancer { return [ $host, $maxLag, $maxIndex ]; } - /** - * Get an estimate of replication lag (in seconds) for each server - * - * Results are cached for a short time in memcached/process cache - * - * Values may be "false" if replication is too broken to estimate - * - * @param string|bool $wiki - * @return int[] Map of (server index => float|int|bool) - */ public function getLagTimes( $wiki = false ) { if ( $this->getServerCount() <= 1 ) { return [ 0 => 0 ]; // no replication = no lag @@ -1657,20 +1479,6 @@ class LoadBalancer { return $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki ); } - /** - * Get the lag in seconds for a given connection, or zero if this load - * balancer does not have replication enabled. - * - * This should be used in preference to Database::getLag() in cases where - * replication may not be in use, since there is no way to determine if - * replication is in use at the connection level without running - * potentially restricted queries such as SHOW SLAVE STATUS. Using this - * function instead of Database::getLag() avoids a fatal error in this - * case on many installations. - * - * @param IDatabase $conn - * @return int|bool Returns false on error - */ public function safeGetLag( IDatabase $conn ) { if ( $this->getServerCount() == 1 ) { return 0; @@ -1718,6 +1526,7 @@ class LoadBalancer { * Clear the cache for slag lag delay times * * This is only used for testing + * @since 1.26 */ public function clearLagTimeCache() { $this->getLoadMonitor()->clearCaches(); -- 2.20.1