summaryrefslogtreecommitdiff
path: root/includes/db/LoadBalancer.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db/LoadBalancer.php')
-rw-r--r--includes/db/LoadBalancer.php203
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() {