* @since 1.22
* @see Database
*/
-abstract class DatabaseMysqlBase extends DatabaseBase {
+abstract class DatabaseMysqlBase extends Database {
/** @var MysqlMasterPos */
protected $lastKnownSlavePos;
-
- /** @var null|int */
- protected $mFakeSlaveLag = null;
-
- protected $mFakeMaster = false;
+ /** @var string Method to detect slave lag */
+ protected $lagDetectionMethod;
/** @var string|null */
private $serverVersion = null;
+ /**
+ * Additional $params include:
+ * - lagDetectionMethod : set to one of (Seconds_Behind_Master,pt-heartbeat).
+ * pt-heartbeat assumes the table is at heartbeat.heartbeat
+ * and uses UTC timestamps in the heartbeat.ts column.
+ * (https://www.percona.com/doc/percona-toolkit/2.2/pt-heartbeat.html)
+ * @param array $params
+ */
+ function __construct( array $params ) {
+ parent::__construct( $params );
+
+ $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
+ ? $params['lagDetectionMethod']
+ : 'Seconds_Behind_Master';
+ }
+
/**
* @return string
*/
return $sQuoted;
}
+ /**
+ * @param string $s
+ * @return mixed
+ */
+ abstract protected function mysqlRealEscapeString( $s );
+
/**
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
*
*/
abstract protected function mysqlPing();
- /**
- * Set lag time in seconds for a fake slave
- *
- * @param int $lag
- */
- public function setFakeSlaveLag( $lag ) {
- $this->mFakeSlaveLag = $lag;
- }
-
- /**
- * Make this connection a fake master
- *
- * @param bool $enabled
- */
- public function setFakeMaster( $enabled = true ) {
- $this->mFakeMaster = $enabled;
- }
-
/**
* Returns slave lag.
*
* @return int
*/
function getLag() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
-
- return $this->mFakeSlaveLag;
+ if ( $this->lagDetectionMethod === 'pt-heartbeat' ) {
+ return $this->getLagFromPtHeartbeat();
+ } else {
+ return $this->getLagFromSlaveStatus();
}
-
- return $this->getLagFromSlaveStatus();
}
/**
* @return bool|int
*/
- function getLagFromSlaveStatus() {
+ protected function getLagFromSlaveStatus() {
$res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
- if ( !$res ) {
- return false;
+ $row = $res ? $res->fetchObject() : false;
+ if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
+ return intval( $row->Seconds_Behind_Master );
}
- $row = $res->fetchObject();
- if ( !$row ) {
- return false;
+
+ return false;
+ }
+
+ /**
+ * @return bool|float
+ */
+ protected function getLagFromPtHeartbeat() {
+ $key = wfMemcKey( 'mysql', 'master-server-id', $this->getServer() );
+ $masterId = intval( $this->srvCache->get( $key ) );
+ if ( !$masterId ) {
+ $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+ $row = $res ? $res->fetchObject() : false;
+ if ( $row && strval( $row->Master_Server_Id ) !== '' ) {
+ $masterId = intval( $row->Master_Server_Id );
+ $this->srvCache->set( $key, $masterId, 30 );
+ }
}
- if ( strval( $row->Seconds_Behind_Master ) === '' ) {
+
+ if ( !$masterId ) {
return false;
- } else {
- return intval( $row->Seconds_Behind_Master );
}
+
+ $res = $this->query(
+ "SELECT TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6)) AS Lag " .
+ "FROM heartbeat.heartbeat WHERE server_id = $masterId"
+ );
+ $row = $res ? $res->fetchObject() : false;
+ if ( $row ) {
+ return max( floatval( $row->Lag ) / 1e6, 0.0 );
+ }
+
+ return false;
+ }
+
+ public function getApproximateLagStatus() {
+ if ( $this->lagDetectionMethod === 'pt-heartbeat' ) {
+ // Disable caching since this is fast enough and we don't wan't
+ // to be *too* pessimistic by having both the cache TTL and the
+ // pt-heartbeat interval count as lag in getSessionLagStatus()
+ return parent::getApproximateLagStatus();
+ }
+
+ $key = wfGlobalCacheKey( 'mysql-lag', $this->getServer() );
+ $approxLag = $this->srvCache->get( $key );
+ if ( !$approxLag ) {
+ $approxLag = parent::getApproximateLagStatus();
+ $this->srvCache->set( $key, $approxLag, 1 );
+ }
+
+ return $approxLag;
}
/**
# Commit any open transactions
$this->commit( __METHOD__, 'flush' );
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
-
- if ( $wait > $timeout * 1e6 ) {
- wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
-
- return -1;
- } elseif ( $wait > 0 ) {
- wfDebug( "Fake slave waiting $wait us\n" );
- usleep( $wait );
-
- return 1;
- } else {
- wfDebug( "Fake slave up to date ($wait us)\n" );
-
- return 0;
- }
- }
-
# Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
$encFile = $this->addQuotes( $pos->file );
$encPos = intval( $pos->pos );
* @return MySQLMasterPos|bool
*/
function getSlavePos() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
- wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
-
- return $pos;
- }
-
$res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
$row = $this->fetchObject( $res );
* @return MySQLMasterPos|bool
*/
function getMasterPos() {
- if ( $this->mFakeMaster ) {
- return new MySQLMasterPos( 'fake', microtime( true ) );
- }
-
$res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
$row = $this->fetchObject( $res );
* @return string
*/
function tableName() {
- return $this->tableName;
+ return $this->tablename;
}
/**
$this->asOfTime = microtime( true );
}
+ function asOfTime() {
+ return $this->asOfTime;
+ }
+
+ function hasReached( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ $thisPos = $this->getCoordinates();
+ $thatPos = $pos->getCoordinates();
+
+ return ( $thisPos && $thatPos && $thisPos >= $thatPos );
+ }
+
function __toString() {
// e.g db1034-bin.000976/843431247
return "{$this->file}/{$this->pos}";
return false;
}
-
- function hasReached( MySQLMasterPos $pos ) {
- $thisPos = $this->getCoordinates();
- $thatPos = $pos->getCoordinates();
-
- return ( $thisPos && $thatPos && $thisPos >= $thatPos );
- }
-
- function asOfTime() {
- return $this->asOfTime;
- }
}