Krinkle has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/252880

Change subject: [WIP] MessageBlobStore: Convert from msg_resource table to 
object cache
......................................................................

[WIP] MessageBlobStore: Convert from msg_resource table to object cache

* Remove all interaction with the msg_resource table.

TODO:
* Make the retrieval more performant by adding MessageBlobStore
  keys in WAN cache. Or by making MessageCache support getMulti.

Bug: T113092
Change-Id: I78bb09bcea384dd166e5c13ff9b2e882c70e4df9
---
M includes/cache/MessageBlobStore.php
M includes/resourceloader/ResourceLoaderFileModule.php
M includes/resourceloader/ResourceLoaderModule.php
M maintenance/cleanupRemovedModules.php
M tests/phpunit/includes/OutputPageTest.php
5 files changed, 124 insertions(+), 367 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/80/252880/1

diff --git a/includes/cache/MessageBlobStore.php 
b/includes/cache/MessageBlobStore.php
index 9d0d599..7d93756 100644
--- a/includes/cache/MessageBlobStore.php
+++ b/includes/cache/MessageBlobStore.php
@@ -31,284 +31,52 @@
  * or the module definition, is changed.
  */
 class MessageBlobStore {
-       /**
-        * In-process cache for message blobs.
-        *
-        * Keyed by language code, then module name.
-        *
-        * @var array
-        */
-       protected $blobCache = array();
 
-       /* @var ResourceLoader */
-       protected $resourceloader;
+       public function __construct( ResourceLoader $unused = null ) {
+       }
 
        /**
-        * @param ResourceLoader $resourceloader
+        * Get the message blob for a module
+        *
+        * @since 1.27
+        * @param ResourceLoaderModule|array $modules Array of module objects 
keyed by module name
+        * @param string $lang Language code
+        * @return string|null JSON blob or null of module has no messages
         */
-       public function __construct( ResourceLoader $resourceloader = null ) {
-               $this->resourceloader = $resourceloader;
+       public function getBlob( ResourceLoaderModule $module, $lang ) {
+               return $this->generateMessageBlob( $module, $lang );
        }
 
        /**
         * Get the message blobs for a set of modules
         *
         * @param ResourceLoader $resourceLoader
-        * @param array $modules Array of module objects keyed by module name
+        * @param ResourceLoaderModule|array $modules Array of module objects 
keyed by module name
         * @param string $lang Language code
         * @return array An array mapping module names to message blobs
         */
        public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
+               $modules = is_array( $modules ) ? $modules : array( $modules );
+
                if ( !count( $modules ) ) {
                        return array();
                }
 
                $blobs = array();
 
-               // Try in-process cache
-               $missingFromCache = array();
-               foreach ( $modules as $name => $module ) {
-                       if ( isset( $this->blobCache[$lang][$name] ) ) {
-                               $blobs[$name] = $this->blobCache[$lang][$name];
-                       } else {
-                               $missingFromCache[$name] = $module;
-                       }
-               }
-
-               // Try DB cache
-               if ( $missingFromCache ) {
-                       $blobs += $this->getFromDB( $missingFromCache, $lang );
-               }
-
-               // Generate new blobs for any remaining modules and store in DB
-               $missingFromDb = array_diff( array_keys( $modules ), 
array_keys( $blobs ) );
-               foreach ( $missingFromDb as $name ) {
-                       $blob = $this->insertMessageBlob( $name, 
$modules[$name], $lang );
+               // Try to generate blobs for each module
+               foreach ( $module as $name => $module ) {
+                       $blob = $this->generateMessageBlob( $module, $lang );
                        if ( $blob ) {
                                $blobs[$name] = $blob;
                        }
-               }
-
-               // Update in-process cache
-               if ( isset( $this->blobCache[$lang] ) ) {
-                       $this->blobCache[$lang] += $blobs;
-               } else {
-                       $this->blobCache[$lang] = $blobs;
                }
 
                return $blobs;
        }
 
        /**
-        * Generate and insert a new message blob. If the blob was already
-        * present, it is not regenerated; instead, the preexisting blob
-        * is fetched and returned.
-        *
-        * @param string $name Module name
-        * @param ResourceLoaderModule $module
-        * @param string $lang Language code
-        * @return mixed Message blob or false if the module has no messages
-        */
-       public function insertMessageBlob( $name, ResourceLoaderModule $module, 
$lang ) {
-               $blob = $this->generateMessageBlob( $module, $lang );
-
-               if ( !$blob ) {
-                       return false;
-               }
-
-               try {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $success = $dbw->insert( 'msg_resource', array(
-                                       'mr_lang' => $lang,
-                                       'mr_resource' => $name,
-                                       'mr_blob' => $blob,
-                                       'mr_timestamp' => $dbw->timestamp()
-                               ),
-                               __METHOD__,
-                               array( 'IGNORE' )
-                       );
-
-                       if ( $success && $dbw->affectedRows() == 0 ) {
-                               // Blob was already present, fetch it
-                               $blob = $dbw->selectField( 'msg_resource', 
'mr_blob', array(
-                                               'mr_resource' => $name,
-                                               'mr_lang' => $lang,
-                                       ),
-                                       __METHOD__
-                               );
-                       }
-               } catch ( DBError $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-               return $blob;
-       }
-
-       /**
-        * Update the message blob for a given module in a given language
-        *
-        * @param string $name Module name
-        * @param ResourceLoaderModule $module
-        * @param string $lang Language code
-        * @return string|null Regenerated message blob, or null if there was 
no blob for
-        *   the given module/language pair.
-        */
-       public function updateModule( $name, ResourceLoaderModule $module, 
$lang ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
-                       array( 'mr_resource' => $name, 'mr_lang' => $lang ),
-                       __METHOD__
-               );
-               if ( !$row ) {
-                       return null;
-               }
-
-               $newBlob = $this->generateMessageBlob( $module, $lang );
-
-               try {
-                       $newRow = array(
-                               'mr_resource' => $name,
-                               'mr_lang' => $lang,
-                               'mr_blob' => $newBlob,
-                               'mr_timestamp' => $dbw->timestamp()
-                       );
-
-                       $dbw->replace( 'msg_resource',
-                               array( array( 'mr_resource', 'mr_lang' ) ),
-                               $newRow, __METHOD__
-                       );
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-               return $newBlob;
-       }
-
-       /**
-        * Update a single message in all message blobs it occurs in.
-        *
-        * @param string $key Message key
-        */
-       public function updateMessage( $key ) {
-               try {
-                       $dbw = wfGetDB( DB_MASTER );
-
-                       // Keep running until the updates queue is empty.
-                       // Due to update conflicts, the queue might not be 
emptied
-                       // in one iteration.
-                       $updates = null;
-                       do {
-                               $updates = $this->getUpdatesForMessage( $key, 
$updates );
-
-                               foreach ( $updates as $k => $update ) {
-                                       // Update the row on the condition that 
it
-                                       // didn't change since we fetched it by 
putting
-                                       // the timestamp in the WHERE clause.
-                                       $success = $dbw->update( 'msg_resource',
-                                               array(
-                                                       'mr_blob' => 
$update['newBlob'],
-                                                       'mr_timestamp' => 
$dbw->timestamp() ),
-                                               array(
-                                                       'mr_resource' => 
$update['resource'],
-                                                       'mr_lang' => 
$update['lang'],
-                                                       'mr_timestamp' => 
$update['timestamp'] ),
-                                               __METHOD__
-                                       );
-
-                                       // Only requeue conflicted updates.
-                                       // If update() returned false, don't 
retry, for
-                                       // fear of getting into an infinite loop
-                                       if ( !( $success && 
$dbw->affectedRows() == 0 ) ) {
-                                               // Not conflicted
-                                               unset( $updates[$k] );
-                                       }
-                               }
-                       } while ( count( $updates ) );
-
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-       }
-
-       public function clear() {
-               try {
-                       // Not using TRUNCATE, because that needs extra 
permissions,
-                       // which maybe not granted to the database user.
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->delete( 'msg_resource', '*', __METHOD__ );
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-       }
-
-       /**
-        * @return ResourceLoader
-        */
-       protected function getResourceLoader() {
-               // For back-compat this class supports instantiation without 
passing ResourceLoader
-               // Lazy-initialise this property because most callers don't 
need it.
-               if ( $this->resourceloader === null ) {
-                       wfDebug( __CLASS__ . ' created without a ResourceLoader 
instance' );
-                       $this->resourceloader = new ResourceLoader();
-               }
-
-               return $this->resourceloader;
-       }
-
-       /**
-        * Create an update queue for updateMessage()
-        *
-        * @param string $key Message key
-        * @param array $prevUpdates Updates queue to refresh or null to build 
a fresh update queue
-        * @return array Updates queue
-        */
-       private function getUpdatesForMessage( $key, $prevUpdates = null ) {
-               $dbw = wfGetDB( DB_MASTER );
-
-               if ( is_null( $prevUpdates ) ) {
-                       $rl = $this->getResourceLoader();
-                       $moduleNames = $rl->getModulesByMessage( $key );
-                       // Fetch all blobs referencing $key
-                       $res = $dbw->select(
-                               array( 'msg_resource' ),
-                               array( 'mr_resource', 'mr_lang', 'mr_blob', 
'mr_timestamp' ),
-                               array(
-                                       'mr_resource' => $moduleNames,
-                               ),
-                               __METHOD__
-                       );
-               } else {
-                       // Refetch the blobs referenced by $prevUpdates
-
-                       // Reorganize the (resource, lang) pairs in the format
-                       // expected by makeWhereFrom2d()
-                       $twoD = array();
-
-                       foreach ( $prevUpdates as $update ) {
-                               $twoD[$update['resource']][$update['lang']] = 
true;
-                       }
-
-                       $res = $dbw->select( 'msg_resource',
-                               array( 'mr_resource', 'mr_lang', 'mr_blob', 
'mr_timestamp' ),
-                               $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 
'mr_lang' ),
-                               __METHOD__
-                       );
-               }
-
-               // Build the new updates queue
-               $updates = array();
-
-               foreach ( $res as $row ) {
-                       $updates[] = array(
-                               'resource' => $row->mr_resource,
-                               'lang' => $row->mr_lang,
-                               'timestamp' => $row->mr_timestamp,
-                               'newBlob' => $this->reencodeBlob( 
$row->mr_blob, $key, $row->mr_lang )
-                       );
-               }
-
-               return $updates;
-       }
-
-       /**
+        * @since 1.27
         * @param string $key Message key
         * @param string $lang Language code
         * @return string
@@ -322,75 +90,77 @@
        }
 
        /**
-        * Reencode a message blob with the updated value for a message
-        *
-        * @param string $blob Message blob (JSON object)
-        * @param string $key Message key
-        * @param string $lang Language code
-        * @return string Message blob with $key replaced with its new value
-        */
-       private function reencodeBlob( $blob, $key, $lang ) {
-               $decoded = FormatJson::decode( $blob, true );
-               $decoded[$key] = $this->fetchMessage( $key, $lang );
-               return FormatJson::encode( (object)$decoded );
-       }
-
-       /**
-        * Get the message blobs for a set of modules from the database.
-        * Modules whose blobs are not in the database are silently dropped.
-        *
-        * @param array $modules Array of module objects by name
-        * @param string $lang Language code
-        * @throws MWException
-        * @return array Array mapping module names to blobs
-        */
-       private function getFromDB( $modules, $lang ) {
-               if ( !count( $modules ) ) {
-                       return array();
-               }
-
-               $retval = array();
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( 'msg_resource',
-                       array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
-                       array( 'mr_resource' => array_keys( $modules ), 
'mr_lang' => $lang ),
-                       __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       if ( !isset( $modules[ $row->mr_resource ] ) {
-                               // This shouldn't be possible
-                               throw new MWException( __METHOD__ . ' passed an 
invalid module name' );
-                       }
-                       $module = $modules[ $row->mr_resource ];
-
-                       // Update the module's blob if the list of messages 
changed
-                       $blobKeys = array_keys( FormatJson::decode( 
$row->mr_blob, true ) );
-                       $moduleMsgs = array_values( array_unique( 
$module->getMessages() ) );
-                       if ( $blobKeys !== $moduleMsgs ) {
-                               $retval[$row->mr_resource] = 
$this->updateModule( $row->mr_resource, $module, $lang );
-                       } else {
-                               $retval[$row->mr_resource] = $row->mr_blob;
-                       }
-               }
-
-               return $retval;
-       }
-
-       /**
         * Generate the message blob for a given module in a given language.
         *
         * @param ResourceLoaderModule $module
         * @param string $lang Language code
-        * @return string JSON object
+        * @return string|null JSON blob or null if the module has no messages
         */
        private function generateMessageBlob( ResourceLoaderModule $module, 
$lang ) {
+               if ( !$module->getMessages() ) {
+                       return null;
+               }
                $messages = array();
-
                foreach ( $module->getMessages() as $key ) {
                        $messages[$key] = $this->fetchMessage( $key, $lang );
                }
 
                return FormatJson::encode( (object)$messages );
        }
+
+       /**
+        * Update a single message in all message blobs it occurs in.
+        *
+        * Called from MessageCache when a message value is edited.
+        *
+        * @param string $key Message key
+        */
+       public function updateMessage( $key ) {
+               // TODO:
+               // - Find which modules use the message
+               // - Purge those keys
+       }
+
+
+       /**
+        * Generate the message blob for a module/language pair.
+        *
+        * Previously used to populate a cache table in the database.
+        *
+        * @deprecated since 1.27 Obsolete
+        * @param string $name Module name
+        * @param ResourceLoaderModule $module
+        * @param string $lang Language code
+        * @return string|bool Message blob or false if the module has no 
messages
+        */
+       public function insertMessageBlob( $name, ResourceLoaderModule $module, 
$lang ) {
+               return $this->generateMessageBlob( $module, $lang ) ?: false;
+       }
+
+       /**
+        * Update the message blob in the database for a module/language pair.
+        *
+        * @deprecated since 1.27 Obsolete
+        * @param string $name Module name
+        * @param ResourceLoaderModule $module
+        * @param string $lang Language code
+        * @return null
+        */
+       public function updateModule( $name, ResourceLoaderModule $module, 
$lang ) {
+               return null;
+       }
+
+       /**
+        * @deprecated since 1.27 Obsolete
+        */
+       public function clear() {
+               try {
+                       // Not using TRUNCATE, because that needs extra 
permissions,
+                       // which maybe not granted to the database user.
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->delete( 'msg_resource', '*', __METHOD__ );
+               } catch ( Exception $e ) {
+                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+               }
+       }
 }
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php 
b/includes/resourceloader/ResourceLoaderFileModule.php
index 1421b10..8e392c1 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -583,7 +583,7 @@
                $summary[] = array(
                        'options' => $options,
                        'fileHashes' => $this->getFileHashes( $context ),
-                       'msgBlobMtime' => $this->getMsgBlobMtime( 
$context->getLanguage() ),
+                       'messageBlob' => $this->getMessageBlob( 
$context->getLanguage() ),
                );
                return $summary;
        }
diff --git a/includes/resourceloader/ResourceLoaderModule.php 
b/includes/resourceloader/ResourceLoaderModule.php
index fcda87b..45ca9df 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -60,8 +60,8 @@
 
        // In-object cache for file dependencies
        protected $fileDeps = array();
-       // In-object cache for message blob mtime
-       protected $msgBlobMtime = array();
+       // In-object cache for message blob (keyed by language)
+       protected $msgBlobs = array();
        // In-object cache for version hash
        protected $versionHash = array();
        // In-object cache for module content
@@ -453,45 +453,18 @@
        }
 
        /**
-        * Get the last modification timestamp of the messages in this module 
for a given language.
+        * Get the hash of the message blob.
+        *
+        * @since 1.27
         * @param string $lang Language code
-        * @return int UNIX timestamp
+        * @return string JSON blob
         */
-       public function getMsgBlobMtime( $lang ) {
-               if ( !isset( $this->msgBlobMtime[$lang] ) ) {
-                       if ( !count( $this->getMessages() ) ) {
-                               return 1;
-                       }
-
-                       $dbr = wfGetDB( DB_SLAVE );
-                       $msgBlobMtime = $dbr->selectField( 'msg_resource',
-                               'mr_timestamp',
-                               array(
-                                       'mr_resource' => $this->getName(),
-                                       'mr_lang' => $lang
-                               ),
-                               __METHOD__
-                       );
-                       // If no blob was found, but the module does have 
messages, that means we need
-                       // to regenerate it. Return NOW
-                       if ( $msgBlobMtime === false ) {
-                               $msgBlobMtime = wfTimestampNow();
-                       }
-                       $this->msgBlobMtime[$lang] = wfTimestamp( TS_UNIX, 
$msgBlobMtime );
+       protected function getMessageBlob( $lang ) {
+               if ( !isset( $this->msgBlobs[$lang] ) ) {
+                       $store = 
$context->getResourceLoader()->getMessageBlobStore();
+                       $this->msgBlobs[$lang] = $store->getBlob( $this, $lang 
);
                }
-               return $this->msgBlobMtime[$lang];
-       }
-
-       /**
-        * Set in-object cache for message blob time.
-        *
-        * This is used to retrieve data in batches. See 
ResourceLoader::preloadModuleInfo().
-        *
-        * @param string $lang Language code
-        * @param int $mtime UNIX timestamp
-        */
-       public function setMsgBlobMtime( $lang, $mtime ) {
-               $this->msgBlobMtime[$lang] = $mtime;
+               return $this->msgBlobs[$lang];
        }
 
        /**
@@ -607,13 +580,9 @@
                }
 
                // Messages
-               $blobs = $rl->getMessageBlobStore()->get(
-                       $rl,
-                       array( $this->getName() => $this ),
-                       $context->getLanguage()
-               );
-               if ( isset( $blobs[$this->getName()] ) ) {
-                       $content['messagesBlob'] = $blobs[$this->getName()];
+               $blob = $this->getMessageBlob( $context->getLanguage() );
+               if ( $blob ) {
+                       $content['messagesBlob'] = $blob;
                }
 
                $templates = $this->getTemplates();
@@ -746,7 +715,7 @@
         * A number of utility methods are available to help you gather data. 
These are not
         * called by default and must be included by the subclass' 
getDefinitionSummary().
         *
-        * - getMsgBlobMtime()
+        * - getMessageBlob()
         *
         * @since 1.23
         * @param ResourceLoaderContext $context
@@ -817,6 +786,34 @@
        }
 
        /**
+        * Get the last modification timestamp of the message blob for this 
module in a given language.
+        *
+        * @deprecated since 1.27 Superseded by getVersionHash()
+        * @param string $lang Language code
+        * @return int UNIX timestamp
+        */
+       public function getMsgBlobMtime( $lang ) {
+               if ( !$this->getMessages() ) {
+                       return 1;
+               }
+               // Dummy that is > 1
+               return 2;
+       }
+
+       /**
+        * Set in-object cache for message blob time. Obsolete.
+        *
+        * Previously used to allow fetching of message blob store mtimes in 
batches and
+        * then prepopulate the in-process. See 
ResourceLoader::preloadModuleInfo().
+        *
+        * @param string $lang Language code
+        * @param int $mtime UNIX timestamp
+        */
+       public function setMsgBlobMtime( $lang, $mtime ) {
+               // No-op
+       }
+
+       /**
         * Check whether this module is known to be empty. If a child class
         * has an easy and cheap way to determine that this module is
         * definitely going to be empty, it should override this method to
diff --git a/maintenance/cleanupRemovedModules.php 
b/maintenance/cleanupRemovedModules.php
index ae05930..c22f7aa 100644
--- a/maintenance/cleanupRemovedModules.php
+++ b/maintenance/cleanupRemovedModules.php
@@ -59,18 +59,9 @@
                } while ( $numRows > 0 );
                $this->output( "done\n" );
 
-               $this->output( "Cleaning up msg_resource table...\n" );
-               $i = 1;
-
-               $mrRes = $dbw->tableName( 'msg_resource' );
-               do {
-                       $where = $moduleList ? "mr_resource NOT IN 
($moduleList)" : '1=1';
-                       $dbw->query( "DELETE FROM $mrRes WHERE $where LIMIT 
$limit", __METHOD__ );
-                       $numRows = $dbw->affectedRows();
-                       $this->output( "Batch $i: $numRows rows\n" );
-                       $i++;
-                       wfWaitForSlaves();
-               } while ( $numRows > 0 );
+               $this->output( "Purging unused msg_resource table...\n" );
+               $mbs = $rl->getMessageBlobStore();
+               $mbs->clear();
                $this->output( "done\n" );
        }
 }
diff --git a/tests/phpunit/includes/OutputPageTest.php 
b/tests/phpunit/includes/OutputPageTest.php
index 5f21e07..f5ef016 100644
--- a/tests/phpunit/includes/OutputPageTest.php
+++ b/tests/phpunit/includes/OutputPageTest.php
@@ -375,7 +375,6 @@
        }
 
        public function updateModule( $name, ResourceLoaderModule $module, 
$lang ) {
-               return;
        }
 
        public function updateMessage( $key ) {

-- 
To view, visit https://gerrit.wikimedia.org/r/252880
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I78bb09bcea384dd166e5c13ff9b2e882c70e4df9
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to