summaryrefslogtreecommitdiff
path: root/www/wiki/includes/deferred/WANCacheReapUpdate.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/includes/deferred/WANCacheReapUpdate.php')
-rw-r--r--www/wiki/includes/deferred/WANCacheReapUpdate.php133
1 files changed, 133 insertions, 0 deletions
diff --git a/www/wiki/includes/deferred/WANCacheReapUpdate.php b/www/wiki/includes/deferred/WANCacheReapUpdate.php
new file mode 100644
index 00000000..5ffc9388
--- /dev/null
+++ b/www/wiki/includes/deferred/WANCacheReapUpdate.php
@@ -0,0 +1,133 @@
+<?php
+
+use Psr\Log\LoggerInterface;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Class for fixing stale WANObjectCache keys using a purge event source
+ *
+ * This is useful for expiring keys that missed fire-and-forget purges. This uses the
+ * recentchanges table as a reliable stream to make certain keys reach consistency
+ * as soon as the underlying replica database catches up. These means that critical
+ * keys will not escape getting purged simply due to brief hiccups in the network,
+ * which are more prone to happen accross datacenters.
+ *
+ * ----
+ * "I was trying to cheat death. I was only trying to surmount for a little while the
+ * darkness that all my life I surely knew was going to come rolling in on me some day
+ * and obliterate me. I was only to stay alive a little brief while longer, after I was
+ * already gone. To stay in the light, to be with the living, a little while past my time."
+ * -- Notes for "Blues of a Lifetime", by [[Cornell Woolrich]]
+ *
+ * @since 1.28
+ */
+class WANCacheReapUpdate implements DeferrableUpdate {
+ /** @var IDatabase */
+ private $db;
+ /** @var LoggerInterface */
+ private $logger;
+
+ /**
+ * @param IDatabase $db
+ * @param LoggerInterface $logger
+ */
+ public function __construct( IDatabase $db, LoggerInterface $logger ) {
+ $this->db = $db;
+ $this->logger = $logger;
+ }
+
+ function doUpdate() {
+ $reaper = new WANObjectCacheReaper(
+ ObjectCache::getMainWANInstance(),
+ ObjectCache::getLocalClusterInstance(),
+ [ $this, 'getTitleChangeEvents' ],
+ [ $this, 'getEventAffectedKeys' ],
+ [
+ 'channel' => 'table:recentchanges:' . $this->db->getDomainID(),
+ 'logger' => $this->logger
+ ]
+ );
+
+ $reaper->invoke( 100 );
+ }
+
+ /**
+ * @see WANObjectCacheRepear
+ *
+ * @param int $start
+ * @param int $id
+ * @param int $end
+ * @param int $limit
+ * @return TitleValue[]
+ */
+ public function getTitleChangeEvents( $start, $id, $end, $limit ) {
+ $db = $this->db;
+ $encStart = $db->addQuotes( $db->timestamp( $start ) );
+ $encEnd = $db->addQuotes( $db->timestamp( $end ) );
+ $id = (int)$id; // cast NULL => 0 since rc_id is an integer
+
+ $res = $db->select(
+ 'recentchanges',
+ [ 'rc_namespace', 'rc_title', 'rc_timestamp', 'rc_id' ],
+ [
+ $db->makeList( [
+ "rc_timestamp > $encStart",
+ "rc_timestamp = $encStart AND rc_id > " . $db->addQuotes( $id )
+ ], LIST_OR ),
+ "rc_timestamp < $encEnd"
+ ],
+ __METHOD__,
+ [ 'ORDER BY' => 'rc_timestamp ASC, rc_id ASC', 'LIMIT' => $limit ]
+ );
+
+ $events = [];
+ foreach ( $res as $row ) {
+ $events[] = [
+ 'id' => (int)$row->rc_id,
+ 'pos' => (int)wfTimestamp( TS_UNIX, $row->rc_timestamp ),
+ 'item' => new TitleValue( (int)$row->rc_namespace, $row->rc_title )
+ ];
+ }
+
+ return $events;
+ }
+
+ /**
+ * Gets a list of important cache keys associated with a title
+ *
+ * @see WANObjectCacheRepear
+ * @param WANObjectCache $cache
+ * @param TitleValue $t
+ * @return string[]
+ */
+ public function getEventAffectedKeys( WANObjectCache $cache, TitleValue $t ) {
+ /** @var WikiPage[]|LocalFile[]|User[] $entities */
+ $entities = [];
+
+ // You can't create a WikiPage for special pages (-1) or other virtual
+ // namespaces, but special pages do appear in RC sometimes, e.g. for logs
+ // of AbuseFilter filter changes.
+ if ( $t->getNamespace() >= 0 ) {
+ $entities[] = WikiPage::factory( Title::newFromTitleValue( $t ) );
+ }
+
+ if ( $t->inNamespace( NS_FILE ) ) {
+ $entities[] = wfLocalFile( $t->getText() );
+ }
+ if ( $t->inNamespace( NS_USER ) ) {
+ $entities[] = User::newFromName( $t->getText(), false );
+ }
+
+ $keys = [];
+ foreach ( $entities as $entity ) {
+ if ( $entity ) {
+ $keys = array_merge( $keys, $entity->getMutableCacheKeys( $cache ) );
+ }
+ }
+ if ( $keys ) {
+ $this->logger->debug( __CLASS__ . ': got key(s) ' . implode( ', ', $keys ) );
+ }
+
+ return $keys;
+ }
+}