From: Aaron Schulz Date: Wed, 14 Sep 2016 07:10:11 +0000 (-0700) Subject: Move LoadMonitor classes to libs/rdbms/loadmonitor X-Git-Tag: 1.31.0-rc.0~5588^2 X-Git-Url: http://git.cyclocoop.org/data/%24oldEdit?a=commitdiff_plain;h=9c8d885060363a2c5df3ab8f2e68a5a5ef5d7894;p=lhc%2Fweb%2Fwiklou.git Move LoadMonitor classes to libs/rdbms/loadmonitor Change-Id: Ib18108f24ff8b9d05dc324bad132f597b3e2ddef --- diff --git a/autoload.php b/autoload.php index 6654f5bf74..a71d9432e7 100644 --- a/autoload.php +++ b/autoload.php @@ -729,9 +729,9 @@ $wgAutoloadLocalClasses = [ 'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php', 'LoadBalancer' => __DIR__ . '/includes/db/loadbalancer/LoadBalancer.php', 'LoadBalancerSingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php', - 'LoadMonitor' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php', - 'LoadMonitorMySQL' => __DIR__ . '/includes/db/loadbalancer/LoadMonitorMySQL.php', - 'LoadMonitorNull' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php', + 'LoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitor.php', + 'LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php', + 'LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php', 'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php', 'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php', 'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFile.php', diff --git a/includes/db/loadbalancer/LBFactory.php b/includes/db/loadbalancer/LBFactory.php index 22a6597c2d..5115fbecae 100644 --- a/includes/db/loadbalancer/LBFactory.php +++ b/includes/db/loadbalancer/LBFactory.php @@ -41,6 +41,8 @@ abstract class LBFactory implements DestructibleService { protected $replLogger; /** @var BagOStuff */ protected $srvCache; + /** @var BagOStuff */ + protected $memCache; /** @var WANObjectCache */ protected $wanCache; @@ -69,12 +71,18 @@ abstract class LBFactory implements DestructibleService { $this->chronProt = $this->newChronologyProtector(); $this->trxProfiler = Profiler::instance()->getTransactionProfiler(); // Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804) - $cache = ObjectCache::getLocalServerInstance(); - if ( $cache->getQoS( $cache::ATTR_EMULATION ) > $cache::QOS_EMULATION_SQL ) { - $this->srvCache = $cache; + $sCache = ObjectCache::getLocalServerInstance(); + if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) { + $this->srvCache = $sCache; } else { $this->srvCache = new EmptyBagOStuff(); } + $cCache = ObjectCache::getLocalClusterInstance(); + if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) { + $this->memCache = $cCache; + } else { + $this->memCache = new EmptyBagOStuff(); + } $wCache = ObjectCache::getMainWANInstance(); if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) { $this->wanCache = $wCache; @@ -655,6 +663,7 @@ abstract class LBFactory implements DestructibleService { 'localDomain' => wfWikiID(), 'readOnlyReason' => $this->readOnlyReason, 'srvCache' => $this->srvCache, + 'memCache' => $this->memCache, 'wanCache' => $this->wanCache, 'trxProfiler' => $this->trxProfiler, 'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ), diff --git a/includes/db/loadbalancer/LoadBalancer.php b/includes/db/loadbalancer/LoadBalancer.php index 687317cb5d..8069cf6fc7 100644 --- a/includes/db/loadbalancer/LoadBalancer.php +++ b/includes/db/loadbalancer/LoadBalancer.php @@ -47,6 +47,8 @@ class LoadBalancer implements ILoadBalancer { private $mLoadMonitor; /** @var BagOStuff */ private $srvCache; + /** @var BagOStuff */ + private $memCache; /** @var WANObjectCache */ private $wanCache; /** @var TransactionProfiler */ @@ -149,6 +151,11 @@ class LoadBalancer implements ILoadBalancer { } else { $this->srvCache = new EmptyBagOStuff(); } + if ( isset( $params['memCache'] ) ) { + $this->memCache = $params['memCache']; + } else { + $this->memCache = new EmptyBagOStuff(); + } if ( isset( $params['wanCache'] ) ) { $this->wanCache = $params['wanCache']; } else { @@ -179,7 +186,7 @@ class LoadBalancer implements ILoadBalancer { private function getLoadMonitor() { if ( !isset( $this->mLoadMonitor ) ) { $class = $this->mLoadMonitorClass; - $this->mLoadMonitor = new $class( $this ); + $this->mLoadMonitor = new $class( $this, $this->srvCache, $this->memCache ); $this->mLoadMonitor->setLogger( $this->replLogger ); } diff --git a/includes/db/loadbalancer/LoadMonitor.php b/includes/db/loadbalancer/LoadMonitor.php deleted file mode 100644 index 247955a8f0..0000000000 --- a/includes/db/loadbalancer/LoadMonitor.php +++ /dev/null @@ -1,83 +0,0 @@ - float|int|bool) - */ - public function getLagTimes( $serverIndexes, $wiki ); - - /** - * Clear any process and persistent cache of lag times - * @since 1.27 - */ - public function clearCaches(); -} - -class LoadMonitorNull implements LoadMonitor { - public function __construct( LoadBalancer $parent ) { - } - - public function setLogger( LoggerInterface $logger ) { - } - - public function scaleLoads( &$loads, $group = false, $wiki = false ) { - } - - public function getLagTimes( $serverIndexes, $wiki ) { - return array_fill_keys( $serverIndexes, 0 ); - } - - public function clearCaches() { - - } -} diff --git a/includes/db/loadbalancer/LoadMonitorMySQL.php b/includes/db/loadbalancer/LoadMonitorMySQL.php deleted file mode 100644 index 7238388944..0000000000 --- a/includes/db/loadbalancer/LoadMonitorMySQL.php +++ /dev/null @@ -1,156 +0,0 @@ -parent = $parent; - $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' ); - $this->mainCache = ObjectCache::getLocalClusterInstance(); - $this->replLogger = new \Psr\Log\NullLogger(); - } - - public function setLogger( LoggerInterface $logger ) { - $this->replLogger = $logger; - } - - public function scaleLoads( &$loads, $group = false, $wiki = false ) { - } - - public function getLagTimes( $serverIndexes, $wiki ) { - if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) { - # Single server only, just return zero without caching - return [ 0 => 0 ]; - } - - $key = $this->getLagTimeCacheKey(); - # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec) - $ttl = mt_rand( 4e6, 5e6 ) / 1e6; - # Keep keys around longer as fallbacks - $staleTTL = 60; - - # (a) Check the local APC cache - $value = $this->srvCache->get( $key ); - if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) { - $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" ); - return $value['lagTimes']; // cache hit - } - $staleValue = $value ?: false; - - # (b) Check the shared cache and backfill APC - $value = $this->mainCache->get( $key ); - if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) { - $this->srvCache->set( $key, $value, $staleTTL ); - $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" ); - - return $value['lagTimes']; // cache hit - } - $staleValue = $value ?: $staleValue; - - # (c) Cache key missing or expired; regenerate and backfill - if ( $this->mainCache->lock( $key, 0, 10 ) ) { - # Let this process alone update the cache value - $cache = $this->mainCache; - /** @noinspection PhpUnusedLocalVariableInspection */ - $unlocker = new ScopedCallback( function () use ( $cache, $key ) { - $cache->unlock( $key ); - } ); - } elseif ( $staleValue ) { - # Could not acquire lock but an old cache exists, so use it - return $staleValue['lagTimes']; - } - - $lagTimes = []; - foreach ( $serverIndexes as $i ) { - if ( $i == $this->parent->getWriterIndex() ) { - $lagTimes[$i] = 0; // master always has no lag - continue; - } - - $conn = $this->parent->getAnyOpenConnection( $i ); - if ( $conn ) { - $close = false; // already open - } else { - $conn = $this->parent->openConnection( $i, $wiki ); - $close = true; // new connection - } - - if ( !$conn ) { - $lagTimes[$i] = false; - $host = $this->parent->getServerName( $i ); - $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" ); - continue; - } - - $lagTimes[$i] = $conn->getLag(); - if ( $lagTimes[$i] === false ) { - $host = $this->parent->getServerName( $i ); - $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" ); - } - - if ( $close ) { - # Close the connection to avoid sleeper connections piling up. - # Note that the caller will pick one of these DBs and reconnect, - # which is slightly inefficient, but this only matters for the lag - # time cache miss cache, which is far less common that cache hits. - $this->parent->closeConnection( $conn ); - } - } - - # Add a timestamp key so we know when it was cached - $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ]; - $this->mainCache->set( $key, $value, $staleTTL ); - $this->srvCache->set( $key, $value, $staleTTL ); - $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" ); - - return $value['lagTimes']; - } - - public function clearCaches() { - $key = $this->getLagTimeCacheKey(); - $this->srvCache->delete( $key ); - $this->mainCache->delete( $key ); - } - - private function getLagTimeCacheKey() { - $writerIndex = $this->parent->getWriterIndex(); - // Lag is per-server, not per-DB, so key on the master DB name - return $this->srvCache->makeGlobalKey( - 'lag-times', $this->parent->getServerName( $writerIndex ) - ); - } -} diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitor.php b/includes/libs/rdbms/loadmonitor/LoadMonitor.php new file mode 100644 index 0000000000..46af068bab --- /dev/null +++ b/includes/libs/rdbms/loadmonitor/LoadMonitor.php @@ -0,0 +1,65 @@ + float|int|bool) + */ + public function getLagTimes( $serverIndexes, $domain ); + + /** + * Clear any process and persistent cache of lag times + * @since 1.27 + */ + public function clearCaches(); +} diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php b/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php new file mode 100644 index 0000000000..83f4462287 --- /dev/null +++ b/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php @@ -0,0 +1,157 @@ +parent = $parent; + $this->srvCache = $sCache; + $this->mainCache = $cCache; + $this->replLogger = new \Psr\Log\NullLogger(); + } + + public function setLogger( LoggerInterface $logger ) { + $this->replLogger = $logger; + } + + public function scaleLoads( &$loads, $group = false, $wiki = false ) { + } + + public function getLagTimes( $serverIndexes, $wiki ) { + if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) { + # Single server only, just return zero without caching + return [ 0 => 0 ]; + } + + $key = $this->getLagTimeCacheKey(); + # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec) + $ttl = mt_rand( 4e6, 5e6 ) / 1e6; + # Keep keys around longer as fallbacks + $staleTTL = 60; + + # (a) Check the local APC cache + $value = $this->srvCache->get( $key ); + if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) { + $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" ); + return $value['lagTimes']; // cache hit + } + $staleValue = $value ?: false; + + # (b) Check the shared cache and backfill APC + $value = $this->mainCache->get( $key ); + if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) { + $this->srvCache->set( $key, $value, $staleTTL ); + $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" ); + + return $value['lagTimes']; // cache hit + } + $staleValue = $value ?: $staleValue; + + # (c) Cache key missing or expired; regenerate and backfill + if ( $this->mainCache->lock( $key, 0, 10 ) ) { + # Let this process alone update the cache value + $cache = $this->mainCache; + /** @noinspection PhpUnusedLocalVariableInspection */ + $unlocker = new ScopedCallback( function () use ( $cache, $key ) { + $cache->unlock( $key ); + } ); + } elseif ( $staleValue ) { + # Could not acquire lock but an old cache exists, so use it + return $staleValue['lagTimes']; + } + + $lagTimes = []; + foreach ( $serverIndexes as $i ) { + if ( $i == $this->parent->getWriterIndex() ) { + $lagTimes[$i] = 0; // master always has no lag + continue; + } + + $conn = $this->parent->getAnyOpenConnection( $i ); + if ( $conn ) { + $close = false; // already open + } else { + $conn = $this->parent->openConnection( $i, $wiki ); + $close = true; // new connection + } + + if ( !$conn ) { + $lagTimes[$i] = false; + $host = $this->parent->getServerName( $i ); + $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" ); + continue; + } + + $lagTimes[$i] = $conn->getLag(); + if ( $lagTimes[$i] === false ) { + $host = $this->parent->getServerName( $i ); + $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" ); + } + + if ( $close ) { + # Close the connection to avoid sleeper connections piling up. + # Note that the caller will pick one of these DBs and reconnect, + # which is slightly inefficient, but this only matters for the lag + # time cache miss cache, which is far less common that cache hits. + $this->parent->closeConnection( $conn ); + } + } + + # Add a timestamp key so we know when it was cached + $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ]; + $this->mainCache->set( $key, $value, $staleTTL ); + $this->srvCache->set( $key, $value, $staleTTL ); + $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" ); + + return $value['lagTimes']; + } + + public function clearCaches() { + $key = $this->getLagTimeCacheKey(); + $this->srvCache->delete( $key ); + $this->mainCache->delete( $key ); + } + + private function getLagTimeCacheKey() { + $writerIndex = $this->parent->getWriterIndex(); + // Lag is per-server, not per-DB, so key on the master DB name + return $this->srvCache->makeGlobalKey( + 'lag-times', + $this->parent->getServerName( $writerIndex ) + ); + } +} diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php b/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php new file mode 100644 index 0000000000..df95b0a4e2 --- /dev/null +++ b/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php @@ -0,0 +1,40 @@ +