* @ingroup Database
*/
-use Psr\Log\LoggerInterface;
+namespace Wikimedia\Rdbms;
+
+use IDatabase;
+use BagOStuff;
/**
* Basic MySQL load monitor with no external dependencies
*
* @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;
+class LoadMonitorMySQL extends LoadMonitor {
+ /** @var float What buffer pool use ratio counts as "warm" (e.g. 0.5 for 50% usage) */
+ private $warmCacheRatio;
+
+ public function __construct(
+ ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache, array $options = []
+ ) {
+ parent::__construct( $lb, $srvCache, $cache, $options );
+
+ $this->warmCacheRatio = isset( $options['warmCacheRatio'] )
+ ? $options['warmCacheRatio']
+ : 0.0;
}
- 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'];
+ protected function getWeightScale( $index, IDatabase $conn = null ) {
+ if ( !$conn ) {
+ return 0.0;
}
- $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
+ $weight = 1.0;
+ if ( $this->warmCacheRatio > 0 ) {
+ $res = $conn->query( 'SHOW STATUS', false );
+ $s = $res ? $conn->fetchObject( $res ) : false;
+ if ( $s === false ) {
+ $host = $this->parent->getServerName( $index );
+ $this->replLogger->error( __METHOD__ . ": could not get status for $host" );
} 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 );
+ // https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html
+ if ( $s->Innodb_buffer_pool_pages_total > 0 ) {
+ $ratio = $s->Innodb_buffer_pool_pages_data / $s->Innodb_buffer_pool_pages_total;
+ } else {
+ $ratio = 1.0;
+ }
+ // Stop caring once $ratio >= $this->warmCacheRatio
+ $weight *= min( $ratio / $this->warmCacheRatio, 1.0 );
}
}
- # 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 )
- );
+ return $weight;
}
}