objectcache: make use of new modtoken field in SqlBagOStuff

Add a multi-primary mode option that supports MySQL DB setups
that use circular replication with STATEMENT formatted binlogs.
The `modtoken` column is only used when multi-primary mode is
explicitly enabled in configuration. The column is used by write
queries to determine the "winning" version of keys, with the goal
of approximating "Last-Write-Wins" eventual consistency.

Writes with different timestamps can be handled by picking the
one with the highest timestamp as the "winner". Writes with the
same timestamp, from different primary DBs, can be handled by
picking the one from the primary DB with the highest server_id.
Writes with the same token timestamp from the same primary DB can
be handled by picking the last write to appear in the binlog.
The delete() operation uses tombstones in multi-primary mode,
since there must be a key version to actually compare with the
versions from other operations.

Also:
* Remove "LOCK IN SHARE MODE" that was made obsolete by the
  CONN_TRX_AUTOCOMMIT flag. For the SQLite transaction case,
  it is serializable anyway.
* Simplified handleWriteError() to match handleReadError()
  and merged them into handleDBError().

Bug: T274174
Change-Id: Icc5eff9a032dd3403b5718058f20e38f8ea84af5
This commit is contained in:
Aaron Schulz 2021-02-24 18:02:23 -08:00 committed by Timo Tijhof
parent 579b057f29
commit d56a686f83
3 changed files with 1062 additions and 432 deletions

View file

@ -51,7 +51,7 @@ use Wikimedia\ScopedCallback;
* among datacenters.
*
* Subclasses should override the default "segmentationSize" field with an appropriate value.
* The value should not be larger than what the storage backend (by default) supports. It also
* The value should not be larger than what the backing store (by default) supports. It also
* should be roughly informed by common performance bottlenecks (e.g. values over a certain size
* having poor scalability). The same goes for the "segmentedValueMaxSize" member, which limits
* the maximum size and chunk count (indirectly) of values.

File diff suppressed because it is too large Load diff

View file

@ -13,14 +13,16 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
private $cache;
private const TEST_KEY = 'test';
private const TEST_TIME = 1563892142;
protected function setUp(): void {
parent::setUp();
$this->cache = $this->newCacheInstance();
$this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) );
$this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) . ':lock' );
$this->cache->deleteMulti( [
$this->cache->makeKey( self::TEST_KEY ),
$this->cache->makeKey( self::TEST_KEY ) . ':lock'
] );
}
/**
@ -123,9 +125,6 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
* @covers MediumSpecificBagOStuff::changeTTL
*/
public function testChangeTTLRenew() {
$now = microtime( true ); // need real time
$this->cache->setMockTime( $now );
$key = $this->cache->makeKey( self::TEST_KEY );
$value = 'meow';
@ -144,39 +143,36 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
* @covers MediumSpecificBagOStuff::changeTTL
*/
public function testChangeTTLExpireRel() {
$now = microtime( true ); // need real time
$this->cache->setMockTime( $now );
$key = $this->cache->makeKey( self::TEST_KEY );
$value = 'meow';
$this->cache->add( $key, $value, 5 );
$this->assertSame( $value, $this->cache->get( $key ) );
$this->assertTrue( $this->cache->changeTTL( $key, -3600 ) );
$this->assertFalse( $this->cache->get( $key ) );
$this->assertFalse( $this->cache->changeTTL( $key, -3600 ) );
}
/**
* @covers MediumSpecificBagOStuff::changeTTL
*/
public function testChangeTTLExpireAbs() {
$now = microtime( true ); // need real time
$this->cache->setMockTime( $now );
$key = $this->cache->makeKey( self::TEST_KEY );
$value = 'meow';
$this->cache->add( $key, $value, 5 );
$this->assertTrue( $this->cache->changeTTL( $key, $now - 3600 ) );
$this->assertSame( $value, $this->cache->get( $key ) );
$now = $this->cache->getCurrentTime();
$this->assertTrue( $this->cache->changeTTL( $key, (int)$now - 3600 ) );
$this->assertFalse( $this->cache->get( $key ) );
$this->assertFalse( $this->cache->changeTTL( $key, (int)$now - 3600 ) );
}
/**
* @covers MediumSpecificBagOStuff::changeTTLMulti
*/
public function testChangeTTLMulti() {
$now = 1563892142;
$this->cache->setMockTime( $now );
$key1 = $this->cache->makeKey( 'test-key1' );
$key2 = $this->cache->makeKey( 'test-key2' );
$key3 = $this->cache->makeKey( 'test-key3' );
@ -206,11 +202,11 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
$this->assertFalse( $ok, "One key missing" );
$this->assertSame( 1, $this->cache->get( $key1 ), "Key still live" );
$now = microtime( true ); // real time
$ok = $this->cache->setMulti( [ $key1 => 1, $key2 => 2, $key3 => 3 ] );
$this->assertTrue( $ok, "setMulti() succeeded" );
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], $now + 86400 );
$now = $this->cache->getCurrentTime();
$ok = $this->cache->changeTTLMulti( [ $key1, $key2, $key3 ], (int)$now + 86400 );
$this->assertTrue( $ok, "Expiry set for all keys" );
$this->assertSame( 1, $this->cache->get( $key1 ), "Key still live" );
@ -240,7 +236,7 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
$key = $this->cache->makeKey( self::TEST_KEY );
$this->cache->add( $key, $value, 5 );
$this->assertEquals( $this->cache->get( $key ), $value );
$this->assertSame( $this->cache->get( $key ), $value );
}
/**
@ -249,7 +245,7 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
* @covers MediumSpecificBagOStuff::getWithSetCallback
*/
public function testGetWithSetCallback() {
$now = 1563892142;
$now = self::TEST_TIME;
$cache = new HashBagOStuff( [] );
$cache->setMockTime( $now );
$key = $cache->makeKey( self::TEST_KEY );
@ -504,11 +500,4 @@ abstract class BagOStuffTestBase extends MediaWikiIntegrationTestCase {
$this->assertTrue( $this->cache->unlock( $key2 ) );
$this->assertTrue( $this->cache->unlock( $key2 ) );
}
protected function tearDown(): void {
$this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) );
$this->cache->delete( $this->cache->makeKey( self::TEST_KEY ) . ':lock' );
parent::tearDown();
}
}