Aaron Schulz has uploaded a new change for review.
https://gerrit.wikimedia.org/r/305764
Change subject: Various database class cleanups
......................................................................
Various database class cleanups
* Refactor out some code duplication in query() into a
separate private method.
* Remove the total master/slave query profiling, which is not
necessary and redundant.
* Make ping() catch errors so it can match the docs that say
it returns true/false to indicate failure.
* Optimize ping() to no-op if there was obvious recent activity.
* Move the ping() round in JobRunner to approveMasterChanges.
This way, all commit rounds benefit from this logic.
* Add more doc comments for DatabaseBase fields.
Change-Id: Ic90ce2be4187244a0e8d44854c39d4b78be8e642
---
M includes/db/Database.php
M includes/db/loadbalancer/LoadBalancer.php
M includes/jobqueue/JobRunner.php
M tests/phpunit/includes/db/DatabaseSqliteTest.php
4 files changed, 100 insertions(+), 70 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core
refs/changes/64/305764/1
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 528a298..88d7687 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -32,24 +32,35 @@
abstract class DatabaseBase implements IDatabase {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
-
/** Minimum time to wait before retry, in microseconds */
const DEADLOCK_DELAY_MIN = 500000;
-
/** Maximum time to wait before retry */
const DEADLOCK_DELAY_MAX = 1500000;
- protected $mLastQuery = '';
- protected $mDoneWrites = false;
- protected $mPHPError = false;
+ /** How long before it is worth doing a dummy query to test the
connection */
+ const PING_TTL = 5.0;
- protected $mServer, $mUser, $mPassword, $mDBname;
+ /** @var string SQL query */
+ protected $mLastQuery = '';
+ /** @var bool */
+ protected $mDoneWrites = false;
+ /** @var string|bool */
+ protected $mPHPError = false;
+ /** @var string */
+ protected $mServer;
+ /** @var string */
+ protected $mUser;
+ /** @var string */
+ protected $mPassword;
+ /** @var string */
+ protected $mDBname;
/** @var BagOStuff APC cache */
protected $srvCache;
/** @var resource Database connection */
protected $mConn = null;
+ /** @var bool */
protected $mOpened = false;
/** @var array[] List of (callable, method name) */
@@ -61,20 +72,27 @@
/** @var bool Whether to suppress triggering of post-commit callbacks */
protected $suppressPostCommitCallbacks = false;
+ /** @var string */
protected $mTablePrefix;
+ /** @var string */
protected $mSchema;
+ /** @var integer */
protected $mFlags;
+ /** @var bool */
protected $mForeign;
+ /** @var array */
protected $mLBInfo = [];
+ /** @var bool|null */
protected $mDefaultBigSelects = null;
+ /** @var array|bool */
protected $mSchemaVars = false;
/** @var array */
protected $mSessionVars = [];
-
+ /** @var array|null */
protected $preparedArgs;
-
+ /** @var string|bool|null Stashed value of html_errors INI setting */
protected $htmlErrors;
-
+ /** @var string */
protected $delimiter = ';';
/**
@@ -176,6 +194,9 @@
* @var string[] Process cache of VIEWs names in the database
*/
protected $allViews = null;
+
+ /** @var float UNIX timestamp */
+ protected $lastPing = 0.0;
/** @var TransactionProfiler */
protected $trxProfiler;
@@ -786,8 +807,8 @@
$priorWritesPending = $this->writesOrCallbacksPending();
$this->mLastQuery = $sql;
- $isWriteQuery = $this->isWriteQuery( $sql );
- if ( $isWriteQuery ) {
+ $isWrite = $this->isWriteQuery( $sql );
+ if ( $isWrite ) {
$reason = $this->getReadOnlyReason();
if ( $reason !== false ) {
throw new DBReadOnlyError( $this, "Database is
read-only: $reason" );
@@ -820,49 +841,23 @@
}
# Keep track of whether the transaction has write queries
pending
- if ( $this->mTrxLevel && !$this->mTrxDoneWrites &&
$isWriteQuery ) {
+ if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
$this->mTrxDoneWrites = true;
$this->getTransactionProfiler()->transactionWritingIn(
$this->mServer, $this->mDBname,
$this->mTrxShortId );
- }
-
- $isMaster = !is_null( $this->getLBInfo( 'master' ) );
- # generalizeSQL will probably cut down the query to reasonable
- # logging size most of the time. The substr is really just a
sanity check.
- if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr(
DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query-master';
- } else {
- $queryProf = 'query: ' . substr(
DatabaseBase::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'DatabaseBase::query';
- }
- # Include query transaction state
- $queryProf .= $this->mTrxShortId ? "
[TRX#{$this->mTrxShortId}]" : "";
-
- $profiler = Profiler::instance();
- if ( !$profiler instanceof ProfilerStub ) {
- $totalProfSection = $profiler->scopedProfileIn(
$totalProf );
- $queryProfSection = $profiler->scopedProfileIn(
$queryProf );
}
if ( $this->debug() ) {
wfDebugLog( 'queries', sprintf( "%s: %s",
$this->mDBname, $commentedSql ) );
}
- $queryId = MWDebug::query( $sql, $fname, $isMaster );
-
# Avoid fatals if close() was called
$this->assertOpen();
- # Do the query and handle errors
- $startTime = microtime( true );
- $ret = $this->doQuery( $commentedSql );
- $queryRuntime = microtime( true ) - $startTime;
- # Log the query time and feed it into the DB trx profiler
- $this->getTransactionProfiler()->recordQueryCompletion(
- $queryProf, $startTime, $isWriteQuery,
$this->affectedRows() );
-
- MWDebug::queryTime( $queryId );
+ # Send the query to the server
+ $isMaster = !is_null( $this->getLBInfo( 'master' ) );
+ $queryId = MWDebug::query( $sql, $fname, $isMaster );
+ $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite,
$fname, $queryId );
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
@@ -883,12 +878,8 @@
$this->reportQueryError( $lastError,
$lastErrno, $sql, $fname );
} else {
# Should be safe to silently retry the
query
- $startTime = microtime( true );
- $ret = $this->doQuery( $commentedSql );
- $queryRuntime = microtime( true ) -
$startTime;
- # Log the query time and feed it into
the DB trx profiler
-
$this->getTransactionProfiler()->recordQueryCompletion(
- $queryProf, $startTime,
$isWriteQuery, $this->affectedRows() );
+ $ret = $this->doProfiledQuery(
+ $sql, $commentedSql, $isWrite,
$fname, $queryId );
}
} else {
wfDebug( "Failed\n" );
@@ -913,16 +904,45 @@
$res = $this->resultObject( $ret );
- // Destroy profile sections in the opposite order to their
creation
- ScopedCallback::consume( $queryProfSection );
- ScopedCallback::consume( $totalProfSection );
+ return $res;
+ }
- if ( $isWriteQuery && $this->mTrxLevel ) {
- $this->mTrxWriteDuration += $queryRuntime;
- $this->mTrxWriteCallers[] = $fname;
+ private function doProfiledQuery( $sql, $commentedSql, $isWrite,
$fname, $queryId ) {
+ # generalizeSQL() will probably cut down the query to reasonable
+ # logging size most of the time. The substr is really just a
sanity check.
+ if ( !is_null( $this->getLBInfo( 'master' ) ) ) {
+ $queryProf = 'query-m: ' . substr(
DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ } else {
+ $queryProf = 'query: ' . substr(
DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ }
+ # Include query transaction state
+ $queryProf .= $this->mTrxShortId ? "
[TRX#{$this->mTrxShortId}]" : "";
+
+ $profiler = Profiler::instance();
+ if ( !( $profiler instanceof ProfilerStub ) ) {
+ $queryProfSection = $profiler->scopedProfileIn(
$queryProf );
}
- return $res;
+ $startTime = microtime( true );
+ $ret = $this->doQuery( $commentedSql );
+ $queryRuntime = microtime( true ) - $startTime;
+
+ unset( $queryProfSection ); // profile out (if set)
+
+ if ( $ret !== false ) {
+ $this->lastPing = $startTime;
+ if ( $isWrite && $this->mTrxLevel ) {
+ $this->mTrxWriteDuration += $queryRuntime;
+ $this->mTrxWriteCallers[] = $fname;
+ }
+ }
+
+ $this->getTransactionProfiler()->recordQueryCompletion(
+ $queryProf, $startTime, $isWrite, $this->affectedRows()
+ );
+ MWDebug::queryTime( $queryId );
+
+ return $ret;
}
private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
@@ -2931,6 +2951,9 @@
}
public function ping() {
+ if ( $this->isOpen() && ( microtime( true ) - $this->lastPing )
< self::PING_TTL ) {
+ return true;
+ }
try {
// This will reconnect if possible, or error out if not
$this->query( "SELECT 1 AS ping", __METHOD__ );
@@ -2944,8 +2967,18 @@
* @return bool
*/
protected function reconnect() {
- # Stub. Not essential to override.
- return true;
+ $this->closeConnection();
+ $this->mOpened = false;
+ $this->mConn = false;
+ try {
+ $this->open( $this->mServer, $this->mUser,
$this->mPassword, $this->mDBname );
+ $this->lastPing = microtime( true );
+ $ok = true;
+ } catch ( DBConnectionError $e ) {
+ $ok = false;
+ }
+
+ return $ok;
}
public function getSessionLagStatus() {
diff --git a/includes/db/loadbalancer/LoadBalancer.php
b/includes/db/loadbalancer/LoadBalancer.php
index 93ec037..e5e79d6 100644
--- a/includes/db/loadbalancer/LoadBalancer.php
+++ b/includes/db/loadbalancer/LoadBalancer.php
@@ -1108,6 +1108,14 @@
wfMessage(
'transaction-duration-limit-exceeded', $time, $limit )->text()
);
}
+ // If a connection sits idle while slow queries execute
on another, that connection
+ // may end up dropped before the commit round is
reached. Ping servers to detect this.
+ if ( $conn->writesOrCallbacksPending() &&
!$conn->ping() ) {
+ throw new DBTransactionError(
+ $conn,
+ "A connection to server
{$conn->getServer()} was lost before commit."
+ );
+ }
} );
}
diff --git a/includes/jobqueue/JobRunner.php b/includes/jobqueue/JobRunner.php
index 4b906a7..ad0abf2 100644
--- a/includes/jobqueue/JobRunner.php
+++ b/includes/jobqueue/JobRunner.php
@@ -528,17 +528,6 @@
$lb->waitForAll( $pos );
}
- $fname = __METHOD__;
- // Re-ping all masters with transactions. This throws DBError
if some
- // connection died while waiting on locks/slaves, triggering a
rollback.
- wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use (
$fname ) {
- $lb->forEachOpenConnection( function( IDatabase $conn )
use ( $fname ) {
- if ( $conn->writesOrCallbacksPending() ) {
- $conn->ping();
- }
- } );
- } );
-
// Actually commit the DB master changes
wfGetLBFactory()->commitMasterChanges( __METHOD__ );
diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php
b/tests/phpunit/includes/db/DatabaseSqliteTest.php
index 80fb826..f256235 100644
--- a/tests/phpunit/includes/db/DatabaseSqliteTest.php
+++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php
@@ -1,7 +1,7 @@
<?php
class DatabaseSqliteMock extends DatabaseSqlite {
- private $lastQuery;
+ private $mLastQuery;
public static function newInstance( array $p = [] ) {
$p['dbFilePath'] = ':memory:';
@@ -11,7 +11,7 @@
}
function query( $sql, $fname = '', $tempIgnore = false ) {
- $this->lastQuery = $sql;
+ $this->mLastQuery = $sql;
return true;
}
--
To view, visit https://gerrit.wikimedia.org/r/305764
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic90ce2be4187244a0e8d44854c39d4b78be8e642
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits