jenkins-bot has submitted this change and it was merged.

Change subject: Try to avoid huge inserts in DatabaseMessageIndex
......................................................................


Try to avoid huge inserts in DatabaseMessageIndex

Replaced delete_all + insert with delete + replace.
Added locking so that only one rebuild is run at a time,
but only implemented for DatabaseMessageIndex. Also added
a flag to load old state from master when rebuilding.

Bug: T86385
Change-Id: I742884cf48eec807c623d3c7e249f2b254406e8f
---
M tests/phpunit/MessageIndexTest.php
M utils/MessageIndex.php
2 files changed, 243 insertions(+), 58 deletions(-)

Approvals:
  Amire80: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/tests/phpunit/MessageIndexTest.php 
b/tests/phpunit/MessageIndexTest.php
index 07c7359..5d9136f 100644
--- a/tests/phpunit/MessageIndexTest.php
+++ b/tests/phpunit/MessageIndexTest.php
@@ -20,6 +20,99 @@
                ) );
        }
 
+       /**
+        * @dataProvider provideTestGetArrayDiff
+        */
+       public function testGetArrayDiff( $expected, $old, $new ) {
+               $actual = MessageIndex::getArrayDiff( $old, $new );
+               $this->assertEquals( $expected['keys'], $actual['keys'], 'key 
diff' );
+               $this->assertEquals( $expected['values'], $actual['values'], 
'value diff' );
+       }
+
+       public function provideTestGetArrayDiff() {
+               $tests = array();
+
+               // Addition
+               $old = array();
+               $new = array(
+                       'label' => 'carpet',
+               );
+               $expected = array(
+                       'keys' => array(
+                               'add' => array(
+                                       'label' => array(
+                                               array(),
+                                               array( 'carpet' ),
+                                       ),
+                               ),
+                               'del' => array(),
+                               'mod' => array(),
+                       ),
+                       'values' => array( 'carpet' ),
+               );
+               $tests[] = array( $expected, $old, $new );
+
+               // Deletion
+               $old = array(
+                       'bath' => array( 'goal', 'morals', 'coronation' ),
+               );
+               $new = array();
+               $expected = array(
+                       'keys' => array(
+                               'add' => array(),
+                               'del' => array(
+                                       'bath' => array(
+                                               array( 'goal', 'morals', 
'coronation' ),
+                                               array(),
+                                       ),
+                               ),
+                               'mod' => array(),
+                       ),
+                       'values' => array( 'goal', 'morals', 'coronation' ),
+               );
+               $tests[] = array( $expected, $old, $new );
+
+               // No change
+               $old = $new = array(
+                       'label' => 'carpet',
+                       'salt' => array( 'morals' ),
+                       'bath' => array( 'goal', 'morals', 'coronation' ),
+               );
+               $expected = array(
+                       'keys' => array(
+                               'add' => array(),
+                               'del' => array(),
+                               'mod' => array(),
+                       ),
+                       'values' => array(),
+               );
+               $tests[] = array( $expected, $old, $new );
+
+               // Modification
+               $old = array(
+                       'bath' => array( 'goal', 'morals', 'coronation' ),
+               );
+               $new = array(
+                       'bath' => array( 'goal', 'beliefs', 'coronation', 
'showcase' ),
+               );
+               $expected = array(
+                       'keys' => array(
+                               'add' => array(),
+                               'del' => array(),
+                               'mod' => array(
+                                       'bath' => array(
+                                               array( 'goal', 'morals', 
'coronation' ),
+                                               array( 'goal', 'beliefs', 
'coronation', 'showcase' ),
+                                       ),
+                               ),
+                       ),
+                       'values' => array( 'morals', 'beliefs', 'showcase' ),
+               );
+               $tests[] = array( $expected, $old, $new );
+
+               return $tests;
+       }
+
        protected static function getTestData() {
                static $data = null;
                if ( $data === null ) {
@@ -35,7 +128,8 @@
        public function testMessageIndexImplementation( $mi ) {
                $data = self::getTestData();
                /** @var 
TestableDatabaseMessageIndex|TestableCDBMessageIndex|TestableSerializedMessageIndex
 */
-               $mi->store( $data );
+               $diff = MessageIndex::getArrayDiff( array(), $data );
+               $mi->store( $data, $diff['keys'] );
 
                $tests = array_rand( $data, 10 );
                foreach ( $tests as $key ) {
@@ -79,8 +173,8 @@
 class TestableDatabaseMessageIndex extends DatabaseMessageIndex {
        // @codingStandardsIgnoreStart PHP CodeSniffer warns "Useless method 
overriding
        // detected", but store() and get() are protected in parent.
-       public function store( array $a ) {
-               parent::store( $a );
+       public function store( array $a, array $diff ) {
+               parent::store( $a, $diff );
        }
 
        public function get( $a ) {
@@ -91,8 +185,8 @@
 class TestableCDBMessageIndex extends CDBMessageIndex {
        // @codingStandardsIgnoreStart PHP CodeSniffer warns "Useless method 
overriding
        // detected", but store() and get() are protected in parent.
-       public function store( array $a ) {
-               parent::store( $a );
+       public function store( array $a, array $diff ) {
+               parent::store( $a, $diff );
        }
 
        public function get( $a ) {
@@ -103,8 +197,8 @@
 class TestableSerializedMessageIndex extends SerializedMessageIndex {
        // @codingStandardsIgnoreStart PHP CodeSniffer warns "Useless method 
overriding
        // detected", but store() and get() are protected in parent.
-       public function store( array $a ) {
-               parent::store( $a );
+       public function store( array $a, array $diff ) {
+               parent::store( $a, $diff );
        }
 
        public function get( $a ) {
@@ -115,8 +209,8 @@
 class TestableHashMessageIndex extends HashMessageIndex {
        // @codingStandardsIgnoreStart PHP CodeSniffer warns "Useless method 
overriding
        // detected", but store() and get() are protected in parent.
-       public function store( array $a ) {
-               parent::store( $a );
+       public function store( array $a, array $diff ) {
+               parent::store( $a, $diff );
        }
 
        public function get( $a ) {
diff --git a/utils/MessageIndex.php b/utils/MessageIndex.php
index b5505fe..09aa05e 100644
--- a/utils/MessageIndex.php
+++ b/utils/MessageIndex.php
@@ -91,9 +91,17 @@
        }
 
        /// @return array
-       abstract public function retrieve();
+       abstract public function retrieve( $forRebuild = false );
 
-       abstract protected function store( array $array );
+       abstract protected function store( array $array, array $diff );
+
+       protected function lock() {
+               return true;
+       }
+
+       protected function unlock() {
+               return true;
+       }
 
        public function rebuild() {
                static $recursion = 0;
@@ -109,8 +117,12 @@
 
                $groups = MessageGroups::singleton()->getGroups();
 
+               if ( !$this->lock() ) {
+                       throw new Exception( __CLASS__ . ': unable to acquire 
lock' );
+               }
+
                $new = $old = array();
-               $old = $this->retrieve();
+               $old = $this->retrieve( 'rebuild' );
                $postponed = array();
 
                /**
@@ -134,56 +146,101 @@
                        $this->checkAndAdd( $new, $g, true );
                }
 
-               $this->store( $new );
-               $this->clearMessageGroupStats( $old, $new );
+               $diff = self::getArrayDiff( $old, $new );
+               $this->store( $new, $diff['keys'] );
+               $this->unlock();
+               $this->clearMessageGroupStats( $diff );
+
                $recursion--;
 
                return $new;
        }
 
        /**
-        * Purge message group stats when set of keys have changed.
+        * Compares two associative arrays.
+        *
+        * Values must be a string or list of strings. Returns an array of 
added,
+        * deleted and modified keys as well as value changes (you can think 
values
+        * as categories and keys as pages). Each of the keys ('add', 'del', 
'mod'
+        * respectively) maps to an array whose keys are the changed keys of the
+        * original arrays and values are lists where first element contains the
+        * old value and the second element the new value.
+        *
+        * @code
+        * $a = [ 'a' => '1', 'b' => '2', 'c' => '3' ];
+        * $b = [ 'b' => '2', 'c' => [ '3', '2' ], 'd' => '4' ];
+        *
+        * self::getArrayDiff( $a, $b ) ) === [
+        *   'keys' => [
+        *     'add' => [ 'd' => [ [], [ '4' ] ] ],
+        *     'del' => [ 'a' => [ [ '1' ], [] ] ],
+        *     'mod' => [ 'c' => [ [ '3' ], [ '3', '2' ] ] ],
+        *   ],
+        *   'values' => [ 2, 4, 1 ]
+        * ];
+        * @endcode
+        *
         * @param array $old
         * @param array $new
+        * @return array
         */
-       protected function clearMessageGroupStats( array $old, array $new ) {
-               $changes = array();
+       public static function getArrayDiff( array $old, array $new ) {
+               $values = array();
+               $record = function ( $groups ) use ( &$values ) {
+                       foreach ( $groups as $group ) {
+                               $values[$group] = true;
+                       }
+               };
+
+               $keys = array(
+                       'add' => array(),
+                       'del' => array(),
+                       'mod' => array(),
+               );
 
                foreach ( $new as $key => $groups ) {
-                       // Using != here on purpose to ignore order of items
                        if ( !isset( $old[$key] ) ) {
-                               $changes[$key] = array( array(), (array)$groups 
);
+                               $keys['add'][$key] = array( array(), 
(array)$groups );
+                               $record( (array)$groups );
+                       // Using != here on purpose to ignore the order of items
                        } elseif ( $groups != $old[$key] ) {
-                               $changes[$key] = array( (array)$old[$key], 
(array)$groups );
+                               $keys['mod'][$key] = array( (array)$old[$key], 
(array)$groups );
+                               $record( array_diff( (array)$old[$key], 
(array)$groups ) );
+                               $record( array_diff( (array)$groups, 
(array)$old[$key] ) );
                        }
                }
 
                foreach ( $old as $key => $groups ) {
                        if ( !isset( $new[$key] ) ) {
-                               $changes[$key] = array( (array)$groups, array() 
);
+                               $keys['del'][$key] = array( (array)$groups, 
array() );
+                               $record( (array)$groups, array() );
                        }
                        // We already checked for diffs above
                }
 
-               $changedGroups = array();
-               foreach ( $changes as $data ) {
-                       foreach ( $data[0] as $group ) {
-                               $changedGroups[$group] = true;
-                       }
-                       foreach ( $data[1] as $group ) {
-                               $changedGroups[$group] = true;
-                       }
-               }
+               return array(
+                       'keys' => $keys,
+                       'values' => array_keys( $values ),
+               );
+       }
 
-               MessageGroupStats::clearGroup( array_keys( $changedGroups ) );
+       /**
+        * Purge stuff when set of keys have changed.
+        *
+        * @param array $diff
+        */
+       protected function clearMessageGroupStats( array $diff ) {
+               MessageGroupStats::clearGroup( $diff['values'] );
 
-               foreach ( $changes as $key => $data ) {
-                       list( $ns, $pagename ) = explode( ':', $key, 2 );
-                       $title = Title::makeTitle( $ns, $pagename );
-                       $handle = new MessageHandle( $title );
-                       list ( $oldGroups, $newGroups ) = $data;
-                       Hooks::run( 'TranslateEventMessageMembershipChange',
-                               array( $handle, $oldGroups, $newGroups ) );
+               foreach ( $diff['keys'] as $keys ) {
+                       foreach ( $keys as $key => $data ) {
+                               list( $ns, $pagename ) = explode( ':', $key, 2 
);
+                               $title = Title::makeTitle( $ns, $pagename );
+                               $handle = new MessageHandle( $title );
+                               list ( $oldGroups, $newGroups ) = $data;
+                               Hooks::run( 
'TranslateEventMessageMembershipChange',
+                                       array( $handle, $oldGroups, $newGroups 
) );
+                       }
                }
        }
 
@@ -281,7 +338,7 @@
        protected $filename = 'translate_messageindex.ser';
 
        /** @return array */
-       public function retrieve() {
+       public function retrieve( $forRebuild = false ) {
                if ( $this->index !== null ) {
                        return $this->index;
                }
@@ -296,7 +353,7 @@
                return $this->index;
        }
 
-       protected function store( array $array ) {
+       protected function store( array $array, array $diff ) {
                $file = TranslateUtils::cacheFile( $this->filename );
                file_put_contents( $file, serialize( $array ) );
                $this->index = $array;
@@ -324,13 +381,32 @@
         */
        protected $index;
 
+       protected function lock() {
+               $db = wfGetDB( DB_MASTER );
+
+               // Any transaction should be flushed after getting the lock to 
avoid
+               // stale pre-lock REPEATABLE-READ snapshot data.
+               $ok = $db->lock( 'translate-messageindex', __METHOD__, 30 );
+               if ( $ok ) {
+                       $db->commit( __METHOD__, 'flush' );
+               }
+
+               return $ok;
+       }
+
+       protected function unlock() {
+               $db = wfGetDB( DB_MASTER );
+
+               return $db->unlock( 'translate-messageindex', __METHOD__ );
+       }
+
        /** @return array */
-       public function retrieve() {
-               if ( $this->index !== null ) {
+       public function retrieve( $forRebuild = false ) {
+               if ( $this->index !== null && !$forRebuild ) {
                        return $this->index;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( $forRebuild ? DB_MASTER : DB_SLAVE );
                $res = $dbr->select( 'translate_messageindex', '*', array(), 
__METHOD__ );
                $this->index = array();
                foreach ( $res as $row ) {
@@ -358,18 +434,33 @@
                return $value;
        }
 
-       protected function store( array $array ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $rows = array();
+       protected function store( array $array, array $diff ) {
+               $updates = array();
 
-               foreach ( $array as $key => $value ) {
-                       $value = $this->serialize( $value );
-                       $rows[] = array( 'tmi_key' => $key, 'tmi_value' => 
$value );
+               foreach ( array( $diff['add'], $diff['mod'] ) as $changes ) {
+                       foreach ( $changes as $key => $data ) {
+                               list( $old, $new ) = $data;
+                               $updates[] = array(
+                                       'tmi_key' => $key,
+                                       'tmi_value' => $this->serialize( $new ),
+                               );
+                       }
                }
 
+               $index = array( 'tmi_key' );
+               $deletions = array_keys( $diff['del'] );
+
+               $dbw = wfGetDB( DB_MASTER );
                $dbw->startAtomic( __METHOD__ );
-               $dbw->delete( 'translate_messageindex', '*', __METHOD__ );
-               $dbw->insert( 'translate_messageindex', $rows, __METHOD__ );
+
+               if ( $updates !== array() ) {
+                       $dbw->replace( 'translate_messageindex', array( $index 
), $updates, __METHOD__ );
+               }
+
+               if ( $deletions !== array() ) {
+                       $dbw->delete( 'translate_messageindex', array( 
'tmi_key' => $deletions ), __METHOD__ );
+               }
+
                $dbw->endAtomic( __METHOD__ );
 
                $this->index = $array;
@@ -398,7 +489,7 @@
        }
 
        /** @return array */
-       public function retrieve() {
+       public function retrieve( $forRebuild = false ) {
                if ( $this->index !== null ) {
                        return $this->index;
                }
@@ -414,7 +505,7 @@
                return $this->index;
        }
 
-       protected function store( array $array ) {
+       protected function store( array $array, array $diff ) {
                $key = wfMemckey( $this->key );
                $this->cache->set( $key, $array );
 
@@ -452,7 +543,7 @@
        protected $filename = 'translate_messageindex.cdb';
 
        /** @return array */
-       public function retrieve() {
+       public function retrieve( $forRebuild = false ) {
                $reader = $this->getReader();
                // This must be below the line above, which may fill the index
                if ( $this->index !== null ) {
@@ -489,7 +580,7 @@
                return $value;
        }
 
-       protected function store( array $array ) {
+       protected function store( array $array, array $diff ) {
                $this->reader = null;
 
                $file = TranslateUtils::cacheFile( $this->filename );
@@ -538,7 +629,7 @@
        protected $index = array();
 
        /** @return array */
-       public function retrieve() {
+       public function retrieve( $forRebuild = false ) {
                return $this->index;
        }
 
@@ -555,10 +646,10 @@
                }
        }
 
-       protected function store( array $array ) {
+       protected function store( array $array, array $diff ) {
                $this->index = $array;
        }
 
-       protected function clearMessageGroupStats( array $old, array $new ) {
+       protected function clearMessageGroupStats( array $diff ) {
        }
 }

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I742884cf48eec807c623d3c7e249f2b254406e8f
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/extensions/Translate
Gerrit-Branch: master
Gerrit-Owner: Nikerabbit <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Amire80 <[email protected]>
Gerrit-Reviewer: Nikerabbit <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: Santhosh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to