2015-07-15 01:32:18 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
2016-09-12 22:10:16 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
|
2015-07-15 01:32:18 +00:00
|
|
|
/**
|
|
|
|
|
* 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 {
|
2016-09-14 07:10:11 +00:00
|
|
|
/** @var ILoadBalancer */
|
|
|
|
|
protected $parent;
|
2015-07-15 01:32:18 +00:00
|
|
|
/** @var BagOStuff */
|
|
|
|
|
protected $srvCache;
|
|
|
|
|
/** @var BagOStuff */
|
|
|
|
|
protected $mainCache;
|
2016-09-12 22:10:16 +00:00
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
protected $replLogger;
|
2015-07-15 01:32:18 +00:00
|
|
|
|
2016-09-14 07:10:11 +00:00
|
|
|
public function __construct( ILoadBalancer $parent, BagOStuff $sCache, BagOStuff $cCache ) {
|
2015-07-15 01:32:18 +00:00
|
|
|
$this->parent = $parent;
|
2016-09-14 07:10:11 +00:00
|
|
|
$this->srvCache = $sCache;
|
|
|
|
|
$this->mainCache = $cCache;
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger = new \Psr\Log\NullLogger();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setLogger( LoggerInterface $logger ) {
|
|
|
|
|
$this->replLogger = $logger;
|
2015-07-15 01:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2016-02-17 09:09:32 +00:00
|
|
|
return [ 0 => 0 ];
|
2015-07-15 01:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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 ) ) {
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
|
2015-07-15 01:32:18 +00:00
|
|
|
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 );
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
|
2015-07-15 01:32:18 +00:00
|
|
|
|
|
|
|
|
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;
|
2015-07-15 01:01:11 +00:00
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
2015-07-15 01:32:18 +00:00
|
|
|
$unlocker = new ScopedCallback( function () use ( $cache, $key ) {
|
|
|
|
|
$cache->unlock( $key );
|
|
|
|
|
} );
|
|
|
|
|
} elseif ( $staleValue ) {
|
|
|
|
|
# Could not acquire lock but an old cache exists, so use it
|
2015-07-16 19:01:55 +00:00
|
|
|
return $staleValue['lagTimes'];
|
2015-07-15 01:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$lagTimes = [];
|
2015-07-15 01:32:18 +00:00
|
|
|
foreach ( $serverIndexes as $i ) {
|
2016-01-13 22:33:38 +00:00
|
|
|
if ( $i == $this->parent->getWriterIndex() ) {
|
|
|
|
|
$lagTimes[$i] = 0; // master always has no lag
|
2015-11-18 18:32:05 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2016-01-13 22:33:38 +00:00
|
|
|
|
2015-11-18 18:32:05 +00:00
|
|
|
$conn = $this->parent->getAnyOpenConnection( $i );
|
2016-01-13 22:33:38 +00:00
|
|
|
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 );
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
|
2015-11-18 18:32:05 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2016-01-13 22:33:38 +00:00
|
|
|
|
|
|
|
|
$lagTimes[$i] = $conn->getLag();
|
|
|
|
|
if ( $lagTimes[$i] === false ) {
|
|
|
|
|
$host = $this->parent->getServerName( $i );
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
|
2016-01-13 22:33:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $close ) {
|
2015-07-15 01:32:18 +00:00
|
|
|
# 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
|
2016-02-17 09:09:32 +00:00
|
|
|
$value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
|
2015-07-15 01:32:18 +00:00
|
|
|
$this->mainCache->set( $key, $value, $staleTTL );
|
|
|
|
|
$this->srvCache->set( $key, $value, $staleTTL );
|
2016-09-12 22:10:16 +00:00
|
|
|
$this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
|
2015-07-15 01:32:18 +00:00
|
|
|
|
|
|
|
|
return $value['lagTimes'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function clearCaches() {
|
|
|
|
|
$key = $this->getLagTimeCacheKey();
|
|
|
|
|
$this->srvCache->delete( $key );
|
|
|
|
|
$this->mainCache->delete( $key );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getLagTimeCacheKey() {
|
2016-01-13 22:33:38 +00:00
|
|
|
$writerIndex = $this->parent->getWriterIndex();
|
2015-11-18 00:21:02 +00:00
|
|
|
// Lag is per-server, not per-DB, so key on the master DB name
|
2016-01-13 22:33:38 +00:00
|
|
|
return $this->srvCache->makeGlobalKey(
|
2016-09-14 07:10:11 +00:00
|
|
|
'lag-times',
|
|
|
|
|
$this->parent->getServerName( $writerIndex )
|
2016-01-13 22:33:38 +00:00
|
|
|
);
|
2015-07-15 01:32:18 +00:00
|
|
|
}
|
|
|
|
|
}
|