diff options
Diffstat (limited to 'includes/db/LoadBalancer.php')
-rw-r--r-- | includes/db/LoadBalancer.php | 203 |
1 files changed, 148 insertions, 55 deletions
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php index d899ce07..c7210c4c 100644 --- a/includes/db/LoadBalancer.php +++ b/includes/db/LoadBalancer.php @@ -13,13 +13,13 @@ * @ingroup Database */ class LoadBalancer { - /* private */ var $mServers, $mConns, $mLoads, $mGroupLoads; - /* private */ var $mErrorConnection; - /* private */ var $mReadIndex, $mAllowLagged; - /* private */ var $mWaitForPos, $mWaitTimeout; - /* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error'; - /* private */ var $mParentInfo, $mLagTimes; - /* private */ var $mLoadMonitorClass, $mLoadMonitor; + private $mServers, $mConns, $mLoads, $mGroupLoads; + private $mErrorConnection; + private $mReadIndex, $mAllowLagged; + private $mWaitForPos, $mWaitTimeout; + private $mLaggedSlaveMode, $mLastError = 'Unknown error'; + private $mParentInfo, $mLagTimes; + private $mLoadMonitorClass, $mLoadMonitor; /** * @param $params Array with keys: @@ -27,8 +27,7 @@ class LoadBalancer { * masterWaitTimeout Replication lag wait timeout * loadMonitor Name of a class used to fetch server lag and load. */ - function __construct( $params ) - { + function __construct( $params ) { if ( !isset( $params['servers'] ) ) { throw new MWException( __CLASS__.': missing servers parameter' ); } @@ -51,8 +50,17 @@ class LoadBalancer { $this->mLaggedSlaveMode = false; $this->mErrorConnection = false; $this->mAllowLagged = false; - $this->mLoadMonitorClass = isset( $params['loadMonitor'] ) - ? $params['loadMonitor'] : 'LoadMonitor_MySQL'; + + if ( isset( $params['loadMonitor'] ) ) { + $this->mLoadMonitorClass = $params['loadMonitor']; + } else { + $master = reset( $params['servers'] ); + if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) { + $this->mLoadMonitorClass = 'LoadMonitor_MySQL'; + } else { + $this->mLoadMonitorClass = 'LoadMonitor_Null'; + } + } foreach( $params['servers'] as $i => $server ) { $this->mLoads[$i] = $server['load']; @@ -69,6 +77,8 @@ class LoadBalancer { /** * Get a LoadMonitor instance + * + * @return LoadMonitor */ function getLoadMonitor() { if ( !isset( $this->mLoadMonitor ) ) { @@ -88,9 +98,12 @@ class LoadBalancer { /** * Given an array of non-normalised probabilities, this function will select * an element and return the appropriate key + * + * @param $weights + * + * @return int */ - function pickRandom( $weights ) - { + function pickRandom( $weights ) { if ( !is_array( $weights ) || count( $weights ) == 0 ) { return false; } @@ -116,6 +129,11 @@ class LoadBalancer { return $i; } + /** + * @param $loads + * @param $wiki bool + * @return bool|int|string + */ function getRandomNonLagged( $loads, $wiki = false ) { # Unset excessively lagged servers $lags = $this->getLagTimes( $wiki ); @@ -160,11 +178,14 @@ class LoadBalancer { * always return a consistent index during a given invocation * * Side effect: opens connections to databases + * @param $group bool + * @param $wiki bool + * @return bool|int|string */ function getReaderIndex( $group = false, $wiki = false ) { global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype; - # FIXME: For now, only go through all this for mysql databases + # @todo FIXME: For now, only go through all this for mysql databases if ($wgDBtype != 'mysql') { return $this->getWriterIndex(); } @@ -220,7 +241,7 @@ class LoadBalancer { $i = $this->getRandomNonLagged( $currentLoads, $wiki ); if ( $i === false && count( $currentLoads ) != 0 ) { # All slaves lagged. Switch to read-only mode - $wgReadOnly = 'The database has been automatically locked ' . + $wgReadOnly = 'The database has been automatically locked ' . 'while the slave database servers catch up to the master'; $i = $this->pickRandom( $currentLoads ); $laggedSlaveMode = true; @@ -229,7 +250,7 @@ class LoadBalancer { if ( $i === false ) { # pickRandom() returned false - # This is permanent and means the configuration or the load monitor + # This is permanent and means the configuration or the load monitor # wants us to return false. wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" ); wfProfileOut( __METHOD__ ); @@ -247,7 +268,7 @@ class LoadBalancer { } // Perform post-connection backoff - $threshold = isset( $this->mServers[$i]['max threads'] ) + $threshold = isset( $this->mServers[$i]['max threads'] ) ? $this->mServers[$i]['max threads'] : false; $backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold ); @@ -256,7 +277,7 @@ class LoadBalancer { if ( $wiki !== false ) { $this->reuseConnection( $conn ); } - + if ( $backoff ) { # Post-connection overload, don't use this server for now $totalThreadsConnected += $backoff; @@ -339,7 +360,7 @@ class LoadBalancer { } wfProfileOut( __METHOD__ ); } - + /** * Set the master wait position and wait for ALL slaves to catch up to it */ @@ -347,7 +368,7 @@ class LoadBalancer { wfProfileIn( __METHOD__ ); $this->mWaitForPos = $pos; for ( $i = 1; $i < count( $this->mServers ); $i++ ) { - $this->doWait( $i ); + $this->doWait( $i , true ); } wfProfileOut( __METHOD__ ); } @@ -355,6 +376,8 @@ class LoadBalancer { /** * Get any open connection to a given server index, local or foreign * Returns false if there is no connection open + * + * @return DatabaseBase */ function getAnyOpenConnection( $i ) { foreach ( $this->mConns as $conns ) { @@ -368,12 +391,20 @@ class LoadBalancer { /** * Wait for a given slave to catch up to the master pos stored in $this */ - function doWait( $index ) { + function doWait( $index, $open = false ) { # Find a connection to wait on $conn = $this->getAnyOpenConnection( $index ); if ( !$conn ) { - wfDebug( __METHOD__ . ": no connection open\n" ); - return false; + if ( !$open ) { + wfDebug( __METHOD__ . ": no connection open\n" ); + return false; + } else { + $conn = $this->openConnection( $index ); + if ( !$conn ) { + wfDebug( __METHOD__ . ": failed to open connection\n" ); + return false; + } + } } wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" ); @@ -392,11 +423,11 @@ class LoadBalancer { /** * Get a connection by index * This is the main entry point for this class. - * + * * @param $i Integer: server index * @param $groups Array: query groups * @param $wiki String: wiki ID - * + * * @return DatabaseBase */ public function &getConnection( $i, $groups = array(), $wiki = false ) { @@ -460,6 +491,8 @@ class LoadBalancer { * 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 */ public function reuseConnection( $conn ) { $serverIndex = $conn->getLBInfo('serverIndex'); @@ -506,8 +539,8 @@ class LoadBalancer { * On error, returns false, and the connection which caused the * error will be available via $this->mErrorConnection. * - * @param $i Integer: server index - * @param $wiki String: wiki ID to open + * @param $i Integer server index + * @param $wiki String wiki ID to open * @return DatabaseBase * * @access private @@ -615,6 +648,7 @@ class LoadBalancer { * * @param $index Integer: server index * @access private + * @return bool */ function isOpen( $index ) { if( !is_integer( $index ) ) { @@ -627,10 +661,13 @@ class LoadBalancer { * Really opens a connection. Uncached. * Returns a Database object whether or not the connection was successful. * @access private + * + * @return DatabaseBase */ function reallyOpenConnection( $server, $dbNameOverride = false ) { if( !is_array( $server ) ) { - throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' ); + throw new MWException( 'You must update your load-balancing configuration. ' . + 'See DefaultSettings.php entry for $wgDBservers.' ); } $host = $server['host']; @@ -643,12 +680,13 @@ class LoadBalancer { # Create object wfDebug( "Connecting to $host $dbname...\n" ); try { - $db = DatabaseBase::newFromType( $server['type'], $server ); + $db = DatabaseBase::factory( $server['type'], $server ); } catch ( DBConnectionError $e ) { - // FIXME: This is probably the ugliest thing I have ever done to + // FIXME: This is probably the ugliest thing I have ever done to // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS $db = $e->db; } + if ( $db->isOpen() ) { wfDebug( "Connected to $host $dbname.\n" ); } else { @@ -669,7 +707,7 @@ class LoadBalancer { if ( !is_object( $conn ) ) { // No last connection, probably due to all servers being too busy - wfLogDBError( "LB failure with no last connection\n" ); + wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" ); $conn = new Database; // If all servers were busy, mLastError will contain something sensible throw new DBConnectionError( $conn, $this->mLastError ); @@ -687,6 +725,8 @@ class LoadBalancer { /** * Returns true if the specified index is a valid server index + * + * @return bool */ function haveIndex( $i ) { return array_key_exists( $i, $this->mServers ); @@ -694,6 +734,8 @@ class LoadBalancer { /** * Returns true if the specified index is valid and has non-zero load + * + * @return bool */ function isNonZeroLoad( $i ) { return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0; @@ -701,6 +743,8 @@ class LoadBalancer { /** * Get the number of defined servers (not the number of open connections) + * + * @return int */ function getServerCount() { return count( $this->mServers ); @@ -732,6 +776,13 @@ class LoadBalancer { } /** + * Sets the server info structure for the given index. Entry at index $i is created if it doesn't exist + */ + function setServerInfo( $i, $serverInfo ) { + $this->mServers[$i] = $serverInfo; + } + + /** * Get the current master position for chronology control purposes * @return mixed */ @@ -774,6 +825,8 @@ class LoadBalancer { /** * Deprecated function, typo in function name + * + * @deprecated in 1.18 */ function closeConnecton( $conn ) { $this->closeConnection( $conn ); @@ -783,7 +836,7 @@ class LoadBalancer { * 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 $conn + * @param $conn * @return void */ function closeConnection( $conn ) { @@ -818,7 +871,9 @@ class LoadBalancer { } } - /* Issue COMMIT only on master, only if queries were done on connection */ + /** + * Issue COMMIT only on master, only if queries were done on connection + */ function commitMasterChanges() { // Always 0, but who knows.. :) $masterIndex = $this->getWriterIndex(); @@ -843,10 +898,11 @@ class LoadBalancer { } /* Disables/enables lag checks */ - function allowLagged($mode=null) { - if ($mode===null) + function allowLagged( $mode = null ) { + if ( $mode === null) { return $this->mAllowLagged; - $this->mAllowLagged=$mode; + } + $this->mAllowLagged = $mode; } function pingAll() { @@ -880,47 +936,84 @@ class LoadBalancer { /** * Get the hostname and lag time of the most-lagged slave. * This is useful for maintenance scripts that need to throttle their updates. - * May attempt to open connections to slaves on the default DB. + * May attempt to open connections to slaves on the default DB. If there is + * no lag, the maximum lag will be reported as -1. + * * @param $wiki string Wiki ID, or false for the default database + * + * @return array ( host, max lag, index of max lagged host ) */ function getMaxLag( $wiki = false ) { $maxLag = -1; $host = ''; - foreach ( $this->mServers as $i => $conn ) { - $conn = false; - if ( $wiki === false ) { - $conn = $this->getAnyOpenConnection( $i ); - } - if ( !$conn ) { - $conn = $this->openConnection( $i, $wiki ); - } - if ( !$conn ) { - continue; - } - $lag = $conn->getLag(); - if ( $lag > $maxLag ) { - $maxLag = $lag; - $host = $this->mServers[$i]['host']; + $maxIndex = 0; + if ( $this->getServerCount() > 1 ) { // no replication = no lag + foreach ( $this->mServers as $i => $conn ) { + $conn = false; + if ( $wiki === false ) { + $conn = $this->getAnyOpenConnection( $i ); + } + if ( !$conn ) { + $conn = $this->openConnection( $i, $wiki ); + } + if ( !$conn ) { + continue; + } + $lag = $conn->getLag(); + if ( $lag > $maxLag ) { + $maxLag = $lag; + $host = $this->mServers[$i]['host']; + $maxIndex = $i; + } } } - return array( $host, $maxLag ); + return array( $host, $maxLag, $maxIndex ); } /** * Get lag time for each server * Results are cached for a short time in memcached, and indefinitely in the process cache + * + * @param $wiki + * + * @return array */ function getLagTimes( $wiki = false ) { # Try process cache if ( isset( $this->mLagTimes ) ) { return $this->mLagTimes; } - # No, send the request to the load monitor - $this->mLagTimes = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki ); + if ( $this->getServerCount() == 1 ) { + # No replication + $this->mLagTimes = array( 0 => 0 ); + } else { + # Send the request to the load monitor + $this->mLagTimes = $this->getLoadMonitor()->getLagTimes( + array_keys( $this->mServers ), $wiki ); + } return $this->mLagTimes; } /** + * 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. + */ + function safeGetLag( $conn ) { + if ( $this->getServerCount() == 1 ) { + return 0; + } else { + return $conn->getLag(); + } + } + + /** * Clear the cache for getLagTimes */ function clearLagTimeCache() { |