messagecache: avoid caching message pages that do not override
authorAaron Schulz <aschulz@wikimedia.org>
Tue, 2 Oct 2018 17:59:28 +0000 (10:59 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Wed, 10 Oct 2018 19:35:07 +0000 (12:35 -0700)
Clean up individual message cache handling when the master key is
volatile. The goal is not to treat the message as gone but to re-fetch
it when accessed rather than cache the value in the main/process cache.

Bug: T193271
Change-Id: I4bcaf10c1516e7c96c2d0963722affeaf80272e0

includes/cache/MessageCache.php

index bf2ed2e..69cdf04 100644 (file)
@@ -464,13 +464,7 @@ class MessageCache {
 
                $cache = [];
 
-               # Common conditions
-               $conds = [
-                       'page_is_redirect' => 0,
-                       'page_namespace' => NS_MEDIAWIKI,
-               ];
-
-               $mostused = [];
+               $mostused = []; // list of "<cased message key>/<code>"
                if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
                        if ( !$this->cache->has( $wgLanguageCode ) ) {
                                $this->load( $wgLanguageCode );
@@ -481,6 +475,14 @@ class MessageCache {
                        }
                }
 
+               // Get the list of software-defined messages in core/extensions
+               $overridable = array_flip( Language::getMessageKeysFor( $wgLanguageCode ) );
+
+               // Common conditions
+               $conds = [
+                       'page_is_redirect' => 0,
+                       'page_namespace' => NS_MEDIAWIKI,
+               ];
                if ( count( $mostused ) ) {
                        $conds['page_title'] = $mostused;
                } elseif ( $code !== $wgLanguageCode ) {
@@ -492,31 +494,28 @@ class MessageCache {
                                $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
                }
 
-               # Conditions to fetch oversized pages to ignore them
-               $bigConds = $conds;
-               $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
-
-               # Load titles for all oversized pages in the MediaWiki namespace
+               // Set the stubs for oversized software-defined messages in the main cache map
                $res = $dbr->select(
                        'page',
                        [ 'page_title', 'page_latest' ],
-                       $bigConds,
+                       array_merge( $conds, [ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-big"
                );
                foreach ( $res as $row ) {
-                       $cache[$row->page_title] = '!TOO BIG';
+                       $name = $this->contLang->lcfirst( $row->page_title );
+                       // Include entries/stubs for all keys in $mostused in adaptive mode
+                       if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+                               $cache[$row->page_title] = '!TOO BIG';
+                       }
                        // At least include revision ID so page changes are reflected in the hash
                        $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                }
 
-               # Conditions to load the remaining pages with their contents
-               $smallConds = $conds;
-               $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
-
+               // Set the text for small software-defined messages in the main cache map
                $res = $dbr->select(
                        [ 'page', 'revision', 'text' ],
-                       [ 'page_title', 'old_id', 'old_text', 'old_flags' ],
-                       $smallConds,
+                       [ 'page_title', 'page_latest', 'old_id', 'old_text', 'old_flags' ],
+                       array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
                        __METHOD__ . "($code)-small",
                        [],
                        [
@@ -524,23 +523,30 @@ class MessageCache {
                                'text' => [ 'JOIN', 'rev_text_id=old_id' ],
                        ]
                );
-
                foreach ( $res as $row ) {
-                       $text = Revision::getRevisionText( $row );
-                       if ( $text === false ) {
-                               // Failed to fetch data; possible ES errors?
-                               // Store a marker to fetch on-demand as a workaround...
-                               // TODO Use a differnt marker
-                               $entry = '!TOO BIG';
-                               wfDebugLog(
-                                       'MessageCache',
-                                       __METHOD__
-                                       . ": failed to load message page text for {$row->page_title} ($code)"
-                               );
+                       $name = $this->contLang->lcfirst( $row->page_title );
+                       // Include entries/stubs for all keys in $mostused in adaptive mode
+                       if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+                               $text = Revision::getRevisionText( $row );
+                               if ( $text === false ) {
+                                       // Failed to fetch data; possible ES errors?
+                                       // Store a marker to fetch on-demand as a workaround...
+                                       // TODO Use a differnt marker
+                                       $entry = '!TOO BIG';
+                                       wfDebugLog(
+                                               'MessageCache',
+                                               __METHOD__
+                                               . ": failed to load message page text for {$row->page_title} ($code)"
+                                       );
+                               } else {
+                                       $entry = ' ' . $text;
+                               }
+                               $cache[$row->page_title] = $entry;
                        } else {
-                               $entry = ' ' . $text;
+                               // T193271: cache object gets too big and slow to generate.
+                               // At least include revision ID so page changes are reflected in the hash.
+                               $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
                        }
-                       $cache[$row->page_title] = $entry;
                }
 
                $cache['VERSION'] = MSG_CACHE_VERSION;
@@ -799,9 +805,8 @@ class MessageCache {
                Hooks::run( 'MessageCache::get', [ &$lckey ] );
 
                // Loop through each language in the fallback list until we find something useful
-               $lang = wfGetLangObj( $langcode );
                $message = $this->getMessageFromFallbackChain(
-                       $lang,
+                       wfGetLangObj( $langcode ),
                        $lckey,
                        !$this->mDisable && $useDB
                );
@@ -893,7 +898,6 @@ class MessageCache {
                                        $this->getMessagePageName( $langcode, $uckey ),
                                        $langcode
                                );
-
                                if ( $message !== false ) {
                                        return $message;
                                }
@@ -968,44 +972,54 @@ class MessageCache {
                $this->load( $code );
 
                $entry = $this->cache->getField( $code, $title );
+
                if ( $entry !== null ) {
+                       // Message page exists as an override of a software messages
                        if ( substr( $entry, 0, 1 ) === ' ' ) {
                                // The message exists and is not '!TOO BIG'
                                return (string)substr( $entry, 1 );
                        } elseif ( $entry === '!NONEXISTENT' ) {
+                               // The text might be '-' or missing due to some data loss
                                return false;
                        }
-                       // Fall through and try invididual message cache below
-               } else {
-                       // Message does not have a MediaWiki page definition
-                       $message = false;
-                       Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
-                       if ( $message !== false ) {
-                               $this->cache->setField( $code, $title, ' ' . $message );
-                       } else {
-                               $this->cache->setField( $code, $title, '!NONEXISTENT' );
-                       }
-
-                       return $message;
-               }
-
-               if ( $this->cacheVolatile[$code] ) {
-                       $entry = false;
-                       // Make sure that individual keys respect the WAN cache holdoff period too
-                       LoggerFactory::getInstance( 'MessageCache' )->debug(
-                               __METHOD__ . ': loading volatile key \'{titleKey}\'',
-                               [ 'titleKey' => $title, 'code' => $code ] );
+                       // Load the message page, utilizing the individual message cache.
+                       // If the page does not exist, there will be no hook handler fallbacks.
+                       $entry = $this->loadCachedMessagePageEntry(
+                               $title,
+                               $code,
+                               $this->cache->getField( $code, 'HASH' )
+                       );
                } else {
-                       // Try the individual message cache
+                       // Message page does not exist or does not override a software message.
+                       // Load the message page, utilizing the individual message cache.
                        $entry = $this->loadCachedMessagePageEntry(
                                $title,
                                $code,
                                $this->cache->getField( $code, 'HASH' )
                        );
+                       if ( substr( $entry, 0, 1 ) !== ' ' ) {
+                               // Message does not have a MediaWiki page definition; try hook handlers
+                               $message = false;
+                               Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
+                               if ( $message !== false ) {
+                                       $this->cache->setField( $code, $title, ' ' . $message );
+                               } else {
+                                       $this->cache->setField( $code, $title, '!NONEXISTENT' );
+                               }
+
+                               return $message;
+                       }
                }
 
                if ( $entry !== false && substr( $entry, 0, 1 ) === ' ' ) {
-                       $this->cache->setField( $code, $title, $entry );
+                       if ( $this->cacheVolatile[$code] ) {
+                               // Make sure that individual keys respect the WAN cache holdoff period too
+                               LoggerFactory::getInstance( 'MessageCache' )->debug(
+                                       __METHOD__ . ': loading volatile key \'{titleKey}\'',
+                                       [ 'titleKey' => $title, 'code' => $code ] );
+                       } else {
+                               $this->cache->setField( $code, $title, $entry );
+                       }
                        // The message exists, so make sure a string is returned
                        return (string)substr( $entry, 1 );
                }