Merge "rdbms: avoid "SHOW MASTER/SLAVE STATUS" queries in the GTID case"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 14 Feb 2018 23:37:34 +0000 (23:37 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 14 Feb 2018 23:37:34 +0000 (23:37 +0000)
1  2 
includes/libs/rdbms/database/DatabaseMysqlBase.php
tests/phpunit/includes/db/LBFactoryTest.php

@@@ -24,7 -24,7 +24,7 @@@ namespace Wikimedia\Rdbms
  
  use DateTime;
  use DateTimeZone;
 -use MediaWiki;
 +use Wikimedia;
  use InvalidArgumentException;
  use Exception;
  use stdClass;
@@@ -159,10 -159,10 +159,10 @@@ abstract class DatabaseMysqlBase extend
                        $this->reportConnectionError( $error );
                }
  
 -              if ( $dbName != '' ) {
 -                      MediaWiki\suppressWarnings();
 +              if ( strlen( $dbName ) ) {
 +                      Wikimedia\suppressWarnings();
                        $success = $this->selectDB( $dbName );
 -                      MediaWiki\restoreWarnings();
 +                      Wikimedia\restoreWarnings();
                        if ( !$success ) {
                                $this->queryLogger->error(
                                        "Error selecting database {db_name} on server {db_server}",
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
 -              MediaWiki\suppressWarnings();
 +              Wikimedia\suppressWarnings();
                $ok = $this->mysqlFreeResult( $res );
 -              MediaWiki\restoreWarnings();
 +              Wikimedia\restoreWarnings();
                if ( !$ok ) {
                        throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
                }
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
 -              MediaWiki\suppressWarnings();
 +              Wikimedia\suppressWarnings();
                $row = $this->mysqlFetchObject( $res );
 -              MediaWiki\restoreWarnings();
 +              Wikimedia\restoreWarnings();
  
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_object does not reset the last errno.
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
 -              MediaWiki\suppressWarnings();
 +              Wikimedia\suppressWarnings();
                $row = $this->mysqlFetchArray( $res );
 -              MediaWiki\restoreWarnings();
 +              Wikimedia\restoreWarnings();
  
                $errno = $this->lastErrno();
                // Unfortunately, mysql_fetch_array does not reset the last errno.
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
 -              MediaWiki\suppressWarnings();
 +              Wikimedia\suppressWarnings();
                $n = $this->mysqlNumRows( $res );
 -              MediaWiki\restoreWarnings();
 +              Wikimedia\restoreWarnings();
  
                // Unfortunately, mysql_num_rows does not reset the last errno.
                // We are not checking for any errors here, since
        public function lastError() {
                if ( $this->mConn ) {
                        # Even if it's non-zero, it can still be invalid
 -                      MediaWiki\suppressWarnings();
 +                      Wikimedia\suppressWarnings();
                        $error = $this->mysqlError( $this->mConn );
                        if ( !$error ) {
                                $error = $this->mysqlError();
                        }
 -                      MediaWiki\restoreWarnings();
 +                      Wikimedia\restoreWarnings();
                } else {
                        $error = $this->mysqlError();
                }
                        return 0; // already reached this point for sure
                }
  
-               $useGTID = ( $this->useGTIDs && $pos->gtids );
                // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
-               if ( $useGTID ) {
+               if ( $pos->gtids ) {
                        // Wait on the GTID set (MariaDB only)
                        $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
                        $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
                } else {
                        // Wait on the binlog coordinates
-                       $encFile = $this->addQuotes( $pos->file );
-                       $encPos = intval( $pos->pos );
+                       $encFile = $this->addQuotes( $pos->getLogFile() );
+                       $encPos = intval( $pos->pos[1] );
                        $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
                }
  
                // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
                $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
                if ( $status === null ) {
-                       if ( !$useGTID ) {
+                       if ( !$pos->gtids ) {
                                // T126436: jobs programmed to wait on master positions might be referencing
                                // binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null.
                                // Try to detect this case and treat the replica DB as having reached the given
         * @return MySQLMasterPos|bool
         */
        public function getReplicaPos() {
-               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
-               $row = $this->fetchObject( $res );
+               $now = microtime( true );
  
-               if ( $row ) {
-                       $pos = $row->Exec_Master_Log_Pos;
-                       // Also fetch the last-applied GTID set (MariaDB)
-                       if ( $this->useGTIDs ) {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
-                               $gtidRow = $this->fetchObject( $res );
-                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
-                       } else {
-                               $gtidSet = '';
+               if ( $this->useGTIDs ) {
+                       $res = $this->query( "SELECT @@global.gtid_slave_pos AS Value", __METHOD__ );
+                       $gtidRow = $this->fetchObject( $res );
+                       if ( $gtidRow && strlen( $gtidRow->Value ) ) {
+                               return new MySQLMasterPos( $gtidRow->Value, $now );
                        }
+               }
  
-                       return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
-               } else {
-                       return false;
+               $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+               $row = $this->fetchObject( $res );
+               if ( $row && strlen( $row->Relay_Master_Log_File ) ) {
+                       return new MySQLMasterPos(
+                               "{$row->Relay_Master_Log_File}/{$row->Exec_Master_Log_Pos}",
+                               $now
+                       );
                }
+               return false;
        }
  
        /**
         * @return MySQLMasterPos|bool
         */
        public function getMasterPos() {
-               $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
-               $row = $this->fetchObject( $res );
+               $now = microtime( true );
  
-               if ( $row ) {
-                       // Also fetch the last-written GTID set (MariaDB)
-                       if ( $this->useGTIDs ) {
-                               $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
-                               $gtidRow = $this->fetchObject( $res );
-                               $gtidSet = $gtidRow ? $gtidRow->Value : '';
-                       } else {
-                               $gtidSet = '';
+               if ( $this->useGTIDs ) {
+                       $res = $this->query( "SELECT @@global.gtid_binlog_pos AS Value", __METHOD__ );
+                       $gtidRow = $this->fetchObject( $res );
+                       if ( $gtidRow && strlen( $gtidRow->Value ) ) {
+                               return new MySQLMasterPos( $gtidRow->Value, $now );
                        }
+               }
  
-                       return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
-               } else {
-                       return false;
+               $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
+               $row = $this->fetchObject( $res );
+               if ( $row && strlen( $row->File ) ) {
+                       return new MySQLMasterPos( "{$row->File}/{$row->Position}", $now );
                }
+               return false;
        }
  
        public function serverIsReadOnly() {
@@@ -194,12 -194,12 +194,12 @@@ class LBFactoryTest extends MediaWikiTe
         * @covers \Wikimedia\Rdbms\ChronologyProtector
         */
        public function testChronologyProtector() {
-               // (a) First HTTP request
-               $m1Pos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
-               $m2Pos = new MySQLMasterPos( 'db1064-bin.002400', '794074907' );
                $now = microtime( true );
  
+               // (a) First HTTP request
+               $m1Pos = new MySQLMasterPos( 'db1034-bin.000976/843431247', $now );
+               $m2Pos = new MySQLMasterPos( 'db1064-bin.002400/794074907', $now );
                // Master DB 1
                $mockDB1 = $this->getMockBuilder( DatabaseMysqli::class )
                        ->disableOriginalConstructor()
                $db = $lb->getConnection( DB_MASTER, [], '' );
  
                $this->assertEquals(
 -                      $wgDBname,
 +                      '',
                        $db->getDomainId(),
 -                      'Main domain ID handle used; same DB name'
 +                      'Null domain ID handle used'
                );
                $this->assertEquals(
 -                      $wgDBname,
 +                      '',
                        $db->getDBname(),
 -                      'Main domain ID handle used; same DB name'
 +                      'Null domain ID handle used'
                );
                $this->assertEquals(
                        '',
                $dbname = 'unittest-domain'; // explodes if DB is selected
                $factory = $this->newLBFactoryMulti(
                        [ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
 -                      [ 'dbFilePath' => $dbPath ]
 +                      [
 +                              'dbFilePath' => $dbPath,
 +                              'dbName' => 'do_not_select_me' // explodes if DB is selected
 +                      ]
                );
                $lb = $factory->getMainLB();
                /** @var Database $db */
                $db = $lb->getConnection( DB_MASTER, [], '' );
  
 -              $this->assertEquals(
 -                      $wgDBname,
 -                      $db->getDomainID()
 -              );
 +              $this->assertEquals( '', $db->getDomainID(), "Null domain used" );
  
                $this->assertEquals(
                        $this->quoteTable( $db, 'page' ),
                        $this->assertInstanceOf( \Wikimedia\Rdbms\DBConnectionError::class, $e );
                        $this->assertFalse( $db->isOpen() );
                } else {
 -                      \MediaWiki\suppressWarnings();
 +                      \Wikimedia\suppressWarnings();
                        $this->assertFalse( $db->selectDB( 'garbage-db' ) );
 -                      \MediaWiki\restoreWarnings();
 +                      \Wikimedia\restoreWarnings();
                }
  
                $lb->reuseConnection( $db ); // don't care