private $mLaggedSlaveMode;
/** @var string The last DB selection or connection error */
private $mLastError = 'Unknown error';
+ /** @var integer Total connections opened */
+ private $connsOpened = 0;
/** @var ProcessCacheLRU */
private $mProcCache;
+ /** @var integer Warn when this many connection are held */
+ const CONN_HELD_WARN_THRESHOLD = 10;
+
/**
* @param array $params Array with keys:
* servers Required. Array of server info structures.
/**
* @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
* @return bool|int|string
*/
- private function getRandomNonLagged( array $loads, $wiki = false ) {
- # Unset excessively lagged servers
+ private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
$lags = $this->getLagTimes( $wiki );
+
+ # Unset excessively lagged servers
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
+ $maxServerLag = $maxLag;
+ if ( isset( $this->mServers[$i]['max lag'] ) ) {
+ $maxServerLag = min( $maxServerLag, $this->mServers[$i]['max lag'] );
+ }
if ( $lag === false ) {
wfDebugLog( 'replication', "Server #$i is not replicating" );
unset( $loads[$i] );
- } elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
+ } elseif ( $lag > $maxServerLag ) {
wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)" );
unset( $loads[$i] );
}
return $this->mReadIndex;
}
- new ProfileSection( __METHOD__ );
-
# Find the relevant load array
if ( $group !== false ) {
if ( isset( $this->mGroupLoads[$group] ) ) {
if ( $wgReadOnly || $this->mAllowLagged || $laggedSlaveMode ) {
$i = ArrayUtils::pickRandom( $currentLoads );
} else {
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ $i = false;
+ if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+ # ChronologyProtecter causes mWaitForPos to be set via sessions.
+ # This triggers doWait() after connect, so it's especially good to
+ # avoid lagged servers so as to avoid just blocking in that method.
+ $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+ # Aim for <= 1 second of waiting (being too picky can backfire)
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
+ }
+ if ( $i === false ) {
+ # Any server with less lag than it's 'max lag' param is preferable
+ $i = $this->getRandomNonLagged( $currentLoads, $wiki );
+ }
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode" );
* @param DBMasterPos $pos
*/
public function waitFor( $pos ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$i = $this->mReadIndex;
$this->mLaggedSlaveMode = true;
}
}
- wfProfileOut( __METHOD__ );
}
/**
* @return bool Success (able to connect and no timeouts reached)
*/
public function waitForAll( $pos, $timeout = null ) {
- wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
$serverCount = count( $this->mServers );
$ok = $this->doWait( $i, true, $timeout ) && $ok;
}
}
- wfProfileOut( __METHOD__ );
return $ok;
}
* @throws MWException
* @return DatabaseBase
*/
- public function &getConnection( $i, $groups = array(), $wiki = false ) {
- wfProfileIn( __METHOD__ );
-
+ public function getConnection( $i, $groups = array(), $wiki = false ) {
if ( $i === null || $i === false ) {
- wfProfileOut( __METHOD__ );
throw new MWException( 'Attempt to call ' . __METHOD__ .
' with invalid server index' );
}
$wiki = false;
}
- # Query groups
+ $groups = ( $groups === false || $groups === array() )
+ ? array( false ) // check one "group": the generic pool
+ : (array)$groups;
+
if ( $i == DB_MASTER ) {
$i = $this->getWriterIndex();
- } elseif ( !is_array( $groups ) ) {
- $groupIndex = $this->getReaderIndex( $groups, $wiki );
- if ( $groupIndex !== false ) {
- $serverName = $this->getServerName( $groupIndex );
- wfDebug( __METHOD__ . ": using server $serverName for group $groups\n" );
- $i = $groupIndex;
- }
} else {
+ # Try to find an available server in any the query groups (in order)
foreach ( $groups as $group ) {
$groupIndex = $this->getReaderIndex( $group, $wiki );
if ( $groupIndex !== false ) {
# Operation-based index
if ( $i == DB_SLAVE ) {
$this->mLastError = 'Unknown error'; // reset error string
- $i = $this->getReaderIndex( false, $wiki );
+ # Try the general server pool if $groups are unavailable.
+ $i = in_array( false, $groups, true )
+ ? false // don't bother with this if that is what was tried above
+ : $this->getReaderIndex( false, $wiki );
# Couldn't find a working server in getReaderIndex()?
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
# Now we have an explicit index into the servers array
$conn = $this->openConnection( $i, $wiki );
if ( !$conn ) {
- wfProfileOut( __METHOD__ );
return $this->reportConnectionError();
}
- wfProfileOut( __METHOD__ );
-
return $conn;
}
* @access private
*/
public function openConnection( $i, $wiki = false ) {
- wfProfileIn( __METHOD__ );
if ( $wiki !== false ) {
$conn = $this->openForeignConnection( $i, $wiki );
- wfProfileOut( __METHOD__ );
return $conn;
}
$conn = false;
}
}
- wfProfileOut( __METHOD__ );
return $conn;
}
* @return DatabaseBase
*/
private function openForeignConnection( $i, $wiki ) {
- wfProfileIn( __METHOD__ );
list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
// Reuse an already-used connection
$refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
$conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
}
- wfProfileOut( __METHOD__ );
return $conn;
}
$server['dbname'] = $dbNameOverride;
}
+ // Log when many connection are made on requests
+ if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
+ $masterAddr = $this->getServerName( 0 );
+ wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
+ "{$this->connsOpened}+ connections made (master=$masterAddr)\n" .
+ wfBacktrace( true ) );
+ }
+
# Create object
try {
$db = DatabaseBase::factory( $server['type'], $server );
'foreignFree' => array(),
'foreignUsed' => array(),
);
+ $this->connsOpened = 0;
}
/**
if ( $conn === $candidateConn ) {
$conn->close();
unset( $this->mConns[$i1][$i2][$i3] );
+ --$this->connsOpened;
$done = true;
break;
}