EBernhardson (WMF) has submitted this change and it was merged.

Change subject: Remove unnecessary round trips to memcache
......................................................................


Remove unnecessary round trips to memcache

Couple small optimizations that remove trips to memcache when the indexed
values dont change.

Change-Id: Ie4c2d48f4f7a56ccbc87365be575982fa4ad8596
---
M includes/Data/ObjectManager.php
1 file changed, 129 insertions(+), 57 deletions(-)

Approvals:
  EBernhardson (WMF): Verified; Looks good to me, approved



diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index 08657a7..3d1d999 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -47,6 +47,8 @@
 }
 
 // Backing stores, typically in SQL
+// Note that while ObjectLocator implements the above ObjectStorage interface, 
ObjectManger
+// cant use this interface because backing stores deal in rows, and OM deals 
in objects.
 interface WritableObjectStorage extends ObjectStorage {
        function insert( array $row );
        function update( array $old, array $new );
@@ -164,8 +166,9 @@
        }
 
        public function getIterator() {
-               throw new \Exception( 'Not Implemented' );
+               return $this->storage->getIterator();
        }
+
        /**
         * All queries must be against the same index. Results are equivilent to
         * array_map, maintaining order and key relationship between input 
$queries
@@ -293,7 +296,6 @@
 
 /**
  * Writable indexes. Error handling is all wrong currently.
- * Cant implement WritableObjectStorage because 'update' method signature 
differs
  */
 class ObjectManager extends ObjectLocator {
        // @var SplObjectStorage $loaded If the object exists then an 'update' 
is issued, otherwise 'insert'
@@ -343,13 +345,13 @@
                try {
                        $old = $this->loaded[$object];
                        $new = $this->mapper->toStorageRow( $object );
+                       if ( self::arrayEquals( $old, $new ) ) {
+                               return;
+                       }
                        foreach ( $new as $k => $x ) {
                                if ( $x !== null && !is_scalar( $x ) ) {
                                        throw new \RuntimeException( "Expected 
mapper to return all scalars, but '$k' is " . gettype( $x ) );
                                }
-                       }
-                       if ( self::arrayEquals( $old, $new ) ) {
-                               return;
                        }
                        $this->storage->update( $old, $new );
                        foreach ( $this->lifecycleHandlers as $handler ) {
@@ -358,19 +360,6 @@
                        $this->loaded[$object] = $new;
                } catch ( \Exception $e ) {
                        throw new PersistenceException( 'failed update', null, 
$e );
-               }
-       }
-
-       static public function arrayEquals( array $old, array $new ) {
-               return array_diff_assoc( $old, $new ) === array()
-                       && array_diff_assoc( $new, $old ) === array();
-       }
-
-       static public function makeArray( $input ) {
-               if ( is_array( $input ) ) {
-                       return $input;
-               } else {
-                       return array( $input );
                }
        }
 
@@ -395,6 +384,19 @@
                $object = parent::load( $row );
                $this->loaded[$object] = $row;
                return $object;
+       }
+
+       static public function arrayEquals( array $old, array $new ) {
+               return array_diff_assoc( $old, $new ) === array()
+                       && array_diff_assoc( $new, $old ) === array();
+       }
+
+       static public function makeArray( $input ) {
+               if ( is_array( $input ) ) {
+                       return $input;
+               } else {
+                       return array( $input );
+               }
        }
 
        /**
@@ -626,9 +628,9 @@
                return $this->indexed;
        }
 
-       public function canAnswer( array $keys, array $options ) {
-               sort( $keys );
-               if ( $keys !== $this->indexedOrdered ) {
+       public function canAnswer( array $featureColumns, array $options ) {
+               sort( $featureColumns );
+               if ( $featureColumns !== $this->indexedOrdered ) {
                        return false;
                }
                if ( isset( $options['limit'] ) ) {
@@ -643,29 +645,43 @@
                return true;
        }
 
-       public function onAfterInsert( $object, array $new) {
+       public function onAfterInsert( $object, array $new ) {
                $indexed = ObjectManager::splitFromRow( $new , $this->indexed );
-               // is un-indexable a bail-worthy occasion? Probably not
+               // is un-indexable a bail-worthy occasion? Probably not but 
makes debugging easier
                if ( !$indexed ) {
                        throw new \MWException( 'Unindexable row: ' 
.json_encode( $new ) );
                }
-               $this->addToIndex( $indexed, $new );
+               $compacted = $this->rowCompactor->compactRow( $new );
+               // give implementing index option to create rather than append
+               if ( !$this->maybeCreateIndex( $indexed, $new, $compacted ) ) {
+                       // fallback to append
+                       $this->addToIndex( $indexed, $compacted );
+               }
        }
 
        public function onAfterUpdate( $object, array $old, array $new ) {
                $oldIndexed = ObjectManager::splitFromRow( $old, $this->indexed 
);
                $newIndexed = ObjectManager::splitFromRow( $new, $this->indexed 
);
-               // TODO: optimize $oldIndexed === $newIndexed, which should be 
common
-               // TODO: optimize out occurances where data changed but not the 
values
-               //       indexed or stored in the index(for shallow indexes)
                if ( !$oldIndexed ) {
                        throw new \MWException( 'Unindexable row: ' 
.json_encode( $oldIndexed ) );
                }
                if ( !$newIndexed ) {
                        throw new \MWException( 'Unindexable row: ' 
.json_encode( $newIndexed ) );
                }
-               $this->removeFromIndex( $oldIndexed, $old );
-               $this->addToIndex( $newIndexed, $new );
+               $oldCompacted = $this->rowCompactor->compactRow( $old );
+               $newCompacted = $this->rowCompactor->compactRow( $new );
+               if ( ObjectManager::arrayEquals( $oldIndexed, $newIndexed ) ) {
+                       if ( ObjectManager::arrayEquals( $oldCompacted, 
$newCompacted ) ) {
+                               // Nothing changed in the index
+                               return;
+                       }
+                       // object representation in feature bucket has changed
+                       $this->replaceInIndex( $oldIndexed, $oldCompacted, 
$newCompacted );
+               } else {
+                       // object has moved from one feature bucket to another
+                       $this->removeFromIndex( $oldIndexed, $oldCompacted );
+                       $this->addToIndex( $newIndexed, $newCompacted );
+               }
        }
 
        public function onAfterRemove( $object, array $old ) {
@@ -702,8 +718,10 @@
                        $key = $this->cacheKey( $query );
                        // allow for duplicate queries
                        $keyToIdx[$key][] = $idx;
+                       $idxToKey[$idx] = $key;
                        if ( !isset( $keyToQuery[$key] ) ) {
-                               $idxToKey[$idx] = $key;
+                               // These results will be merged into the query 
results, and as such need binary
+                               // uuid's as would be received from storage
                                $keyToQuery[$key] = UUID::convertUUIDs( $query 
);
                        }
                }
@@ -748,6 +766,19 @@
                return $results;
        }
 
+       // Called prior to self::addToIndex only when new objects as inserted.  
Gives the
+       // opportunity for indexes to create rather than append if this object 
signifys a new
+       // feature list.
+       protected function maybeCreateIndex( array $indexed, array $sourceRow, 
array $compacted ) {
+               return false;
+       }
+
+       // Since these affect the same $indexed bucket implementing classes can 
likely
+       // do less round trips than this.
+       protected function replaceInIndex( array $indexed, array $old, array 
$new ) {
+               $this->removeFromIndex( $indexed, $old );
+               $this->addToIndex( $indexed, $new );
+       }
 
        protected function cacheKey( array $attributes ) {
                global $wgFlowDefaultWikiDb; // meh
@@ -792,12 +823,15 @@
        }
 
        protected function addToIndex( array $indexed, array $row ) {
-               $row = $this->rowCompactor->compactRow( $row );
                $this->cache->set( $this->cacheKey( $indexed ), array( $row ) );
        }
 
        protected function removeFromIndex( array $indexed, array $row ) {
                $this->cache->delete( $this->cacheKey( $indexed ) );
+       }
+
+       protected function replaceInIndex( array $indexed, array $oldRow, array 
$newRow ) {
+               $this->cache->set( $this->cacheKey( $indexed ), array( $newRow 
) );
        }
 }
 
@@ -827,24 +861,35 @@
                }
        }
 
+       public function canAnswer( array $keys, array $options ) {
+               if ( !parent::canAnswer( $keys, $options ) ) {
+                       return false;
+               }
+               if ( isset( $options['sort'], $options['order'] ) ) {
+                       return $options['sort'] === $this->options['sort']
+                               && $options['order'] === 
$this->options['order'];
+               }
+               return true;
+       }
+
        public function getLimit() {
                return $this->options['limit'];
        }
 
-       protected function addToIndex( array $indexed, array $row ) {
-               $create = call_user_func( $this->options['create'], $indexed + 
$row );
-
-               $row = $this->rowCompactor->compactRow( $row );
-               $cacheKey = $this->cacheKey( $indexed );
-               if ( $create ) {
-                       $this->cache->set( $cacheKey, array( $row ) );
-                       return;
+       protected function maybeCreateIndex( array $indexed, array $sourceRow, 
array $compacted ) {
+               if ( call_user_func( $this->options['create'], $sourceRow ) ) {
+                       $this->cache->set( $this->cacheKey( $indexed ), array( 
$compacted ) );
+                       return true;
                }
+               return false;
+       }
+
+       protected function addToIndex( array $indexed, array $row ) {
                $self = $this;
                // If this used redis instead of memcached, could it add to 
index in position
                // without retry possibility? need a single number that will 
properly sort rows.
                $this->cache->merge(
-                       $cacheKey,
+                       $this->cacheKey( $indexed ),
                        function( BagOStuff $cache, $key, $value ) use( $self, 
$row ) {
                                if ( $value === false ) {
                                        return false;
@@ -853,16 +898,21 @@
                                if ( $idx !== false ) {
                                        return false; // This row already 
exists somehow
                                }
-                               $value[] = $row;
-                               $value = $self->sortIndex( $value );
-                               return $self->limitIndexSize( $value );
+                               $retval = $value;
+                               $retval[] = $row;
+                               $retval = $self->sortIndex( $retval );
+                               $retval = $self->limitIndexSize( $retval );
+                               if ( $retval === $value ) {
+                                       // object didnt fit in index
+                                       return false;
+                               } else {
+                                       return $retval;
+                               }
                        }
                );
        }
 
        protected function removeFromIndex( array $indexed, array $row ) {
-               $row = $this->rowCompactor->compactRow( $row );
-
                $this->cache->merge(
                        $this->cacheKey( $indexed ),
                        function( BagOStuff $cache, $key, $value ) use( $row ) {
@@ -875,6 +925,31 @@
                                }
                                unset( $value[$idx] );
                                return $value;
+                       }
+               );
+       }
+
+       protected function replaceInIndex( array $indexed, array $oldRow, array 
$newRow ) {
+               $this->cache->merge(
+                       $this->cacheKey( $indexed ),
+                       function( BagOStuff $cache, $key, $value ) use( 
$oldRow, $newRow ) {
+                               if ( $value === false ) {
+                                       return false;
+                               }
+                               $retval = $value;
+                               $idx = array_search( $oldRow, $retval );
+                               if ( $idx !== false ) {
+                                       unset( $retval[$idx] );
+                               }
+                               $retval[] = $newRow;
+                               $retval = $self->sortIndex( $retval );
+                               $retval = $self->limitIndexSize( $retval );
+                               if ( $value === $retval ) {
+                                       // new item didnt fit in index and old 
item wasnt found in index
+                                       return false;
+                               } else {
+                                       return $value;
+                               }
                        }
                );
        }
@@ -907,20 +982,12 @@
 
                return $options;
        }
-
-       public function canAnswer( array $keys, array $options ) {
-               if ( !parent::canAnswer( $keys, $options ) ) {
-                       return false;
-               }
-               if ( isset( $options['sort'], $options['order'] ) ) {
-                       return $options['sort'] === $this->options['sort']
-                               && $options['order'] === 
$this->options['order'];
-               }
-               return true;
-       }
-
 }
 
+/**
+ * Removes the feature fields from stored array since its duplicating the 
cache key values
+ * Re-adds them when retreiving from cache.
+ */
 class FeatureCompactor implements Compactor {
        public function __construct( array $indexedColumns ) {
                $this->indexed = $indexedColumns;
@@ -981,6 +1048,11 @@
        }
 }
 
+/**
+ * Backs an index with a UniqueFeatureIndex.  This index will store only the 
primary key
+ * values from the unique index, and on retrieval from cache will materialize 
the primary key
+ * values into full rows from the unique index.
+ */
 class ShallowCompactor implements Compactor {
        public function __construct( Compactor $inner, UniqueFeatureIndex 
$shallow, array $sortedColumns ) {
                $this->inner = $inner;

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ie4c2d48f4f7a56ccbc87365be575982fa4ad8596
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: EBernhardson (WMF) <ebernhard...@wikimedia.org>
Gerrit-Reviewer: EBernhardson (WMF) <ebernhard...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to