From 8ebec93feab9b844f3229645962d82c429ba84fa Mon Sep 17 00:00:00 2001 From: Aaron Schulz Date: Fri, 2 Sep 2016 07:19:13 -0700 Subject: [PATCH] Cache NS_TEMPLATE/NS_FILE titles in LinkCache persistently Queries from parsing are the top use of selectRow() according to reverse flame graphs on xenon. This does not bother with pages in namespaces less likely to be included. Change-Id: Ica5d6e52c830cd71effff21933b8c64691082c11 --- includes/ServiceWiring.php | 3 +- includes/Title.php | 1 + includes/cache/LinkCache.php | 82 +++++++++++++++---- includes/page/WikiPage.php | 7 ++ .../includes/linker/LinkRendererTest.php | 3 +- 5 files changed, 77 insertions(+), 19 deletions(-) diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 33569e6d1b..8734bd6836 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -166,7 +166,8 @@ return [ 'LinkCache' => function( MediaWikiServices $services ) { return new LinkCache( - $services->getTitleFormatter() + $services->getTitleFormatter(), + ObjectCache::getMainWANInstance() ); }, diff --git a/includes/Title.php b/includes/Title.php index 24bad815ea..5e5a1b72b0 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -4385,6 +4385,7 @@ class Title implements LinkTarget { $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ], $fname ); + MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this ); } ), DeferredUpdates::PRESEND diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php index 6a602dfb2f..23cc26d5e2 100644 --- a/includes/cache/LinkCache.php +++ b/includes/cache/LinkCache.php @@ -29,19 +29,17 @@ use MediaWiki\MediaWikiServices; * @ingroup Cache */ class LinkCache { - /** - * @var HashBagOStuff - */ + /** @var HashBagOStuff */ private $mGoodLinks; - /** - * @var HashBagOStuff - */ + /** @var HashBagOStuff */ private $mBadLinks; + /** @var WANObjectCache */ + private $wanCache; + + /** @var bool */ private $mForUpdate = false; - /** - * @var TitleFormatter - */ + /** @var TitleFormatter */ private $titleFormatter; /** @@ -50,9 +48,10 @@ class LinkCache { */ const MAX_SIZE = 10000; - public function __construct( TitleFormatter $titleFormatter ) { + public function __construct( TitleFormatter $titleFormatter, WANObjectCache $cache ) { $this->mGoodLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] ); $this->mBadLinks = new HashBagOStuff( [ 'maxKeys' => self::MAX_SIZE ] ); + $this->wanCache = $cache; $this->titleFormatter = $titleFormatter; } @@ -244,15 +243,31 @@ class LinkCache { return 0; } - // Some fields heavily used for linking... - $db = $this->mForUpdate ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA ); + // Cache template/file pages as they are less often viewed but heavily used + if ( $this->mForUpdate ) { + $row = $this->fetchPageRow( wfGetDB( DB_MASTER ), $nt ); + } elseif ( $this->isCacheable( $nt ) ) { + // These pages are often transcluded heavily, so cache them + $cache = $this->wanCache; + $row = $cache->getWithSetCallback( + $cache->makeKey( 'page', $nt->getNamespace(), sha1( $nt->getDBkey() ) ), + $cache::TTL_DAY, + function ( $curValue, &$ttl, array &$setOpts ) use ( $cache, $nt ) { + $dbr = wfGetDB( DB_REPLICA ); + $setOpts += Database::getCacheSetOptions( $dbr ); - $row = $db->selectRow( 'page', self::getSelectFields(), - [ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ], - __METHOD__ - ); + $row = $this->fetchPageRow( $dbr, $nt ); + $mtime = $row ? wfTimestamp( TS_UNIX, $row->page_touched ) : false; + $ttl = $cache->adaptiveTTL( $mtime, $ttl ); - if ( $row !== false ) { + return $row; + } + ); + } else { + $row = $this->fetchPageRow( wfGetDB( DB_REPLICA ), $nt ); + } + + if ( $row ) { $this->addGoodLinkObjFromRow( $nt, $row ); $id = intval( $row->page_id ); } else { @@ -263,6 +278,39 @@ class LinkCache { return $id; } + private function isCacheable( LinkTarget $title ) { + return ( $title->inNamespace( NS_TEMPLATE ) || $title->inNamespace( NS_FILE ) ); + } + + private function fetchPageRow( IDatabase $db, LinkTarget $nt ) { + $fields = self::getSelectFields(); + if ( $this->isCacheable( $nt ) ) { + $fields[] = 'page_touched'; + } + + return $db->selectRow( + 'page', + $fields, + [ 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ], + __METHOD__ + ); + } + + /** + * Purge the link cache for a title + * + * @param LinkTarget $title + * @since 1.28 + */ + public function invalidateTitle( LinkTarget $title ) { + if ( $this->isCacheable( $title ) ) { + $cache = ObjectCache::getMainWANInstance(); + $cache->delete( + $cache->makeKey( 'page', $title->getNamespace(), sha1( $title->getDBkey() ) ) + ); + } + } + /** * Clears cache */ diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index 8c2d59449b..f1e59de4b5 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -21,6 +21,7 @@ */ use \MediaWiki\Logger\LoggerFactory; +use \MediaWiki\MediaWikiServices; /** * Class representing a MediaWiki article and history. @@ -3321,6 +3322,8 @@ class WikiPage implements Page, IDBAccessObject { $title->purgeSquid(); $title->deleteTitleProtection(); + MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); + if ( $title->getNamespace() == NS_CATEGORY ) { // Load the Category object, which will schedule a job to create // the category table row if necessary. Checking a replica DB is ok @@ -3346,6 +3349,8 @@ class WikiPage implements Page, IDBAccessObject { $title->touchLinks(); $title->purgeSquid(); + MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); + // File cache HTMLFileCache::clearFileCache( $title ); InfoAction::invalidateCache( $title ); @@ -3389,6 +3394,8 @@ class WikiPage implements Page, IDBAccessObject { // Invalidate the caches of all pages which redirect here DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'redirect' ) ); + MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title ); + // Purge CDN for this page only $title->purgeSquid(); // Clear file cache for this page only diff --git a/tests/phpunit/includes/linker/LinkRendererTest.php b/tests/phpunit/includes/linker/LinkRendererTest.php index 91789c53b5..70c0ece215 100644 --- a/tests/phpunit/includes/linker/LinkRendererTest.php +++ b/tests/phpunit/includes/linker/LinkRendererTest.php @@ -135,8 +135,9 @@ class LinkRendererTest extends MediaWikiLangTestCase { } public function testGetLinkClasses() { + $wanCache = ObjectCache::getMainWANInstance(); $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter(); - $linkCache = new LinkCache( $titleFormatter ); + $linkCache = new LinkCache( $titleFormatter, $wanCache ); $foobarTitle = new TitleValue( NS_MAIN, 'FooBar' ); $redirectTitle = new TitleValue( NS_MAIN, 'Redirect' ); $userTitle = new TitleValue( NS_USER, 'Someuser' ); -- 2.20.1