Improve LoadBalancer::doWait() performance via APC
authorAaron Schulz <aschulz@wikimedia.org>
Mon, 9 Nov 2015 20:44:06 +0000 (12:44 -0800)
committerTim Starling <tstarling@wikimedia.org>
Tue, 10 Nov 2015 05:07:39 +0000 (05:07 +0000)
* This avoids bothering with MASTER_POS_WAIT() if an equal/higher
  position was already successfully waited on by another process.
* Add DBMasterPos toString() and hasReached() methods, which
  MySQLMasterPos already implemented and used.
* Moved more wfDebug() statements to the 'replication' log.

Change-Id: I423b5fe2da8d97889a6d204a635e351342de7649

includes/db/ChronologyProtector.php
includes/db/DatabaseMysqlBase.php
includes/db/DatabaseUtility.php
includes/db/loadbalancer/LoadBalancer.php
tests/phpunit/includes/db/DatabaseMysqlBaseTest.php

index 0c7b612..6840d17 100644 (file)
@@ -60,7 +60,7 @@ class ChronologyProtector {
                if ( !empty( $this->startupPositions[$masterName] ) ) {
                        $info = $lb->parentInfo();
                        $pos = $this->startupPositions[$masterName];
-                       wfDebug( __METHOD__ . ": LB " . $info['id'] . " waiting for master pos $pos\n" );
+                       wfDebug( __METHOD__ . ": LB '" . $info['id'] . "' waiting for master pos $pos\n" );
                        $lb->waitFor( $pos );
                }
        }
index 907cdbf..4f93419 100644 (file)
@@ -1354,6 +1354,21 @@ class MySQLMasterPos implements DBMasterPos {
                $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}";
@@ -1370,15 +1385,4 @@ class MySQLMasterPos implements DBMasterPos {
 
                return false;
        }
-
-       function hasReached( MySQLMasterPos $pos ) {
-               $thisPos = $this->getCoordinates();
-               $thatPos = $pos->getCoordinates();
-
-               return ( $thisPos && $thatPos && $thisPos >= $thatPos );
-       }
-
-       function asOfTime() {
-               return $this->asOfTime;
-       }
 }
index 969ed5e..dea7d94 100644 (file)
@@ -321,6 +321,20 @@ class LikeMatch {
 interface DBMasterPos {
        /**
         * @return float UNIX timestamp
+        * @since 1.25
         */
        public function asOfTime();
+
+       /**
+        * @param DBMasterPos $pos
+        * @return bool Whether this position is at or higher than $pos
+        * @since 1.27
+        */
+       public function hasReached( DBMasterPos $pos );
+
+       /**
+        * @return string
+        * @since 1.27
+        */
+       public function __toString();
 }
index a5a5c37..65ee9c7 100644 (file)
@@ -47,6 +47,8 @@ class LoadBalancer {
        private $mLoadMonitorClass;
        /** @var LoadMonitor */
        private $mLoadMonitor;
+       /** @var BagOStuff */
+       private $srvCache;
 
        /** @var bool|DatabaseBase Database connection that caused a problem */
        private $mErrorConnection;
@@ -123,6 +125,8 @@ class LoadBalancer {
                                }
                        }
                }
+
+               $this->srvCache = ObjectCache::getLocalServerInstance();
        }
 
        /**
@@ -445,17 +449,27 @@ class LoadBalancer {
        protected function doWait( $index, $open = false, $timeout = null ) {
                $close = false; // close the connection afterwards
 
-               # Find a connection to wait on, creating one if needed and allowed
+               // Check if we already know that the DB has reached this point
+               $server = $this->getServerName( $index );
+               $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
+               /** @var DBMasterPos $knownReachedPos */
+               $knownReachedPos = $this->srvCache->get( $key );
+               if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
+                       wfDebugLog( 'replication', __METHOD__ . ": Slave $server known to be caught up.\n" );
+                       return true;
+               }
+
+               // Find a connection to wait on, creating one if needed and allowed
                $conn = $this->getAnyOpenConnection( $index );
                if ( !$conn ) {
                        if ( !$open ) {
-                               wfDebug( __METHOD__ . ": no connection open\n" );
+                               wfDebugLog( 'replication', __METHOD__ . ": no connection open for $server\n" );
 
                                return false;
                        } else {
                                $conn = $this->openConnection( $index, '' );
                                if ( !$conn ) {
-                                       wfDebug( __METHOD__ . ": failed to open connection\n" );
+                                       wfDebugLog( 'replication', __METHOD__ . ": failed to open connection to $server\n" );
 
                                        return false;
                                }
@@ -465,20 +479,21 @@ class LoadBalancer {
                        }
                }
 
-               wfDebug( __METHOD__ . ": Waiting for slave #$index to catch up...\n" );
+               wfDebugLog( 'replication', __METHOD__ . ": Waiting for slave $server to catch up...\n" );
                $timeout = $timeout ?: $this->mWaitTimeout;
                $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
 
                if ( $result == -1 || is_null( $result ) ) {
-                       # Timed out waiting for slave, use master instead
-                       $server = $server = $this->getServerName( $index );
+                       // Timed out waiting for slave, use master instead
                        $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
-                       wfDebug( "$msg\n" );
+                       wfDebugLog( 'replication', "$msg\n" );
                        wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
                        $ok = false;
                } else {
-                       wfDebug( __METHOD__ . ": Done\n" );
+                       wfDebugLog( 'replication', __METHOD__ . ": Done\n" );
                        $ok = true;
+                       // Remember that the DB reached this point
+                       $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
                }
 
                if ( $close ) {
index 8c09471..31e4f5b 100644 (file)
@@ -247,4 +247,13 @@ class DatabaseMysqlBaseTest extends MediaWikiTestCase {
                );
        }
 
+       function testMasterPos() {
+               $pos1 = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
+               $pos2 = new MySQLMasterPos( 'db1034-bin.000976', '843431248' );
+
+               $this->assertTrue( $pos1->hasReached( $pos1 ) );
+               $this->assertTrue( $pos2->hasReached( $pos2 ) );
+               $this->assertTrue( $pos2->hasReached( $pos1 ) );
+               $this->assertFalse( $pos1->hasReached( $pos2 ) );
+       }
 }