X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fdb%2Floadbalancer%2FLoadBalancer.php;h=052a0fade5deec98c9127c9d32a0b7c355b31848;hb=2ece425940fc431d8e5e8f73535a2f6e82b9d484;hp=fbc8c8cdd30db8a9c07ae261f704eec76de6937e;hpb=0e1c391d97418e646a58d6f6f2545a83271b07ff;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/loadbalancer/LoadBalancer.php b/includes/db/loadbalancer/LoadBalancer.php index fbc8c8cdd3..052a0fade5 100644 --- a/includes/db/loadbalancer/LoadBalancer.php +++ b/includes/db/loadbalancer/LoadBalancer.php @@ -47,6 +47,8 @@ class LoadBalancer { private $mLoadMonitorClass; /** @var LoadMonitor */ private $mLoadMonitor; + /** @var BagOStuff */ + private $srvCache; /** @var bool|DatabaseBase Database connection that caused a problem */ private $mErrorConnection; @@ -55,21 +57,28 @@ class LoadBalancer { /** @var bool|DBMasterPos False if not set */ private $mWaitForPos; /** @var bool Whether the generic reader fell back to a lagged slave */ - private $mLaggedSlaveMode; + private $laggedSlaveMode = false; + /** @var bool Whether the generic reader fell back to a lagged slave */ + private $slavesDownMode = false; /** @var string The last DB selection or connection error */ private $mLastError = 'Unknown error'; + /** @var string|bool Reason the LB is read-only or false if not */ + private $readOnlyReason = false; /** @var integer Total connections opened */ private $connsOpened = 0; /** @var integer Warn when this many connection are held */ const CONN_HELD_WARN_THRESHOLD = 10; /** @var integer Default 'max lag' when unspecified */ - const MAX_LAG = 30; + const MAX_LAG = 10; + /** @var integer Max time to wait for a slave to catch up (e.g. ChronologyProtector) */ + const POS_WAIT_TIMEOUT = 10; /** * @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. + * - 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] * @throws MWException */ public function __construct( array $params ) { @@ -77,7 +86,7 @@ class LoadBalancer { throw new MWException( __CLASS__ . ': missing servers parameter' ); } $this->mServers = $params['servers']; - $this->mWaitTimeout = 10; + $this->mWaitTimeout = self::POS_WAIT_TIMEOUT; $this->mReadIndex = -1; $this->mWriteIndex = -1; @@ -87,10 +96,13 @@ class LoadBalancer { 'foreignFree' => array() ); $this->mLoads = array(); $this->mWaitForPos = false; - $this->mLaggedSlaveMode = false; $this->mErrorConnection = false; $this->mAllowLagged = false; + if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) { + $this->readOnlyReason = $params['readOnlyReason']; + } + if ( isset( $params['loadMonitor'] ) ) { $this->mLoadMonitorClass = $params['loadMonitor']; } else { @@ -113,6 +125,8 @@ class LoadBalancer { } } } + + $this->srvCache = ObjectCache::getLocalServerInstance(); } /** @@ -154,7 +168,7 @@ class LoadBalancer { /** * @param array $loads * @param bool|string $wiki Wiki to get non-lagged for - * @param float $maxLag Restrict the maximum allowed lag to this many seconds + * @param int $maxLag Restrict the maximum allowed lag to this many seconds * @return bool|int|string */ private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = self::MAX_LAG ) { @@ -329,7 +343,7 @@ class LoadBalancer { $this->mReadIndex = $i; # Record if the generic reader index is in "lagged slave" mode if ( $laggedSlaveMode ) { - $this->mLaggedSlaveMode = true; + $this->laggedSlaveMode = true; } } $serverName = $this->getServerName( $i ); @@ -352,7 +366,7 @@ class LoadBalancer { if ( $i > 0 ) { if ( !$this->doWait( $i ) ) { $this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos(); - $this->mLaggedSlaveMode = true; + $this->laggedSlaveMode = true; } } } @@ -435,17 +449,27 @@ class LoadBalancer { protected function doWait( $index, $open = false, $timeout = null ) { $close = false; // close the connection afterwards - # Find a connection to wait on, creating one if needed and allowed + // Check if we already know that the DB has reached this point + $server = $this->getServerName( $index ); + $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server ); + /** @var DBMasterPos $knownReachedPos */ + $knownReachedPos = $this->srvCache->get( $key ); + if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) { + wfDebugLog( 'replication', __METHOD__ . ": Slave $server known to be caught up.\n" ); + return true; + } + + // Find a connection to wait on, creating one if needed and allowed $conn = $this->getAnyOpenConnection( $index ); if ( !$conn ) { if ( !$open ) { - wfDebug( __METHOD__ . ": no connection open\n" ); + wfDebugLog( 'replication', __METHOD__ . ": no connection open for $server\n" ); return false; } else { $conn = $this->openConnection( $index, '' ); if ( !$conn ) { - wfDebug( __METHOD__ . ": failed to open connection\n" ); + wfDebugLog( 'replication', __METHOD__ . ": failed to open connection to $server\n" ); return false; } @@ -455,20 +479,21 @@ class LoadBalancer { } } - wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" ); + wfDebugLog( 'replication', __METHOD__ . ": Waiting for slave $server to catch up...\n" ); $timeout = $timeout ?: $this->mWaitTimeout; $result = $conn->masterPosWait( $this->mWaitForPos, $timeout ); if ( $result == -1 || is_null( $result ) ) { - # Timed out waiting for slave, use master instead - $server = $server = $this->getServerName( $index ); + // Timed out waiting for slave, use master instead $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}"; - wfDebug( "$msg\n" ); + wfDebugLog( 'replication', "$msg\n" ); wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) ); $ok = false; } else { - wfDebug( __METHOD__ . ": Done\n" ); + wfDebugLog( 'replication', __METHOD__ . ": Done\n" ); $ok = true; + // Remember that the DB reached this point + $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY ); } if ( $close ) { @@ -548,12 +573,9 @@ class LoadBalancer { $trxProf->recordConnection( $host, $dbname, $masterOnly ); } - # Make master connections read only if in lagged slave mode - if ( $masterOnly && $this->getServerCount() > 1 && $this->getLaggedSlaveMode() ) { - $conn->setLBInfo( 'readOnlyReason', - 'The database has been automatically locked ' . - 'while the slave database servers catch up to the master' - ); + if ( $masterOnly ) { + # Make master-requested DB handles inherit any read-only mode setting + $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki ) ); } return $conn; @@ -572,7 +594,6 @@ class LoadBalancer { $refCount = $conn->getLBInfo( 'foreignPoolRefCount' ); if ( $serverIndex === null || $refCount === null ) { wfDebug( __METHOD__ . ": this connection was not opened as a foreign connection\n" ); - /** * This can happen in code like: * foreach ( $dbs as $db ) { @@ -583,7 +604,6 @@ class LoadBalancer { * When a connection to the local DB is opened in this way, reuseConnection() * should be ignored */ - return; } @@ -1142,13 +1162,25 @@ class LoadBalancer { /** * @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 getLaggedSlaveMode() { - # Get a generic reader connection - $this->getConnection( DB_SLAVE ); + public function getLaggedSlaveMode( $wiki = false ) { + // No-op if there is only one DB (also avoids recursion) + if ( !$this->laggedSlaveMode && $this->getServerCount() > 1 ) { + try { + // See if laggedSlaveMode gets set + $conn = $this->getConnection( DB_SLAVE, false, $wiki ); + $this->reuseConnection( $conn ); + } catch ( DBConnectionError $e ) { + // Avoid expensive re-connect attempts and failures + $this->slavesDownMode = true; + $this->laggedSlaveMode = true; + } + } - return $this->mLaggedSlaveMode; + return $this->laggedSlaveMode; } /** @@ -1157,7 +1189,29 @@ class LoadBalancer { * @since 1.27 */ public function laggedSlaveUsed() { - return $this->mLaggedSlaveMode; + return $this->laggedSlaveMode; + } + + /** + * @note This method may trigger a DB connection if not yet done + * @param string|bool $wiki Wiki ID, or false for the current wiki + * @return string|bool Reason the master is read-only or false if it is not + * @since 1.27 + */ + public function getReadOnlyReason( $wiki = false ) { + if ( $this->readOnlyReason !== false ) { + return $this->readOnlyReason; + } elseif ( $this->getLaggedSlaveMode( $wiki ) ) { + if ( $this->slavesDownMode ) { + return 'The database has been automatically locked ' . + 'until the slave database servers become available'; + } else { + return 'The database has been automatically locked ' . + 'while the slave database servers catch up to the master.'; + } + } + + return false; } /**