From: Aaron Schulz Date: Thu, 15 Sep 2016 05:38:47 +0000 (-0700) Subject: Move LoadMonitorMySQL to LoadMonitor X-Git-Tag: 1.31.0-rc.0~5534^2 X-Git-Url: https://git.cyclocoop.org/%242?a=commitdiff_plain;h=21a9bb06b418149d02c5c01abc33fc0ad5f3dce4;p=lhc%2Fweb%2Fwiklou.git Move LoadMonitorMySQL to LoadMonitor * The LoadMonitor interface is now ILoadMonitor. * Nothing in this code was MySQL specific. Now any DB type can benefit from a LoadMonitor. Change-Id: I272a72cd55a70f99a866d518d44cb3bcaca91b9e --- diff --git a/autoload.php b/autoload.php index 9fd83ebeb8..1cb71d8267 100644 --- a/autoload.php +++ b/autoload.php @@ -583,6 +583,7 @@ $wgAutoloadLocalClasses = [ 'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php', 'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php', 'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php', + 'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php', 'IP' => __DIR__ . '/includes/utils/IP.php', 'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php', 'IPTC' => __DIR__ . '/includes/media/IPTC.php', diff --git a/includes/libs/rdbms/loadbalancer/LoadBalancer.php b/includes/libs/rdbms/loadbalancer/LoadBalancer.php index cea7523ba5..36279bcc59 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -45,7 +45,7 @@ class LoadBalancer implements ILoadBalancer { /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */ private $tableAliases = []; - /** @var LoadMonitor */ + /** @var ILoadMonitor */ private $mLoadMonitor; /** @var BagOStuff */ private $srvCache; @@ -190,7 +190,7 @@ class LoadBalancer implements ILoadBalancer { /** * Get a LoadMonitor instance * - * @return LoadMonitor + * @return ILoadMonitor */ private function getLoadMonitor() { if ( !isset( $this->mLoadMonitor ) ) { diff --git a/includes/libs/rdbms/loadmonitor/ILoadMonitor.php b/includes/libs/rdbms/loadmonitor/ILoadMonitor.php new file mode 100644 index 0000000000..e355c0363c --- /dev/null +++ b/includes/libs/rdbms/loadmonitor/ILoadMonitor.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/LoadMonitor.php b/includes/libs/rdbms/loadmonitor/LoadMonitor.php index 460746baa5..1da8f4e51f 100644 --- a/includes/libs/rdbms/loadmonitor/LoadMonitor.php +++ b/includes/libs/rdbms/loadmonitor/LoadMonitor.php @@ -1,7 +1,5 @@ float|int|bool) - */ - public function getLagTimes( $serverIndexes, $domain ); - - /** - * Clear any process and persistent cache of lag times - * @since 1.27 - */ - public function clearCaches(); +class LoadMonitor implements ILoadMonitor { + /** @var ILoadBalancer */ + protected $parent; + /** @var BagOStuff */ + protected $srvCache; + /** @var BagOStuff */ + protected $mainCache; + /** @var LoggerInterface */ + protected $replLogger; + + public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) { + $this->parent = $lb; + $this->srvCache = $srvCache; + $this->mainCache = $cache; + $this->replLogger = new \Psr\Log\NullLogger(); + } + + public function setLogger( LoggerInterface $logger ) { + $this->replLogger = $logger; + } + + public function scaleLoads( &$loads, $group = false, $domain = false ) { + } + + public function getLagTimes( $serverIndexes, $domain ) { + 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, $domain ); + $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/LoadMonitorMySQL.php b/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php index 02babfe677..7286417096 100644 --- a/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php +++ b/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php @@ -19,139 +19,15 @@ * @ingroup Database */ -use Psr\Log\LoggerInterface; - /** * Basic MySQL load monitor with no external dependencies * Uses memcached to cache the replication lag for a short time * * @ingroup Database */ -class LoadMonitorMySQL implements LoadMonitor { - /** @var ILoadBalancer */ - protected $parent; - /** @var BagOStuff */ - protected $srvCache; - /** @var BagOStuff */ - protected $mainCache; - /** @var LoggerInterface */ - protected $replLogger; - - public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) { - $this->parent = $lb; - $this->srvCache = $srvCache; - $this->mainCache = $cache; - $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 ) - ); +class LoadMonitorMySQL extends LoadMonitor { + public function scaleLoads( &$loads, $group = false, $domain = false ) { + // @TODO: maybe use Threads_running/Threads_created ratio to guess load + // and Queries/Uptime to guess if a server is warming up the buffer pool } } diff --git a/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php b/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php index 1a40b8f8ea..8062001eae 100644 --- a/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php +++ b/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php @@ -20,7 +20,7 @@ */ use Psr\Log\LoggerInterface; -class LoadMonitorNull implements LoadMonitor { +class LoadMonitorNull implements ILoadMonitor { public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ) { }