jenkins-bot has submitted this change and it was merged. Change subject: Move LoadMonitorMySQL to LoadMonitor ......................................................................
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 --- M autoload.php M includes/libs/rdbms/loadbalancer/LoadBalancer.php A includes/libs/rdbms/loadmonitor/ILoadMonitor.php M includes/libs/rdbms/loadmonitor/LoadMonitor.php M includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php M includes/libs/rdbms/loadmonitor/LoadMonitorNull.php 6 files changed, 201 insertions(+), 167 deletions(-) Approvals: Legoktm: Looks good to me, approved jenkins-bot: Verified diff --git a/autoload.php b/autoload.php index 9fd83eb..1cb71d8 100644 --- a/autoload.php +++ b/autoload.php @@ -583,6 +583,7 @@ '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 cea7523..36279bc 100644 --- a/includes/libs/rdbms/loadbalancer/LoadBalancer.php +++ b/includes/libs/rdbms/loadbalancer/LoadBalancer.php @@ -45,7 +45,7 @@ /** @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 @@ /** * 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 0000000..e355c03 --- /dev/null +++ b/includes/libs/rdbms/loadmonitor/ILoadMonitor.php @@ -0,0 +1,65 @@ +<?php +/** + * Database load monitoring interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Database + */ +use Psr\Log\LoggerAwareInterface; + +/** + * An interface for database load monitoring + * + * @ingroup Database + */ +interface ILoadMonitor extends LoggerAwareInterface { + /** + * Construct a new LoadMonitor with a given LoadBalancer parent + * + * @param ILoadBalancer $lb LoadBalancer this instance serves + * @param BagOStuff $sCache Local server memory cache + * @param BagOStuff $cCache Local cluster memory cache + */ + public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ); + + /** + * Perform pre-connection load ratio adjustment. + * @param int[] &$loads + * @param string|bool $group The selected query group. Default: false + * @param string|bool $domain Default: false + */ + public function scaleLoads( &$loads, $group = false, $domain = false ); + + /** + * Get an estimate of replication lag (in seconds) for each server + * + * Values may be "false" if replication is too broken to estimate + * + * @param integer[] $serverIndexes + * @param string $domain + * + * @return array Map of (server index => 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 460746b..1da8f4e 100644 --- a/includes/libs/rdbms/loadmonitor/LoadMonitor.php +++ b/includes/libs/rdbms/loadmonitor/LoadMonitor.php @@ -1,7 +1,5 @@ <?php /** - * Database load monitoring. - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -20,46 +18,140 @@ * @file * @ingroup Database */ -use Psr\Log\LoggerAwareInterface; + +use Psr\Log\LoggerInterface; /** - * An interface for database load monitoring + * Basic DB load monitor with no external dependencies + * Uses memcached to cache the replication lag for a short time * * @ingroup Database */ -interface LoadMonitor extends LoggerAwareInterface { - /** - * Construct a new LoadMonitor with a given LoadBalancer parent - * - * @param ILoadBalancer $lb LoadBalancer this instance serves - * @param BagOStuff $sCache Local server memory cache - * @param BagOStuff $cCache Local cluster memory cache - */ - public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ); +class LoadMonitor implements ILoadMonitor { + /** @var ILoadBalancer */ + protected $parent; + /** @var BagOStuff */ + protected $srvCache; + /** @var BagOStuff */ + protected $mainCache; + /** @var LoggerInterface */ + protected $replLogger; - /** - * Perform pre-connection load ratio adjustment. - * @param int[] &$loads - * @param string|bool $group The selected query group. Default: false - * @param string|bool $domain Default: false - */ - public function scaleLoads( &$loads, $group = false, $domain = false ); + public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) { + $this->parent = $lb; + $this->srvCache = $srvCache; + $this->mainCache = $cache; + $this->replLogger = new \Psr\Log\NullLogger(); + } - /** - * Get an estimate of replication lag (in seconds) for each server - * - * Values may be "false" if replication is too broken to estimate - * - * @param integer[] $serverIndexes - * @param string $domain - * - * @return array Map of (server index => float|int|bool) - */ - public function getLagTimes( $serverIndexes, $domain ); + public function setLogger( LoggerInterface $logger ) { + $this->replLogger = $logger; + } - /** - * Clear any process and persistent cache of lag times - * @since 1.27 - */ - public function clearCaches(); + 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 02babfe..7286417 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 1a40b8f..8062001 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 ) { } -- To view, visit https://gerrit.wikimedia.org/r/310733 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I272a72cd55a70f99a866d518d44cb3bcaca91b9e Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Aaron Schulz <asch...@wikimedia.org> Gerrit-Reviewer: Legoktm <legoktm.wikipe...@gmail.com> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits