Skip user namespace prefix for rights log entry
[lhc/web/wiklou.git] / includes / db / DatabaseMysqlBase.php
index 1f87055..907cdbf 100644 (file)
  * @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
         */
@@ -138,7 +151,7 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
 
                if ( $set ) {
                        // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
-                       $success = $this->doQuery( 'SET ' . implode( ', ', $set ), __METHOD__ );
+                       $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
                        if ( !$success ) {
                                wfLogDBError(
                                        'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
@@ -554,6 +567,12 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                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".
         *
@@ -601,24 +620,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         */
        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.
         *
@@ -627,32 +628,73 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @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;
        }
 
        /**
@@ -673,25 +715,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                # 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 );
@@ -715,13 +738,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @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 );
 
@@ -742,10 +758,6 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
         * @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 );
 
@@ -940,7 +952,8 @@ abstract class DatabaseMysqlBase extends DatabaseBase {
                                $value = $this->mDefaultBigSelects;
                        }
                } elseif ( $this->mDefaultBigSelects === null ) {
-                       $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
+                       $this->mDefaultBigSelects =
+                               (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
                }
                $encValue = $value ? '1' : '0';
                $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
@@ -1256,7 +1269,7 @@ class MySQLField implements Field {
         * @return string
         */
        function tableName() {
-               return $this->tableName;
+               return $this->tablename;
        }
 
        /**