MessageCache: use APC for local caching, rather than files
authorOri Livneh <ori@wikimedia.org>
Sun, 9 Aug 2015 04:59:47 +0000 (21:59 -0700)
committerOri Livneh <ori@wikimedia.org>
Mon, 10 Aug 2015 19:39:11 +0000 (12:39 -0700)
In addition to eliminating disk IO in a hot path, using APC spares us from
having to serialize and unserialize cache arrays. Since we're not serializing,
though, we don't have a string representation to hash, so use a random string
instead. (The code already treats the association of hash string to cache as
purely symbolic, so this is not problematic.)

Whereas the hash was previously stored as the first 32 bytes of each cache
file, we now store it as an array key instead (like VERSION and EXPIRY were
already). Because this changes the structure of cached data, we have to bump
MSG_CACHE_VERSION.

While we're here, make MessageCache::getLocalCache() and
MessageCache::saveToLocal() protected, make their signatures more
consistent with other methods in this class. While they were (implicitly)
public before, there are absolutely no external callers in Core or
extensions[0][1], so we can skip the standard deprecation process.

[0]: https://github.com/search?q=%40wikimedia+getlocalcache&type=Code&utf8=%E2%9C%93
[1]: https://github.com/search?utf8=%E2%9C%93&q=%40wikimedia+savetolocal&type=Code

Change-Id: I020617d2df2a8f0f243b85f3383dc7b16f15aaad

RELEASE-NOTES-1.26
includes/cache/MessageCache.php

index b470272..914bf15 100644 (file)
@@ -53,6 +53,8 @@ production.
   the $wgMainCacheType settings.
 * Callers needing fast light-weight data stores use $wgMainStash to select
   the store type from $wgObjectCaches. The default is the local database.
+* Interface message overrides in the MediaWiki namespace will now be cached in
+  memcached and APC (if available), rather than memcached and local files.
 
 ==== External libraries ====
 * Update es5-shim from v4.0.0 to v4.1.5.
index 67a7461..8fe0bf9 100644 (file)
@@ -25,7 +25,7 @@
  * MediaWiki message cache structure version.
  * Bump this whenever the message cache format has changed.
  */
-define( 'MSG_CACHE_VERSION', 1 );
+define( 'MSG_CACHE_VERSION', 2 );
 
 /**
  * Memcached timeout when loading a key.
@@ -58,6 +58,7 @@ class MessageCache {
         * second level is message key and the values are either message
         * content prefixed with space, or !NONEXISTENT for negative
         * caching.
+        * @var array $mCache
         */
        protected $mCache;
 
@@ -154,6 +155,8 @@ class MessageCache {
         * @param int $expiry Lifetime for cache. @see $mExpiry.
         */
        function __construct( $memCached, $useDB, $expiry ) {
+               global $wgUseLocalMessageCache;
+
                if ( !$memCached ) {
                        $memCached = wfGetCache( CACHE_NONE );
                }
@@ -162,6 +165,12 @@ class MessageCache {
                $this->mDisable = !$useDB;
                $this->mExpiry = $expiry;
 
+               if ( $wgUseLocalMessageCache ) {
+                       $this->localCache = ObjectCache::newAccelerator( array(), CACHE_NONE );
+               } else {
+                       $this->localCache = wfGetCache( CACHE_NONE );
+               }
+
                $this->wanCache = ObjectCache::getMainWANInstance();
        }
 
@@ -180,70 +189,25 @@ class MessageCache {
        }
 
        /**
-        * Try to load the cache from a local file.
+        * Try to load the cache from APC.
         *
-        * @param string $hash The hash of contents, to check validity.
         * @param string $code Optional language code, see documenation of load().
-        * @return array The cache array
+        * @return array|bool The cache array, or false if not in cache.
         */
-       function getLocalCache( $hash, $code ) {
-               global $wgCacheDirectory;
-
-               $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
-
-               # Check file existence
-               MediaWiki\suppressWarnings();
-               $file = fopen( $filename, 'r' );
-               MediaWiki\restoreWarnings();
-               if ( !$file ) {
-                       return false; // No cache file
-               }
-
-               // Check to see if the file has the hash specified
-               $localHash = fread( $file, 32 );
-               if ( $hash === $localHash ) {
-                       // All good, get the rest of it
-                       $serialized = '';
-                       while ( !feof( $file ) ) {
-                               $serialized .= fread( $file, 100000 );
-                       }
-                       fclose( $file );
-
-                       return unserialize( $serialized );
-               } else {
-                       fclose( $file );
-
-                       return false; // Wrong hash
-               }
+       protected function getLocalCache( $code ) {
+               $cacheKey = wfMemcKey( __CLASS__, $code );
+               return $this->localCache->get( $cacheKey );
        }
 
        /**
-        * Save the cache to a local file.
-        * @param string $serialized
-        * @param string $hash
+        * Save the cache to APC.
+        *
         * @param string $code
+        * @param array $cache The cache array
         */
-       function saveToLocal( $serialized, $hash, $code ) {
-               global $wgCacheDirectory;
-
-               $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
-               wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
-
-               MediaWiki\suppressWarnings();
-               $file = fopen( $filename, 'w' );
-               MediaWiki\restoreWarnings();
-
-               if ( !$file ) {
-                       wfDebug( "Unable to open local cache file for writing\n" );
-
-                       return;
-               }
-
-               fwrite( $file, $hash . $serialized );
-               fclose( $file );
-               MediaWiki\suppressWarnings();
-               chmod( $filename, 0666 );
-               MediaWiki\restoreWarnings();
+       protected function saveToLocalCache( $code, $cache ) {
+               $cacheKey = wfMemcKey( __CLASS__, $code );
+               $this->localCache->set( $cacheKey, $cache );
        }
 
        /**
@@ -305,8 +269,8 @@ class MessageCache {
                if ( $wgUseLocalMessageCache ) {
                        list( $hash, $hashExpired ) = $this->getValidationHash( $code );
                        if ( $hash ) {
-                               $cache = $this->getLocalCache( $hash, $code );
-                               if ( !$cache ) {
+                               $cache = $this->getLocalCache( $code );
+                               if ( !$cache || !isset( $cache['HASH'] ) || $cache['HASH'] !== $hash ) {
                                        $where[] = 'local cache is empty or has the wrong hash';
                                } elseif ( $this->isCacheExpired( $cache ) ) {
                                        $where[] = 'local cache is expired';
@@ -559,6 +523,7 @@ class MessageCache {
                }
 
                $cache['VERSION'] = MSG_CACHE_VERSION;
+               $cache['HASH'] = wfRandomString( 8 );
                $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
 
                return $cache;
@@ -668,10 +633,8 @@ class MessageCache {
 
                # Save to local cache
                if ( $wgUseLocalMessageCache ) {
-                       $serialized = serialize( $cache );
-                       $hash = md5( $serialized );
-                       $this->setValidationHash( $code, $hash );
-                       $this->saveToLocal( $serialized, $hash, $code );
+                       $this->setValidationHash( $code, $cache['HASH'] );
+                       $this->saveToLocalCache( $code, $cache );
                }
 
                return $success;