use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\Rdbms\LoadMonitorNull;
+use Wikimedia\TestingAccessWrapper;
/**
* @group Database
+ * @group medium
* @covers \Wikimedia\Rdbms\LoadBalancer
*/
class LoadBalancerTest extends MediaWikiTestCase {
global $wgDBserver;
// Simulate web request with DBO_TRX
- $lb = $this->newMultiServerLocalLoadBalancer( DBO_TRX );
+ $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
+
+ $this->assertEquals( 8, $lb->getServerCount() );
+ $this->assertTrue( $lb->hasReplicaServers() );
+ $this->assertTrue( $lb->hasStreamingReplicaServers() );
$dbw = $lb->getConnection( DB_MASTER );
$this->assertTrue( $dbw->getLBInfo( 'master' ), 'master shows as master' );
$dbr = $lb->getConnection( DB_REPLICA );
$this->assertTrue( $dbr->getLBInfo( 'replica' ), 'replica shows as replica' );
+ $this->assertTrue( $dbr->isReadOnly(), 'replica shows as replica' );
$this->assertEquals(
( $wgDBserver != '' ) ? $wgDBserver : 'localhost',
$dbr->getLBInfo( 'clusterMasterHost' ),
] );
}
- private function newMultiServerLocalLoadBalancer( $flags = DBO_DEFAULT ) {
+ private function newMultiServerLocalLoadBalancer(
+ $lbExtra = [], $srvExtra = [], $masterOnly = false
+ ) {
global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
$servers = [
- [ // master
+ // Master DB
+ 0 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => $masterOnly ? 100 : 0,
+ ],
+ // Main replica DBs
+ 1 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => $masterOnly ? 0 : 100,
+ ],
+ 2 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => $masterOnly ? 0 : 100,
+ ],
+ // RC replica DBs
+ 3 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
'load' => 0,
- 'flags' => $flags
+ 'groupLoads' => [
+ 'recentchanges' => 100,
+ 'watchlist' => 100
+ ],
],
- [ // emulated replica
+ // Logging replica DBs
+ 4 => $srvExtra + [
'host' => $wgDBserver,
'dbname' => $wgDBname,
'tablePrefix' => $this->dbPrefix(),
'password' => $wgDBpassword,
'type' => $wgDBtype,
'dbDirectory' => $wgSQLiteDataDir,
- 'load' => 100,
- 'flags' => $flags
+ 'load' => 0,
+ 'groupLoads' => [
+ 'logging' => 100
+ ],
+ ],
+ 5 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 0,
+ 'groupLoads' => [
+ 'logging' => 100
+ ],
+ ],
+ // Maintenance query replica DBs
+ 6 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 0,
+ 'groupLoads' => [
+ 'vslow' => 100
+ ],
+ ],
+ // Replica DB that only has a copy of some static tables
+ 7 => $srvExtra + [
+ 'host' => $wgDBserver,
+ 'dbname' => $wgDBname,
+ 'tablePrefix' => $this->dbPrefix(),
+ 'user' => $wgDBuser,
+ 'password' => $wgDBpassword,
+ 'type' => $wgDBtype,
+ 'dbDirectory' => $wgSQLiteDataDir,
+ 'load' => 0,
+ 'groupLoads' => [
+ 'archive' => 100
+ ],
+ 'is static' => true
]
];
- return new LoadBalancer( [
+ return new LoadBalancer( $lbExtra + [
'servers' => $servers,
'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
$rConn->insert( 'test', [ 't' => 1 ], __METHOD__ );
}
+
+ public function testQueryGroupIndex() {
+ $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
+ /** @var LoadBalancer $lbWrapper */
+ $lbWrapper = TestingAccessWrapper::newFromObject( $lb );
+
+ $rGeneric = $lb->getConnectionRef( DB_REPLICA );
+ $mainIndexPicked = $rGeneric->getLBInfo( 'serverIndex' );
+
+ $this->assertEquals( $mainIndexPicked, $lbWrapper->getExistingReaderIndex( false ) );
+ $this->assertTrue( in_array( $mainIndexPicked, [ 1, 2 ] ) );
+ for ( $i = 0; $i < 300; ++$i ) {
+ $rLog = $lb->getConnectionRef( DB_REPLICA, [] );
+ $this->assertEquals(
+ $mainIndexPicked,
+ $rLog->getLBInfo( 'serverIndex' ),
+ "Main index unchanged" );
+ }
+
+ $rRC = $lb->getConnectionRef( DB_REPLICA, [ 'recentchanges' ] );
+ $rWL = $lb->getConnectionRef( DB_REPLICA, [ 'watchlist' ] );
+
+ $this->assertEquals( 3, $rRC->getLBInfo( 'serverIndex' ) );
+ $this->assertEquals( 3, $rWL->getLBInfo( 'serverIndex' ) );
+
+ $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
+ $logIndexPicked = $rLog->getLBInfo( 'serverIndex' );
+
+ $this->assertEquals( $logIndexPicked, $lbWrapper->getExistingReaderIndex( 'logging' ) );
+ $this->assertTrue( in_array( $logIndexPicked, [ 4, 5 ] ) );
+
+ for ( $i = 0; $i < 300; ++$i ) {
+ $rLog = $lb->getConnectionRef( DB_REPLICA, [ 'logging', 'watchlist' ] );
+ $this->assertEquals(
+ $logIndexPicked, $rLog->getLBInfo( 'serverIndex' ), "Index unchanged" );
+ }
+
+ $rVslow = $lb->getConnectionRef( DB_REPLICA, [ 'vslow', 'logging' ] );
+ $vslowIndexPicked = $rVslow->getLBInfo( 'serverIndex' );
+
+ $this->assertEquals( $vslowIndexPicked, $lbWrapper->getExistingReaderIndex( 'vslow' ) );
+ $this->assertEquals( 6, $vslowIndexPicked );
+ }
+
+ public function testNonZeroMasterLoad() {
+ $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_DEFAULT ], true );
+ // Make sure that no infinite loop occurs (T226678)
+ $rGeneric = $lb->getConnectionRef( DB_REPLICA );
+ $this->assertEquals( $lb->getWriterIndex(), $rGeneric->getLBInfo( 'serverIndex' ) );
+ }
}