Move l10n_cache table to a separate DB for sqlite via the installer
authorAaron Schulz <aschulz@wikimedia.org>
Thu, 5 Jul 2018 11:58:48 +0000 (12:58 +0100)
committerAaron Schulz <aschulz@wikimedia.org>
Tue, 14 Aug 2018 17:29:01 +0000 (17:29 +0000)
This does not set 'db' as the cache type so that admins can still
easily set the cache directory to use the file-based cdb system.
If they do not, then at least the second DB file will be used to
avoid heavy contention.

Bug: T93097
Change-Id: Ib3912f00cf12de99801ebda4f06135b2987ce71a

includes/cache/localisation/LCStoreDB.php
includes/cache/localisation/LocalisationCache.php
includes/installer/SqliteInstaller.php

index c57145c..2d8e4d2 100644 (file)
@@ -18,6 +18,7 @@
  * @file
  */
 
+use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\DBQueryError;
 
@@ -26,21 +27,29 @@ use Wikimedia\Rdbms\DBQueryError;
  * This will work on any MediaWiki installation.
  */
 class LCStoreDB implements LCStore {
-
        /** @var string */
        private $currentLang;
        /** @var bool */
        private $writesDone = false;
-       /** @var IDatabase */
+       /** @var IDatabase|null */
        private $dbw;
        /** @var array */
        private $batch = [];
        /** @var bool */
        private $readOnly = false;
+       /** @var array Server configuration map */
+       private $server;
+
+       public function __construct( $params ) {
+               $this->server = $params['server'] ?? [];
+       }
 
        public function get( $code, $key ) {
-               if ( $this->writesDone && $this->dbw ) {
-                       $db = $this->dbw; // see the changes in finishWrite()
+               if ( $this->server || $this->writesDone ) {
+                       // If a server configuration map is specified, always used that connection
+                       // for reads and writes. Otherwise, if writes occurred in finishWrite(), make
+                       // sure those changes are always visible.
+                       $db = $this->getWriteConnection();
                } else {
                        $db = wfGetDB( DB_REPLICA );
                }
@@ -62,8 +71,8 @@ class LCStoreDB implements LCStore {
                        throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
                }
 
-               $this->dbw = wfGetDB( DB_MASTER );
-               $this->readOnly = $this->dbw->isReadOnly();
+               $dbw = $this->getWriteConnection();
+               $this->readOnly = $dbw->isReadOnly();
 
                $this->currentLang = $code;
                $this->batch = [];
@@ -76,25 +85,22 @@ class LCStoreDB implements LCStore {
                        throw new MWException( __CLASS__ . ': must call startWrite() before finishWrite()' );
                }
 
-               $this->dbw->startAtomic( __METHOD__ );
+               $dbw = $this->getWriteConnection();
+               $dbw->startAtomic( __METHOD__ );
                try {
-                       $this->dbw->delete(
-                               'l10n_cache',
-                               [ 'lc_lang' => $this->currentLang ],
-                               __METHOD__
-                       );
+                       $dbw->delete( 'l10n_cache', [ 'lc_lang' => $this->currentLang ], __METHOD__ );
                        foreach ( array_chunk( $this->batch, 500 ) as $rows ) {
-                               $this->dbw->insert( 'l10n_cache', $rows, __METHOD__ );
+                               $dbw->insert( 'l10n_cache', $rows, __METHOD__ );
                        }
                        $this->writesDone = true;
                } catch ( DBQueryError $e ) {
-                       if ( $this->dbw->wasReadOnlyError() ) {
+                       if ( $dbw->wasReadOnlyError() ) {
                                $this->readOnly = true; // just avoid site down time
                        } else {
                                throw $e;
                        }
                }
-               $this->dbw->endAtomic( __METHOD__ );
+               $dbw->endAtomic( __METHOD__ );
 
                $this->currentLang = null;
                $this->batch = [];
@@ -107,11 +113,30 @@ class LCStoreDB implements LCStore {
                        throw new MWException( __CLASS__ . ': must call startWrite() before set()' );
                }
 
+               $dbw = $this->getWriteConnection();
+
                $this->batch[] = [
                        'lc_lang' => $this->currentLang,
                        'lc_key' => $key,
-                       'lc_value' => $this->dbw->encodeBlob( serialize( $value ) )
+                       'lc_value' => $dbw->encodeBlob( serialize( $value ) )
                ];
        }
 
+       /**
+        * @return IDatabase
+        */
+       private function getWriteConnection() {
+               if ( !$this->dbw ) {
+                       if ( $this->server ) {
+                               $this->dbw = Database::factory( $this->server['type'], $this->server );
+                               if ( !$this->dbw ) {
+                                       throw new MWException( __CLASS__ . ': failed to obtain a DB connection' );
+                               }
+                       } else {
+                               $this->dbw = wfGetDB( DB_MASTER );
+                       }
+               }
+
+               return $this->dbw;
+       }
 }
index 9e2a2fd..75e5e19 100644 (file)
@@ -203,6 +203,7 @@ class LocalisationCache {
                                        break;
                                case 'db':
                                        $storeClass = LCStoreDB::class;
+                                       $storeConf['server'] = $conf['storeServer'] ?? [];
                                        break;
                                case 'array':
                                        $storeClass = LCStoreStaticArray::class;
@@ -215,6 +216,7 @@ class LocalisationCache {
                                                $storeClass = LCStoreCDB::class;
                                        } else {
                                                $storeClass = LCStoreDB::class;
+                                               $storeConf['server'] = $conf['storeServer'] ?? [];
                                        }
                                        break;
                                default:
index 6f16872..aa95438 100644 (file)
@@ -230,6 +230,7 @@ class SqliteInstaller extends DatabaseInstaller {
                $status = Status::newGood();
                $status->merge( $this->makeStubDBFile( $dir, $db ) );
                $status->merge( $this->makeStubDBFile( $dir, "wikicache" ) );
+               $status->merge( $this->makeStubDBFile( $dir, "{$db}_l10n_cache" ) );
                if ( !$status->isOK() ) {
                        return $status;
                }
@@ -242,7 +243,8 @@ class SqliteInstaller extends DatabaseInstaller {
 
                # Create the global cache DB
                try {
-                       $conn = Database::factory( 'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
+                       $conn = Database::factory(
+                               'sqlite', [ 'dbname' => 'wikicache', 'dbDirectory' => $dir ] );
                        # @todo: don't duplicate objectcache definition, though it's very simple
                        $sql =
 <<<EOT
@@ -260,6 +262,27 @@ EOT;
                        return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
                }
 
+               # Create the l10n cache DB
+               try {
+                       $conn = Database::factory(
+                               'sqlite', [ 'dbname' => "{$db}_l10n_cache", 'dbDirectory' => $dir ] );
+                       # @todo: don't duplicate l10n_cache definition, though it's very simple
+                       $sql =
+<<<EOT
+       CREATE TABLE l10n_cache (
+               lc_lang BLOB NOT NULL,
+               lc_key TEXT NOT NULL,
+               lc_value BLOB NOT NULL,
+               PRIMARY KEY (lc_lang, lc_key)
+       );
+EOT;
+                       $conn->query( $sql );
+                       $conn->query( "PRAGMA journal_mode=WAL" ); // this is permanent
+                       $conn->close();
+               } catch ( DBConnectionError $e ) {
+                       return Status::newFatal( 'config-sqlite-connection-error', $e->getMessage() );
+               }
+
                # Open the main DB
                return $this->getConnection();
        }
@@ -330,6 +353,13 @@ EOT;
                'dbDirectory' => \$wgSQLiteDataDir,
                'flags' => 0
        ]
+];
+\$wgLocalisationCacheConf['storeServer'] = [
+       'type' => 'sqlite',
+       'dbname' => \"{\$wgDBname}_l10n_cache\",
+       'tablePrefix' => '',
+       'dbDirectory' => \$wgSQLiteDataDir,
+       'flags' => 0
 ];";
        }
 }